PluginProbe
Gutenberg / 20.0.1
Gutenberg v20.0.1
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 20.0.1, at lib/blocks.php

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