PluginProbe
Gutenberg / 24.0.0
Gutenberg v24.0.0
24.0.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 All 403 releases
gutenberg / lib / blocks.php

blocks.php in Gutenberg 24.0.0, at lib/blocks.php

396 lines 12.0 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_dirs = array(
14 __DIR__ . '/../build/scripts/block-library/',
15 __DIR__ . '/../build/scripts/edit-widgets/blocks/',
16 __DIR__ . '/../build/scripts/widgets/blocks/',
17 );
18
19 foreach ( $blocks_dirs as $blocks_dir ) {
20 $manifest_path = $blocks_dir . 'blocks-manifest.php';
21 $blocks = require $manifest_path;
22
23 foreach ( $blocks as $block_name_folder => $metadata ) {
24 if ( ! is_array( $metadata ) || ! isset( $metadata['name'] ) ) {
25 continue;
26 }
27
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 );
36 }
37 }
38 }
39 }
40
41 add_action( 'init', 'gutenberg_reregister_core_block_types' );
42
43 /*
44 * Remove the WordPress core filter to avoid generating a second tabs ID for
45 * every tabs block: `wp_unique_prefixed_id()` advances a counter, so running
46 * both copies makes the numbering skip.
47 */
48 remove_filter( 'render_block_context', 'block_core_tabs_provide_context', 10 );
49
50 /**
51 * Adds the defer loading strategy to all registered blocks.
52 *
53 * This function would not be part of core merge. Instead, the register_block_script_handle() function would be patched
54 * as follows.
55 *
56 * ```
57 * --- a/wp-includes/blocks.php
58 * +++ b/wp-includes/blocks.php
59 * @ @ -153,7 +153,8 @ @ function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
60 * $script_handle,
61 * $script_uri,
62 * $script_dependencies,
63 * - $script_asset['version'] ?? false
64 * + $script_asset['version'] ?? false,
65 * + array( 'strategy' => 'defer' )
66 * );
67 * if ( ! $result ) {
68 * return false;
69 * ```
70 *
71 * @see register_block_script_handle()
72 */
73 function gutenberg_defer_block_view_scripts() {
74 $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
75 foreach ( $block_types as $block_type ) {
76 foreach ( $block_type->view_script_handles as $view_script_handle ) {
77 wp_script_add_data( $view_script_handle, 'strategy', 'defer' );
78 }
79 }
80 }
81
82 add_action( 'init', 'gutenberg_defer_block_view_scripts', 100 );
83
84 /**
85 * Deregisters the existing core block type and its assets.
86 *
87 * @param string $block_name The name of the block.
88 *
89 * @return void
90 */
91 function gutenberg_deregister_core_block_and_assets( $block_name ) {
92 $registry = WP_Block_Type_Registry::get_instance();
93 if ( $registry->is_registered( $block_name ) ) {
94 $block_type = $registry->get_registered( $block_name );
95 if ( ! empty( $block_type->view_script_handles ) ) {
96 foreach ( $block_type->view_script_handles as $view_script_handle ) {
97 if ( str_starts_with( $view_script_handle, 'wp-block-' ) ) {
98 wp_deregister_script( $view_script_handle );
99 }
100 }
101 }
102 $registry->unregister( $block_name );
103 }
104 }
105
106 /**
107 * Registers block styles for a core block.
108 *
109 * @param string $block_name The block-name.
110 *
111 * @return void
112 */
113 function gutenberg_register_core_block_assets( $block_name ) {
114 static $gutenberg_url_root = null;
115 // Running `gutenberg_url` inside of a loop can be expensive in systems with
116 // many callbacks attached to the `plugins_url` hook.
117 // Since all of the paths have the same root, we can instead retrieve the
118 // corresponding URL root once, and manually concatenate the URL below.
119 if ( is_null( $gutenberg_url_root ) ) {
120 $gutenberg_url_root = gutenberg_url( '/' );
121 }
122
123 if ( ! wp_should_load_separate_core_block_assets() ) {
124 return;
125 }
126
127 $block_name = str_replace( 'core/', '', $block_name );
128
129 // When in production, use the plugin's version as the default asset version;
130 // else (for development or test) default to use the current time.
131 $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time();
132 $suffix = SCRIPT_DEBUG ? '' : '.min';
133
134 $style_path = "build/styles/block-library/$block_name/";
135 $stylesheet_url = $gutenberg_url_root . $style_path . 'style' . $suffix . '.css';
136 $stylesheet_path = gutenberg_dir_path() . $style_path . ( is_rtl() ? 'style-rtl' . $suffix . '.css' : 'style' . $suffix . '.css' );
137
138 if ( file_exists( $stylesheet_path ) ) {
139
140 wp_deregister_style( "wp-block-{$block_name}" );
141 wp_register_style(
142 "wp-block-{$block_name}",
143 $stylesheet_url,
144 array(),
145 $default_version
146 );
147 wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' );
148 wp_style_add_data( "wp-block-{$block_name}", 'suffix', $suffix );
149 // Add a reference to the stylesheet's path to allow calculations for inlining styles in `wp_head`.
150 wp_style_add_data( "wp-block-{$block_name}", 'path', $stylesheet_path );
151 } else {
152 wp_register_style( "wp-block-{$block_name}", false, array() );
153 }
154
155 /*
156 * If the current theme supports wp-block-styles, dequeue the core styles
157 * and enqueue the plugin ones instead.
158 */
159 if ( current_theme_supports( 'wp-block-styles' ) ) {
160
161 // Get the path to the block's stylesheet.
162 $theme_style_path = is_rtl()
163 ? "build/styles/block-library/$block_name/theme-rtl{$suffix}.css"
164 : "build/styles/block-library/$block_name/theme{$suffix}.css";
165
166 // If the file exists, enqueue it.
167 if ( file_exists( gutenberg_dir_path() . $theme_style_path ) ) {
168 wp_deregister_style( "wp-block-{$block_name}-theme" );
169 wp_register_style(
170 "wp-block-{$block_name}-theme",
171 $gutenberg_url_root . $theme_style_path,
172 array(),
173 $default_version
174 );
175 wp_style_add_data( "wp-block-{$block_name}-theme", 'path', gutenberg_dir_path() . $theme_style_path );
176 wp_style_add_data( "wp-block-{$block_name}-theme", 'suffix', $suffix );
177 }
178 }
179
180 $editor_style_path = "build/styles/block-library/$block_name/style-editor{$suffix}.css";
181 if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) {
182 wp_deregister_style( "wp-block-{$block_name}-editor" );
183 wp_register_style(
184 "wp-block-{$block_name}-editor",
185 $gutenberg_url_root . $editor_style_path,
186 array(),
187 $default_version
188 );
189 wp_style_add_data( "wp-block-{$block_name}-editor", 'rtl', 'replace' );
190 wp_style_add_data( "wp-block-{$block_name}-editor", 'suffix', $suffix );
191 } else {
192 wp_register_style( "wp-block-{$block_name}-editor", false );
193 }
194 }
195
196 /**
197 * Complements the implementation of block type `core/social-icon`, whether it
198 * be provided by core or the plugin, with derived block types for each
199 * "service" (WordPress, Twitter, etc.) supported by Social Links.
200 *
201 * This ensures backwards compatibility for any users running the Gutenberg
202 * plugin who have used Social Links prior to their conversion to block
203 * variations.
204 *
205 * This shim is INTENTIONALLY left out of core, as Social Links have never
206 * landed there.
207 *
208 * @link https://github.com/WordPress/gutenberg/pull/19887
209 */
210 function gutenberg_register_legacy_social_link_blocks() {
211 $services = array(
212 'amazon',
213 'bandcamp',
214 'behance',
215 'chain',
216 'codepen',
217 'deviantart',
218 'dribbble',
219 'dropbox',
220 'etsy',
221 'facebook',
222 'feed',
223 'fivehundredpx',
224 'flickr',
225 'foursquare',
226 'goodreads',
227 'google',
228 'github',
229 'instagram',
230 'lastfm',
231 'linkedin',
232 'mail',
233 'mastodon',
234 'meetup',
235 'medium',
236 'pinterest',
237 'pocket',
238 'reddit',
239 'skype',
240 'snapchat',
241 'soundcloud',
242 'spotify',
243 'tumblr',
244 'twitch',
245 'twitter',
246 'vimeo',
247 'vk',
248 'wordpress',
249 'yelp',
250 'youtube',
251 );
252
253 foreach ( $services as $service ) {
254 register_block_type(
255 'core/social-link-' . $service,
256 array(
257 'category' => 'widgets',
258 'attributes' => array(
259 'url' => array(
260 'type' => 'string',
261 ),
262 'service' => array(
263 'type' => 'string',
264 'default' => $service,
265 ),
266 'label' => array(
267 'type' => 'string',
268 ),
269 ),
270 'render_callback' => 'gutenberg_render_block_core_social_link',
271 )
272 );
273 }
274 }
275
276 add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );
277
278 /**
279 * Migrate the legacy `sync_status` meta key (added 16.1) to the new `wp_pattern_sync_status` meta key (16.1.1).
280 *
281 * 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`.
282 * see https://github.com/WordPress/gutenberg/pull/52232
283 *
284 * @param mixed $value The value to return, either a single metadata value or an array of values depending on the value of $single.
285 * @param int $object_id ID of the object metadata is for.
286 * @param string $meta_key Metadata key.
287 * @param bool $single Whether to return only the first value of the specified $meta_key.
288 */
289 function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $single ) {
290 if ( 'wp_pattern_sync_status' !== $meta_key ) {
291 return $value;
292 }
293
294 $sync_status = get_post_meta( $object_id, 'sync_status', $single );
295
296 if ( $single && 'unsynced' === $sync_status ) {
297 return $sync_status;
298 } elseif ( isset( $sync_status[0] ) && 'unsynced' === $sync_status[0] ) {
299 return $sync_status;
300 }
301
302 return $value;
303 }
304
305 add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 );
306
307
308
309 /**
310 * Strips all HTML from the content of footnotes, and sanitizes the ID.
311 *
312 * This function expects slashed data on the footnotes content.
313 *
314 * @access private
315 *
316 * @param string $footnotes JSON encoded string of an array containing the content and ID of each footnote.
317 * @return string Filtered content without any HTML on the footnote content and with the sanitized id.
318 */
319 function _gutenberg_filter_post_meta_footnotes( $footnotes ) {
320 $footnotes_decoded = json_decode( $footnotes, true );
321 if ( ! is_array( $footnotes_decoded ) ) {
322 return '';
323 }
324 $footnotes_sanitized = array();
325 foreach ( $footnotes_decoded as $footnote ) {
326 if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
327 $footnotes_sanitized[] = array(
328 'id' => sanitize_key( $footnote['id'] ),
329 'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ),
330 );
331 }
332 }
333 return wp_json_encode( $footnotes_sanitized );
334 }
335
336 /**
337 * Adds the filters to filter footnotes meta field.
338 *
339 * @access private
340 */
341 function _gutenberg_footnotes_kses_init_filters() {
342 add_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
343 }
344
345 /**
346 * Removes the filters that filter footnotes meta field.
347 *
348 * @access private
349 */
350 function _gutenberg_footnotes_remove_filters() {
351 remove_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
352 }
353
354 /**
355 * Registers the filter of footnotes meta field if the user does not have unfiltered_html capability.
356 *
357 * @access private
358 */
359 function _gutenberg_footnotes_kses_init() {
360 if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
361 return;
362 }
363 _gutenberg_footnotes_remove_filters();
364 if ( ! current_user_can( 'unfiltered_html' ) ) {
365 _gutenberg_footnotes_kses_init_filters();
366 }
367 }
368
369 /**
370 * Initializes footnotes meta field filters when imported data should be filtered.
371 *
372 * This filter is the last being executed on force_filtered_html_on_import.
373 * If the input of the filter is true it means we are in an import situation and should
374 * enable kses, independently of the user capabilities.
375 * So in that case we call _gutenberg_footnotes_kses_init_filters;
376 *
377 * @access private
378 *
379 * @param string $arg Input argument of the filter.
380 * @return string Input argument of the filter.
381 */
382 function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) {
383 if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
384 return $arg;
385 }
386 // force_filtered_html_on_import is true we need to init the global styles kses filters.
387 if ( $arg ) {
388 _gutenberg_footnotes_kses_init_filters();
389 }
390 return $arg;
391 }
392
393 add_action( 'init', '_gutenberg_footnotes_kses_init' );
394 add_action( 'set_current_user', '_gutenberg_footnotes_kses_init' );
395 add_filter( 'force_filtered_html_on_import', '_gutenberg_footnotes_force_filtered_html_on_import_filter', 999 );
396