PluginProbe
Gutenberg / 9.6.1
Gutenberg v9.6.1
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 7.4.0 All 402 releases
gutenberg / lib / compat.php

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

603 lines 20.8 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 * Adds a wp.date.setSettings with timezone abbr parameter
238 *
239 * This can be removed when plugin support requires WordPress 5.6.0+.
240 *
241 * The script registration occurs in core wp-includes/script-loader.php
242 * wp_default_packages_inline_scripts()
243 *
244 * @since 8.6.0
245 *
246 * @param WP_Scripts $scripts WP_Scripts object.
247 */
248 function gutenberg_add_date_settings_timezone( $scripts ) {
249 if ( ! did_action( 'init' ) ) {
250 return;
251 }
252
253 global $wp_locale;
254
255 // Calculate the timezone abbr (EDT, PST) if possible.
256 $timezone_string = get_option( 'timezone_string', 'UTC' );
257 $timezone_abbr = '';
258
259 if ( ! empty( $timezone_string ) ) {
260 $timezone_date = new DateTime( null, new DateTimeZone( $timezone_string ) );
261 $timezone_abbr = $timezone_date->format( 'T' );
262 }
263
264 $scripts->add_inline_script(
265 'wp-date',
266 sprintf(
267 'wp.date.setSettings( %s );',
268 wp_json_encode(
269 array(
270 'l10n' => array(
271 'locale' => get_user_locale(),
272 'months' => array_values( $wp_locale->month ),
273 'monthsShort' => array_values( $wp_locale->month_abbrev ),
274 'weekdays' => array_values( $wp_locale->weekday ),
275 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
276 'meridiem' => (object) $wp_locale->meridiem,
277 'relative' => array(
278 /* translators: %s: Duration. */
279 'future' => __( '%s from now', 'default' ),
280 /* translators: %s: Duration. */
281 'past' => __( '%s ago', 'default' ),
282 ),
283 ),
284 'formats' => array(
285 /* translators: Time format, see https://www.php.net/date */
286 'time' => get_option( 'time_format', __( 'g:i a', 'default' ) ),
287 /* translators: Date format, see https://www.php.net/date */
288 'date' => get_option( 'date_format', __( 'F j, Y', 'default' ) ),
289 /* translators: Date/Time format, see https://www.php.net/date */
290 'datetime' => __( 'F j, Y g:i a', 'default' ),
291 /* translators: Abbreviated date/time format, see https://www.php.net/date */
292 'datetimeAbbreviated' => __( 'M j, Y g:i a', 'default' ),
293 ),
294 'timezone' => array(
295 'offset' => get_option( 'gmt_offset', 0 ),
296 'string' => $timezone_string,
297 'abbr' => $timezone_abbr,
298 ),
299 )
300 )
301 ),
302 'after'
303 );
304 }
305 add_action( 'wp_default_scripts', 'gutenberg_add_date_settings_timezone', 20 );
306
307 /**
308 * Filters default block categories to substitute legacy category names with new
309 * block categories.
310 *
311 * This can be removed when plugin support requires WordPress 5.5.0+.
312 *
313 * @see https://core.trac.wordpress.org/ticket/50278
314 * @see https://core.trac.wordpress.org/changeset/48177
315 *
316 * @param array[] $default_categories Array of block categories.
317 *
318 * @return array[] Filtered block categories.
319 */
320 function gutenberg_replace_default_block_categories( $default_categories ) {
321 $substitution = array(
322 'common' => array(
323 'slug' => 'text',
324 'title' => __( 'Text', 'gutenberg' ),
325 'icon' => null,
326 ),
327 'formatting' => array(
328 'slug' => 'media',
329 'title' => __( 'Media', 'gutenberg' ),
330 'icon' => null,
331 ),
332 'layout' => array(
333 'slug' => 'design',
334 'title' => __( 'Design', 'gutenberg' ),
335 'icon' => null,
336 ),
337 );
338
339 // Loop default categories to perform in-place substitution by legacy slug.
340 foreach ( $default_categories as $i => $default_category ) {
341 $slug = $default_category['slug'];
342 if ( isset( $substitution[ $slug ] ) ) {
343 $default_categories[ $i ] = $substitution[ $slug ];
344 unset( $substitution[ $slug ] );
345 }
346 }
347
348 /*
349 * At this point, `$substitution` should contain only the categories which
350 * could not be in-place substituted with a default category, likely in the
351 * case that core has since been updated to use the default categories.
352 * Check to verify they exist.
353 */
354 $default_category_slugs = wp_list_pluck( $default_categories, 'slug' );
355 foreach ( $substitution as $i => $substitute_category ) {
356 if ( in_array( $substitute_category['slug'], $default_category_slugs, true ) ) {
357 unset( $substitution[ $i ] );
358 }
359 }
360
361 /*
362 * Any substitutes remaining should be appended, as they are not yet
363 * assigned in the default categories array.
364 */
365 return array_merge( $default_categories, array_values( $substitution ) );
366 }
367 add_filter( 'block_categories', 'gutenberg_replace_default_block_categories' );
368
369 /**
370 * Shim that hooks into `pre_render_block` so as to override `render_block` with
371 * a function that assigns block context.
372 *
373 * The context handling can be removed when plugin support requires WordPress 5.5.0+.
374 *
375 * @see https://core.trac.wordpress.org/ticket/49927
376 * @see https://core.trac.wordpress.org/changeset/48243
377 *
378 * @param string|null $pre_render The pre-rendered content. Defaults to null.
379 * @param array $parsed_block The parsed block being rendered.
380 *
381 * @return string String of rendered HTML.
382 */
383 function gutenberg_render_block_with_assigned_block_context( $pre_render, $parsed_block ) {
384 $already_supports_context = version_compare( get_bloginfo( 'version' ), '5.5', '>=' );
385
386 /*
387 * If a non-null value is provided, a filter has run at an earlier priority
388 * and has already handled custom rendering and should take precedence.
389 */
390 if ( null !== $pre_render || $already_supports_context ) {
391 return $pre_render;
392 }
393
394 $source_block = $parsed_block;
395
396 /** This filter is documented in src/wp-includes/blocks.php */
397 $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
398
399 $context = array();
400
401 /**
402 * Filters the default context provided to a rendered block.
403 *
404 * @param array $context Default context.
405 * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
406 */
407 $context = apply_filters( 'render_block_context', $context, $parsed_block );
408
409 $block = new WP_Block( $parsed_block, $context );
410
411 return $block->render();
412 }
413 add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 );
414
415 /**
416 * Determine if the current theme needs to load separate block styles or not.
417 *
418 * @return bool
419 */
420 function gutenberg_should_load_separate_block_styles() {
421 $load_separate_styles = gutenberg_is_fse_theme();
422 /**
423 * Determine if separate styles will be loaded for blocks on-render or not.
424 *
425 * @param bool $load_separate_styles Whether separate styles will be loaded or not.
426 *
427 * @return bool
428 */
429 return apply_filters( 'load_separate_block_styles', $load_separate_styles );
430 }
431
432 /**
433 * Remove the `wp_enqueue_registered_block_scripts_and_styles` hook if needed.
434 *
435 * @return void
436 */
437 function gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles() {
438 if ( gutenberg_should_load_separate_block_styles() ) {
439 /**
440 * Avoid enqueueing block assets of all registered blocks for all posts, instead
441 * deferring to block render mechanics to enqueue scripts, thereby ensuring only
442 * blocks of the content have their assets enqueued.
443 *
444 * This can be removed once minimum support for the plugin is outside the range
445 * of the version associated with closure of the following ticket.
446 *
447 * @see https://core.trac.wordpress.org/ticket/50328
448 *
449 * @see WP_Block::render
450 */
451 remove_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' );
452 }
453 }
454
455 add_action( 'init', 'gutenberg_remove_hook_wp_enqueue_registered_block_scripts_and_styles' );
456
457 /**
458 * Callback hooked to the register_block_type_args filter.
459 *
460 * This hooks into block registration to inject the default context into the block object.
461 * It can be removed once the default context is added into Core.
462 *
463 * @param array $args Block attributes.
464 * @return array Block attributes.
465 */
466 function gutenberg_inject_default_block_context( $args ) {
467 if ( is_callable( $args['render_callback'] ) ) {
468 $block_render_callback = $args['render_callback'];
469 $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) {
470 global $post, $wp_query;
471
472 // Check for null for back compatibility with WP_Block_Type->render
473 // which is unused since the introduction of WP_Block class.
474 //
475 // See:
476 // - https://core.trac.wordpress.org/ticket/49927
477 // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop.
478
479 if ( null === $block ) {
480 return $block_render_callback( $attributes, $content );
481 }
482
483 $registry = WP_Block_Type_Registry::get_instance();
484 $block_type = $registry->get_registered( $block->name );
485
486 // For WordPress versions that don't support the context API.
487 if ( ! $block->context ) {
488 $block->context = array();
489 }
490
491 // Inject the post context if not done by Core.
492 $needs_post_id = ! empty( $block_type->uses_context ) && in_array( 'postId', $block_type->uses_context, true );
493 if ( $post instanceof WP_Post && $needs_post_id && ! isset( $block->context['postId'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) {
494 $block->context['postId'] = $post->ID;
495 }
496 $needs_post_type = ! empty( $block_type->uses_context ) && in_array( 'postType', $block_type->uses_context, true );
497 if ( $post instanceof WP_Post && $needs_post_type && ! isset( $block->context['postType'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) {
498 /*
499 * The `postType` context is largely unnecessary server-side, since the
500 * ID is usually sufficient on its own. That being said, since a block's
501 * manifest is expected to be shared between the server and the client,
502 * it should be included to consistently fulfill the expectation.
503 */
504 $block->context['postType'] = $post->post_type;
505 }
506
507 // Inject the query context if not done by Core.
508 $needs_query = ! empty( $block_type->uses_context ) && in_array( 'query', $block_type->uses_context, true );
509 if ( ! isset( $block->context['query'] ) && $needs_query ) {
510 if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) {
511 $block->context['query'] = array( 'categoryIds' => array() );
512
513 foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) {
514 $block->context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id;
515 }
516 }
517
518 if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) {
519 if ( isset( $block->context['query'] ) ) {
520 $block->context['query']['tagIds'] = array();
521 } else {
522 $block->context['query'] = array( 'tagIds' => array() );
523 }
524
525 foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) {
526 $tag_ID = $tag_slug_or_id;
527
528 if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) {
529 $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' );
530
531 if ( $tag ) {
532 $tag_ID = $tag->term_id;
533 }
534 }
535 $block->context['query']['tagIds'][] = $tag_ID;
536 }
537 }
538 }
539
540 return $block_render_callback( $attributes, $content, $block );
541 };
542 }
543 return $args;
544 }
545
546 add_filter( 'register_block_type_args', 'gutenberg_inject_default_block_context' );
547
548 /**
549 * Amends the paths to preload when initializing edit post.
550 *
551 * @see https://core.trac.wordpress.org/ticket/50606
552 *
553 * @since 8.4.0
554 *
555 * @param array $preload_paths Default path list that will be preloaded.
556 * @return array Modified path list to preload.
557 */
558 function gutenberg_preload_edit_post( $preload_paths ) {
559 $additional_paths = array( '/?context=edit' );
560 return array_merge( $preload_paths, $additional_paths );
561 }
562
563 add_filter( 'block_editor_preload_paths', 'gutenberg_preload_edit_post' );
564
565 /**
566 * Override post type labels for Reusable Block custom post type.
567 *
568 * This shim can be removed when the Gutenberg plugin requires a WordPress
569 * version that has the ticket below.
570 *
571 * @see https://core.trac.wordpress.org/ticket/50755
572 *
573 * @since 8.6.0
574 *
575 * @return array Array of new labels for Reusable Block post type.
576 */
577 function gutenberg_override_reusable_block_post_type_labels() {
578 return array(
579 'name' => _x( 'Reusable Blocks', 'post type general name', 'gutenberg' ),
580 'singular_name' => _x( 'Reusable Block', 'post type singular name', 'gutenberg' ),
581 'menu_name' => _x( 'Reusable Blocks', 'admin menu', 'gutenberg' ),
582 'name_admin_bar' => _x( 'Reusable Block', 'add new on admin bar', 'gutenberg' ),
583 'add_new' => _x( 'Add New', 'Reusable Block', 'gutenberg' ),
584 'add_new_item' => __( 'Add New Reusable Block', 'gutenberg' ),
585 'new_item' => __( 'New Reusable Block', 'gutenberg' ),
586 'edit_item' => __( 'Edit Reusable Block', 'gutenberg' ),
587 'view_item' => __( 'View Reusable Block', 'gutenberg' ),
588 'all_items' => __( 'All Reusable Blocks', 'gutenberg' ),
589 'search_items' => __( 'Search Reusable Blocks', 'gutenberg' ),
590 'not_found' => __( 'No reusable blocks found.', 'gutenberg' ),
591 'not_found_in_trash' => __( 'No reusable blocks found in Trash.', 'gutenberg' ),
592 'filter_items_list' => __( 'Filter reusable blocks list', 'gutenberg' ),
593 'items_list_navigation' => __( 'Reusable Blocks list navigation', 'gutenberg' ),
594 'items_list' => __( 'Reusable Blocks list', 'gutenberg' ),
595 'item_published' => __( 'Reusable Block published.', 'gutenberg' ),
596 'item_published_privately' => __( 'Reusable Block published privately.', 'gutenberg' ),
597 'item_reverted_to_draft' => __( 'Reusable Block reverted to draft.', 'gutenberg' ),
598 'item_scheduled' => __( 'Reusable Block scheduled.', 'gutenberg' ),
599 'item_updated' => __( 'Reusable Block updated.', 'gutenberg' ),
600 );
601 }
602 add_filter( 'post_type_labels_wp_block', 'gutenberg_override_reusable_block_post_type_labels', 10, 0 );
603