PluginProbe
Gutenberg / 17.1.0
Gutenberg v17.1.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 7.4.0 All 402 releases
gutenberg / lib / blocks.php

blocks.php in Gutenberg 17.1.0, at lib/blocks.php

579 lines 19.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block functions specific for the Gutenberg editor plugin.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Substitutes the implementation of a core-registered block type, if exists,
10 * with the built result from the plugin.
11 */
12 function gutenberg_reregister_core_block_types() {
13 // Blocks directory may not exist if working from a fresh clone.
14 $blocks_dirs = array(
15 __DIR__ . '/../build/block-library/blocks/' => array(
16 'block_folders' => array(
17 'audio',
18 'button',
19 'buttons',
20 'freeform',
21 'code',
22 'column',
23 'columns',
24 'details',
25 'form-input',
26 'form-submit-button',
27 'group',
28 'html',
29 'list',
30 'list-item',
31 'media-text',
32 'missing',
33 'more',
34 'nextpage',
35 'paragraph',
36 'preformatted',
37 'pullquote',
38 'quote',
39 'separator',
40 'social-links',
41 'spacer',
42 'table',
43 'table-of-contents',
44 'text-columns',
45 'verse',
46 'video',
47 'embed',
48 ),
49 'block_names' => array(
50 'archives.php' => 'core/archives',
51 'avatar.php' => 'core/avatar',
52 'block.php' => 'core/block',
53 'calendar.php' => 'core/calendar',
54 'categories.php' => 'core/categories',
55 'cover.php' => 'core/cover',
56 'comment-author-avatar.php' => 'core/comment-author-avatar',
57 'comment-author-name.php' => 'core/comment-author-name',
58 'comment-content.php' => 'core/comment-content',
59 'comment-date.php' => 'core/comment-date',
60 'comment-edit-link.php' => 'core/comment-edit-link',
61 'comment-reply-link.php' => 'core/comment-reply-link',
62 'comment-template.php' => 'core/comment-template',
63 'comments-pagination.php' => 'core/comments-pagination',
64 'comments-pagination-next.php' => 'core/comments-pagination-next',
65 'comments-pagination-numbers.php' => 'core/comments-pagination-numbers',
66 'comments-pagination-previous.php' => 'core/comments-pagination-previous',
67 'comments-title.php' => 'core/comments-title',
68 'comments.php' => 'core/comments',
69 'footnotes.php' => 'core/footnotes',
70 'file.php' => 'core/file',
71 'form.php' => 'core/form',
72 'form-input.php' => 'core/form-input',
73 'form-submission-notification.php' => 'core/form-submission-notification',
74 'home-link.php' => 'core/home-link',
75 'image.php' => 'core/image',
76 'gallery.php' => 'core/gallery',
77 'heading.php' => 'core/heading',
78 'latest-comments.php' => 'core/latest-comments',
79 'latest-posts.php' => 'core/latest-posts',
80 'loginout.php' => 'core/loginout',
81 'navigation.php' => 'core/navigation',
82 'navigation-link.php' => 'core/navigation-link',
83 'navigation-submenu.php' => 'core/navigation-submenu',
84 'page-list.php' => 'core/page-list',
85 'page-list-item.php' => 'core/page-list-item',
86 'pattern.php' => 'core/pattern',
87 'post-author.php' => 'core/post-author',
88 'post-author-name.php' => 'core/post-author-name',
89 'post-author-biography.php' => 'core/post-author-biography',
90 'post-comment.php' => 'core/post-comment',
91 'post-comments-count.php' => 'core/post-comments-count',
92 'post-comments-form.php' => 'core/post-comments-form',
93 'post-comments-link.php' => 'core/post-comments-link',
94 'post-content.php' => 'core/post-content',
95 'post-date.php' => 'core/post-date',
96 'post-excerpt.php' => 'core/post-excerpt',
97 'post-featured-image.php' => 'core/post-featured-image',
98 'post-navigation-link.php' => 'core/post-navigation-link',
99 'post-terms.php' => 'core/post-terms',
100 'post-time-to-read.php' => 'core/post-time-to-read',
101 'post-title.php' => 'core/post-title',
102 'query.php' => 'core/query',
103 'post-template.php' => 'core/post-template',
104 'query-no-results.php' => 'core/query-no-results',
105 'query-pagination.php' => 'core/query-pagination',
106 'query-pagination-next.php' => 'core/query-pagination-next',
107 'query-pagination-numbers.php' => 'core/query-pagination-numbers',
108 'query-pagination-previous.php' => 'core/query-pagination-previous',
109 'query-title.php' => 'core/query-title',
110 'read-more.php' => 'core/read-more',
111 'rss.php' => 'core/rss',
112 'search.php' => 'core/search',
113 'shortcode.php' => 'core/shortcode',
114 'social-link.php' => 'core/social-link',
115 'site-logo.php' => 'core/site-logo',
116 'site-tagline.php' => 'core/site-tagline',
117 'site-title.php' => 'core/site-title',
118 'tag-cloud.php' => 'core/tag-cloud',
119 'template-part.php' => 'core/template-part',
120 'term-description.php' => 'core/term-description',
121 ),
122 ),
123 __DIR__ . '/../build/edit-widgets/blocks/' => array(
124 'block_folders' => array(
125 'widget-area',
126 ),
127 'block_names' => array(),
128 ),
129 __DIR__ . '/../build/widgets/blocks/' => array(
130 'block_folders' => array(
131 'legacy-widget',
132 'widget-group',
133 ),
134 'block_names' => array(
135 'legacy-widget.php' => 'core/legacy-widget',
136 'widget-group.php' => 'core/widget-group',
137 ),
138 ),
139 );
140 foreach ( $blocks_dirs as $blocks_dir => $details ) {
141 $block_folders = $details['block_folders'];
142 $block_names = $details['block_names'];
143
144 foreach ( $block_folders as $folder_name ) {
145 $block_json_file = $blocks_dir . $folder_name . '/block.json';
146
147 // Ideally, all paths to block metadata files should be listed in
148 // WordPress core. In this place we should rather use filter
149 // to replace paths with overrides defined by the plugin.
150 $metadata = json_decode( file_get_contents( $block_json_file ), true );
151 if ( ! is_array( $metadata ) || ! $metadata['name'] ) {
152 continue;
153 }
154
155 gutenberg_deregister_core_block_and_assets( $metadata['name'] );
156 gutenberg_register_core_block_assets( $folder_name );
157 register_block_type_from_metadata( $block_json_file );
158 }
159
160 foreach ( $block_names as $file => $sub_block_names ) {
161 if ( ! file_exists( $blocks_dir . $file ) ) {
162 continue;
163 }
164
165 $sub_block_names_normalized = is_string( $sub_block_names ) ? array( $sub_block_names ) : $sub_block_names;
166 foreach ( $sub_block_names_normalized as $block_name ) {
167 gutenberg_deregister_core_block_and_assets( $block_name );
168 gutenberg_register_core_block_assets( $block_name );
169 }
170
171 require_once $blocks_dir . $file;
172 }
173 }
174 }
175
176 add_action( 'init', 'gutenberg_reregister_core_block_types' );
177
178 /**
179 * Adds the defer loading strategy to all registered blocks.
180 *
181 * This function would not be part of core merge. Instead, the register_block_script_handle() function would be patched
182 * as follows.
183 *
184 * ```
185 * --- a/wp-includes/blocks.php
186 * +++ b/wp-includes/blocks.php
187 * @ @ -153,7 +153,8 @ @ function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
188 * $script_handle,
189 * $script_uri,
190 * $script_dependencies,
191 * - isset( $script_asset['version'] ) ? $script_asset['version'] : false
192 * + isset( $script_asset['version'] ) ? $script_asset['version'] : false,
193 * + array( 'strategy' => 'defer' )
194 * );
195 * if ( ! $result ) {
196 * return false;
197 * ```
198 *
199 * @see register_block_script_handle()
200 */
201 function gutenberg_defer_block_view_scripts() {
202 $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
203 foreach ( $block_types as $block_type ) {
204 foreach ( $block_type->view_script_handles as $view_script_handle ) {
205 wp_script_add_data( $view_script_handle, 'strategy', 'defer' );
206 }
207 }
208 }
209
210 add_action( 'init', 'gutenberg_defer_block_view_scripts', 100 );
211
212 /**
213 * Deregisters the existing core block type and its assets.
214 *
215 * @param string $block_name The name of the block.
216 *
217 * @return void
218 */
219 function gutenberg_deregister_core_block_and_assets( $block_name ) {
220 $registry = WP_Block_Type_Registry::get_instance();
221 if ( $registry->is_registered( $block_name ) ) {
222 $block_type = $registry->get_registered( $block_name );
223 if ( ! empty( $block_type->view_script_handles ) ) {
224 foreach ( $block_type->view_script_handles as $view_script_handle ) {
225 if ( str_starts_with( $view_script_handle, 'wp-block-' ) ) {
226 wp_deregister_script( $view_script_handle );
227 }
228 }
229 }
230 $registry->unregister( $block_name );
231 }
232 }
233
234 /**
235 * Registers block styles for a core block.
236 *
237 * @param string $block_name The block-name.
238 *
239 * @return void
240 */
241 function gutenberg_register_core_block_assets( $block_name ) {
242 if ( ! wp_should_load_separate_core_block_assets() ) {
243 return;
244 }
245
246 $block_name = str_replace( 'core/', '', $block_name );
247
248 // When in production, use the plugin's version as the default asset version;
249 // else (for development or test) default to use the current time.
250 $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time();
251
252 $style_path = "build/block-library/blocks/$block_name/";
253 $stylesheet_url = gutenberg_url( $style_path . 'style.css' );
254 $stylesheet_path = gutenberg_dir_path() . $style_path . ( is_rtl() ? 'style-rtl.css' : 'style.css' );
255
256 if ( file_exists( $stylesheet_path ) ) {
257 wp_deregister_style( "wp-block-{$block_name}" );
258 wp_register_style(
259 "wp-block-{$block_name}",
260 $stylesheet_url,
261 array(),
262 $default_version
263 );
264 wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' );
265
266 // Add a reference to the stylesheet's path to allow calculations for inlining styles in `wp_head`.
267 wp_style_add_data( "wp-block-{$block_name}", 'path', $stylesheet_path );
268 } else {
269 wp_register_style( "wp-block-{$block_name}", false, array() );
270 }
271
272 // If the current theme supports wp-block-styles, dequeue the full stylesheet
273 // and instead attach each block's theme-styles to their block styles stylesheet.
274 if ( current_theme_supports( 'wp-block-styles' ) ) {
275
276 // Dequeue the full stylesheet.
277 // Make sure this only runs once, it doesn't need to run for every block.
278 static $stylesheet_removed;
279 if ( ! $stylesheet_removed ) {
280 add_action(
281 'wp_enqueue_scripts',
282 static function () {
283 wp_dequeue_style( 'wp-block-library-theme' );
284 }
285 );
286 $stylesheet_removed = true;
287 }
288
289 // Get the path to the block's stylesheet.
290 $theme_style_path = is_rtl()
291 ? "build/block-library/blocks/$block_name/theme-rtl.css"
292 : "build/block-library/blocks/$block_name/theme.css";
293
294 // If the file exists, enqueue it.
295 if ( file_exists( gutenberg_dir_path() . $theme_style_path ) ) {
296
297 if ( file_exists( $stylesheet_path ) ) {
298 // If there is a main stylesheet for this block, append the theme styles to main styles.
299 wp_add_inline_style(
300 "wp-block-{$block_name}",
301 file_get_contents( gutenberg_dir_path() . $theme_style_path )
302 );
303 } else {
304 // If there is no main stylesheet for this block, register theme style.
305 wp_register_style(
306 "wp-block-{$block_name}",
307 gutenberg_url( $theme_style_path ),
308 array(),
309 $default_version
310 );
311 wp_style_add_data( "wp-block-{$block_name}", 'path', gutenberg_dir_path() . $theme_style_path );
312 }
313 }
314 }
315
316 $editor_style_path = "build/block-library/blocks/$block_name/style-editor.css";
317 if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) {
318 wp_deregister_style( "wp-block-{$block_name}-editor" );
319 wp_register_style(
320 "wp-block-{$block_name}-editor",
321 gutenberg_url( $editor_style_path ),
322 array(),
323 $default_version
324 );
325 wp_style_add_data( "wp-block-{$block_name}-editor", 'rtl', 'replace' );
326 } else {
327 wp_register_style( "wp-block-{$block_name}-editor", false );
328 }
329 }
330
331 /**
332 * Complements the implementation of block type `core/social-icon`, whether it
333 * be provided by core or the plugin, with derived block types for each
334 * "service" (WordPress, Twitter, etc.) supported by Social Links.
335 *
336 * This ensures backwards compatibility for any users running the Gutenberg
337 * plugin who have used Social Links prior to their conversion to block
338 * variations.
339 *
340 * This shim is INTENTIONALLY left out of core, as Social Links have never
341 * landed there.
342 *
343 * @see https://github.com/WordPress/gutenberg/pull/19887
344 */
345 function gutenberg_register_legacy_social_link_blocks() {
346 $services = array(
347 'amazon',
348 'bandcamp',
349 'behance',
350 'chain',
351 'codepen',
352 'deviantart',
353 'dribbble',
354 'dropbox',
355 'etsy',
356 'facebook',
357 'feed',
358 'fivehundredpx',
359 'flickr',
360 'foursquare',
361 'goodreads',
362 'google',
363 'github',
364 'instagram',
365 'lastfm',
366 'linkedin',
367 'mail',
368 'mastodon',
369 'meetup',
370 'medium',
371 'pinterest',
372 'pocket',
373 'reddit',
374 'skype',
375 'snapchat',
376 'soundcloud',
377 'spotify',
378 'tumblr',
379 'twitch',
380 'twitter',
381 'vimeo',
382 'vk',
383 'wordpress',
384 'yelp',
385 'youtube',
386 );
387
388 foreach ( $services as $service ) {
389 register_block_type(
390 'core/social-link-' . $service,
391 array(
392 'category' => 'widgets',
393 'attributes' => array(
394 'url' => array(
395 'type' => 'string',
396 ),
397 'service' => array(
398 'type' => 'string',
399 'default' => $service,
400 ),
401 'label' => array(
402 'type' => 'string',
403 ),
404 ),
405 'render_callback' => 'gutenberg_render_block_core_social_link',
406 )
407 );
408 }
409 }
410
411 add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );
412
413 /**
414 * Migrate the legacy `sync_status` meta key (added 16.1) to the new `wp_pattern_sync_status` meta key (16.1.1).
415 *
416 * This filter is INTENTIONALLY left out of core as the meta key was fist introduced to core in 6.3 as `wp_pattern_sync_status`.
417 * see https://github.com/WordPress/gutenberg/pull/52232
418 *
419 * @param mixed $value The value to return, either a single metadata value or an array of values depending on the value of $single.
420 * @param int $object_id ID of the object metadata is for.
421 * @param string $meta_key Metadata key.
422 * @param bool $single Whether to return only the first value of the specified $meta_key.
423 */
424 function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $single ) {
425 if ( 'wp_pattern_sync_status' !== $meta_key ) {
426 return $value;
427 }
428
429 $sync_status = get_post_meta( $object_id, 'sync_status', $single );
430
431 if ( $single && 'unsynced' === $sync_status ) {
432 return $sync_status;
433 } elseif ( isset( $sync_status[0] ) && 'unsynced' === $sync_status[0] ) {
434 return $sync_status;
435 }
436
437 return $value;
438 }
439
440 add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 );
441
442 /**
443 * Complements the lightbox implementation for the 'core/image' block.
444 *
445 * This function is INTENTIONALLY left out of core as it only provides
446 * backwards compatibility for the legacy lightbox syntax that was only
447 * introduced in Gutenberg. The legacy syntax was using the `behaviors` key in
448 * the block attrbutes and the `theme.json` file.
449 *
450 * @since 16.7.0
451 *
452 * @param array $block The block to check.
453 * @return array The block with the legacyLightboxSettings set if available.
454 */
455 function gutenberg_should_render_lightbox( $block ) {
456
457 if ( 'core/image' !== $block['blockName'] ) {
458 return $block;
459 }
460
461 if ( isset( $block['attrs']['behaviors']['lightbox'] ) ) {
462 $block['legacyLightboxSettings'] = $block['attrs']['behaviors']['lightbox'];
463 }
464
465 return $block;
466 }
467
468 add_filter( 'render_block_data', 'gutenberg_should_render_lightbox', 15, 1 );
469
470 /**
471 * Registers the metadata block attribute for all block types.
472 *
473 * @param array $args Array of arguments for registering a block type.
474 * @return array $args
475 */
476 function gutenberg_register_metadata_attribute( $args ) {
477 // Setup attributes if needed.
478 if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
479 $args['attributes'] = array();
480 }
481
482 if ( ! array_key_exists( 'metadata', $args['attributes'] ) ) {
483 $args['attributes']['metadata'] = array(
484 'type' => 'object',
485 );
486 }
487
488 return $args;
489 }
490 add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' );
491
492 /**
493 * Strips all HTML from the content of footnotes, and sanitizes the ID.
494 *
495 * This function expects slashed data on the footnotes content.
496 *
497 * @access private
498 *
499 * @param string $footnotes JSON encoded string of an array containing the content and ID of each footnote.
500 * @return string Filtered content without any HTML on the footnote content and with the sanitized id.
501 */
502 function _gutenberg_filter_post_meta_footnotes( $footnotes ) {
503 $footnotes_decoded = json_decode( $footnotes, true );
504 if ( ! is_array( $footnotes_decoded ) ) {
505 return '';
506 }
507 $footnotes_sanitized = array();
508 foreach ( $footnotes_decoded as $footnote ) {
509 if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
510 $footnotes_sanitized[] = array(
511 'id' => sanitize_key( $footnote['id'] ),
512 'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ),
513 );
514 }
515 }
516 return wp_json_encode( $footnotes_sanitized );
517 }
518
519 /**
520 * Adds the filters to filter footnotes meta field.
521 *
522 * @access private
523 */
524 function _gutenberg_footnotes_kses_init_filters() {
525 add_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
526 }
527
528 /**
529 * Removes the filters that filter footnotes meta field.
530 *
531 * @access private
532 */
533 function _gutenberg_footnotes_remove_filters() {
534 remove_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
535 }
536
537 /**
538 * Registers the filter of footnotes meta field if the user does not have unfiltered_html capability.
539 *
540 * @access private
541 */
542 function _gutenberg_footnotes_kses_init() {
543 if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
544 return;
545 }
546 _gutenberg_footnotes_remove_filters();
547 if ( ! current_user_can( 'unfiltered_html' ) ) {
548 _gutenberg_footnotes_kses_init_filters();
549 }
550 }
551
552 /**
553 * Initializes footnotes meta field filters when imported data should be filtered.
554 *
555 * This filter is the last being executed on force_filtered_html_on_import.
556 * If the input of the filter is true it means we are in an import situation and should
557 * enable kses, independently of the user capabilities.
558 * So in that case we call _gutenberg_footnotes_kses_init_filters;
559 *
560 * @access private
561 *
562 * @param string $arg Input argument of the filter.
563 * @return string Input argument of the filter.
564 */
565 function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) {
566 if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
567 return;
568 }
569 // force_filtered_html_on_import is true we need to init the global styles kses filters.
570 if ( $arg ) {
571 _gutenberg_footnotes_kses_init_filters();
572 }
573 return $arg;
574 }
575
576 add_action( 'init', '_gutenberg_footnotes_kses_init' );
577 add_action( 'set_current_user', '_gutenberg_footnotes_kses_init' );
578 add_filter( 'force_filtered_html_on_import', '_gutenberg_footnotes_force_filtered_html_on_import_filter', 999 );
579