PluginProbe
Gutenberg / 12.1.0
Gutenberg v12.1.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
← All changes | lib/blocks.php +487 -197 23.3.112.1.0 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Block functions specific for the Gutenberg editor plugin.
3 + * Block and style registration functions.
4 4 *
5 5 * @package gutenberg
6 6 */
7 7
@@ -9,94 +9,170 @@
9 9 * Substitutes the implementation of a core-registered block type, if exists,
10 10 * with the built result from the plugin.
11 11 */
12 12 function gutenberg_reregister_core_block_types() {
13 + // Blocks directory may not exist if working from a fresh clone.
13 14 $blocks_dirs = array(
14 - __DIR__ . '/../build/scripts/block-library/',
15 - __DIR__ . '/../build/scripts/edit-widgets/blocks/',
16 - __DIR__ . '/../build/scripts/widgets/blocks/',
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 + 'comments-query-loop',
25 + 'cover',
26 + 'gallery',
27 + 'group',
28 + 'heading',
29 + 'html',
30 + 'home-link',
31 + 'image',
32 + 'list',
33 + 'media-text',
34 + 'missing',
35 + 'more',
36 + 'navigation-area',
37 + 'navigation-link',
38 + 'navigation-submenu',
39 + 'nextpage',
40 + 'pattern',
41 + 'paragraph',
42 + 'preformatted',
43 + 'pullquote',
44 + 'quote',
45 + 'separator',
46 + 'social-links',
47 + 'spacer',
48 + 'table',
49 + // 'table-of-contents',
50 + 'text-columns',
51 + 'verse',
52 + 'video',
53 + 'embed',
54 + ),
55 + 'block_names' => array(
56 + 'archives.php' => 'core/archives',
57 + 'block.php' => 'core/block',
58 + 'calendar.php' => 'core/calendar',
59 + 'categories.php' => 'core/categories',
60 + 'comment-author-avatar.php' => 'core/comment-author-avatar',
61 + 'comment-author-name.php' => 'core/comment-author-name',
62 + 'comment-content.php' => 'core/comment-content',
63 + 'comment-date.php' => 'core/comment-date',
64 + 'comment-edit-link.php' => 'core/comment-edit-link',
65 + 'comment-reply-link.php' => 'core/comment-reply-link',
66 + 'comment-template.php' => 'core/comment-template',
67 + 'file.php' => 'core/file',
68 + 'home-link.php' => 'core/home-link',
69 + 'image.php' => 'core/image',
70 + 'gallery.php' => 'core/gallery',
71 + 'latest-comments.php' => 'core/latest-comments',
72 + 'latest-posts.php' => 'core/latest-posts',
73 + 'loginout.php' => 'core/loginout',
74 + 'navigation.php' => 'core/navigation',
75 + 'navigation-area.php' => 'core/navigation-area',
76 + 'navigation-link.php' => 'core/navigation-link',
77 + 'navigation-submenu.php' => 'core/navigation-submenu',
78 + 'page-list.php' => 'core/page-list',
79 + 'pattern.php' => 'core/pattern',
80 + 'post-author.php' => 'core/post-author',
81 + 'post-comment.php' => 'core/post-comment',
82 + 'post-comments.php' => 'core/post-comments',
83 + 'post-comments-count.php' => 'core/post-comments-count',
84 + 'post-comments-form.php' => 'core/post-comments-form',
85 + 'post-comments-link.php' => 'core/post-comments-link',
86 + 'post-content.php' => 'core/post-content',
87 + 'post-date.php' => 'core/post-date',
88 + 'post-excerpt.php' => 'core/post-excerpt',
89 + 'post-featured-image.php' => 'core/post-featured-image',
90 + 'post-navigation-link.php' => 'core/post-navigation-link',
91 + 'post-terms.php' => 'core/post-terms',
92 + 'post-title.php' => 'core/post-title',
93 + 'query.php' => 'core/query',
94 + 'post-template.php' => 'core/post-template',
95 + 'query-pagination.php' => 'core/query-pagination',
96 + 'query-pagination-next.php' => 'core/query-pagination-next',
97 + 'query-pagination-numbers.php' => 'core/query-pagination-numbers',
98 + 'query-pagination-previous.php' => 'core/query-pagination-previous',
99 + 'query-title.php' => 'core/query-title',
100 + 'rss.php' => 'core/rss',
101 + 'search.php' => 'core/search',
102 + 'shortcode.php' => 'core/shortcode',
103 + 'social-link.php' => 'core/social-link',
104 + 'site-logo.php' => 'core/site-logo',
105 + 'site-tagline.php' => 'core/site-tagline',
106 + 'site-title.php' => 'core/site-title',
107 + // 'table-of-contents.php' => 'core/table-of-contents',
108 + 'tag-cloud.php' => 'core/tag-cloud',
109 + 'template-part.php' => 'core/template-part',
110 + 'term-description.php' => 'core/term-description',
111 + ),
112 + ),
113 + __DIR__ . '/../build/edit-widgets/blocks/' => array(
114 + 'block_folders' => array(
115 + 'widget-area',
116 + ),
117 + 'block_names' => array(),
118 + ),
119 + __DIR__ . '/../build/widgets/blocks/' => array(
120 + 'block_folders' => array(
121 + 'legacy-widget',
122 + 'widget-group',
123 + ),
124 + 'block_names' => array(
125 + 'legacy-widget.php' => 'core/legacy-widget',
126 + 'widget-group.php' => 'core/widget-group',
127 + ),
128 + ),
17 129 );
130 + foreach ( $blocks_dirs as $blocks_dir => $details ) {
131 + $block_folders = $details['block_folders'];
132 + $block_names = $details['block_names'];
18 133
19 - foreach ( $blocks_dirs as $blocks_dir ) {
20 - $manifest_path = $blocks_dir . 'blocks-manifest.php';
21 - $blocks = require $manifest_path;
134 + $registry = WP_Block_Type_Registry::get_instance();
22 135
23 - foreach ( $blocks as $block_name_folder => $metadata ) {
24 - if ( ! is_array( $metadata ) || ! isset( $metadata['name'] ) ) {
25 - continue;
136 + foreach ( $block_folders as $folder_name ) {
137 + $block_json_file = $blocks_dir . $folder_name . '/block.json';
138 +
139 + // Ideally, all paths to block metadata files should be listed in
140 + // WordPress core. In this place we should rather use filter
141 + // to replace paths with overrides defined by the plugin.
142 + $metadata = json_decode( file_get_contents( $block_json_file ), true );
143 + if ( ! is_array( $metadata ) || ! $metadata['name'] ) {
144 + return false;
26 145 }
27 146
28 - gutenberg_deregister_core_block_and_assets( $metadata['name'] );
29 - gutenberg_register_core_block_assets( $block_name_folder );
30 -
31 - $php_file = $blocks_dir . $block_name_folder . '.php';
32 - if ( file_exists( $php_file ) ) {
33 - require_once $php_file;
34 - } else {
35 - register_block_type_from_metadata( $blocks_dir . $block_name_folder );
147 + if ( $registry->is_registered( $metadata['name'] ) ) {
148 + $registry->unregister( $metadata['name'] );
36 149 }
37 - }
38 - }
39 -}
40 150
41 -add_action( 'init', 'gutenberg_reregister_core_block_types' );
42 -
43 -/**
44 - * Adds the defer loading strategy to all registered blocks.
45 - *
46 - * This function would not be part of core merge. Instead, the register_block_script_handle() function would be patched
47 - * as follows.
48 - *
49 - * ```
50 - * --- a/wp-includes/blocks.php
51 - * +++ b/wp-includes/blocks.php
52 - * @ @ -153,7 +153,8 @ @ function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
53 - * $script_handle,
54 - * $script_uri,
55 - * $script_dependencies,
56 - * - $script_asset['version'] ?? false
57 - * + $script_asset['version'] ?? false,
58 - * + array( 'strategy' => 'defer' )
59 - * );
60 - * if ( ! $result ) {
61 - * return false;
62 - * ```
63 - *
64 - * @see register_block_script_handle()
65 - */
66 -function gutenberg_defer_block_view_scripts() {
67 - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
68 - foreach ( $block_types as $block_type ) {
69 - foreach ( $block_type->view_script_handles as $view_script_handle ) {
70 - wp_script_add_data( $view_script_handle, 'strategy', 'defer' );
151 + gutenberg_register_core_block_assets( $folder_name );
152 + register_block_type_from_metadata( $block_json_file );
71 153 }
72 - }
73 -}
74 154
75 -add_action( 'init', 'gutenberg_defer_block_view_scripts', 100 );
155 + foreach ( $block_names as $file => $sub_block_names ) {
156 + if ( ! file_exists( $blocks_dir . $file ) ) {
157 + return;
158 + }
76 159
77 -/**
78 - * Deregisters the existing core block type and its assets.
79 - *
80 - * @param string $block_name The name of the block.
81 - *
82 - * @return void
83 - */
84 -function gutenberg_deregister_core_block_and_assets( $block_name ) {
85 - $registry = WP_Block_Type_Registry::get_instance();
86 - if ( $registry->is_registered( $block_name ) ) {
87 - $block_type = $registry->get_registered( $block_name );
88 - if ( ! empty( $block_type->view_script_handles ) ) {
89 - foreach ( $block_type->view_script_handles as $view_script_handle ) {
90 - if ( str_starts_with( $view_script_handle, 'wp-block-' ) ) {
91 - wp_deregister_script( $view_script_handle );
160 + $sub_block_names_normalized = is_string( $sub_block_names ) ? array( $sub_block_names ) : $sub_block_names;
161 + foreach ( $sub_block_names_normalized as $block_name ) {
162 + if ( $registry->is_registered( $block_name ) ) {
163 + $registry->unregister( $block_name );
92 164 }
165 + gutenberg_register_core_block_assets( $block_name );
93 166 }
167 +
168 + require_once $blocks_dir . $file;
94 169 }
95 - $registry->unregister( $block_name );
96 170 }
97 171 }
98 172
173 +add_action( 'init', 'gutenberg_reregister_core_block_types' );
174 +
99 175 /**
100 176 * Registers block styles for a core block.
101 177 *
102 178 * @param string $block_name The block-name.
@@ -103,17 +179,8 @@
103 179 *
104 180 * @return void
105 181 */
106 182 function gutenberg_register_core_block_assets( $block_name ) {
107 - static $gutenberg_url_root = null;
108 - // Running `gutenberg_url` inside of a loop can be expensive in systems with
109 - // many callbacks attached to the `plugins_url` hook.
110 - // Since all of the paths have the same root, we can instead retrieve the
111 - // corresponding URL root once, and manually concatenate the URL below.
112 - if ( is_null( $gutenberg_url_root ) ) {
113 - $gutenberg_url_root = gutenberg_url( '/' );
114 - }
115 -
116 183 if ( ! wp_should_load_separate_core_block_assets() ) {
117 184 return;
118 185 }
119 186
@@ -120,68 +187,82 @@
120 187 $block_name = str_replace( 'core/', '', $block_name );
121 188
122 189 // When in production, use the plugin's version as the default asset version;
123 190 // else (for development or test) default to use the current time.
124 - $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time();
125 - $suffix = SCRIPT_DEBUG ? '' : '.min';
191 + $default_version = defined( 'GUTENBERG_VERSION' ) && ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? GUTENBERG_VERSION : time();
126 192
127 - $style_path = "build/styles/block-library/$block_name/";
128 - $stylesheet_url = $gutenberg_url_root . $style_path . 'style' . $suffix . '.css';
129 - $stylesheet_path = gutenberg_dir_path() . $style_path . ( is_rtl() ? 'style-rtl' . $suffix . '.css' : 'style' . $suffix . '.css' );
193 + $style_path = "build/block-library/blocks/$block_name/style.css";
194 + $editor_style_path = "build/block-library/blocks/$block_name/style-editor.css";
130 195
131 - if ( file_exists( $stylesheet_path ) ) {
132 -
196 + if ( file_exists( gutenberg_dir_path() . $style_path ) ) {
133 197 wp_deregister_style( "wp-block-{$block_name}" );
134 198 wp_register_style(
135 199 "wp-block-{$block_name}",
136 - $stylesheet_url,
200 + gutenberg_url( $style_path ),
137 201 array(),
138 202 $default_version
139 203 );
140 204 wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' );
141 - wp_style_add_data( "wp-block-{$block_name}", 'suffix', $suffix );
205 +
142 206 // Add a reference to the stylesheet's path to allow calculations for inlining styles in `wp_head`.
143 - wp_style_add_data( "wp-block-{$block_name}", 'path', $stylesheet_path );
207 + wp_style_add_data( "wp-block-{$block_name}", 'path', gutenberg_dir_path() . $style_path );
144 208 } else {
145 - wp_register_style( "wp-block-{$block_name}", false, array() );
209 + wp_register_style( "wp-block-{$block_name}", false );
146 210 }
147 211
148 - /*
149 - * If the current theme supports wp-block-styles, dequeue the core styles
150 - * and enqueue the plugin ones instead.
151 - */
212 + // If the current theme supports wp-block-styles, dequeue the full stylesheet
213 + // and instead attach each block's theme-styles to their block styles stylesheet.
152 214 if ( current_theme_supports( 'wp-block-styles' ) ) {
153 215
216 + // Dequeue the full stylesheet.
217 + // Make sure this only runs once, it doesn't need to run for every block.
218 + static $stylesheet_removed;
219 + if ( ! $stylesheet_removed ) {
220 + add_action(
221 + 'wp_enqueue_scripts',
222 + function() {
223 + wp_dequeue_style( 'wp-block-library-theme' );
224 + }
225 + );
226 + $stylesheet_removed = true;
227 + }
228 +
154 229 // Get the path to the block's stylesheet.
155 230 $theme_style_path = is_rtl()
156 - ? "build/styles/block-library/$block_name/theme-rtl{$suffix}.css"
157 - : "build/styles/block-library/$block_name/theme{$suffix}.css";
231 + ? "build/block-library/blocks/$block_name/theme-rtl.css"
232 + : "build/block-library/blocks/$block_name/theme.css";
158 233
159 234 // If the file exists, enqueue it.
160 235 if ( file_exists( gutenberg_dir_path() . $theme_style_path ) ) {
161 - wp_deregister_style( "wp-block-{$block_name}-theme" );
162 - wp_register_style(
163 - "wp-block-{$block_name}-theme",
164 - $gutenberg_url_root . $theme_style_path,
165 - array(),
166 - $default_version
167 - );
168 - wp_style_add_data( "wp-block-{$block_name}-theme", 'path', gutenberg_dir_path() . $theme_style_path );
169 - wp_style_add_data( "wp-block-{$block_name}-theme", 'suffix', $suffix );
236 +
237 + if ( file_exists( gutenberg_dir_path() . $style_path ) ) {
238 + // If there is a main stylesheet for this block, append the theme styles to main styles.
239 + wp_add_inline_style(
240 + "wp-block-{$block_name}",
241 + file_get_contents( gutenberg_dir_path() . $theme_style_path )
242 + );
243 + } else {
244 + // If there is no main stylesheet for this block, register theme style.
245 + wp_register_style(
246 + "wp-block-{$block_name}",
247 + gutenberg_url( $theme_style_path ),
248 + array(),
249 + $default_version
250 + );
251 + wp_style_add_data( "wp-block-{$block_name}", 'path', gutenberg_dir_path() . $theme_style_path );
252 + }
170 253 }
171 254 }
172 255
173 - $editor_style_path = "build/styles/block-library/$block_name/style-editor{$suffix}.css";
174 256 if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) {
175 257 wp_deregister_style( "wp-block-{$block_name}-editor" );
176 258 wp_register_style(
177 259 "wp-block-{$block_name}-editor",
178 - $gutenberg_url_root . $editor_style_path,
260 + gutenberg_url( $editor_style_path ),
179 261 array(),
180 262 $default_version
181 263 );
182 264 wp_style_add_data( "wp-block-{$block_name}-editor", 'rtl', 'replace' );
183 - wp_style_add_data( "wp-block-{$block_name}-editor", 'suffix', $suffix );
184 265 } else {
185 266 wp_register_style( "wp-block-{$block_name}-editor", false );
186 267 }
187 268 }
@@ -186,8 +267,137 @@
186 267 }
187 268 }
188 269
189 270 /**
271 + * Change the way styles get loaded depending on their size.
272 + *
273 + * Optimizes performance and sustainability of styles by inlining smaller stylesheets.
274 + *
275 + * @todo Remove this function when the minimum supported version is WordPress 5.8.
276 + *
277 + * @return void
278 + */
279 +function gutenberg_maybe_inline_styles() {
280 +
281 + // Early exit if the "wp_maybe_inline_styles" function exists.
282 + if ( function_exists( 'wp_maybe_inline_styles' ) ) {
283 + return;
284 + }
285 +
286 + $total_inline_limit = 20000;
287 + /**
288 + * The maximum size of inlined styles in bytes.
289 + *
290 + * @param int $total_inline_limit The file-size threshold, in bytes. Defaults to 20000.
291 + * @return int The file-size threshold, in bytes.
292 + */
293 + $total_inline_limit = apply_filters( 'styles_inline_size_limit', $total_inline_limit );
294 +
295 + global $wp_styles;
296 + $styles = array();
297 +
298 + // Build an array of styles that have a path defined.
299 + foreach ( $wp_styles->queue as $handle ) {
300 + if ( wp_styles()->get_data( $handle, 'path' ) && file_exists( $wp_styles->registered[ $handle ]->extra['path'] ) ) {
301 + $styles[] = array(
302 + 'handle' => $handle,
303 + 'src' => $wp_styles->registered[ $handle ]->src,
304 + 'path' => $wp_styles->registered[ $handle ]->extra['path'],
305 + 'size' => filesize( $wp_styles->registered[ $handle ]->extra['path'] ),
306 + );
307 + }
308 + }
309 +
310 + if ( ! empty( $styles ) ) {
311 + // Reorder styles array based on size.
312 + usort(
313 + $styles,
314 + function( $a, $b ) {
315 + return ( $a['size'] <= $b['size'] ) ? -1 : 1;
316 + }
317 + );
318 +
319 + /**
320 + * The total inlined size.
321 + *
322 + * On each iteration of the loop, if a style gets added inline the value of this var increases
323 + * to reflect the total size of inlined styles.
324 + */
325 + $total_inline_size = 0;
326 +
327 + // Loop styles.
328 + foreach ( $styles as $style ) {
329 +
330 + // Size check. Since styles are ordered by size, we can break the loop.
331 + if ( $total_inline_size + $style['size'] > $total_inline_limit ) {
332 + break;
333 + }
334 +
335 + // Get the styles if we don't already have them.
336 + $style['css'] = file_get_contents( $style['path'] );
337 +
338 + // Check if the style contains relative URLs that need to be modified.
339 + // URLs relative to the stylesheet's path should be converted to relative to the site's root.
340 + $style['css'] = _wp_normalize_relative_css_links( $style['css'], $style['src'] );
341 +
342 + // Set `src` to `false` and add styles inline.
343 + $wp_styles->registered[ $style['handle'] ]->src = false;
344 + if ( empty( $wp_styles->registered[ $style['handle'] ]->extra['after'] ) ) {
345 + $wp_styles->registered[ $style['handle'] ]->extra['after'] = array();
346 + }
347 + array_unshift( $wp_styles->registered[ $style['handle'] ]->extra['after'], $style['css'] );
348 +
349 + // Add the styles size to the $total_inline_size var.
350 + $total_inline_size += (int) $style['size'];
351 + }
352 + }
353 +}
354 +// Run for styles enqueued in <head>.
355 +add_action( 'wp_head', 'gutenberg_maybe_inline_styles', 1 );
356 +// Run for late-loaded styles in the footer.
357 +add_action( 'wp_footer', 'gutenberg_maybe_inline_styles', 1 );
358 +
359 +if ( ! function_exists( '_wp_normalize_relative_css_links' ) ) {
360 + /**
361 + * Make URLs relative to the WordPress installation.
362 + *
363 + * @since 5.8.2
364 + *
365 + * @param string $css The CSS to make URLs relative to the WordPress installation.
366 + * @param string $stylesheet_url The URL to the stylesheet.
367 + *
368 + * @return string The CSS with URLs made relative to the WordPress installation.
369 + */
370 + function _wp_normalize_relative_css_links( $css, $stylesheet_url ) {
371 + $has_src_results = preg_match_all( '#url\s*\(\s*[\'"]?\s*([^\'"\)]+)#', $css, $src_results );
372 + if ( $has_src_results ) {
373 + // Loop through the URLs to find relative ones.
374 + foreach ( $src_results[1] as $src_index => $src_result ) {
375 + // Skip if this is an absolute URL.
376 + if ( 0 === strpos( $src_result, 'http' ) || 0 === strpos( $src_result, '//' ) ) {
377 + continue;
378 + }
379 +
380 + // Build the absolute URL.
381 + $absolute_url = dirname( $stylesheet_url ) . '/' . $src_result;
382 + $absolute_url = str_replace( '/./', '/', $absolute_url );
383 + // Convert to URL related to the site root.
384 + $relative_url = wp_make_link_relative( $absolute_url );
385 +
386 + // Replace the URL in the CSS.
387 + $css = str_replace(
388 + $src_results[0][ $src_index ],
389 + str_replace( $src_result, $relative_url, $src_results[0][ $src_index ] ),
390 + $css
391 + );
392 + }
393 + }
394 +
395 + return $css;
396 + }
397 +}
398 +
399 +/**
190 400 * Complements the implementation of block type `core/social-icon`, whether it
191 401 * be provided by core or the plugin, with derived block types for each
192 402 * "service" (WordPress, Twitter, etc.) supported by Social Links.
193 403 *
@@ -194,12 +404,12 @@
194 404 * This ensures backwards compatibility for any users running the Gutenberg
195 405 * plugin who have used Social Links prior to their conversion to block
196 406 * variations.
197 407 *
198 - * This shim is INTENTIONALLY left out of core, as Social Links have never
408 + * This shim is INTENTIONALLY left out of core, as Social Links haven't yet
199 409 * landed there.
200 410 *
201 - * @link https://github.com/WordPress/gutenberg/pull/19887
411 + * @see https://github.com/WordPress/gutenberg/pull/19887
202 412 */
203 413 function gutenberg_register_legacy_social_link_blocks() {
204 414 $services = array(
205 415 'amazon',
@@ -268,121 +478,201 @@
268 478
269 479 add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );
270 480
271 481 /**
272 - * Migrate the legacy `sync_status` meta key (added 16.1) to the new `wp_pattern_sync_status` meta key (16.1.1).
482 + * Filters the default block categories array to add a new one for themes.
273 483 *
274 - * 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`.
275 - * see https://github.com/WordPress/gutenberg/pull/52232
484 + * This can be removed when plugin support requires WordPress 5.8.0+.
276 485 *
277 - * @param mixed $value The value to return, either a single metadata value or an array of values depending on the value of $single.
278 - * @param int $object_id ID of the object metadata is for.
279 - * @param string $meta_key Metadata key.
280 - * @param bool $single Whether to return only the first value of the specified $meta_key.
486 + * @see https://core.trac.wordpress.org/ticket/52883
487 + *
488 + * @param array[] $categories The list of default block categories.
489 + *
490 + * @return array[] Filtered block categories.
281 491 */
282 -function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $single ) {
283 - if ( 'wp_pattern_sync_status' !== $meta_key ) {
284 - return $value;
492 +function gutenberg_register_theme_block_category( $categories ) {
493 + foreach ( $categories as $category ) {
494 + // Skip when the category is already set in WordPress core.
495 + if (
496 + isset( $category['slug'] ) &&
497 + 'theme' === $category['slug']
498 + ) {
499 + return $categories;
500 + }
285 501 }
286 502
287 - $sync_status = get_post_meta( $object_id, 'sync_status', $single );
288 -
289 - if ( $single && 'unsynced' === $sync_status ) {
290 - return $sync_status;
291 - } elseif ( isset( $sync_status[0] ) && 'unsynced' === $sync_status[0] ) {
292 - return $sync_status;
293 - }
294 -
295 - return $value;
503 + $categories[] = array(
504 + 'slug' => 'theme',
505 + 'title' => _x( 'Theme', 'block category', 'gutenberg' ),
506 + 'icon' => null,
507 + );
508 + return $categories;
296 509 }
510 +// This can be removed when plugin support requires WordPress 5.8.0+.
511 +if ( ! function_exists( 'get_default_block_categories' ) ) {
512 + add_filter( 'block_categories', 'gutenberg_register_theme_block_category' );
513 +}
297 514
298 -add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 );
299 -
300 -
301 -
302 515 /**
303 - * Strips all HTML from the content of footnotes, and sanitizes the ID.
516 + * Checks whether the current block type supports the feature requested.
304 517 *
305 - * This function expects slashed data on the footnotes content.
518 + * @param WP_Block_Type $block_type Block type to check for support.
519 + * @param array $feature Path of the feature to check support for.
520 + * @param mixed $default Fallback value for feature support, defaults to false.
306 521 *
307 - * @access private
308 - *
309 - * @param string $footnotes JSON encoded string of an array containing the content and ID of each footnote.
310 - * @return string Filtered content without any HTML on the footnote content and with the sanitized id.
522 + * @return boolean Whether or not the feature is supported.
311 523 */
312 -function _gutenberg_filter_post_meta_footnotes( $footnotes ) {
313 - $footnotes_decoded = json_decode( $footnotes, true );
314 - if ( ! is_array( $footnotes_decoded ) ) {
315 - return '';
524 +function gutenberg_block_has_support( $block_type, $feature, $default = false ) {
525 + $block_support = $default;
526 + if ( $block_type && property_exists( $block_type, 'supports' ) ) {
527 + $block_support = _wp_array_get( $block_type->supports, $feature, $default );
316 528 }
317 - $footnotes_sanitized = array();
318 - foreach ( $footnotes_decoded as $footnote ) {
319 - if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
320 - $footnotes_sanitized[] = array(
321 - 'id' => sanitize_key( $footnote['id'] ),
322 - 'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ),
323 - );
324 - }
325 - }
326 - return wp_json_encode( $footnotes_sanitized );
529 +
530 + return true === $block_support || is_array( $block_support );
327 531 }
328 532
329 533 /**
330 - * Adds the filters to filter footnotes meta field.
534 + * Updates the shape of supports for declaring fontSize and lineHeight.
331 535 *
332 - * @access private
536 + * @param array $metadata Metadata for registering a block type.
537 + * @return array Metadata for registering a block type with the supports shape updated.
333 538 */
334 -function _gutenberg_footnotes_kses_init_filters() {
335 - add_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
539 +function gutenberg_migrate_old_typography_shape( $metadata ) {
540 + // Temporarily disable migrations from core blocks to avoid warnings on versions older than 5.8.
541 + if ( isset( $metadata['supports'] ) && false === strpos( $metadata['file'], '/wp-includes/blocks/' ) ) {
542 + $typography_keys = array(
543 + '__experimentalFontFamily',
544 + '__experimentalFontStyle',
545 + '__experimentalFontWeight',
546 + '__experimentalLetterSpacing',
547 + '__experimentalTextDecoration',
548 + '__experimentalTextTransform',
549 + 'fontSize',
550 + 'lineHeight',
551 + );
552 + foreach ( $typography_keys as $typography_key ) {
553 + $support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null );
554 + if ( null !== $support_for_key ) {
555 + trigger_error(
556 + /* translators: %1$s: Block type, %2$s: typography supports key e.g: fontSize, lineHeight etc... */
557 + sprintf( __( 'Block %1$s is declaring %2$s support on block.json under supports.%2$s. %2$s support is now declared under supports.typography.%2$s.', 'gutenberg' ), $metadata['name'], $typography_key ),
558 + headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE
559 + );
560 + _wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key );
561 + unset( $metadata['supports'][ $typography_key ] );
562 + }
563 + }
564 + }
565 + return $metadata;
336 566 }
337 567
338 -/**
339 - * Removes the filters that filter footnotes meta field.
340 - *
341 - * @access private
342 - */
343 -function _gutenberg_footnotes_remove_filters() {
344 - remove_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
568 +if ( ! function_exists( 'wp_migrate_old_typography_shape' ) ) {
569 + add_filter( 'block_type_metadata', 'gutenberg_migrate_old_typography_shape' );
345 570 }
346 571
347 -/**
348 - * Registers the filter of footnotes meta field if the user does not have unfiltered_html capability.
349 - *
350 - * @access private
351 - */
352 -function _gutenberg_footnotes_kses_init() {
353 - if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
354 - return;
572 +if ( ! function_exists( 'wp_enqueue_block_style' ) ) {
573 + /**
574 + * Enqueue a stylesheet for a specific block.
575 + *
576 + * If the theme has opted-in to separate-styles loading,
577 + * then the stylesheet will be enqueued on-render,
578 + * otherwise when the block inits.
579 + *
580 + * @param string $block_name The block-name, including namespace.
581 + * @param array $args An array of arguments [handle,src,deps,ver,media].
582 + *
583 + * @return void
584 + */
585 + function wp_enqueue_block_style( $block_name, $args ) {
586 + $args = wp_parse_args(
587 + $args,
588 + array(
589 + 'handle' => '',
590 + 'src' => '',
591 + 'deps' => array(),
592 + 'ver' => false,
593 + 'media' => 'all',
594 + )
595 + );
596 +
597 + /**
598 + * Callback function to register and enqueue styles.
599 + *
600 + * @param string $content When the callback is used for the render_block filter,
601 + * the content needs to be returned so the function parameter
602 + * is to ensure the content exists.
603 + *
604 + * @return string
605 + */
606 + $callback = function( $content ) use ( $args ) {
607 + // Register the stylesheet.
608 + if ( ! empty( $args['src'] ) ) {
609 + wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] );
610 + }
611 +
612 + // Add `path` data if provided.
613 + if ( isset( $args['path'] ) ) {
614 + wp_style_add_data( $args['handle'], 'path', $args['path'] );
615 +
616 + // Get the RTL file path.
617 + $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] );
618 +
619 + // Add RTL stylesheet.
620 + if ( file_exists( $rtl_file_path ) ) {
621 + wp_style_add_data( $args['hanle'], 'rtl', 'replace' );
622 +
623 + if ( is_rtl() ) {
624 + wp_style_add_data( $args['handle'], 'path', $rtl_file_path );
625 + }
626 + }
627 + }
628 +
629 + // Enqueue the stylesheet.
630 + wp_enqueue_style( $args['handle'] );
631 +
632 + return $content;
633 + };
634 +
635 + $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts';
636 + if ( wp_should_load_separate_core_block_assets() ) {
637 + $hook = "render_block_$block_name";
638 + }
639 +
640 + // Enqueue assets in the frontend.
641 + add_filter( $hook, $callback );
642 +
643 + // Enqueue assets in the editor.
644 + add_action( 'enqueue_block_assets', $callback );
355 645 }
356 - _gutenberg_footnotes_remove_filters();
357 - if ( ! current_user_can( 'unfiltered_html' ) ) {
358 - _gutenberg_footnotes_kses_init_filters();
359 - }
360 646 }
361 647
362 648 /**
363 - * Initializes footnotes meta field filters when imported data should be filtered.
649 + * Allow multiple block styles.
364 650 *
365 - * This filter is the last being executed on force_filtered_html_on_import.
366 - * If the input of the filter is true it means we are in an import situation and should
367 - * enable kses, independently of the user capabilities.
368 - * So in that case we call _gutenberg_footnotes_kses_init_filters;
651 + * @param array $metadata Metadata for registering a block type.
369 652 *
370 - * @access private
371 - *
372 - * @param string $arg Input argument of the filter.
373 - * @return string Input argument of the filter.
653 + * @return array
374 654 */
375 -function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) {
376 - if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
377 - return;
655 +function gutenberg_multiple_block_styles( $metadata ) {
656 + foreach ( array( 'style', 'editorStyle' ) as $key ) {
657 + if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) {
658 + $default_style = array_shift( $metadata[ $key ] );
659 + foreach ( $metadata[ $key ] as $handle ) {
660 + $args = array( 'handle' => $handle );
661 + if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) {
662 + $style_path = remove_block_asset_path_prefix( $handle );
663 + $args = array(
664 + 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ),
665 + 'src' => plugins_url( $style_path, $metadata['file'] ),
666 + );
667 + }
668 +
669 + wp_enqueue_block_style( $metadata['name'], $args );
670 + }
671 +
672 + // Only return the 1st item in the array.
673 + $metadata[ $key ] = $default_style;
674 + }
378 675 }
379 - // force_filtered_html_on_import is true we need to init the global styles kses filters.
380 - if ( $arg ) {
381 - _gutenberg_footnotes_kses_init_filters();
382 - }
383 - return $arg;
676 + return $metadata;
384 677 }
385 -
386 -add_action( 'init', '_gutenberg_footnotes_kses_init' );
387 -add_action( 'set_current_user', '_gutenberg_footnotes_kses_init' );
388 -add_filter( 'force_filtered_html_on_import', '_gutenberg_footnotes_force_filtered_html_on_import_filter', 999 );
678 +add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' );