PluginProbe
Gutenberg / 23.2.1
Gutenberg v23.2.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 23.2.1, at lib/blocks.php

389 lines 11.8 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 * 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' );
71 }
72 }
73 }
74
75 add_action( 'init', 'gutenberg_defer_block_view_scripts', 100 );
76
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 );
92 }
93 }
94 }
95 $registry->unregister( $block_name );
96 }
97 }
98
99 /**
100 * Registers block styles for a core block.
101 *
102 * @param string $block_name The block-name.
103 *
104 * @return void
105 */
106 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 if ( ! wp_should_load_separate_core_block_assets() ) {
117 return;
118 }
119
120 $block_name = str_replace( 'core/', '', $block_name );
121
122 // When in production, use the plugin's version as the default asset version;
123 // 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';
126
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' );
130
131 if ( file_exists( $stylesheet_path ) ) {
132
133 wp_deregister_style( "wp-block-{$block_name}" );
134 wp_register_style(
135 "wp-block-{$block_name}",
136 $stylesheet_url,
137 array(),
138 $default_version
139 );
140 wp_style_add_data( "wp-block-{$block_name}", 'rtl', 'replace' );
141 wp_style_add_data( "wp-block-{$block_name}", 'suffix', $suffix );
142 // 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 );
144 } else {
145 wp_register_style( "wp-block-{$block_name}", false, array() );
146 }
147
148 /*
149 * If the current theme supports wp-block-styles, dequeue the core styles
150 * and enqueue the plugin ones instead.
151 */
152 if ( current_theme_supports( 'wp-block-styles' ) ) {
153
154 // Get the path to the block's stylesheet.
155 $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";
158
159 // If the file exists, enqueue it.
160 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 );
170 }
171 }
172
173 $editor_style_path = "build/styles/block-library/$block_name/style-editor{$suffix}.css";
174 if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) {
175 wp_deregister_style( "wp-block-{$block_name}-editor" );
176 wp_register_style(
177 "wp-block-{$block_name}-editor",
178 $gutenberg_url_root . $editor_style_path,
179 array(),
180 $default_version
181 );
182 wp_style_add_data( "wp-block-{$block_name}-editor", 'rtl', 'replace' );
183 wp_style_add_data( "wp-block-{$block_name}-editor", 'suffix', $suffix );
184 } else {
185 wp_register_style( "wp-block-{$block_name}-editor", false );
186 }
187 }
188
189 /**
190 * Complements the implementation of block type `core/social-icon`, whether it
191 * be provided by core or the plugin, with derived block types for each
192 * "service" (WordPress, Twitter, etc.) supported by Social Links.
193 *
194 * This ensures backwards compatibility for any users running the Gutenberg
195 * plugin who have used Social Links prior to their conversion to block
196 * variations.
197 *
198 * This shim is INTENTIONALLY left out of core, as Social Links have never
199 * landed there.
200 *
201 * @link https://github.com/WordPress/gutenberg/pull/19887
202 */
203 function gutenberg_register_legacy_social_link_blocks() {
204 $services = array(
205 'amazon',
206 'bandcamp',
207 'behance',
208 'chain',
209 'codepen',
210 'deviantart',
211 'dribbble',
212 'dropbox',
213 'etsy',
214 'facebook',
215 'feed',
216 'fivehundredpx',
217 'flickr',
218 'foursquare',
219 'goodreads',
220 'google',
221 'github',
222 'instagram',
223 'lastfm',
224 'linkedin',
225 'mail',
226 'mastodon',
227 'meetup',
228 'medium',
229 'pinterest',
230 'pocket',
231 'reddit',
232 'skype',
233 'snapchat',
234 'soundcloud',
235 'spotify',
236 'tumblr',
237 'twitch',
238 'twitter',
239 'vimeo',
240 'vk',
241 'wordpress',
242 'yelp',
243 'youtube',
244 );
245
246 foreach ( $services as $service ) {
247 register_block_type(
248 'core/social-link-' . $service,
249 array(
250 'category' => 'widgets',
251 'attributes' => array(
252 'url' => array(
253 'type' => 'string',
254 ),
255 'service' => array(
256 'type' => 'string',
257 'default' => $service,
258 ),
259 'label' => array(
260 'type' => 'string',
261 ),
262 ),
263 'render_callback' => 'gutenberg_render_block_core_social_link',
264 )
265 );
266 }
267 }
268
269 add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );
270
271 /**
272 * Migrate the legacy `sync_status` meta key (added 16.1) to the new `wp_pattern_sync_status` meta key (16.1.1).
273 *
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
276 *
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.
281 */
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;
285 }
286
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;
296 }
297
298 add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 );
299
300
301
302 /**
303 * Strips all HTML from the content of footnotes, and sanitizes the ID.
304 *
305 * This function expects slashed data on the footnotes content.
306 *
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.
311 */
312 function _gutenberg_filter_post_meta_footnotes( $footnotes ) {
313 $footnotes_decoded = json_decode( $footnotes, true );
314 if ( ! is_array( $footnotes_decoded ) ) {
315 return '';
316 }
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 );
327 }
328
329 /**
330 * Adds the filters to filter footnotes meta field.
331 *
332 * @access private
333 */
334 function _gutenberg_footnotes_kses_init_filters() {
335 add_filter( 'sanitize_post_meta_footnotes', '_gutenberg_filter_post_meta_footnotes' );
336 }
337
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' );
345 }
346
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;
355 }
356 _gutenberg_footnotes_remove_filters();
357 if ( ! current_user_can( 'unfiltered_html' ) ) {
358 _gutenberg_footnotes_kses_init_filters();
359 }
360 }
361
362 /**
363 * Initializes footnotes meta field filters when imported data should be filtered.
364 *
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;
369 *
370 * @access private
371 *
372 * @param string $arg Input argument of the filter.
373 * @return string Input argument of the filter.
374 */
375 function _gutenberg_footnotes_force_filtered_html_on_import_filter( $arg ) {
376 if ( function_exists( '_wp_filter_post_meta_footnotes' ) ) {
377 return;
378 }
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;
384 }
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 );
389