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

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