PluginProbe
Gutenberg / 9.5.1
Gutenberg v9.5.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.5.1, at lib/compat.php

561 lines 19.4 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 * Callback hooked to the register_block_type_args filter.
417 *
418 * This hooks into block registration to inject the default context into the block object.
419 * It can be removed once the default context is added into Core.
420 *
421 * @param array $args Block attributes.
422 * @return array Block attributes.
423 */
424 function gutenberg_inject_default_block_context( $args ) {
425 if ( is_callable( $args['render_callback'] ) ) {
426 $block_render_callback = $args['render_callback'];
427 $args['render_callback'] = function( $attributes, $content, $block = null ) use ( $block_render_callback ) {
428 global $post, $wp_query;
429
430 // Check for null for back compatibility with WP_Block_Type->render
431 // which is unused since the introduction of WP_Block class.
432 //
433 // See:
434 // - https://core.trac.wordpress.org/ticket/49927
435 // - commit 910de8f6890c87f93359c6f2edc6c27b9a3f3292 at wordpress-develop.
436
437 if ( null === $block ) {
438 return $block_render_callback( $attributes, $content );
439 }
440
441 $registry = WP_Block_Type_Registry::get_instance();
442 $block_type = $registry->get_registered( $block->name );
443
444 // For WordPress versions that don't support the context API.
445 if ( ! $block->context ) {
446 $block->context = array();
447 }
448
449 // Inject the post context if not done by Core.
450 $needs_post_id = ! empty( $block_type->uses_context ) && in_array( 'postId', $block_type->uses_context, true );
451 if ( $post instanceof WP_Post && $needs_post_id && ! isset( $block->context['postId'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) {
452 $block->context['postId'] = $post->ID;
453 }
454 $needs_post_type = ! empty( $block_type->uses_context ) && in_array( 'postType', $block_type->uses_context, true );
455 if ( $post instanceof WP_Post && $needs_post_type && ! isset( $block->context['postType'] ) && 'wp_template' !== $post->post_type && 'wp_template_part' !== $post->post_type ) {
456 /*
457 * The `postType` context is largely unnecessary server-side, since the
458 * ID is usually sufficient on its own. That being said, since a block's
459 * manifest is expected to be shared between the server and the client,
460 * it should be included to consistently fulfill the expectation.
461 */
462 $block->context['postType'] = $post->post_type;
463 }
464
465 // Inject the query context if not done by Core.
466 $needs_query = ! empty( $block_type->uses_context ) && in_array( 'query', $block_type->uses_context, true );
467 if ( ! isset( $block->context['query'] ) && $needs_query ) {
468 if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) {
469 $block->context['query'] = array( 'categoryIds' => array() );
470
471 foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) {
472 $block->context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id;
473 }
474 }
475
476 if ( isset( $wp_query->tax_query->queried_terms['post_tag'] ) ) {
477 if ( isset( $block->context['query'] ) ) {
478 $block->context['query']['tagIds'] = array();
479 } else {
480 $block->context['query'] = array( 'tagIds' => array() );
481 }
482
483 foreach ( $wp_query->tax_query->queried_terms['post_tag']['terms'] as $tag_slug_or_id ) {
484 $tag_ID = $tag_slug_or_id;
485
486 if ( 'slug' === $wp_query->tax_query->queried_terms['post_tag']['field'] ) {
487 $tag = get_term_by( 'slug', $tag_slug_or_id, 'post_tag' );
488
489 if ( $tag ) {
490 $tag_ID = $tag->term_id;
491 }
492 }
493 $block->context['query']['tagIds'][] = $tag_ID;
494 }
495 }
496 }
497
498 return $block_render_callback( $attributes, $content, $block );
499 };
500 }
501 return $args;
502 }
503
504 add_filter( 'register_block_type_args', 'gutenberg_inject_default_block_context' );
505
506 /**
507 * Amends the paths to preload when initializing edit post.
508 *
509 * @see https://core.trac.wordpress.org/ticket/50606
510 *
511 * @since 8.4.0
512 *
513 * @param array $preload_paths Default path list that will be preloaded.
514 * @return array Modified path list to preload.
515 */
516 function gutenberg_preload_edit_post( $preload_paths ) {
517 $additional_paths = array( '/?context=edit' );
518 return array_merge( $preload_paths, $additional_paths );
519 }
520
521 add_filter( 'block_editor_preload_paths', 'gutenberg_preload_edit_post' );
522
523 /**
524 * Override post type labels for Reusable Block custom post type.
525 *
526 * This shim can be removed when the Gutenberg plugin requires a WordPress
527 * version that has the ticket below.
528 *
529 * @see https://core.trac.wordpress.org/ticket/50755
530 *
531 * @since 8.6.0
532 *
533 * @return array Array of new labels for Reusable Block post type.
534 */
535 function gutenberg_override_reusable_block_post_type_labels() {
536 return array(
537 'name' => _x( 'Reusable Blocks', 'post type general name', 'gutenberg' ),
538 'singular_name' => _x( 'Reusable Block', 'post type singular name', 'gutenberg' ),
539 'menu_name' => _x( 'Reusable Blocks', 'admin menu', 'gutenberg' ),
540 'name_admin_bar' => _x( 'Reusable Block', 'add new on admin bar', 'gutenberg' ),
541 'add_new' => _x( 'Add New', 'Reusable Block', 'gutenberg' ),
542 'add_new_item' => __( 'Add New Reusable Block', 'gutenberg' ),
543 'new_item' => __( 'New Reusable Block', 'gutenberg' ),
544 'edit_item' => __( 'Edit Reusable Block', 'gutenberg' ),
545 'view_item' => __( 'View Reusable Block', 'gutenberg' ),
546 'all_items' => __( 'All Reusable Blocks', 'gutenberg' ),
547 'search_items' => __( 'Search Reusable Blocks', 'gutenberg' ),
548 'not_found' => __( 'No reusable blocks found.', 'gutenberg' ),
549 'not_found_in_trash' => __( 'No reusable blocks found in Trash.', 'gutenberg' ),
550 'filter_items_list' => __( 'Filter reusable blocks list', 'gutenberg' ),
551 'items_list_navigation' => __( 'Reusable Blocks list navigation', 'gutenberg' ),
552 'items_list' => __( 'Reusable Blocks list', 'gutenberg' ),
553 'item_published' => __( 'Reusable Block published.', 'gutenberg' ),
554 'item_published_privately' => __( 'Reusable Block published privately.', 'gutenberg' ),
555 'item_reverted_to_draft' => __( 'Reusable Block reverted to draft.', 'gutenberg' ),
556 'item_scheduled' => __( 'Reusable Block scheduled.', 'gutenberg' ),
557 'item_updated' => __( 'Reusable Block updated.', 'gutenberg' ),
558 );
559 }
560 add_filter( 'post_type_labels_wp_block', 'gutenberg_override_reusable_block_post_type_labels', 10, 0 );
561