PluginProbe
Gutenberg / 8.5.1
Gutenberg v8.5.1
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / lib / compat.php

compat.php in Gutenberg 8.5.1, at lib/compat.php

571 lines 19.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Temporary compatibility shims for features present in Gutenberg, pending
4 * upstream commit to the WordPress core source repository. Functions here
5 * exist only as long as necessary for corresponding WordPress support, and
6 * each should be associated with a Trac ticket.
7 *
8 * @package gutenberg
9 */
10
11 /**
12 * These functions can be removed when plugin support requires WordPress 5.5.0+.
13 *
14 * @see https://core.trac.wordpress.org/ticket/50263
15 * @see https://core.trac.wordpress.org/changeset/48141
16 */
17 if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
18 /**
19 * Removes the block asset's path prefix if provided.
20 *
21 * @since 5.5.0
22 *
23 * @param string $asset_handle_or_path Asset handle or prefixed path.
24 *
25 * @return string Path without the prefix or the original value.
26 */
27 function remove_block_asset_path_prefix( $asset_handle_or_path ) {
28 $path_prefix = 'file:';
29 if ( strpos( $asset_handle_or_path, $path_prefix ) !== 0 ) {
30 return $asset_handle_or_path;
31 }
32 return substr(
33 $asset_handle_or_path,
34 strlen( $path_prefix )
35 );
36 }
37
38 /**
39 * Generates the name for an asset based on the name of the block
40 * and the field name provided.
41 *
42 * @since 5.5.0
43 *
44 * @param string $block_name Name of the block.
45 * @param string $field_name Name of the metadata field.
46 *
47 * @return string Generated asset name for the block's field.
48 */
49 function generate_block_asset_handle( $block_name, $field_name ) {
50 $field_mappings = array(
51 'editorScript' => 'editor-script',
52 'script' => 'script',
53 'editorStyle' => 'editor-style',
54 'style' => 'style',
55 );
56 return str_replace( '/', '-', $block_name ) .
57 '-' . $field_mappings[ $field_name ];
58 }
59
60 /**
61 * Finds a script handle for the selected block metadata field. It detects
62 * when a path to file was provided and finds a corresponding
63 * asset file with details necessary to register the script under
64 * automatically generated handle name. It returns unprocessed script handle
65 * otherwise.
66 *
67 * @since 5.5.0
68 *
69 * @param array $metadata Block metadata.
70 * @param string $field_name Field name to pick from metadata.
71 *
72 * @return string|boolean Script handle provided directly or created through
73 * script's registration, or false on failure.
74 */
75 function register_block_script_handle( $metadata, $field_name ) {
76 if ( empty( $metadata[ $field_name ] ) ) {
77 return false;
78 }
79 $script_handle = $metadata[ $field_name ];
80 $script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
81 if ( $script_handle === $script_path ) {
82 return $script_handle;
83 }
84
85 $script_handle = generate_block_asset_handle( $metadata['name'], $field_name );
86 $script_asset_path = realpath(
87 dirname( $metadata['file'] ) . '/' .
88 substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
89 );
90 if ( ! file_exists( $script_asset_path ) ) {
91 $message = sprintf(
92 /* translators: %1: field name. %2: block name */
93 __( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
94 $field_name,
95 $metadata['name']
96 );
97 _doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
98 return false;
99 }
100 $script_asset = require( $script_asset_path );
101 $result = wp_register_script(
102 $script_handle,
103 plugins_url( $script_path, $metadata['file'] ),
104 $script_asset['dependencies'],
105 $script_asset['version']
106 );
107 return $result ? $script_handle : false;
108 }
109
110 /**
111 * Finds a style handle for the block metadata field. It detects when a path
112 * to file was provided and registers the style under automatically
113 * generated handle name. It returns unprocessed style handle otherwise.
114 *
115 * @since 5.5.0
116 *
117 * @param array $metadata Block metadata.
118 * @param string $field_name Field name to pick from metadata.
119 *
120 * @return string|boolean Style handle provided directly or created through
121 * style's registration, or false on failure.
122 */
123 function register_block_style_handle( $metadata, $field_name ) {
124 if ( empty( $metadata[ $field_name ] ) ) {
125 return false;
126 }
127 $style_handle = $metadata[ $field_name ];
128 $style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
129 if ( $style_handle === $style_path ) {
130 return $style_handle;
131 }
132
133 $style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
134 $block_dir = dirname( $metadata['file'] );
135 $result = wp_register_style(
136 $style_handle,
137 plugins_url( $style_path, $metadata['file'] ),
138 array(),
139 filemtime( realpath( "$block_dir/$style_path" ) )
140 );
141 return $result ? $style_handle : false;
142 }
143
144 /**
145 * Registers a block type from metadata stored in the `block.json` file.
146 *
147 * @since 7.9.0
148 *
149 * @param string $file_or_folder Path to the JSON file with metadata definition for
150 * the block or path to the folder where the `block.json` file is located.
151 * @param array $args {
152 * Optional. Array of block type arguments. Any arguments may be defined, however the
153 * ones described below are supported by default. Default empty array.
154 *
155 * @type callable $render_callback Callback used to render blocks of this block type.
156 * }
157 * @return WP_Block_Type|false The registered block type on success, or false on failure.
158 */
159 function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
160 $filename = 'block.json';
161 $metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
162 trailingslashit( $file_or_folder ) . $filename :
163 $file_or_folder;
164 if ( ! file_exists( $metadata_file ) ) {
165 return false;
166 }
167
168 $metadata = json_decode( file_get_contents( $metadata_file ), true );
169 if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
170 return false;
171 }
172 $metadata['file'] = $metadata_file;
173
174 $settings = array();
175 $property_mappings = array(
176 'title' => 'title',
177 'category' => 'category',
178 'parent' => 'parent',
179 'icon' => 'icon',
180 'description' => 'description',
181 'keywords' => 'keywords',
182 'attributes' => 'attributes',
183 'providesContext' => 'provides_context',
184 'usesContext' => 'uses_context',
185 // Deprecated: remove with Gutenberg 8.6 release.
186 'context' => 'context',
187 'supports' => 'supports',
188 'styles' => 'styles',
189 'example' => 'example',
190 );
191
192 foreach ( $property_mappings as $key => $mapped_key ) {
193 if ( isset( $metadata[ $key ] ) ) {
194 $settings[ $mapped_key ] = $metadata[ $key ];
195 }
196 }
197
198 if ( ! empty( $metadata['editorScript'] ) ) {
199 $settings['editor_script'] = register_block_script_handle(
200 $metadata,
201 'editorScript'
202 );
203 }
204
205 if ( ! empty( $metadata['script'] ) ) {
206 $settings['script'] = register_block_script_handle(
207 $metadata,
208 'script'
209 );
210 }
211
212 if ( ! empty( $metadata['editorStyle'] ) ) {
213 $settings['editor_style'] = register_block_style_handle(
214 $metadata,
215 'editorStyle'
216 );
217 }
218
219 if ( ! empty( $metadata['style'] ) ) {
220 $settings['style'] = register_block_style_handle(
221 $metadata,
222 'style'
223 );
224 }
225
226 return register_block_type(
227 $metadata['name'],
228 array_merge(
229 $settings,
230 $args
231 )
232 );
233 }
234 }
235
236 /**
237 * Extends block editor settings to include a list of image dimensions per size.
238 *
239 * This can be removed when plugin support requires WordPress 5.4.0+.
240 *
241 * @see https://core.trac.wordpress.org/ticket/49389
242 * @see https://core.trac.wordpress.org/changeset/47240
243 *
244 * @param array $settings Default editor settings.
245 *
246 * @return array Filtered editor settings.
247 */
248 function gutenberg_extend_settings_image_dimensions( $settings ) {
249 /*
250 * Only filter settings if:
251 * 1. `imageDimensions` is not already assigned, in which case it can be
252 * assumed to have been set from WordPress 5.4.0+ default settings.
253 * 2. `imageSizes` is an array. Plugins may run `block_editor_settings`
254 * directly and not provide all properties of the settings array.
255 */
256 if ( ! isset( $settings['imageDimensions'] ) && ! empty( $settings['imageSizes'] ) ) {
257 $image_dimensions = array();
258 $all_sizes = wp_get_registered_image_subsizes();
259 foreach ( $settings['imageSizes'] as $size ) {
260 $key = $size['slug'];
261 if ( isset( $all_sizes[ $key ] ) ) {
262 $image_dimensions[ $key ] = $all_sizes[ $key ];
263 }
264 }
265 $settings['imageDimensions'] = $image_dimensions;
266 }
267
268 return $settings;
269 }
270 add_filter( 'block_editor_settings', 'gutenberg_extend_settings_image_dimensions' );
271
272 /**
273 * Adds a polyfill for the WHATWG URL in environments which do not support it.
274 * The intention in how this action is handled is under the assumption that this
275 * code would eventually be placed at `wp_default_packages_vendor`, which is
276 * called as a result of `wp_default_packages` via the `wp_default_scripts`.
277 *
278 * This can be removed when plugin support requires WordPress 5.4.0+.
279 *
280 * The script registration occurs in `gutenberg_register_vendor_scripts`, which
281 * should be removed in coordination with this function.
282 *
283 * @see gutenberg_register_vendor_scripts
284 * @see https://core.trac.wordpress.org/ticket/49360
285 * @see https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
286 * @see https://developer.wordpress.org/reference/functions/wp_default_packages_vendor/
287 *
288 * @since 7.3.0
289 *
290 * @param WP_Scripts $scripts WP_Scripts object.
291 */
292 function gutenberg_add_url_polyfill( $scripts ) {
293 did_action( 'init' ) && $scripts->add_inline_script(
294 'wp-polyfill',
295 wp_get_script_polyfill(
296 $scripts,
297 array(
298 'window.URL && window.URL.prototype && window.URLSearchParams' => 'wp-polyfill-url',
299 )
300 )
301 );
302 }
303 add_action( 'wp_default_scripts', 'gutenberg_add_url_polyfill', 20 );
304
305 /**
306 * Adds a polyfill for DOMRect in environments which do not support it.
307 *
308 * This can be removed when plugin support requires WordPress 5.4.0+.
309 *
310 * The script registration occurs in `gutenberg_register_vendor_scripts`, which
311 * should be removed in coordination with this function.
312 *
313 * @see gutenberg_register_vendor_scripts
314 * @see gutenberg_add_url_polyfill
315 * @see https://core.trac.wordpress.org/ticket/49360
316 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMRect
317 * @see https://developer.wordpress.org/reference/functions/wp_default_packages_vendor/
318 *
319 * @since 7.5.0
320 *
321 * @param WP_Scripts $scripts WP_Scripts object.
322 */
323 function gutenberg_add_dom_rect_polyfill( $scripts ) {
324 did_action( 'init' ) && $scripts->add_inline_script(
325 'wp-polyfill',
326 wp_get_script_polyfill(
327 $scripts,
328 array(
329 'window.DOMRect' => 'wp-polyfill-dom-rect',
330 )
331 )
332 );
333 }
334 add_action( 'wp_default_scripts', 'gutenberg_add_dom_rect_polyfill', 20 );
335
336 /**
337 * Filters default block categories to substitute legacy category names with new
338 * block categories.
339 *
340 * This can be removed when plugin support requires WordPress 5.5.0+.
341 *
342 * @see https://core.trac.wordpress.org/ticket/50278
343 * @see https://core.trac.wordpress.org/changeset/48177
344 *
345 * @param array[] $default_categories Array of block categories.
346 *
347 * @return array[] Filtered block categories.
348 */
349 function gutenberg_replace_default_block_categories( $default_categories ) {
350 $substitution = array(
351 'common' => array(
352 'slug' => 'text',
353 'title' => __( 'Text', 'gutenberg' ),
354 'icon' => null,
355 ),
356 'formatting' => array(
357 'slug' => 'media',
358 'title' => __( 'Media', 'gutenberg' ),
359 'icon' => null,
360 ),
361 'layout' => array(
362 'slug' => 'design',
363 'title' => __( 'Design', 'gutenberg' ),
364 'icon' => null,
365 ),
366 );
367
368 // Loop default categories to perform in-place substitution by legacy slug.
369 foreach ( $default_categories as $i => $default_category ) {
370 $slug = $default_category['slug'];
371 if ( isset( $substitution[ $slug ] ) ) {
372 $default_categories[ $i ] = $substitution[ $slug ];
373 unset( $substitution[ $slug ] );
374 }
375 }
376
377 /*
378 * At this point, `$substitution` should contain only the categories which
379 * could not be in-place substituted with a default category, likely in the
380 * case that core has since been updated to use the default categories.
381 * Check to verify they exist.
382 */
383 $default_category_slugs = wp_list_pluck( $default_categories, 'slug' );
384 foreach ( $substitution as $i => $substitute_category ) {
385 if ( in_array( $substitute_category['slug'], $default_category_slugs, true ) ) {
386 unset( $substitution[ $i ] );
387 }
388 }
389
390 /*
391 * Any substitutes remaining should be appended, as they are not yet
392 * assigned in the default categories array.
393 */
394 return array_merge( $default_categories, array_values( $substitution ) );
395 }
396 add_filter( 'block_categories', 'gutenberg_replace_default_block_categories' );
397
398 /**
399 * Shim that hooks into `pre_render_block` so as to override `render_block` with
400 * a function that assigns block context.
401 *
402 * This can be removed when plugin support requires WordPress 5.5.0+.
403 *
404 * @see https://core.trac.wordpress.org/ticket/49927
405 * @see https://core.trac.wordpress.org/changeset/48243
406 *
407 * @param string|null $pre_render The pre-rendered content. Defaults to null.
408 * @param array $parsed_block The parsed block being rendered.
409 *
410 * @return string String of rendered HTML.
411 */
412 function gutenberg_render_block_with_assigned_block_context( $pre_render, $parsed_block ) {
413 global $post, $wp_query;
414
415 /*
416 * If a non-null value is provided, a filter has run at an earlier priority
417 * and has already handled custom rendering and should take precedence.
418 */
419 if ( null !== $pre_render ) {
420 return $pre_render;
421 }
422
423 $source_block = $parsed_block;
424
425 /** This filter is documented in src/wp-includes/blocks.php */
426 $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
427
428 $context = array();
429
430 if ( $post instanceof WP_Post ) {
431 $context['postId'] = $post->ID;
432
433 /*
434 * The `postType` context is largely unnecessary server-side, since the
435 * ID is usually sufficient on its own. That being said, since a block's
436 * manifest is expected to be shared between the server and the client,
437 * it should be included to consistently fulfill the expectation.
438 */
439 $context['postType'] = $post->post_type;
440 }
441
442 if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) {
443 $context['query'] = array( 'categoryIds' => array() );
444
445 foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) {
446 $context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id;
447 }
448 }
449
450 /**
451 * Filters the default context provided to a rendered block.
452 *
453 * @param array $context Default context.
454 * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
455 */
456 $context = apply_filters( 'render_block_context', $context, $parsed_block );
457
458 $block = new WP_Block( $parsed_block, $context );
459
460 return $block->render();
461 }
462 add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 );
463
464 /**
465 * Shim that hooks into `wp_update_nav_menu_item` and makes it so that nav menu
466 * items support a 'content' field. This field contains HTML and is used by nav
467 * menu items with `type` set to `'html'`.
468 *
469 * Specifically, this shim makes it so that:
470 *
471 * 1) The `wp_update_nav_menu_item()` function supports setting
472 * `'menu-item-content'` on a menu item. When merged to Core, this functionality
473 * should exist in `wp_update_nav_menu_item()`.
474 *
475 * 2) The `customize_save` ajax action supports setting `'content'` on a nav
476 * menu item. When merged to Core, this functionality should exist in
477 * `WP_Customize_Manager::save()`.
478 *
479 * This shim can be removed when the Gutenberg plugin requires a WordPress
480 * version that has the ticket below.
481 *
482 * @see https://core.trac.wordpress.org/ticket/50544
483 *
484 * @param int $menu_id ID of the updated menu.
485 * @param int $menu_item_db_id ID of the new menu item.
486 * @param array $args An array of arguments used to update/add the menu item.
487 */
488 function gutenberg_update_nav_menu_item_content( $menu_id, $menu_item_db_id, $args ) {
489 global $wp_customize;
490
491 // Support setting content in customize_save admin-ajax.php requests by
492 // grabbing the unsanitized $_POST values.
493 if ( isset( $wp_customize ) ) {
494 $values = $wp_customize->unsanitized_post_values();
495 if ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
496 if ( is_string( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) {
497 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content'];
498 } elseif ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'] ) ) {
499 $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'];
500 }
501 }
502 }
503
504 $defaults = array(
505 'menu-item-content' => '',
506 );
507
508 $args = wp_parse_args( $args, $defaults );
509
510 update_post_meta( $menu_item_db_id, '_menu_item_content', wp_slash( $args['menu-item-content'] ) );
511 }
512 add_action( 'wp_update_nav_menu_item', 'gutenberg_update_nav_menu_item_content', 10, 3 );
513
514 /**
515 * Shim that hooks into `wp_setup_nav_menu_items` and makes it so that nav menu
516 * items have a 'content' field. This field contains HTML and is used by nav
517 * menu items with `type` set to `'html'`.
518 *
519 * Specifically, this shim makes it so that the `wp_setup_nav_menu_item()`
520 * function sets `content` on the returned menu item. When merged to Core, this
521 * functionality should exist in `wp_setup_nav_menu_item()`.
522 *
523 * This shim can be removed when the Gutenberg plugin requires a WordPress
524 * version that has the ticket below.
525 *
526 * @see https://core.trac.wordpress.org/ticket/50544
527 *
528 * @param object $menu_item The menu item object.
529 */
530 function gutenberg_setup_html_nav_menu_item( $menu_item ) {
531 if ( 'html' === $menu_item->type ) {
532 $menu_item->type_label = __( 'HTML', 'gutenberg' );
533 $menu_item->content = ! isset( $menu_item->content ) ? get_post_meta( $menu_item->db_id, '_menu_item_content', true ) : $menu_item->content;
534 }
535
536 return $menu_item;
537 }
538 add_filter( 'wp_setup_nav_menu_item', 'gutenberg_setup_html_nav_menu_item' );
539
540 /**
541 * Shim that hooks into `walker_nav_menu_start_el` and makes it so that the
542 * default walker which renders a menu will correctly render the HTML associated
543 * with any navigation menu item that has `type` set to `'html`'.
544 *
545 * Specifically, this shim makes it so that `Walker_Nav_Menu::start_el()`
546 * renders the `content` of a nav menu item when its `type` is `'html'`. When
547 * merged to Core, this functionality should exist in
548 * `Walker_Nav_Menu::start_el()`.
549 *
550 * This shim can be removed when the Gutenberg plugin requires a WordPress
551 * version that has the ticket below.
552 *
553 * @see https://core.trac.wordpress.org/ticket/50544
554 *
555 * @param string $item_output The menu item's starting HTML output.
556 * @param WP_Post $item Menu item data object.
557 * @param int $depth Depth of menu item. Used for padding.
558 * @param stdClass $args An object of wp_nav_menu() arguments.
559 */
560 function gutenberg_output_html_nav_menu_item( $item_output, $item, $depth, $args ) {
561 if ( 'html' === $item->type ) {
562 $item_output = $args->before;
563 /** This filter is documented in wp-includes/post-template.php */
564 $item_output .= apply_filters( 'the_content', $item->content );
565 $item_output .= $args->after;
566 }
567
568 return $item_output;
569 }
570 add_filter( 'walker_nav_menu_start_el', 'gutenberg_output_html_nav_menu_item', 10, 4 );
571