PluginProbe
Gutenberg / 8.9.3
Gutenberg v8.9.3
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 8.9.3, at lib/compat.php

490 lines 16.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 * This 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 global $post, $wp_query;
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 ) {
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 if ( $post instanceof WP_Post ) {
402 $context['postId'] = $post->ID;
403
404 /*
405 * The `postType` context is largely unnecessary server-side, since the
406 * ID is usually sufficient on its own. That being said, since a block's
407 * manifest is expected to be shared between the server and the client,
408 * it should be included to consistently fulfill the expectation.
409 */
410 $context['postType'] = $post->post_type;
411 }
412
413 if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) {
414 $context['query'] = array( 'categoryIds' => array() );
415
416 foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) {
417 $context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id;
418 }
419 }
420
421 /**
422 * Filters the default context provided to a rendered block.
423 *
424 * @param array $context Default context.
425 * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
426 */
427 $context = apply_filters( 'render_block_context', $context, $parsed_block );
428
429 $block = new WP_Block( $parsed_block, $context );
430
431 return $block->render();
432 }
433 add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 );
434
435 /**
436 * Amends the paths to preload when initializing edit post.
437 *
438 * @see https://core.trac.wordpress.org/ticket/50606
439 *
440 * @since 8.4.0
441 *
442 * @param array $preload_paths Default path list that will be preloaded.
443 * @return array Modified path list to preload.
444 */
445 function gutenberg_preload_edit_post( $preload_paths ) {
446 $additional_paths = array( '/?context=edit' );
447 return array_merge( $preload_paths, $additional_paths );
448 }
449
450 add_filter( 'block_editor_preload_paths', 'gutenberg_preload_edit_post' );
451
452 /**
453 * Override post type labels for Reusable Block custom post type.
454 *
455 * This shim can be removed when the Gutenberg plugin requires a WordPress
456 * version that has the ticket below.
457 *
458 * @see https://core.trac.wordpress.org/ticket/50755
459 *
460 * @since 8.6.0
461 *
462 * @return array Array of new labels for Reusable Block post type.
463 */
464 function gutenberg_override_reusable_block_post_type_labels() {
465 return array(
466 'name' => _x( 'Reusable Blocks', 'post type general name', 'gutenberg' ),
467 'singular_name' => _x( 'Reusable Block', 'post type singular name', 'gutenberg' ),
468 'menu_name' => _x( 'Reusable Blocks', 'admin menu', 'gutenberg' ),
469 'name_admin_bar' => _x( 'Reusable Block', 'add new on admin bar', 'gutenberg' ),
470 'add_new' => _x( 'Add New', 'Reusable Block', 'gutenberg' ),
471 'add_new_item' => __( 'Add New Reusable Block', 'gutenberg' ),
472 'new_item' => __( 'New Reusable Block', 'gutenberg' ),
473 'edit_item' => __( 'Edit Reusable Block', 'gutenberg' ),
474 'view_item' => __( 'View Reusable Block', 'gutenberg' ),
475 'all_items' => __( 'All Reusable Blocks', 'gutenberg' ),
476 'search_items' => __( 'Search Reusable Blocks', 'gutenberg' ),
477 'not_found' => __( 'No reusable blocks found.', 'gutenberg' ),
478 'not_found_in_trash' => __( 'No reusable blocks found in Trash.', 'gutenberg' ),
479 'filter_items_list' => __( 'Filter reusable blocks list', 'gutenberg' ),
480 'items_list_navigation' => __( 'Reusable Blocks list navigation', 'gutenberg' ),
481 'items_list' => __( 'Reusable Blocks list', 'gutenberg' ),
482 'item_published' => __( 'Reusable Block published.', 'gutenberg' ),
483 'item_published_privately' => __( 'Reusable Block published privately.', 'gutenberg' ),
484 'item_reverted_to_draft' => __( 'Reusable Block reverted to draft.', 'gutenberg' ),
485 'item_scheduled' => __( 'Reusable Block scheduled.', 'gutenberg' ),
486 'item_updated' => __( 'Reusable Block updated.', 'gutenberg' ),
487 );
488 }
489 add_filter( 'post_type_labels_wp_block', 'gutenberg_override_reusable_block_post_type_labels', 10, 0 );
490