PluginProbe
Gutenberg / 19.6.4
Gutenberg v19.6.4
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 19.6.4, at lib/blocks.php

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