PluginProbe
Gutenberg / 23.9.1
Gutenberg v23.9.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 / client-assets.php

client-assets.php in Gutenberg 23.9.1, at lib/client-assets.php

490 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions to register client-side assets (scripts and stylesheets) specific
4 * for the Gutenberg editor plugin.
5 *
6 * @package gutenberg
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 die( 'Silence is golden.' );
11 }
12
13 /**
14 * Retrieves the root plugin path.
15 *
16 * @since 0.1.0
17 *
18 * @return string Root path to the gutenberg plugin.
19 */
20 function gutenberg_dir_path() {
21 return plugin_dir_path( __DIR__ );
22 }
23
24 /**
25 * Retrieves a URL to a file in the gutenberg plugin.
26 *
27 * @since 0.1.0
28 *
29 * @param string $path Relative path of the desired file.
30 *
31 * @return string Fully qualified URL pointing to the desired file.
32 */
33 function gutenberg_url( $path ) {
34 return plugins_url( $path, __DIR__ );
35 }
36
37 /**
38 * Filters the default translation file load behavior to load the Gutenberg
39 * plugin translation file, if available.
40 *
41 * @param string|false $file Path to the translation file to load. False if
42 * there isn't one.
43 * @param string $handle Name of the script to register a translation
44 * domain to.
45 *
46 * @return string|false Filtered path to the Gutenberg translation file, if
47 * available.
48 */
49 function gutenberg_override_translation_file( $file, $handle ) {
50 if ( ! $file ) {
51 return $file;
52 }
53
54 // Ignore scripts whose handle does not have the "wp-" prefix.
55 if ( ! str_starts_with( $handle, 'wp-' ) ) {
56 return $file;
57 }
58
59 // Ignore scripts that are not found in the expected `build/scripts/` location.
60 $script_path = gutenberg_dir_path() . 'build/scripts/' . substr( $handle, 3 ) . '/index.min.js';
61 if ( ! file_exists( $script_path ) ) {
62 return $file;
63 }
64
65 /*
66 * The default file will be in the plugins language directory, omitting the
67 * domain since Gutenberg assigns the script translations as the default.
68 *
69 * Example: /www/wp-content/languages/plugins/de_DE-07d88e6a803e01276b9bfcc1203e862e.json
70 *
71 * The logic of `load_script_textdomain` is such that it will assume to
72 * search in the plugins language directory, since the assigned source of
73 * the overridden Gutenberg script originates in the plugins directory.
74 *
75 * The plugin translation files each begin with the slug of the plugin, so
76 * it's a simple matter of prepending the Gutenberg plugin slug.
77 */
78 $path_parts = pathinfo( $file );
79 $plugin_translation_file = (
80 $path_parts['dirname'] .
81 '/gutenberg-' .
82 $path_parts['basename']
83 );
84
85 return $plugin_translation_file;
86 }
87 add_filter( 'load_script_translation_file', 'gutenberg_override_translation_file', 10, 2 );
88
89 /**
90 * Adds the 'editor' dependency to wp-block-library, required by the Classic block.
91 *
92 * @param WP_Scripts $scripts WP_Scripts instance.
93 */
94 function gutenberg_register_block_library_script_special_case( $scripts ) {
95 $handle = 'wp-block-library';
96 $script = $scripts->query( $handle, 'registered' );
97 if ( ! in_array( 'editor', $script->deps, true ) ) {
98 $script->deps[] = 'editor';
99 }
100 }
101 add_action( 'wp_default_scripts', 'gutenberg_register_block_library_script_special_case', 11 );
102
103 /**
104 * Registers WordPress package styles with complex requirements.
105 *
106 * Simple styles (main style.css with inferred dependencies) are auto-registered
107 * via build/styles.php at default priority (10). This function runs at priority 15 to handle:
108 * - Multiple style files per package (content.css, classic.css, etc.)
109 * - Non-WordPress dependencies (dashicons, common, forms)
110 * - Custom handles that don't match wp-{package} pattern
111 * - Conditional dependencies based on theme/settings
112 * - Dynamic filename logic
113 *
114 * These override calls will replace the auto-registered versions as needed.
115 *
116 * @since 6.7.0
117 *
118 * @global array $editor_styles
119 *
120 * @param WP_Styles $styles WP_Styles instance.
121 */
122 function gutenberg_register_packages_styles( $styles ) {
123 // When in production, use the plugin's version as the asset version;
124 // else (for development or test) default to use the current time.
125 $version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time();
126 $suffix = SCRIPT_DEBUG ? '' : '.min';
127
128 // wp-components: add dashicons (icon font dependency) and design tokens.
129 $components_style = $styles->query( 'wp-components', 'registered' );
130 $components_style->deps[] = 'dashicons';
131 $components_style->deps[] = 'wp-theme';
132
133 // wp-edit-post: add wp-edit-blocks (custom handle not auto-inferred)
134 $styles->query( 'wp-edit-post', 'registered' )->deps[] = 'wp-edit-blocks';
135
136 // wp-edit-site: add core WP styles and custom handles
137 $edit_site_style = $styles->query( 'wp-edit-site', 'registered' );
138 $edit_site_style->deps[] = 'common';
139 $edit_site_style->deps[] = 'forms';
140 $edit_site_style->deps[] = 'wp-block-library-editor';
141
142 // wp-edit-widgets: add wp-edit-blocks (custom handle not auto-inferred)
143 $styles->query( 'wp-edit-widgets', 'registered' )->deps[] = 'wp-edit-blocks';
144
145 // wp-customize-widgets: add wp-edit-blocks (custom handle not auto-inferred)
146 $styles->query( 'wp-customize-widgets', 'registered' )->deps[] = 'wp-edit-blocks';
147
148 gutenberg_override_style(
149 $styles,
150 'wp-theme',
151 gutenberg_url( 'build/styles/theme/design-tokens' . $suffix . '.css' ),
152 array(),
153 $version
154 );
155 $styles->add_data( 'wp-theme', 'rtl', 'replace' );
156 $styles->add_data( 'wp-theme', 'suffix', $suffix );
157 $styles->add_data( 'wp-theme', 'path', gutenberg_dir_path() . 'build/styles/theme/design-tokens' . $suffix . '.css' );
158
159 // Register wp-base-styles and add it to the already registered wp-admin stylesheet
160 gutenberg_override_style(
161 $styles,
162 'wp-base-styles',
163 gutenberg_url( 'build/styles/base-styles/admin-schemes' . $suffix . '.css' ),
164 array(),
165 $version
166 );
167 $styles->add_data( 'wp-base-styles', 'rtl', 'replace' );
168 $styles->add_data( 'wp-base-styles', 'suffix', $suffix );
169 $styles->add_data( 'wp-base-styles', 'path', gutenberg_dir_path() . 'build/styles/base-styles/admin-schemes' . $suffix . '.css' );
170 $styles->query( 'wp-admin', 'registered' )->deps[] = 'wp-base-styles';
171
172 gutenberg_override_style(
173 $styles,
174 'wp-block-editor-content',
175 gutenberg_url( 'build/styles/block-editor/content' . $suffix . '.css' ),
176 array( 'wp-components' ),
177 $version
178 );
179 $styles->add_data( 'wp-block-editor-content', 'rtl', 'replace' );
180 $styles->add_data( 'wp-block-editor-content', 'suffix', $suffix );
181
182 $block_library_filename = wp_should_load_separate_core_block_assets() ? 'common' : 'style';
183 gutenberg_override_style(
184 $styles,
185 'wp-block-library',
186 gutenberg_url( 'build/styles/block-library/' . $block_library_filename . $suffix . '.css' ),
187 array(),
188 $version
189 );
190 $styles->add_data( 'wp-block-library', 'rtl', 'replace' );
191 $styles->add_data( 'wp-block-library', 'suffix', $suffix );
192 $styles->add_data( 'wp-block-library', 'path', gutenberg_dir_path() . 'build/styles/block-library/' . $block_library_filename . $suffix . '.css' );
193
194 // Only add CONTENT styles here that should be enqueued in the iframe!
195 $wp_edit_blocks_dependencies = array(
196 // Design System tokens load first so the `:root` CSS custom
197 // properties are defined before any consuming stylesheet reads
198 // them inside the editor iframe.
199 'wp-theme',
200 'wp-components',
201 // This need to be added before the block library styles,
202 // The block library styles override the "reset" styles.
203 'wp-reset-editor-styles',
204 'wp-block-library',
205 // Until #37466, we can't specifically add them as editor styles yet,
206 // so we must hard-code it here as a dependency.
207 'wp-block-editor-content',
208 'wp-base-styles',
209 );
210
211 // Only load the default layout and margin styles for themes without theme.json file.
212 if ( ! wp_theme_has_theme_json() ) {
213 $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles';
214 }
215
216 global $editor_styles;
217 if ( current_theme_supports( 'wp-block-styles' ) && ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) ) {
218 // Include opinionated block styles if the theme supports block styles and no $editor_styles are declared, so the editor never appears broken.
219 $wp_edit_blocks_dependencies[] = 'wp-block-library-theme';
220 }
221
222 gutenberg_override_style(
223 $styles,
224 'wp-reset-editor-styles',
225 gutenberg_url( 'build/styles/block-library/reset' . $suffix . '.css' ),
226 array( 'common', 'forms' ), // Make sure the reset is loaded after the default WP Admin styles.
227 $version
228 );
229 $styles->add_data( 'wp-reset-editor-styles', 'rtl', 'replace' );
230 $styles->add_data( 'wp-reset-editor-styles', 'suffix', $suffix );
231
232 gutenberg_override_style(
233 $styles,
234 'wp-editor-classic-layout-styles',
235 gutenberg_url( 'build/styles/edit-post/classic' . $suffix . '.css' ),
236 array(),
237 $version
238 );
239 $styles->add_data( 'wp-editor-classic-layout-styles', 'rtl', 'replace' );
240 $styles->add_data( 'wp-editor-classic-layout-styles', 'suffix', $suffix );
241
242 gutenberg_override_style(
243 $styles,
244 'wp-block-library-editor',
245 gutenberg_url( 'build/styles/block-library/editor' . $suffix . '.css' ),
246 array(),
247 $version
248 );
249 $styles->add_data( 'wp-block-library-editor', 'rtl', 'replace' );
250 $styles->add_data( 'wp-block-library-editor', 'suffix', $suffix );
251
252 gutenberg_override_style(
253 $styles,
254 'wp-edit-blocks',
255 gutenberg_url( 'build/styles/block-library/editor' . $suffix . '.css' ),
256 $wp_edit_blocks_dependencies,
257 $version
258 );
259 $styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' );
260 $styles->add_data( 'wp-edit-blocks', 'suffix', $suffix );
261
262 gutenberg_override_style(
263 $styles,
264 'wp-block-library-theme',
265 gutenberg_url( 'build/styles/block-library/theme' . $suffix . '.css' ),
266 array(),
267 $version
268 );
269 $styles->add_data( 'wp-block-library-theme', 'rtl', 'replace' );
270 $styles->add_data( 'wp-block-library-theme', 'suffix', $suffix );
271
272 gutenberg_override_style(
273 $styles,
274 'classic-theme-styles',
275 gutenberg_url( 'build/styles/block-library/classic' . $suffix . '.css' ),
276 array(),
277 $version
278 );
279 $styles->add_data( 'classic-theme-styles', 'rtl', 'replace' );
280 $styles->add_data( 'classic-theme-styles', 'suffix', $suffix );
281 $styles->add_data( 'classic-theme-styles', 'path', gutenberg_dir_path() . 'build/styles/block-library/classic' . $suffix . '.css' );
282 }
283 add_action( 'wp_default_styles', 'gutenberg_register_packages_styles', 15 );
284
285 /**
286 * Fetches, processes and compiles stored core styles, then combines and renders them to the page.
287 * Styles are stored via the Style Engine API.
288 *
289 * This hook also exists, and should be backported to Core in future versions.
290 * However, it is envisaged that Gutenberg will continue to use the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes to aid continuous development.
291 *
292 * @since 6.1
293 *
294 * @link https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/
295 *
296 * @param array $options {
297 * Optional. An array of options to pass to gutenberg_style_engine_get_stylesheet_from_context(). Default empty array.
298 *
299 * @type bool $optimize Whether to optimize the CSS output, e.g., combine rules. Default is `false`.
300 * @type bool $prettify Whether to add new lines and indents to output. Default is the test of whether the global constant `SCRIPT_DEBUG` is defined.
301 * }
302 *
303 * @return void
304 */
305 function gutenberg_enqueue_stored_styles( $options = array() ) {
306 $is_block_theme = wp_is_block_theme();
307 $is_classic_theme = ! $is_block_theme;
308
309 /*
310 * For block themes, print stored styles in the header.
311 * For classic themes, in the footer.
312 */
313 if (
314 ( $is_block_theme && doing_action( 'wp_footer' ) ) ||
315 ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) )
316 ) {
317 return;
318 }
319
320 $core_styles_keys = array( 'block-supports' );
321 $compiled_core_stylesheet = '';
322 $style_tag_id = 'core';
323 foreach ( $core_styles_keys as $style_key ) {
324 // Adds comment if code is prettified to identify core styles sections in debugging.
325 $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : SCRIPT_DEBUG;
326 if ( $should_prettify ) {
327 $compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n";
328 }
329 // Chains core store ids to signify what the styles contain.
330 $style_tag_id .= '-' . $style_key;
331 $compiled_core_stylesheet .= gutenberg_style_engine_get_stylesheet_from_context( $style_key, $options );
332 }
333
334 // Combines Core styles.
335 if ( ! empty( $compiled_core_stylesheet ) ) {
336 wp_register_style( $style_tag_id, false, array(), true );
337 wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet );
338 wp_enqueue_style( $style_tag_id );
339 }
340
341 // If there are any other stores registered by themes etc., print them out.
342 $additional_stores = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_stores();
343
344 /*
345 * Since the corresponding action hook in Core is removed below,
346 * this function should still honour any styles stored using the Core Style Engine store.
347 */
348 if ( class_exists( 'WP_Style_Engine_CSS_Rules_Store' ) ) {
349 $additional_stores = array_merge( $additional_stores, WP_Style_Engine_CSS_Rules_Store::get_stores() );
350 }
351
352 foreach ( array_keys( $additional_stores ) as $store_name ) {
353 if ( in_array( $store_name, $core_styles_keys, true ) ) {
354 continue;
355 }
356 $styles = gutenberg_style_engine_get_stylesheet_from_context( $store_name, $options );
357 if ( ! empty( $styles ) ) {
358 $key = "wp-style-engine-$store_name";
359 wp_register_style( $key, false, array(), true );
360 wp_add_inline_style( $key, $styles );
361 wp_enqueue_style( $key );
362 }
363 }
364 }
365
366 /**
367 * Registers vendor JavaScript files to be used as dependencies of the editor
368 * and plugins.
369 *
370 * This function is called from a script during the plugin build process, so it
371 * should not call any WordPress PHP functions.
372 *
373 * @since 13.0
374 *
375 * @param WP_Scripts $scripts WP_Scripts instance.
376 */
377 function gutenberg_register_vendor_scripts( $scripts ) {
378 $extension = SCRIPT_DEBUG ? '.js' : '.min.js';
379 $vendors_dir = gutenberg_dir_path() . 'build/scripts/vendors/';
380
381 // When the React 19 experiment is enabled, register React 19 vendor
382 // scripts under the `react`, `react-dom`, and `react-jsx-runtime` handles.
383 $use_react_19 = gutenberg_is_experiment_enabled( 'gutenberg-react-19' );
384
385 $vendor_handles = array( 'react', 'react-dom', 'react-jsx-runtime' );
386
387 foreach ( $vendor_handles as $handle ) {
388 $source = $use_react_19 ? $handle . '-19' : $handle;
389 $asset_file = $vendors_dir . $source . '.min.asset.php';
390 $asset = file_exists( $asset_file ) ? require $asset_file : array();
391 $dependencies = $asset['dependencies'] ?? array();
392 $version = $asset['version'] ?? '0';
393
394 gutenberg_override_script(
395 $scripts,
396 $handle,
397 gutenberg_url( 'build/scripts/vendors/' . $source . $extension ),
398 $dependencies,
399 $version
400 );
401 }
402
403 // WordPress Core in `wp_register_development_scripts` sets `wp-react-refresh-entry`
404 // as a dependency to `react` when `SCRIPT_DEBUG` is true. Preserve that here.
405 if ( SCRIPT_DEBUG ) {
406 $react = $scripts->query( 'react', 'registered' );
407 if ( $react && ! in_array( 'wp-react-refresh-entry', $react->deps, true ) ) {
408 $react->deps[] = 'wp-react-refresh-entry';
409 }
410 }
411 }
412 add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' );
413
414 /**
415 * Registers or re-registers Gutenberg Script Modules.
416 *
417 * Script modules that are registered by Core will be re-registered by Gutenberg.
418 *
419 * @since 19.3.0
420 */
421 function gutenberg_define_interactivity_modules_support() {
422 // Load the auto-generated module registry.
423 $modules_registry_file = gutenberg_dir_path() . 'build/modules/index.php';
424 if ( ! file_exists( $modules_registry_file ) ) {
425 return;
426 }
427
428 $modules = require $modules_registry_file;
429
430 // Add client navigation support to block library modules.
431 foreach ( $modules as $module ) {
432 if ( str_starts_with( $module['id'], '@wordpress/block-library' ) && method_exists( 'WP_Interactivity_API', 'add_client_navigation_support_to_script_module' ) ) {
433 wp_interactivity()->add_client_navigation_support_to_script_module( $module['id'] );
434 }
435 }
436 }
437 remove_action( 'wp_default_scripts', 'wp_define_interactivity_modules_support' );
438 add_action( 'wp_default_scripts', 'gutenberg_define_interactivity_modules_support' );
439
440 /**
441 * Always remove the Core action hook while gutenberg_enqueue_stored_styles() exists to avoid styles being printed twice.
442 * This is also because gutenberg_enqueue_stored_styles uses the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes,
443 * which are in continuous development and generally ahead of Core.
444 */
445 remove_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' );
446 remove_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 );
447
448 // Enqueue stored styles.
449 add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_stored_styles' );
450 add_action( 'wp_footer', 'gutenberg_enqueue_stored_styles', 1 );
451
452 /**
453 * Enqueues the LaTeX to MathML loader script module in the block editor.
454 */
455 add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_latex_to_mathml_loader' );
456 function gutenberg_enqueue_latex_to_mathml_loader() {
457 wp_enqueue_script_module( '@wordpress/latex-to-mathml/loader' );
458 }
459
460 /**
461 * Enqueue the vips loader script module in the block editor.
462 *
463 * This registers @wordpress/vips/worker as a dynamic dependency in the import map,
464 * enabling on-demand loading of the ~3.8MB WASM-based image processing module
465 * when client-side media processing is triggered via @wordpress/upload-media.
466 *
467 * @see packages/vips/src/loader.ts
468 */
469 add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_vips_loader' );
470 function gutenberg_enqueue_vips_loader() {
471 wp_enqueue_script_module( '@wordpress/vips/loader' );
472 }
473
474 /**
475 * Enqueue the video-conversion loader script module in the block editor.
476 *
477 * This registers @wordpress/video-conversion/worker as a dynamic dependency
478 * in the import map, enabling on-demand loading of the WebCodecs-based
479 * GIF-to-video processing module when animated GIF conversion is triggered
480 * via @wordpress/upload-media.
481 *
482 * @see packages/video-conversion/src/loader.ts
483 */
484 if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
485 add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_video_conversion_loader' );
486 }
487 function gutenberg_enqueue_video_conversion_loader() {
488 wp_enqueue_script_module( '@wordpress/video-conversion/loader' );
489 }
490