summaryrefslogtreecommitdiffstats
path: root/wp-includes/interactivity-api/interactivity-api.php
blob: b8f3e508d0942499ad98a7cbde66d1ee526c6a3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<?php
/**
 * Interactivity API: Functions and hooks
 *
 * @package WordPress
 * @subpackage Interactivity API
 * @since 6.5.0
 */

/**
 * Processes the directives on the rendered HTML of the interactive blocks.
 *
 * This processes only one root interactive block at a time because the
 * rendered HTML of that block contains the rendered HTML of all its inner
 * blocks, including any interactive block. It does so by ignoring all the
 * interactive inner blocks until the root interactive block is processed.
 *
 * @since 6.5.0
 *
 * @param array $parsed_block The parsed block.
 * @return array The same parsed block.
 */
function wp_interactivity_process_directives_of_interactive_blocks( array $parsed_block ): array {
	static $root_interactive_block = null;

	/*
	 * Checks whether a root interactive block is already annotated for
	 * processing, and if it is, it ignores the subsequent ones.
	 */
	if ( null === $root_interactive_block ) {
		$block_name = $parsed_block['blockName'];
		$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );

		if (
			isset( $block_name ) &&
			( ( isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'] ) ||
			( isset( $block_type->supports['interactivity']['interactive'] ) && true === $block_type->supports['interactivity']['interactive'] ) )
		) {
			// Annotates the root interactive block for processing.
			$root_interactive_block = array( $block_name, $parsed_block );

			/*
			 * Adds a filter to process the root interactive block once it has
			 * finished rendering.
			 */
			$process_interactive_blocks = static function ( string $content, array $parsed_block ) use ( &$root_interactive_block, &$process_interactive_blocks ): string {
				// Checks whether the current block is the root interactive block.
				list($root_block_name, $root_parsed_block) = $root_interactive_block;
				if ( $root_block_name === $parsed_block['blockName'] && $parsed_block === $root_parsed_block ) {
					// The root interactive blocks has finished rendering, process it.
					$content = wp_interactivity_process_directives( $content );
					// Removes the filter and reset the root interactive block.
					remove_filter( 'render_block_' . $parsed_block['blockName'], $process_interactive_blocks );
					$root_interactive_block = null;
				}
				return $content;
			};

			/*
			 * Uses a priority of 100 to ensure that other filters can add additional
			 * directives before the processing starts.
			 */
			add_filter( 'render_block_' . $block_name, $process_interactive_blocks, 100, 2 );
		}
	}

	return $parsed_block;
}
/*
 * Uses a priority of 100 to ensure that other filters can add additional attributes to
 * $parsed_block before the processing starts.
 */
add_filter( 'render_block_data', 'wp_interactivity_process_directives_of_interactive_blocks', 100, 1 );

/**
 * Retrieves the main WP_Interactivity_API instance.
 *
 * It provides access to the WP_Interactivity_API instance, creating one if it
 * doesn't exist yet.
 *
 * @since 6.5.0
 *
 * @global WP_Interactivity_API $wp_interactivity
 *
 * @return WP_Interactivity_API The main WP_Interactivity_API instance.
 */
function wp_interactivity(): WP_Interactivity_API {
	global $wp_interactivity;
	if ( ! ( $wp_interactivity instanceof WP_Interactivity_API ) ) {
		$wp_interactivity = new WP_Interactivity_API();
	}
	return $wp_interactivity;
}

/**
 * Processes the interactivity directives contained within the HTML content
 * and updates the markup accordingly.
 *
 * @since 6.5.0
 *
 * @param string $html The HTML content to process.
 * @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags.
 */
function wp_interactivity_process_directives( string $html ): string {
	return wp_interactivity()->process_directives( $html );
}

/**
 * Gets and/or sets the initial state of an Interactivity API store for a
 * given namespace.
 *
 * If state for that store namespace already exists, it merges the new
 * provided state with the existing one.
 *
 * @since 6.5.0
 *
 * @param string $store_namespace The unique store namespace identifier.
 * @param array  $state           Optional. The array that will be merged with the existing state for the specified
 *                                store namespace.
 * @return array The state for the specified store namespace. This will be the updated state if a $state argument was
 *               provided.
 */
function wp_interactivity_state( string $store_namespace, array $state = array() ): array {
	return wp_interactivity()->state( $store_namespace, $state );
}

/**
 * Gets and/or sets the configuration of the Interactivity API for a given
 * store namespace.
 *
 * If configuration for that store namespace exists, it merges the new
 * provided configuration with the existing one.
 *
 * @since 6.5.0
 *
 * @param string $store_namespace The unique store namespace identifier.
 * @param array  $config          Optional. The array that will be merged with the existing configuration for the
 *                                specified store namespace.
 * @return array The configuration for the specified store namespace. This will be the updated configuration if a
 *               $config argument was provided.
 */
function wp_interactivity_config( string $store_namespace, array $config = array() ): array {
	return wp_interactivity()->config( $store_namespace, $config );
}

/**
 * Generates a `data-wp-context` directive attribute by encoding a context
 * array.
 *
 * This helper function simplifies the creation of `data-wp-context` directives
 * by providing a way to pass an array of data, which encodes into a JSON string
 * safe for direct use as a HTML attribute value.
 *
 * Example:
 *
 *     <div <?php echo wp_interactivity_data_wp_context( array( 'isOpen' => true, 'count' => 0 ) ); ?>>
 *
 * @since 6.5.0
 *
 * @param array  $context         The array of context data to encode.
 * @param string $store_namespace Optional. The unique store namespace identifier.
 * @return string A complete `data-wp-context` directive with a JSON encoded value representing the context array and
 *                the store namespace if specified.
 */
function wp_interactivity_data_wp_context( array $context, string $store_namespace = '' ): string {
	return 'data-wp-context=\'' .
		( $store_namespace ? $store_namespace . '::' : '' ) .
		( empty( $context ) ? '{}' : wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ) .
		'\'';
}