PluginProbe
Gutenberg / 21.5.0
Gutenberg v21.5.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 21.5.0, at lib/blocks.php

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