15 * @param string $asset_handle_or_path Asset handle or prefixed path. |
15 * @param string $asset_handle_or_path Asset handle or prefixed path. |
16 * @return string Path without the prefix or the original value. |
16 * @return string Path without the prefix or the original value. |
17 */ |
17 */ |
18 function remove_block_asset_path_prefix( $asset_handle_or_path ) { |
18 function remove_block_asset_path_prefix( $asset_handle_or_path ) { |
19 $path_prefix = 'file:'; |
19 $path_prefix = 'file:'; |
20 if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) { |
20 if ( ! str_starts_with( $asset_handle_or_path, $path_prefix ) ) { |
21 return $asset_handle_or_path; |
21 return $asset_handle_or_path; |
22 } |
22 } |
23 $path = substr( |
23 $path = substr( |
24 $asset_handle_or_path, |
24 $asset_handle_or_path, |
25 strlen( $path_prefix ) |
25 strlen( $path_prefix ) |
26 ); |
26 ); |
27 if ( strpos( $path, './' ) === 0 ) { |
27 if ( str_starts_with( $path, './' ) ) { |
28 $path = substr( $path, 2 ); |
28 $path = substr( $path, 2 ); |
29 } |
29 } |
30 return $path; |
30 return $path; |
31 } |
31 } |
32 |
32 |
33 /** |
33 /** |
34 * Generates the name for an asset based on the name of the block |
34 * Generates the name for an asset based on the name of the block |
35 * and the field name provided. |
35 * and the field name provided. |
36 * |
36 * |
37 * @since 5.5.0 |
37 * @since 5.5.0 |
|
38 * @since 6.1.0 Added `$index` parameter. |
|
39 * @since 6.5.0 Added support for `viewScriptModule` field. |
38 * |
40 * |
39 * @param string $block_name Name of the block. |
41 * @param string $block_name Name of the block. |
40 * @param string $field_name Name of the metadata field. |
42 * @param string $field_name Name of the metadata field. |
|
43 * @param int $index Optional. Index of the asset when multiple items passed. |
|
44 * Default 0. |
41 * @return string Generated asset name for the block's field. |
45 * @return string Generated asset name for the block's field. |
42 */ |
46 */ |
43 function generate_block_asset_handle( $block_name, $field_name ) { |
47 function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) { |
44 if ( 0 === strpos( $block_name, 'core/' ) ) { |
48 if ( str_starts_with( $block_name, 'core/' ) ) { |
45 $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); |
49 $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); |
46 if ( 0 === strpos( $field_name, 'editor' ) ) { |
50 if ( str_starts_with( $field_name, 'editor' ) ) { |
47 $asset_handle .= '-editor'; |
51 $asset_handle .= '-editor'; |
48 } |
52 } |
49 if ( 0 === strpos( $field_name, 'view' ) ) { |
53 if ( str_starts_with( $field_name, 'view' ) ) { |
50 $asset_handle .= '-view'; |
54 $asset_handle .= '-view'; |
51 } |
55 } |
|
56 if ( str_ends_with( strtolower( $field_name ), 'scriptmodule' ) ) { |
|
57 $asset_handle .= '-script-module'; |
|
58 } |
|
59 if ( $index > 0 ) { |
|
60 $asset_handle .= '-' . ( $index + 1 ); |
|
61 } |
52 return $asset_handle; |
62 return $asset_handle; |
53 } |
63 } |
54 |
64 |
55 $field_mappings = array( |
65 $field_mappings = array( |
56 'editorScript' => 'editor-script', |
66 'editorScript' => 'editor-script', |
57 'script' => 'script', |
67 'editorStyle' => 'editor-style', |
58 'viewScript' => 'view-script', |
68 'script' => 'script', |
59 'editorStyle' => 'editor-style', |
69 'style' => 'style', |
60 'style' => 'style', |
70 'viewScript' => 'view-script', |
|
71 'viewScriptModule' => 'view-script-module', |
|
72 'viewStyle' => 'view-style', |
61 ); |
73 ); |
62 return str_replace( '/', '-', $block_name ) . |
74 $asset_handle = str_replace( '/', '-', $block_name ) . |
63 '-' . $field_mappings[ $field_name ]; |
75 '-' . $field_mappings[ $field_name ]; |
64 } |
76 if ( $index > 0 ) { |
65 |
77 $asset_handle .= '-' . ( $index + 1 ); |
66 /** |
78 } |
67 * Finds a script handle for the selected block metadata field. It detects |
79 return $asset_handle; |
68 * when a path to file was provided and finds a corresponding asset file |
80 } |
69 * with details necessary to register the script under automatically |
81 |
70 * generated handle name. It returns unprocessed script handle otherwise. |
82 /** |
71 * |
83 * Gets the URL to a block asset. |
72 * @since 5.5.0 |
84 * |
|
85 * @since 6.4.0 |
|
86 * |
|
87 * @param string $path A normalized path to a block asset. |
|
88 * @return string|false The URL to the block asset or false on failure. |
|
89 */ |
|
90 function get_block_asset_url( $path ) { |
|
91 if ( empty( $path ) ) { |
|
92 return false; |
|
93 } |
|
94 |
|
95 // Path needs to be normalized to work in Windows env. |
|
96 static $wpinc_path_norm = ''; |
|
97 if ( ! $wpinc_path_norm ) { |
|
98 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); |
|
99 } |
|
100 |
|
101 if ( str_starts_with( $path, $wpinc_path_norm ) ) { |
|
102 return includes_url( str_replace( $wpinc_path_norm, '', $path ) ); |
|
103 } |
|
104 |
|
105 static $template_paths_norm = array(); |
|
106 |
|
107 $template = get_template(); |
|
108 if ( ! isset( $template_paths_norm[ $template ] ) ) { |
|
109 $template_paths_norm[ $template ] = wp_normalize_path( realpath( get_template_directory() ) ); |
|
110 } |
|
111 |
|
112 if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $template ] ) ) ) { |
|
113 return get_theme_file_uri( str_replace( $template_paths_norm[ $template ], '', $path ) ); |
|
114 } |
|
115 |
|
116 if ( is_child_theme() ) { |
|
117 $stylesheet = get_stylesheet(); |
|
118 if ( ! isset( $template_paths_norm[ $stylesheet ] ) ) { |
|
119 $template_paths_norm[ $stylesheet ] = wp_normalize_path( realpath( get_stylesheet_directory() ) ); |
|
120 } |
|
121 |
|
122 if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $stylesheet ] ) ) ) { |
|
123 return get_theme_file_uri( str_replace( $template_paths_norm[ $stylesheet ], '', $path ) ); |
|
124 } |
|
125 } |
|
126 |
|
127 return plugins_url( basename( $path ), $path ); |
|
128 } |
|
129 |
|
130 /** |
|
131 * Finds a script module ID for the selected block metadata field. It detects |
|
132 * when a path to file was provided and optionally finds a corresponding asset |
|
133 * file with details necessary to register the script module under with an |
|
134 * automatically generated module ID. It returns unprocessed script module |
|
135 * ID otherwise. |
|
136 * |
|
137 * @since 6.5.0 |
73 * |
138 * |
74 * @param array $metadata Block metadata. |
139 * @param array $metadata Block metadata. |
75 * @param string $field_name Field name to pick from metadata. |
140 * @param string $field_name Field name to pick from metadata. |
|
141 * @param int $index Optional. Index of the script module ID to register when multiple |
|
142 * items passed. Default 0. |
|
143 * @return string|false Script module ID or false on failure. |
|
144 */ |
|
145 function register_block_script_module_id( $metadata, $field_name, $index = 0 ) { |
|
146 if ( empty( $metadata[ $field_name ] ) ) { |
|
147 return false; |
|
148 } |
|
149 |
|
150 $module_id = $metadata[ $field_name ]; |
|
151 if ( is_array( $module_id ) ) { |
|
152 if ( empty( $module_id[ $index ] ) ) { |
|
153 return false; |
|
154 } |
|
155 $module_id = $module_id[ $index ]; |
|
156 } |
|
157 |
|
158 $module_path = remove_block_asset_path_prefix( $module_id ); |
|
159 if ( $module_id === $module_path ) { |
|
160 return $module_id; |
|
161 } |
|
162 |
|
163 $path = dirname( $metadata['file'] ); |
|
164 $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); |
|
165 $module_id = generate_block_asset_handle( $metadata['name'], $field_name, $index ); |
|
166 $module_asset_path = wp_normalize_path( |
|
167 realpath( $module_asset_raw_path ) |
|
168 ); |
|
169 |
|
170 $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); |
|
171 $module_uri = get_block_asset_url( $module_path_norm ); |
|
172 |
|
173 $module_asset = ! empty( $module_asset_path ) ? require $module_asset_path : array(); |
|
174 $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); |
|
175 $block_version = isset( $metadata['version'] ) ? $metadata['version'] : false; |
|
176 $module_version = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version; |
|
177 |
|
178 wp_register_script_module( |
|
179 $module_id, |
|
180 $module_uri, |
|
181 $module_dependencies, |
|
182 $module_version |
|
183 ); |
|
184 |
|
185 return $module_id; |
|
186 } |
|
187 |
|
188 /** |
|
189 * Finds a script handle for the selected block metadata field. It detects |
|
190 * when a path to file was provided and optionally finds a corresponding asset |
|
191 * file with details necessary to register the script under automatically |
|
192 * generated handle name. It returns unprocessed script handle otherwise. |
|
193 * |
|
194 * @since 5.5.0 |
|
195 * @since 6.1.0 Added `$index` parameter. |
|
196 * @since 6.5.0 The asset file is optional. Added script handle support in the asset file. |
|
197 * |
|
198 * @param array $metadata Block metadata. |
|
199 * @param string $field_name Field name to pick from metadata. |
|
200 * @param int $index Optional. Index of the script to register when multiple items passed. |
|
201 * Default 0. |
76 * @return string|false Script handle provided directly or created through |
202 * @return string|false Script handle provided directly or created through |
77 * script's registration, or false on failure. |
203 * script's registration, or false on failure. |
78 */ |
204 */ |
79 function register_block_script_handle( $metadata, $field_name ) { |
205 function register_block_script_handle( $metadata, $field_name, $index = 0 ) { |
80 if ( empty( $metadata[ $field_name ] ) ) { |
206 if ( empty( $metadata[ $field_name ] ) ) { |
81 return false; |
207 return false; |
82 } |
208 } |
83 $script_handle = $metadata[ $field_name ]; |
209 |
84 $script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); |
210 $script_handle_or_path = $metadata[ $field_name ]; |
85 if ( $script_handle === $script_path ) { |
211 if ( is_array( $script_handle_or_path ) ) { |
|
212 if ( empty( $script_handle_or_path[ $index ] ) ) { |
|
213 return false; |
|
214 } |
|
215 $script_handle_or_path = $script_handle_or_path[ $index ]; |
|
216 } |
|
217 |
|
218 $script_path = remove_block_asset_path_prefix( $script_handle_or_path ); |
|
219 if ( $script_handle_or_path === $script_path ) { |
|
220 return $script_handle_or_path; |
|
221 } |
|
222 |
|
223 $path = dirname( $metadata['file'] ); |
|
224 $script_asset_raw_path = $path . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ); |
|
225 $script_asset_path = wp_normalize_path( |
|
226 realpath( $script_asset_raw_path ) |
|
227 ); |
|
228 |
|
229 // Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460. |
|
230 $script_asset = ! empty( $script_asset_path ) ? require $script_asset_path : array(); |
|
231 $script_handle = isset( $script_asset['handle'] ) ? |
|
232 $script_asset['handle'] : |
|
233 generate_block_asset_handle( $metadata['name'], $field_name, $index ); |
|
234 if ( wp_script_is( $script_handle, 'registered' ) ) { |
86 return $script_handle; |
235 return $script_handle; |
87 } |
236 } |
88 |
237 |
89 $script_handle = generate_block_asset_handle( $metadata['name'], $field_name ); |
238 $script_path_norm = wp_normalize_path( realpath( $path . '/' . $script_path ) ); |
90 $script_asset_path = wp_normalize_path( |
239 $script_uri = get_block_asset_url( $script_path_norm ); |
91 realpath( |
|
92 dirname( $metadata['file'] ) . '/' . |
|
93 substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ) |
|
94 ) |
|
95 ); |
|
96 if ( ! file_exists( $script_asset_path ) ) { |
|
97 _doing_it_wrong( |
|
98 __FUNCTION__, |
|
99 sprintf( |
|
100 /* translators: 1: Field name, 2: Block name. */ |
|
101 __( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.' ), |
|
102 $field_name, |
|
103 $metadata['name'] |
|
104 ), |
|
105 '5.5.0' |
|
106 ); |
|
107 return false; |
|
108 } |
|
109 // Path needs to be normalized to work in Windows env. |
|
110 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); |
|
111 $theme_path_norm = wp_normalize_path( get_theme_file_path() ); |
|
112 $script_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $script_path ) ); |
|
113 $is_core_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm ); |
|
114 $is_theme_block = 0 === strpos( $script_path_norm, $theme_path_norm ); |
|
115 |
|
116 $script_uri = plugins_url( $script_path, $metadata['file'] ); |
|
117 if ( $is_core_block ) { |
|
118 $script_uri = includes_url( str_replace( $wpinc_path_norm, '', $script_path_norm ) ); |
|
119 } elseif ( $is_theme_block ) { |
|
120 $script_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $script_path_norm ) ); |
|
121 } |
|
122 |
|
123 $script_asset = require $script_asset_path; |
|
124 $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array(); |
240 $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array(); |
125 $result = wp_register_script( |
241 $block_version = isset( $metadata['version'] ) ? $metadata['version'] : false; |
|
242 $script_version = isset( $script_asset['version'] ) ? $script_asset['version'] : $block_version; |
|
243 $script_args = array(); |
|
244 if ( 'viewScript' === $field_name && $script_uri ) { |
|
245 $script_args['strategy'] = 'defer'; |
|
246 } |
|
247 |
|
248 $result = wp_register_script( |
126 $script_handle, |
249 $script_handle, |
127 $script_uri, |
250 $script_uri, |
128 $script_dependencies, |
251 $script_dependencies, |
129 isset( $script_asset['version'] ) ? $script_asset['version'] : false |
252 $script_version, |
|
253 $script_args |
130 ); |
254 ); |
131 if ( ! $result ) { |
255 if ( ! $result ) { |
132 return false; |
256 return false; |
133 } |
257 } |
134 |
258 |
143 * Finds a style handle for the block metadata field. It detects when a path |
267 * Finds a style handle for the block metadata field. It detects when a path |
144 * to file was provided and registers the style under automatically |
268 * to file was provided and registers the style under automatically |
145 * generated handle name. It returns unprocessed style handle otherwise. |
269 * generated handle name. It returns unprocessed style handle otherwise. |
146 * |
270 * |
147 * @since 5.5.0 |
271 * @since 5.5.0 |
|
272 * @since 6.1.0 Added `$index` parameter. |
148 * |
273 * |
149 * @param array $metadata Block metadata. |
274 * @param array $metadata Block metadata. |
150 * @param string $field_name Field name to pick from metadata. |
275 * @param string $field_name Field name to pick from metadata. |
|
276 * @param int $index Optional. Index of the style to register when multiple items passed. |
|
277 * Default 0. |
151 * @return string|false Style handle provided directly or created through |
278 * @return string|false Style handle provided directly or created through |
152 * style's registration, or false on failure. |
279 * style's registration, or false on failure. |
153 */ |
280 */ |
154 function register_block_style_handle( $metadata, $field_name ) { |
281 function register_block_style_handle( $metadata, $field_name, $index = 0 ) { |
155 if ( empty( $metadata[ $field_name ] ) ) { |
282 if ( empty( $metadata[ $field_name ] ) ) { |
156 return false; |
283 return false; |
157 } |
284 } |
158 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); |
285 |
159 $theme_path_norm = wp_normalize_path( get_theme_file_path() ); |
286 $style_handle = $metadata[ $field_name ]; |
160 $is_core_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm ); |
287 if ( is_array( $style_handle ) ) { |
|
288 if ( empty( $style_handle[ $index ] ) ) { |
|
289 return false; |
|
290 } |
|
291 $style_handle = $style_handle[ $index ]; |
|
292 } |
|
293 |
|
294 $style_handle_name = generate_block_asset_handle( $metadata['name'], $field_name, $index ); |
|
295 // If the style handle is already registered, skip re-registering. |
|
296 if ( wp_style_is( $style_handle_name, 'registered' ) ) { |
|
297 return $style_handle_name; |
|
298 } |
|
299 |
|
300 static $wpinc_path_norm = ''; |
|
301 if ( ! $wpinc_path_norm ) { |
|
302 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); |
|
303 } |
|
304 |
|
305 $is_core_block = isset( $metadata['file'] ) && str_starts_with( $metadata['file'], $wpinc_path_norm ); |
|
306 // Skip registering individual styles for each core block when a bundled version provided. |
161 if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) { |
307 if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) { |
162 return false; |
308 return false; |
163 } |
309 } |
164 |
310 |
|
311 $style_path = remove_block_asset_path_prefix( $style_handle ); |
|
312 $is_style_handle = $style_handle === $style_path; |
|
313 // Allow only passing style handles for core blocks. |
|
314 if ( $is_core_block && ! $is_style_handle ) { |
|
315 return false; |
|
316 } |
|
317 // Return the style handle unless it's the first item for every core block that requires special treatment. |
|
318 if ( $is_style_handle && ! ( $is_core_block && 0 === $index ) ) { |
|
319 return $style_handle; |
|
320 } |
|
321 |
165 // Check whether styles should have a ".min" suffix or not. |
322 // Check whether styles should have a ".min" suffix or not. |
166 $suffix = SCRIPT_DEBUG ? '' : '.min'; |
323 $suffix = SCRIPT_DEBUG ? '' : '.min'; |
167 |
|
168 $style_handle = $metadata[ $field_name ]; |
|
169 $style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); |
|
170 |
|
171 if ( $style_handle === $style_path && ! $is_core_block ) { |
|
172 return $style_handle; |
|
173 } |
|
174 |
|
175 $style_uri = plugins_url( $style_path, $metadata['file'] ); |
|
176 if ( $is_core_block ) { |
324 if ( $is_core_block ) { |
177 $style_path = "style$suffix.css"; |
325 $style_path = ( 'editorStyle' === $field_name ) ? "editor{$suffix}.css" : "style{$suffix}.css"; |
178 $style_uri = includes_url( 'blocks/' . str_replace( 'core/', '', $metadata['name'] ) . "/style$suffix.css" ); |
|
179 } |
326 } |
180 |
327 |
181 $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); |
328 $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); |
182 $is_theme_block = 0 === strpos( $style_path_norm, $theme_path_norm ); |
329 $style_uri = get_block_asset_url( $style_path_norm ); |
183 |
330 |
184 if ( $is_theme_block ) { |
331 $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; |
185 $style_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $style_path_norm ) ); |
332 $result = wp_register_style( |
186 } |
333 $style_handle_name, |
187 |
|
188 $style_handle = generate_block_asset_handle( $metadata['name'], $field_name ); |
|
189 $block_dir = dirname( $metadata['file'] ); |
|
190 $style_file = realpath( "$block_dir/$style_path" ); |
|
191 $has_style_file = false !== $style_file; |
|
192 $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; |
|
193 $style_uri = $has_style_file ? $style_uri : false; |
|
194 $result = wp_register_style( |
|
195 $style_handle, |
|
196 $style_uri, |
334 $style_uri, |
197 array(), |
335 array(), |
198 $version |
336 $version |
199 ); |
337 ); |
200 if ( file_exists( str_replace( '.css', '-rtl.css', $style_file ) ) ) { |
338 if ( ! $result ) { |
201 wp_style_add_data( $style_handle, 'rtl', 'replace' ); |
339 return false; |
202 } |
340 } |
203 if ( $has_style_file ) { |
341 |
204 wp_style_add_data( $style_handle, 'path', $style_file ); |
342 if ( $style_uri ) { |
205 } |
343 wp_style_add_data( $style_handle_name, 'path', $style_path_norm ); |
206 |
344 |
207 $rtl_file = str_replace( "$suffix.css", "-rtl$suffix.css", $style_file ); |
345 if ( $is_core_block ) { |
208 if ( is_rtl() && file_exists( $rtl_file ) ) { |
346 $rtl_file = str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $style_path_norm ); |
209 wp_style_add_data( $style_handle, 'path', $rtl_file ); |
347 } else { |
210 } |
348 $rtl_file = str_replace( '.css', '-rtl.css', $style_path_norm ); |
211 |
349 } |
212 return $result ? $style_handle : false; |
350 |
|
351 if ( is_rtl() && file_exists( $rtl_file ) ) { |
|
352 wp_style_add_data( $style_handle_name, 'rtl', 'replace' ); |
|
353 wp_style_add_data( $style_handle_name, 'suffix', $suffix ); |
|
354 wp_style_add_data( $style_handle_name, 'path', $rtl_file ); |
|
355 } |
|
356 } |
|
357 |
|
358 return $style_handle_name; |
213 } |
359 } |
214 |
360 |
215 /** |
361 /** |
216 * Gets i18n schema for block's metadata read from `block.json` file. |
362 * Gets i18n schema for block's metadata read from `block.json` file. |
217 * |
363 * |
233 * Registers a block type from the metadata stored in the `block.json` file. |
379 * Registers a block type from the metadata stored in the `block.json` file. |
234 * |
380 * |
235 * @since 5.5.0 |
381 * @since 5.5.0 |
236 * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields. |
382 * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields. |
237 * @since 5.9.0 Added support for `variations` and `viewScript` fields. |
383 * @since 5.9.0 Added support for `variations` and `viewScript` fields. |
|
384 * @since 6.1.0 Added support for `render` field. |
|
385 * @since 6.3.0 Added `selectors` field. |
|
386 * @since 6.4.0 Added support for `blockHooks` field. |
|
387 * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields. |
238 * |
388 * |
239 * @param string $file_or_folder Path to the JSON file with metadata definition for |
389 * @param string $file_or_folder Path to the JSON file with metadata definition for |
240 * the block or path to the folder where the `block.json` file is located. |
390 * the block or path to the folder where the `block.json` file is located. |
241 * If providing the path to a JSON file, the filename must end with `block.json`. |
391 * If providing the path to a JSON file, the filename must end with `block.json`. |
242 * @param array $args Optional. Array of block type arguments. Accepts any public property |
392 * @param array $args Optional. Array of block type arguments. Accepts any public property |
243 * of `WP_Block_Type`. See WP_Block_Type::__construct() for information |
393 * of `WP_Block_Type`. See WP_Block_Type::__construct() for information |
244 * on accepted arguments. Default empty array. |
394 * on accepted arguments. Default empty array. |
245 * @return WP_Block_Type|false The registered block type on success, or false on failure. |
395 * @return WP_Block_Type|false The registered block type on success, or false on failure. |
246 */ |
396 */ |
247 function register_block_type_from_metadata( $file_or_folder, $args = array() ) { |
397 function register_block_type_from_metadata( $file_or_folder, $args = array() ) { |
248 $filename = 'block.json'; |
398 /* |
249 $metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ? |
399 * Get an array of metadata from a PHP file. |
250 trailingslashit( $file_or_folder ) . $filename : |
400 * This improves performance for core blocks as it's only necessary to read a single PHP file |
|
401 * instead of reading a JSON file per-block, and then decoding from JSON to PHP. |
|
402 * Using a static variable ensures that the metadata is only read once per request. |
|
403 */ |
|
404 static $core_blocks_meta; |
|
405 if ( ! $core_blocks_meta ) { |
|
406 $core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php'; |
|
407 } |
|
408 |
|
409 $metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ? |
|
410 trailingslashit( $file_or_folder ) . 'block.json' : |
251 $file_or_folder; |
411 $file_or_folder; |
252 if ( ! file_exists( $metadata_file ) ) { |
412 |
|
413 $is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC ); |
|
414 // If the block is not a core block, the metadata file must exist. |
|
415 $metadata_file_exists = $is_core_block || file_exists( $metadata_file ); |
|
416 if ( ! $metadata_file_exists && empty( $args['name'] ) ) { |
253 return false; |
417 return false; |
254 } |
418 } |
255 |
419 |
256 $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) ); |
420 // Try to get metadata from the static cache for core blocks. |
257 if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) { |
421 $metadata = array(); |
|
422 if ( $is_core_block ) { |
|
423 $core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder ); |
|
424 if ( ! empty( $core_blocks_meta[ $core_block_name ] ) ) { |
|
425 $metadata = $core_blocks_meta[ $core_block_name ]; |
|
426 } |
|
427 } |
|
428 |
|
429 // If metadata is not found in the static cache, read it from the file. |
|
430 if ( $metadata_file_exists && empty( $metadata ) ) { |
|
431 $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) ); |
|
432 } |
|
433 |
|
434 if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) { |
258 return false; |
435 return false; |
259 } |
436 } |
260 $metadata['file'] = wp_normalize_path( realpath( $metadata_file ) ); |
437 |
|
438 $metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null; |
261 |
439 |
262 /** |
440 /** |
263 * Filters the metadata provided for registering a block type. |
441 * Filters the metadata provided for registering a block type. |
264 * |
442 * |
265 * @since 5.7.0 |
443 * @since 5.7.0 |
267 * @param array $metadata Metadata for registering a block type. |
445 * @param array $metadata Metadata for registering a block type. |
268 */ |
446 */ |
269 $metadata = apply_filters( 'block_type_metadata', $metadata ); |
447 $metadata = apply_filters( 'block_type_metadata', $metadata ); |
270 |
448 |
271 // Add `style` and `editor_style` for core blocks if missing. |
449 // Add `style` and `editor_style` for core blocks if missing. |
272 if ( ! empty( $metadata['name'] ) && 0 === strpos( $metadata['name'], 'core/' ) ) { |
450 if ( ! empty( $metadata['name'] ) && str_starts_with( $metadata['name'], 'core/' ) ) { |
273 $block_name = str_replace( 'core/', '', $metadata['name'] ); |
451 $block_name = str_replace( 'core/', '', $metadata['name'] ); |
274 |
452 |
275 if ( ! isset( $metadata['style'] ) ) { |
453 if ( ! isset( $metadata['style'] ) ) { |
276 $metadata['style'] = "wp-block-$block_name"; |
454 $metadata['style'] = "wp-block-$block_name"; |
|
455 } |
|
456 if ( current_theme_supports( 'wp-block-styles' ) && wp_should_load_separate_core_block_assets() ) { |
|
457 $metadata['style'] = (array) $metadata['style']; |
|
458 $metadata['style'][] = "wp-block-{$block_name}-theme"; |
277 } |
459 } |
278 if ( ! isset( $metadata['editorStyle'] ) ) { |
460 if ( ! isset( $metadata['editorStyle'] ) ) { |
279 $metadata['editorStyle'] = "wp-block-{$block_name}-editor"; |
461 $metadata['editorStyle'] = "wp-block-{$block_name}-editor"; |
280 } |
462 } |
281 } |
463 } |
282 |
464 |
283 $settings = array(); |
465 $settings = array(); |
284 $property_mappings = array( |
466 $property_mappings = array( |
285 'apiVersion' => 'api_version', |
467 'apiVersion' => 'api_version', |
|
468 'name' => 'name', |
286 'title' => 'title', |
469 'title' => 'title', |
287 'category' => 'category', |
470 'category' => 'category', |
288 'parent' => 'parent', |
471 'parent' => 'parent', |
289 'ancestor' => 'ancestor', |
472 'ancestor' => 'ancestor', |
290 'icon' => 'icon', |
473 'icon' => 'icon', |
291 'description' => 'description', |
474 'description' => 'description', |
292 'keywords' => 'keywords', |
475 'keywords' => 'keywords', |
293 'attributes' => 'attributes', |
476 'attributes' => 'attributes', |
294 'providesContext' => 'provides_context', |
477 'providesContext' => 'provides_context', |
295 'usesContext' => 'uses_context', |
478 'usesContext' => 'uses_context', |
|
479 'selectors' => 'selectors', |
296 'supports' => 'supports', |
480 'supports' => 'supports', |
297 'styles' => 'styles', |
481 'styles' => 'styles', |
298 'variations' => 'variations', |
482 'variations' => 'variations', |
299 'example' => 'example', |
483 'example' => 'example', |
|
484 'allowedBlocks' => 'allowed_blocks', |
300 ); |
485 ); |
301 $textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null; |
486 $textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null; |
302 $i18n_schema = get_block_metadata_i18n_schema(); |
487 $i18n_schema = get_block_metadata_i18n_schema(); |
303 |
488 |
304 foreach ( $property_mappings as $key => $mapped_key ) { |
489 foreach ( $property_mappings as $key => $mapped_key ) { |
305 if ( isset( $metadata[ $key ] ) ) { |
490 if ( isset( $metadata[ $key ] ) ) { |
306 $settings[ $mapped_key ] = $metadata[ $key ]; |
491 $settings[ $mapped_key ] = $metadata[ $key ]; |
307 if ( $textdomain && isset( $i18n_schema->$key ) ) { |
492 if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) { |
308 $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain ); |
493 $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain ); |
309 } |
494 } |
310 } |
495 } |
311 } |
496 } |
312 |
497 |
313 if ( ! empty( $metadata['editorScript'] ) ) { |
498 if ( ! empty( $metadata['render'] ) ) { |
314 $settings['editor_script'] = register_block_script_handle( |
499 $template_path = wp_normalize_path( |
315 $metadata, |
500 realpath( |
316 'editorScript' |
501 dirname( $metadata['file'] ) . '/' . |
|
502 remove_block_asset_path_prefix( $metadata['render'] ) |
|
503 ) |
317 ); |
504 ); |
318 } |
505 if ( $template_path ) { |
319 |
506 /** |
320 if ( ! empty( $metadata['script'] ) ) { |
507 * Renders the block on the server. |
321 $settings['script'] = register_block_script_handle( |
508 * |
322 $metadata, |
509 * @since 6.1.0 |
323 'script' |
510 * |
|
511 * @param array $attributes Block attributes. |
|
512 * @param string $content Block default content. |
|
513 * @param WP_Block $block Block instance. |
|
514 * |
|
515 * @return string Returns the block content. |
|
516 */ |
|
517 $settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) { |
|
518 ob_start(); |
|
519 require $template_path; |
|
520 return ob_get_clean(); |
|
521 }; |
|
522 } |
|
523 } |
|
524 |
|
525 $settings = array_merge( $settings, $args ); |
|
526 |
|
527 $script_fields = array( |
|
528 'editorScript' => 'editor_script_handles', |
|
529 'script' => 'script_handles', |
|
530 'viewScript' => 'view_script_handles', |
|
531 ); |
|
532 foreach ( $script_fields as $metadata_field_name => $settings_field_name ) { |
|
533 if ( ! empty( $settings[ $metadata_field_name ] ) ) { |
|
534 $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; |
|
535 } |
|
536 if ( ! empty( $metadata[ $metadata_field_name ] ) ) { |
|
537 $scripts = $metadata[ $metadata_field_name ]; |
|
538 $processed_scripts = array(); |
|
539 if ( is_array( $scripts ) ) { |
|
540 for ( $index = 0; $index < count( $scripts ); $index++ ) { |
|
541 $result = register_block_script_handle( |
|
542 $metadata, |
|
543 $metadata_field_name, |
|
544 $index |
|
545 ); |
|
546 if ( $result ) { |
|
547 $processed_scripts[] = $result; |
|
548 } |
|
549 } |
|
550 } else { |
|
551 $result = register_block_script_handle( |
|
552 $metadata, |
|
553 $metadata_field_name |
|
554 ); |
|
555 if ( $result ) { |
|
556 $processed_scripts[] = $result; |
|
557 } |
|
558 } |
|
559 $settings[ $settings_field_name ] = $processed_scripts; |
|
560 } |
|
561 } |
|
562 |
|
563 $module_fields = array( |
|
564 'viewScriptModule' => 'view_script_module_ids', |
|
565 ); |
|
566 foreach ( $module_fields as $metadata_field_name => $settings_field_name ) { |
|
567 if ( ! empty( $settings[ $metadata_field_name ] ) ) { |
|
568 $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; |
|
569 } |
|
570 if ( ! empty( $metadata[ $metadata_field_name ] ) ) { |
|
571 $modules = $metadata[ $metadata_field_name ]; |
|
572 $processed_modules = array(); |
|
573 if ( is_array( $modules ) ) { |
|
574 for ( $index = 0; $index < count( $modules ); $index++ ) { |
|
575 $result = register_block_script_module_id( |
|
576 $metadata, |
|
577 $metadata_field_name, |
|
578 $index |
|
579 ); |
|
580 if ( $result ) { |
|
581 $processed_modules[] = $result; |
|
582 } |
|
583 } |
|
584 } else { |
|
585 $result = register_block_script_module_id( |
|
586 $metadata, |
|
587 $metadata_field_name |
|
588 ); |
|
589 if ( $result ) { |
|
590 $processed_modules[] = $result; |
|
591 } |
|
592 } |
|
593 $settings[ $settings_field_name ] = $processed_modules; |
|
594 } |
|
595 } |
|
596 |
|
597 $style_fields = array( |
|
598 'editorStyle' => 'editor_style_handles', |
|
599 'style' => 'style_handles', |
|
600 'viewStyle' => 'view_style_handles', |
|
601 ); |
|
602 foreach ( $style_fields as $metadata_field_name => $settings_field_name ) { |
|
603 if ( ! empty( $settings[ $metadata_field_name ] ) ) { |
|
604 $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; |
|
605 } |
|
606 if ( ! empty( $metadata[ $metadata_field_name ] ) ) { |
|
607 $styles = $metadata[ $metadata_field_name ]; |
|
608 $processed_styles = array(); |
|
609 if ( is_array( $styles ) ) { |
|
610 for ( $index = 0; $index < count( $styles ); $index++ ) { |
|
611 $result = register_block_style_handle( |
|
612 $metadata, |
|
613 $metadata_field_name, |
|
614 $index |
|
615 ); |
|
616 if ( $result ) { |
|
617 $processed_styles[] = $result; |
|
618 } |
|
619 } |
|
620 } else { |
|
621 $result = register_block_style_handle( |
|
622 $metadata, |
|
623 $metadata_field_name |
|
624 ); |
|
625 if ( $result ) { |
|
626 $processed_styles[] = $result; |
|
627 } |
|
628 } |
|
629 $settings[ $settings_field_name ] = $processed_styles; |
|
630 } |
|
631 } |
|
632 |
|
633 if ( ! empty( $metadata['blockHooks'] ) ) { |
|
634 /** |
|
635 * Map camelCased position string (from block.json) to snake_cased block type position. |
|
636 * |
|
637 * @var array |
|
638 */ |
|
639 $position_mappings = array( |
|
640 'before' => 'before', |
|
641 'after' => 'after', |
|
642 'firstChild' => 'first_child', |
|
643 'lastChild' => 'last_child', |
324 ); |
644 ); |
325 } |
645 |
326 |
646 $settings['block_hooks'] = array(); |
327 if ( ! empty( $metadata['viewScript'] ) ) { |
647 foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) { |
328 $settings['view_script'] = register_block_script_handle( |
648 // Avoid infinite recursion (hooking to itself). |
329 $metadata, |
649 if ( $metadata['name'] === $anchor_block_name ) { |
330 'viewScript' |
650 _doing_it_wrong( |
331 ); |
651 __METHOD__, |
332 } |
652 __( 'Cannot hook block to itself.' ), |
333 |
653 '6.4.0' |
334 if ( ! empty( $metadata['editorStyle'] ) ) { |
654 ); |
335 $settings['editor_style'] = register_block_style_handle( |
655 continue; |
336 $metadata, |
656 } |
337 'editorStyle' |
657 |
338 ); |
658 if ( ! isset( $position_mappings[ $position ] ) ) { |
339 } |
659 continue; |
340 |
660 } |
341 if ( ! empty( $metadata['style'] ) ) { |
661 |
342 $settings['style'] = register_block_style_handle( |
662 $settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ]; |
343 $metadata, |
663 } |
344 'style' |
|
345 ); |
|
346 } |
664 } |
347 |
665 |
348 /** |
666 /** |
349 * Filters the settings determined from the block type metadata. |
667 * Filters the settings determined from the block type metadata. |
350 * |
668 * |
351 * @since 5.7.0 |
669 * @since 5.7.0 |
352 * |
670 * |
353 * @param array $settings Array of determined settings for registering a block type. |
671 * @param array $settings Array of determined settings for registering a block type. |
354 * @param array $metadata Metadata provided for registering a block type. |
672 * @param array $metadata Metadata provided for registering a block type. |
355 */ |
673 */ |
356 $settings = apply_filters( |
674 $settings = apply_filters( 'block_type_metadata_settings', $settings, $metadata ); |
357 'block_type_metadata_settings', |
675 |
358 array_merge( |
676 $metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name']; |
359 $settings, |
|
360 $args |
|
361 ), |
|
362 $metadata |
|
363 ); |
|
364 |
677 |
365 return WP_Block_Type_Registry::get_instance()->register( |
678 return WP_Block_Type_Registry::get_instance()->register( |
366 $metadata['name'], |
679 $metadata['name'], |
367 $settings |
680 $settings |
368 ); |
681 ); |
503 $dynamic_block_names[] = $block_type->name; |
820 $dynamic_block_names[] = $block_type->name; |
504 } |
821 } |
505 } |
822 } |
506 |
823 |
507 return $dynamic_block_names; |
824 return $dynamic_block_names; |
|
825 } |
|
826 |
|
827 /** |
|
828 * Retrieves block types hooked into the given block, grouped by anchor block type and the relative position. |
|
829 * |
|
830 * @since 6.4.0 |
|
831 * |
|
832 * @return array[] Array of block types grouped by anchor block type and the relative position. |
|
833 */ |
|
834 function get_hooked_blocks() { |
|
835 $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); |
|
836 $hooked_blocks = array(); |
|
837 foreach ( $block_types as $block_type ) { |
|
838 if ( ! ( $block_type instanceof WP_Block_Type ) || ! is_array( $block_type->block_hooks ) ) { |
|
839 continue; |
|
840 } |
|
841 foreach ( $block_type->block_hooks as $anchor_block_type => $relative_position ) { |
|
842 if ( ! isset( $hooked_blocks[ $anchor_block_type ] ) ) { |
|
843 $hooked_blocks[ $anchor_block_type ] = array(); |
|
844 } |
|
845 if ( ! isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) ) { |
|
846 $hooked_blocks[ $anchor_block_type ][ $relative_position ] = array(); |
|
847 } |
|
848 $hooked_blocks[ $anchor_block_type ][ $relative_position ][] = $block_type->name; |
|
849 } |
|
850 } |
|
851 |
|
852 return $hooked_blocks; |
|
853 } |
|
854 |
|
855 /** |
|
856 * Returns the markup for blocks hooked to the given anchor block in a specific relative position. |
|
857 * |
|
858 * @since 6.5.0 |
|
859 * @access private |
|
860 * |
|
861 * @param array $parsed_anchor_block The anchor block, in parsed block array format. |
|
862 * @param string $relative_position The relative position of the hooked blocks. |
|
863 * Can be one of 'before', 'after', 'first_child', or 'last_child'. |
|
864 * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position. |
|
865 * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to. |
|
866 * @return string |
|
867 */ |
|
868 function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) { |
|
869 $anchor_block_type = $parsed_anchor_block['blockName']; |
|
870 $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) |
|
871 ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] |
|
872 : array(); |
|
873 |
|
874 /** |
|
875 * Filters the list of hooked block types for a given anchor block type and relative position. |
|
876 * |
|
877 * @since 6.4.0 |
|
878 * |
|
879 * @param string[] $hooked_block_types The list of hooked block types. |
|
880 * @param string $relative_position The relative position of the hooked blocks. |
|
881 * Can be one of 'before', 'after', 'first_child', or 'last_child'. |
|
882 * @param string $anchor_block_type The anchor block type. |
|
883 * @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type, |
|
884 * or pattern that the anchor block belongs to. |
|
885 */ |
|
886 $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); |
|
887 |
|
888 $markup = ''; |
|
889 foreach ( $hooked_block_types as $hooked_block_type ) { |
|
890 $parsed_hooked_block = array( |
|
891 'blockName' => $hooked_block_type, |
|
892 'attrs' => array(), |
|
893 'innerBlocks' => array(), |
|
894 'innerContent' => array(), |
|
895 ); |
|
896 |
|
897 /** |
|
898 * Filters the parsed block array for a given hooked block. |
|
899 * |
|
900 * @since 6.5.0 |
|
901 * |
|
902 * @param array|null $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block. |
|
903 * @param string $hooked_block_type The hooked block type name. |
|
904 * @param string $relative_position The relative position of the hooked block. |
|
905 * @param array $parsed_anchor_block The anchor block, in parsed block array format. |
|
906 * @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type, |
|
907 * or pattern that the anchor block belongs to. |
|
908 */ |
|
909 $parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ); |
|
910 |
|
911 /** |
|
912 * Filters the parsed block array for a given hooked block. |
|
913 * |
|
914 * The dynamic portion of the hook name, `$hooked_block_type`, refers to the block type name of the specific hooked block. |
|
915 * |
|
916 * @since 6.5.0 |
|
917 * |
|
918 * @param array|null $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block. |
|
919 * @param string $hooked_block_type The hooked block type name. |
|
920 * @param string $relative_position The relative position of the hooked block. |
|
921 * @param array $parsed_anchor_block The anchor block, in parsed block array format. |
|
922 * @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type, |
|
923 * or pattern that the anchor block belongs to. |
|
924 */ |
|
925 $parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ); |
|
926 |
|
927 if ( null === $parsed_hooked_block ) { |
|
928 continue; |
|
929 } |
|
930 |
|
931 // It's possible that the filter returned a block of a different type, so we explicitly |
|
932 // look for the original `$hooked_block_type` in the `ignoredHookedBlocks` metadata. |
|
933 if ( |
|
934 ! isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) || |
|
935 ! in_array( $hooked_block_type, $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'], true ) |
|
936 ) { |
|
937 $markup .= serialize_block( $parsed_hooked_block ); |
|
938 } |
|
939 } |
|
940 |
|
941 return $markup; |
|
942 } |
|
943 |
|
944 /** |
|
945 * Adds a list of hooked block types to an anchor block's ignored hooked block types. |
|
946 * |
|
947 * This function is meant for internal use only. |
|
948 * |
|
949 * @since 6.5.0 |
|
950 * @access private |
|
951 * |
|
952 * @param array $parsed_anchor_block The anchor block, in parsed block array format. |
|
953 * @param string $relative_position The relative position of the hooked blocks. |
|
954 * Can be one of 'before', 'after', 'first_child', or 'last_child'. |
|
955 * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position. |
|
956 * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to. |
|
957 * @return string Empty string. |
|
958 */ |
|
959 function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) { |
|
960 $anchor_block_type = $parsed_anchor_block['blockName']; |
|
961 $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) |
|
962 ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] |
|
963 : array(); |
|
964 |
|
965 /** This filter is documented in wp-includes/blocks.php */ |
|
966 $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); |
|
967 if ( empty( $hooked_block_types ) ) { |
|
968 return ''; |
|
969 } |
|
970 |
|
971 foreach ( $hooked_block_types as $index => $hooked_block_type ) { |
|
972 $parsed_hooked_block = array( |
|
973 'blockName' => $hooked_block_type, |
|
974 'attrs' => array(), |
|
975 'innerBlocks' => array(), |
|
976 'innerContent' => array(), |
|
977 ); |
|
978 |
|
979 /** This filter is documented in wp-includes/blocks.php */ |
|
980 $parsed_hooked_block = apply_filters( 'hooked_block', $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ); |
|
981 |
|
982 /** This filter is documented in wp-includes/blocks.php */ |
|
983 $parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ); |
|
984 |
|
985 if ( null === $parsed_hooked_block ) { |
|
986 unset( $hooked_block_types[ $index ] ); |
|
987 } |
|
988 } |
|
989 |
|
990 $previously_ignored_hooked_blocks = isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) |
|
991 ? $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] |
|
992 : array(); |
|
993 |
|
994 $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] = array_unique( |
|
995 array_merge( |
|
996 $previously_ignored_hooked_blocks, |
|
997 $hooked_block_types |
|
998 ) |
|
999 ); |
|
1000 |
|
1001 // Markup for the hooked blocks has already been created (in `insert_hooked_blocks`). |
|
1002 return ''; |
|
1003 } |
|
1004 |
|
1005 /** |
|
1006 * Runs the hooked blocks algorithm on the given content. |
|
1007 * |
|
1008 * @since 6.6.0 |
|
1009 * @access private |
|
1010 * |
|
1011 * @param string $content Serialized content. |
|
1012 * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, |
|
1013 * or pattern that the blocks belong to. |
|
1014 * @param callable $callback A function that will be called for each block to generate |
|
1015 * the markup for a given list of blocks that are hooked to it. |
|
1016 * Default: 'insert_hooked_blocks'. |
|
1017 * @return string The serialized markup. |
|
1018 */ |
|
1019 function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) { |
|
1020 $hooked_blocks = get_hooked_blocks(); |
|
1021 if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) { |
|
1022 return $content; |
|
1023 } |
|
1024 |
|
1025 $blocks = parse_blocks( $content ); |
|
1026 |
|
1027 $before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback ); |
|
1028 $after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback ); |
|
1029 |
|
1030 return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); |
|
1031 } |
|
1032 |
|
1033 /** |
|
1034 * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks. |
|
1035 * |
|
1036 * @since 6.6.0 |
|
1037 * @access private |
|
1038 * |
|
1039 * @param string $serialized_block The serialized markup of a block and its inner blocks. |
|
1040 * @return string The serialized markup of the inner blocks. |
|
1041 */ |
|
1042 function remove_serialized_parent_block( $serialized_block ) { |
|
1043 $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); |
|
1044 $end = strrpos( $serialized_block, '<!--' ); |
|
1045 return substr( $serialized_block, $start, $end - $start ); |
|
1046 } |
|
1047 |
|
1048 /** |
|
1049 * Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content. |
|
1050 * Currently only supports `wp_navigation` post types. |
|
1051 * |
|
1052 * @since 6.6.0 |
|
1053 * @access private |
|
1054 * |
|
1055 * @param stdClass $post Post object. |
|
1056 * @return stdClass The updated post object. |
|
1057 */ |
|
1058 function update_ignored_hooked_blocks_postmeta( $post ) { |
|
1059 /* |
|
1060 * In this scenario the user has likely tried to create a navigation via the REST API. |
|
1061 * In which case we won't have a post ID to work with and store meta against. |
|
1062 */ |
|
1063 if ( empty( $post->ID ) ) { |
|
1064 return $post; |
|
1065 } |
|
1066 |
|
1067 /* |
|
1068 * Skip meta generation when consumers intentionally update specific Navigation fields |
|
1069 * and omit the content update. |
|
1070 */ |
|
1071 if ( ! isset( $post->post_content ) ) { |
|
1072 return $post; |
|
1073 } |
|
1074 |
|
1075 /* |
|
1076 * Skip meta generation when the post content is not a navigation block. |
|
1077 */ |
|
1078 if ( ! isset( $post->post_type ) || 'wp_navigation' !== $post->post_type ) { |
|
1079 return $post; |
|
1080 } |
|
1081 |
|
1082 $attributes = array(); |
|
1083 |
|
1084 $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); |
|
1085 if ( ! empty( $ignored_hooked_blocks ) ) { |
|
1086 $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); |
|
1087 $attributes['metadata'] = array( |
|
1088 'ignoredHookedBlocks' => $ignored_hooked_blocks, |
|
1089 ); |
|
1090 } |
|
1091 |
|
1092 $markup = get_comment_delimited_block_content( |
|
1093 'core/navigation', |
|
1094 $attributes, |
|
1095 $post->post_content |
|
1096 ); |
|
1097 |
|
1098 $serialized_block = apply_block_hooks_to_content( $markup, get_post( $post->ID ), 'set_ignored_hooked_blocks_metadata' ); |
|
1099 $root_block = parse_blocks( $serialized_block )[0]; |
|
1100 |
|
1101 $ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] ) |
|
1102 ? $root_block['attrs']['metadata']['ignoredHookedBlocks'] |
|
1103 : array(); |
|
1104 |
|
1105 if ( ! empty( $ignored_hooked_blocks ) ) { |
|
1106 $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); |
|
1107 if ( ! empty( $existing_ignored_hooked_blocks ) ) { |
|
1108 $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); |
|
1109 $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); |
|
1110 } |
|
1111 update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) ); |
|
1112 } |
|
1113 |
|
1114 $post->post_content = remove_serialized_parent_block( $serialized_block ); |
|
1115 return $post; |
|
1116 } |
|
1117 |
|
1118 /** |
|
1119 * Returns the markup for blocks hooked to the given anchor block in a specific relative position and then |
|
1120 * adds a list of hooked block types to an anchor block's ignored hooked block types. |
|
1121 * |
|
1122 * This function is meant for internal use only. |
|
1123 * |
|
1124 * @since 6.6.0 |
|
1125 * @access private |
|
1126 * |
|
1127 * @param array $parsed_anchor_block The anchor block, in parsed block array format. |
|
1128 * @param string $relative_position The relative position of the hooked blocks. |
|
1129 * Can be one of 'before', 'after', 'first_child', or 'last_child'. |
|
1130 * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position. |
|
1131 * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to. |
|
1132 * @return string |
|
1133 */ |
|
1134 function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) { |
|
1135 $markup = insert_hooked_blocks( $parsed_anchor_block, $relative_position, $hooked_blocks, $context ); |
|
1136 $markup .= set_ignored_hooked_blocks_metadata( $parsed_anchor_block, $relative_position, $hooked_blocks, $context ); |
|
1137 |
|
1138 return $markup; |
|
1139 } |
|
1140 |
|
1141 /** |
|
1142 * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks. |
|
1143 * |
|
1144 * @since 6.6.0 |
|
1145 * |
|
1146 * @param WP_REST_Response $response The response object. |
|
1147 * @param WP_Post $post Post object. |
|
1148 * @return WP_REST_Response The response object. |
|
1149 */ |
|
1150 function insert_hooked_blocks_into_rest_response( $response, $post ) { |
|
1151 if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) { |
|
1152 return $response; |
|
1153 } |
|
1154 |
|
1155 $attributes = array(); |
|
1156 $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); |
|
1157 if ( ! empty( $ignored_hooked_blocks ) ) { |
|
1158 $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); |
|
1159 $attributes['metadata'] = array( |
|
1160 'ignoredHookedBlocks' => $ignored_hooked_blocks, |
|
1161 ); |
|
1162 } |
|
1163 $content = get_comment_delimited_block_content( |
|
1164 'core/navigation', |
|
1165 $attributes, |
|
1166 $response->data['content']['raw'] |
|
1167 ); |
|
1168 |
|
1169 $content = apply_block_hooks_to_content( $content, $post ); |
|
1170 |
|
1171 // Remove mock Navigation block wrapper. |
|
1172 $content = remove_serialized_parent_block( $content ); |
|
1173 |
|
1174 $response->data['content']['raw'] = $content; |
|
1175 |
|
1176 /** This filter is documented in wp-includes/post-template.php */ |
|
1177 $response->data['content']['rendered'] = apply_filters( 'the_content', $content ); |
|
1178 |
|
1179 return $response; |
|
1180 } |
|
1181 |
|
1182 /** |
|
1183 * Returns a function that injects the theme attribute into, and hooked blocks before, a given block. |
|
1184 * |
|
1185 * The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`, |
|
1186 * where it will inject the `theme` attribute into all Template Part blocks, and prepend the markup for |
|
1187 * any blocks hooked `before` the given block and as its parent's `first_child`, respectively. |
|
1188 * |
|
1189 * This function is meant for internal use only. |
|
1190 * |
|
1191 * @since 6.4.0 |
|
1192 * @since 6.5.0 Added $callback argument. |
|
1193 * @access private |
|
1194 * |
|
1195 * @param array $hooked_blocks An array of blocks hooked to another given block. |
|
1196 * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, |
|
1197 * or pattern that the blocks belong to. |
|
1198 * @param callable $callback A function that will be called for each block to generate |
|
1199 * the markup for a given list of blocks that are hooked to it. |
|
1200 * Default: 'insert_hooked_blocks'. |
|
1201 * @return callable A function that returns the serialized markup for the given block, |
|
1202 * including the markup for any hooked blocks before it. |
|
1203 */ |
|
1204 function make_before_block_visitor( $hooked_blocks, $context, $callback = 'insert_hooked_blocks' ) { |
|
1205 /** |
|
1206 * Injects hooked blocks before the given block, injects the `theme` attribute into Template Part blocks, and returns the serialized markup. |
|
1207 * |
|
1208 * If the current block is a Template Part block, inject the `theme` attribute. |
|
1209 * Furthermore, prepend the markup for any blocks hooked `before` the given block and as its parent's |
|
1210 * `first_child`, respectively, to the serialized markup for the given block. |
|
1211 * |
|
1212 * @param array $block The block to inject the theme attribute into, and hooked blocks before. Passed by reference. |
|
1213 * @param array $parent_block The parent block of the given block. Passed by reference. Default null. |
|
1214 * @param array $prev The previous sibling block of the given block. Default null. |
|
1215 * @return string The serialized markup for the given block, with the markup for any hooked blocks prepended to it. |
|
1216 */ |
|
1217 return function ( &$block, &$parent_block = null, $prev = null ) use ( $hooked_blocks, $context, $callback ) { |
|
1218 _inject_theme_attribute_in_template_part_block( $block ); |
|
1219 |
|
1220 $markup = ''; |
|
1221 |
|
1222 if ( $parent_block && ! $prev ) { |
|
1223 // Candidate for first-child insertion. |
|
1224 $markup .= call_user_func_array( |
|
1225 $callback, |
|
1226 array( &$parent_block, 'first_child', $hooked_blocks, $context ) |
|
1227 ); |
|
1228 } |
|
1229 |
|
1230 $markup .= call_user_func_array( |
|
1231 $callback, |
|
1232 array( &$block, 'before', $hooked_blocks, $context ) |
|
1233 ); |
|
1234 |
|
1235 return $markup; |
|
1236 }; |
|
1237 } |
|
1238 |
|
1239 /** |
|
1240 * Returns a function that injects the hooked blocks after a given block. |
|
1241 * |
|
1242 * The returned function can be used as `$post_callback` argument to `traverse_and_serialize_block(s)`, |
|
1243 * where it will append the markup for any blocks hooked `after` the given block and as its parent's |
|
1244 * `last_child`, respectively. |
|
1245 * |
|
1246 * This function is meant for internal use only. |
|
1247 * |
|
1248 * @since 6.4.0 |
|
1249 * @since 6.5.0 Added $callback argument. |
|
1250 * @access private |
|
1251 * |
|
1252 * @param array $hooked_blocks An array of blocks hooked to another block. |
|
1253 * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, |
|
1254 * or pattern that the blocks belong to. |
|
1255 * @param callable $callback A function that will be called for each block to generate |
|
1256 * the markup for a given list of blocks that are hooked to it. |
|
1257 * Default: 'insert_hooked_blocks'. |
|
1258 * @return callable A function that returns the serialized markup for the given block, |
|
1259 * including the markup for any hooked blocks after it. |
|
1260 */ |
|
1261 function make_after_block_visitor( $hooked_blocks, $context, $callback = 'insert_hooked_blocks' ) { |
|
1262 /** |
|
1263 * Injects hooked blocks after the given block, and returns the serialized markup. |
|
1264 * |
|
1265 * Append the markup for any blocks hooked `after` the given block and as its parent's |
|
1266 * `last_child`, respectively, to the serialized markup for the given block. |
|
1267 * |
|
1268 * @param array $block The block to inject the hooked blocks after. Passed by reference. |
|
1269 * @param array $parent_block The parent block of the given block. Passed by reference. Default null. |
|
1270 * @param array $next The next sibling block of the given block. Default null. |
|
1271 * @return string The serialized markup for the given block, with the markup for any hooked blocks appended to it. |
|
1272 */ |
|
1273 return function ( &$block, &$parent_block = null, $next = null ) use ( $hooked_blocks, $context, $callback ) { |
|
1274 $markup = call_user_func_array( |
|
1275 $callback, |
|
1276 array( &$block, 'after', $hooked_blocks, $context ) |
|
1277 ); |
|
1278 |
|
1279 if ( $parent_block && ! $next ) { |
|
1280 // Candidate for last-child insertion. |
|
1281 $markup .= call_user_func_array( |
|
1282 $callback, |
|
1283 array( &$parent_block, 'last_child', $hooked_blocks, $context ) |
|
1284 ); |
|
1285 } |
|
1286 |
|
1287 return $markup; |
|
1288 }; |
508 } |
1289 } |
509 |
1290 |
510 /** |
1291 /** |
511 * Given an array of attributes, returns a string in the serialized attributes |
1292 * Given an array of attributes, returns a string in the serialized attributes |
512 * format prepared for post content. |
1293 * format prepared for post content. |
617 $block_content |
1409 $block_content |
618 ); |
1410 ); |
619 } |
1411 } |
620 |
1412 |
621 /** |
1413 /** |
622 * Returns a joined string of the aggregate serialization of the given parsed |
1414 * Returns a joined string of the aggregate serialization of the given |
623 * blocks. |
1415 * parsed blocks. |
624 * |
1416 * |
625 * @since 5.3.1 |
1417 * @since 5.3.1 |
626 * |
1418 * |
627 * @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block(). |
1419 * @param array[] $blocks { |
|
1420 * Array of block structures. |
|
1421 * |
|
1422 * @type array ...$0 { |
|
1423 * A representative array of a single parsed block object. See WP_Block_Parser_Block. |
|
1424 * |
|
1425 * @type string $blockName Name of block. |
|
1426 * @type array $attrs Attributes from block comment delimiters. |
|
1427 * @type array[] $innerBlocks List of inner blocks. An array of arrays that |
|
1428 * have the same structure as this one. |
|
1429 * @type string $innerHTML HTML from inside block comment delimiters. |
|
1430 * @type array $innerContent List of string fragments and null markers where |
|
1431 * inner blocks were found. |
|
1432 * } |
|
1433 * } |
628 * @return string String of rendered HTML. |
1434 * @return string String of rendered HTML. |
629 */ |
1435 */ |
630 function serialize_blocks( $blocks ) { |
1436 function serialize_blocks( $blocks ) { |
631 return implode( '', array_map( 'serialize_block', $blocks ) ); |
1437 return implode( '', array_map( 'serialize_block', $blocks ) ); |
632 } |
1438 } |
633 |
1439 |
634 /** |
1440 /** |
635 * Filters and sanitizes block content to remove non-allowable HTML from |
1441 * Traverses a parsed block tree and applies callbacks before and after serializing it. |
636 * parsed block attribute values. |
1442 * |
|
1443 * Recursively traverses the block and its inner blocks and applies the two callbacks provided as |
|
1444 * arguments, the first one before serializing the block, and the second one after serializing it. |
|
1445 * If either callback returns a string value, it will be prepended and appended to the serialized |
|
1446 * block markup, respectively. |
|
1447 * |
|
1448 * The callbacks will receive a reference to the current block as their first argument, so that they |
|
1449 * can also modify it, and the current block's parent block as second argument. Finally, the |
|
1450 * `$pre_callback` receives the previous block, whereas the `$post_callback` receives |
|
1451 * the next block as third argument. |
|
1452 * |
|
1453 * Serialized blocks are returned including comment delimiters, and with all attributes serialized. |
|
1454 * |
|
1455 * This function should be used when there is a need to modify the saved block, or to inject markup |
|
1456 * into the return value. Prefer `serialize_block` when preparing a block to be saved to post content. |
|
1457 * |
|
1458 * This function is meant for internal use only. |
|
1459 * |
|
1460 * @since 6.4.0 |
|
1461 * @access private |
|
1462 * |
|
1463 * @see serialize_block() |
|
1464 * |
|
1465 * @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block. |
|
1466 * @param callable $pre_callback Callback to run on each block in the tree before it is traversed and serialized. |
|
1467 * It is called with the following arguments: &$block, $parent_block, $previous_block. |
|
1468 * Its string return value will be prepended to the serialized block markup. |
|
1469 * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized. |
|
1470 * It is called with the following arguments: &$block, $parent_block, $next_block. |
|
1471 * Its string return value will be appended to the serialized block markup. |
|
1472 * @return string Serialized block markup. |
|
1473 */ |
|
1474 function traverse_and_serialize_block( $block, $pre_callback = null, $post_callback = null ) { |
|
1475 $block_content = ''; |
|
1476 $block_index = 0; |
|
1477 |
|
1478 foreach ( $block['innerContent'] as $chunk ) { |
|
1479 if ( is_string( $chunk ) ) { |
|
1480 $block_content .= $chunk; |
|
1481 } else { |
|
1482 $inner_block = $block['innerBlocks'][ $block_index ]; |
|
1483 |
|
1484 if ( is_callable( $pre_callback ) ) { |
|
1485 $prev = 0 === $block_index |
|
1486 ? null |
|
1487 : $block['innerBlocks'][ $block_index - 1 ]; |
|
1488 |
|
1489 $block_content .= call_user_func_array( |
|
1490 $pre_callback, |
|
1491 array( &$inner_block, &$block, $prev ) |
|
1492 ); |
|
1493 } |
|
1494 |
|
1495 if ( is_callable( $post_callback ) ) { |
|
1496 $next = count( $block['innerBlocks'] ) - 1 === $block_index |
|
1497 ? null |
|
1498 : $block['innerBlocks'][ $block_index + 1 ]; |
|
1499 |
|
1500 $post_markup = call_user_func_array( |
|
1501 $post_callback, |
|
1502 array( &$inner_block, &$block, $next ) |
|
1503 ); |
|
1504 } |
|
1505 |
|
1506 $block_content .= traverse_and_serialize_block( $inner_block, $pre_callback, $post_callback ); |
|
1507 $block_content .= isset( $post_markup ) ? $post_markup : ''; |
|
1508 |
|
1509 ++$block_index; |
|
1510 } |
|
1511 } |
|
1512 |
|
1513 if ( ! is_array( $block['attrs'] ) ) { |
|
1514 $block['attrs'] = array(); |
|
1515 } |
|
1516 |
|
1517 return get_comment_delimited_block_content( |
|
1518 $block['blockName'], |
|
1519 $block['attrs'], |
|
1520 $block_content |
|
1521 ); |
|
1522 } |
|
1523 |
|
1524 /** |
|
1525 * Replaces patterns in a block tree with their content. |
|
1526 * |
|
1527 * @since 6.6.0 |
|
1528 * |
|
1529 * @param array $blocks An array blocks. |
|
1530 * |
|
1531 * @return array An array of blocks with patterns replaced by their content. |
|
1532 */ |
|
1533 function resolve_pattern_blocks( $blocks ) { |
|
1534 static $inner_content; |
|
1535 // Keep track of seen references to avoid infinite loops. |
|
1536 static $seen_refs = array(); |
|
1537 $i = 0; |
|
1538 while ( $i < count( $blocks ) ) { |
|
1539 if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) { |
|
1540 $attrs = $blocks[ $i ]['attrs']; |
|
1541 |
|
1542 if ( empty( $attrs['slug'] ) ) { |
|
1543 ++$i; |
|
1544 continue; |
|
1545 } |
|
1546 |
|
1547 $slug = $attrs['slug']; |
|
1548 |
|
1549 if ( isset( $seen_refs[ $slug ] ) ) { |
|
1550 // Skip recursive patterns. |
|
1551 array_splice( $blocks, $i, 1 ); |
|
1552 continue; |
|
1553 } |
|
1554 |
|
1555 $registry = WP_Block_Patterns_Registry::get_instance(); |
|
1556 $pattern = $registry->get_registered( $slug ); |
|
1557 |
|
1558 // Skip unknown patterns. |
|
1559 if ( ! $pattern ) { |
|
1560 ++$i; |
|
1561 continue; |
|
1562 } |
|
1563 |
|
1564 $blocks_to_insert = parse_blocks( $pattern['content'] ); |
|
1565 $seen_refs[ $slug ] = true; |
|
1566 $prev_inner_content = $inner_content; |
|
1567 $inner_content = null; |
|
1568 $blocks_to_insert = resolve_pattern_blocks( $blocks_to_insert ); |
|
1569 $inner_content = $prev_inner_content; |
|
1570 unset( $seen_refs[ $slug ] ); |
|
1571 array_splice( $blocks, $i, 1, $blocks_to_insert ); |
|
1572 |
|
1573 // If we have inner content, we need to insert nulls in the |
|
1574 // inner content array, otherwise serialize_blocks will skip |
|
1575 // blocks. |
|
1576 if ( $inner_content ) { |
|
1577 $null_indices = array_keys( $inner_content, null, true ); |
|
1578 $content_index = $null_indices[ $i ]; |
|
1579 $nulls = array_fill( 0, count( $blocks_to_insert ), null ); |
|
1580 array_splice( $inner_content, $content_index, 1, $nulls ); |
|
1581 } |
|
1582 |
|
1583 // Skip inserted blocks. |
|
1584 $i += count( $blocks_to_insert ); |
|
1585 } else { |
|
1586 if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { |
|
1587 $prev_inner_content = $inner_content; |
|
1588 $inner_content = $blocks[ $i ]['innerContent']; |
|
1589 $blocks[ $i ]['innerBlocks'] = resolve_pattern_blocks( |
|
1590 $blocks[ $i ]['innerBlocks'] |
|
1591 ); |
|
1592 $blocks[ $i ]['innerContent'] = $inner_content; |
|
1593 $inner_content = $prev_inner_content; |
|
1594 } |
|
1595 ++$i; |
|
1596 } |
|
1597 } |
|
1598 return $blocks; |
|
1599 } |
|
1600 |
|
1601 /** |
|
1602 * Given an array of parsed block trees, applies callbacks before and after serializing them and |
|
1603 * returns their concatenated output. |
|
1604 * |
|
1605 * Recursively traverses the blocks and their inner blocks and applies the two callbacks provided as |
|
1606 * arguments, the first one before serializing a block, and the second one after serializing. |
|
1607 * If either callback returns a string value, it will be prepended and appended to the serialized |
|
1608 * block markup, respectively. |
|
1609 * |
|
1610 * The callbacks will receive a reference to the current block as their first argument, so that they |
|
1611 * can also modify it, and the current block's parent block as second argument. Finally, the |
|
1612 * `$pre_callback` receives the previous block, whereas the `$post_callback` receives |
|
1613 * the next block as third argument. |
|
1614 * |
|
1615 * Serialized blocks are returned including comment delimiters, and with all attributes serialized. |
|
1616 * |
|
1617 * This function should be used when there is a need to modify the saved blocks, or to inject markup |
|
1618 * into the return value. Prefer `serialize_blocks` when preparing blocks to be saved to post content. |
|
1619 * |
|
1620 * This function is meant for internal use only. |
|
1621 * |
|
1622 * @since 6.4.0 |
|
1623 * @access private |
|
1624 * |
|
1625 * @see serialize_blocks() |
|
1626 * |
|
1627 * @param array[] $blocks An array of parsed blocks. See WP_Block_Parser_Block. |
|
1628 * @param callable $pre_callback Callback to run on each block in the tree before it is traversed and serialized. |
|
1629 * It is called with the following arguments: &$block, $parent_block, $previous_block. |
|
1630 * Its string return value will be prepended to the serialized block markup. |
|
1631 * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized. |
|
1632 * It is called with the following arguments: &$block, $parent_block, $next_block. |
|
1633 * Its string return value will be appended to the serialized block markup. |
|
1634 * @return string Serialized block markup. |
|
1635 */ |
|
1636 function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) { |
|
1637 $result = ''; |
|
1638 $parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference. |
|
1639 |
|
1640 foreach ( $blocks as $index => $block ) { |
|
1641 if ( is_callable( $pre_callback ) ) { |
|
1642 $prev = 0 === $index |
|
1643 ? null |
|
1644 : $blocks[ $index - 1 ]; |
|
1645 |
|
1646 $result .= call_user_func_array( |
|
1647 $pre_callback, |
|
1648 array( &$block, &$parent_block, $prev ) |
|
1649 ); |
|
1650 } |
|
1651 |
|
1652 if ( is_callable( $post_callback ) ) { |
|
1653 $next = count( $blocks ) - 1 === $index |
|
1654 ? null |
|
1655 : $blocks[ $index + 1 ]; |
|
1656 |
|
1657 $post_markup = call_user_func_array( |
|
1658 $post_callback, |
|
1659 array( &$block, &$parent_block, $next ) |
|
1660 ); |
|
1661 } |
|
1662 |
|
1663 $result .= traverse_and_serialize_block( $block, $pre_callback, $post_callback ); |
|
1664 $result .= isset( $post_markup ) ? $post_markup : ''; |
|
1665 } |
|
1666 |
|
1667 return $result; |
|
1668 } |
|
1669 |
|
1670 /** |
|
1671 * Filters and sanitizes block content to remove non-allowable HTML |
|
1672 * from parsed block attribute values. |
637 * |
1673 * |
638 * @since 5.3.1 |
1674 * @since 5.3.1 |
639 * |
1675 * |
640 * @param string $text Text that may contain block content. |
1676 * @param string $text Text that may contain block content. |
641 * @param array[]|string $allowed_html An array of allowed HTML elements |
1677 * @param array[]|string $allowed_html Optional. An array of allowed HTML elements and attributes, |
642 * and attributes, or a context name |
1678 * or a context name such as 'post'. See wp_kses_allowed_html() |
643 * such as 'post'. |
1679 * for the list of accepted context names. Default 'post'. |
644 * @param string[] $allowed_protocols Array of allowed URL protocols. |
1680 * @param string[] $allowed_protocols Optional. Array of allowed URL protocols. |
|
1681 * Defaults to the result of wp_allowed_protocols(). |
645 * @return string The filtered and sanitized content result. |
1682 * @return string The filtered and sanitized content result. |
646 */ |
1683 */ |
647 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) { |
1684 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) { |
648 $result = ''; |
1685 $result = ''; |
|
1686 |
|
1687 if ( str_contains( $text, '<!--' ) && str_contains( $text, '--->' ) ) { |
|
1688 $text = preg_replace_callback( '%<!--(.*?)--->%', '_filter_block_content_callback', $text ); |
|
1689 } |
649 |
1690 |
650 $blocks = parse_blocks( $text ); |
1691 $blocks = parse_blocks( $text ); |
651 foreach ( $blocks as $block ) { |
1692 foreach ( $blocks as $block ) { |
652 $block = filter_block_kses( $block, $allowed_html, $allowed_protocols ); |
1693 $block = filter_block_kses( $block, $allowed_html, $allowed_protocols ); |
653 $result .= serialize_block( $block ); |
1694 $result .= serialize_block( $block ); |