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

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