PluginProbe
Gutenberg / 23.5.1
Gutenberg v23.5.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.5.1, at lib/client-assets.php

477 lines 17.4 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)
129 $styles->query( 'wp-components', 'registered' )->deps[] = 'dashicons';
130
131 // wp-edit-post: add wp-edit-blocks (custom handle not auto-inferred)
132 $styles->query( 'wp-edit-post', 'registered' )->deps[] = 'wp-edit-blocks';
133
134 // wp-edit-site: add core WP styles and custom handles
135 $edit_site_style = $styles->query( 'wp-edit-site', 'registered' );
136 $edit_site_style->deps[] = 'common';
137 $edit_site_style->deps[] = 'forms';
138 $edit_site_style->deps[] = 'wp-block-library-editor';
139
140 // wp-edit-widgets: add wp-edit-blocks (custom handle not auto-inferred)
141 $styles->query( 'wp-edit-widgets', 'registered' )->deps[] = 'wp-edit-blocks';
142
143 // wp-customize-widgets: add wp-edit-blocks (custom handle not auto-inferred)
144 $styles->query( 'wp-customize-widgets', 'registered' )->deps[] = 'wp-edit-blocks';
145
146 // Register wp-theme (Design System tokens from @wordpress/theme) as a
147 // dependency of wp-base-styles so its `:root` token block loads
148 // everywhere wp-base-styles does, including the editor iframe via
149 // $wp_edit_blocks_dependencies below.
150 gutenberg_override_style(
151 $styles,
152 'wp-theme',
153 gutenberg_url( 'build/styles/theme/design-tokens' . $suffix . '.css' ),
154 array(),
155 $version
156 );
157 $styles->add_data( 'wp-theme', 'rtl', 'replace' );
158 $styles->add_data( 'wp-theme', 'suffix', $suffix );
159 $styles->add_data( 'wp-theme', 'path', gutenberg_dir_path() . 'build/styles/theme/design-tokens' . $suffix . '.css' );
160
161 // Register wp-base-styles and add it to the already registered wp-admin stylesheet
162 gutenberg_override_style(
163 $styles,
164 'wp-base-styles',
165 gutenberg_url( 'build/styles/base-styles/admin-schemes' . $suffix . '.css' ),
166 array( 'wp-theme' ),
167 $version
168 );
169 $styles->add_data( 'wp-base-styles', 'rtl', 'replace' );
170 $styles->add_data( 'wp-base-styles', 'suffix', $suffix );
171 $styles->add_data( 'wp-base-styles', 'path', gutenberg_dir_path() . 'build/styles/base-styles/admin-schemes' . $suffix . '.css' );
172 $styles->query( 'wp-admin', 'registered' )->deps[] = 'wp-base-styles';
173
174 gutenberg_override_style(
175 $styles,
176 'wp-block-editor-content',
177 gutenberg_url( 'build/styles/block-editor/content' . $suffix . '.css' ),
178 array( 'wp-components' ),
179 $version
180 );
181 $styles->add_data( 'wp-block-editor-content', 'rtl', 'replace' );
182 $styles->add_data( 'wp-block-editor-content', 'suffix', $suffix );
183
184 $block_library_filename = wp_should_load_separate_core_block_assets() ? 'common' : 'style';
185 gutenberg_override_style(
186 $styles,
187 'wp-block-library',
188 gutenberg_url( 'build/styles/block-library/' . $block_library_filename . $suffix . '.css' ),
189 array(),
190 $version
191 );
192 $styles->add_data( 'wp-block-library', 'rtl', 'replace' );
193 $styles->add_data( 'wp-block-library', 'suffix', $suffix );
194 $styles->add_data( 'wp-block-library', 'path', gutenberg_dir_path() . 'build/styles/block-library/' . $block_library_filename . $suffix . '.css' );
195
196 // Only add CONTENT styles here that should be enqueued in the iframe!
197 $wp_edit_blocks_dependencies = array(
198 // Design System tokens load first so the `:root` CSS custom
199 // properties are defined before any consuming stylesheet reads
200 // them inside the editor iframe.
201 'wp-theme',
202 'wp-components',
203 // This need to be added before the block library styles,
204 // The block library styles override the "reset" styles.
205 'wp-reset-editor-styles',
206 'wp-block-library',
207 // Until #37466, we can't specifically add them as editor styles yet,
208 // so we must hard-code it here as a dependency.
209 'wp-block-editor-content',
210 'wp-base-styles',
211 );
212
213 // Only load the default layout and margin styles for themes without theme.json file.
214 if ( ! wp_theme_has_theme_json() ) {
215 $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles';
216 }
217
218 global $editor_styles;
219 if ( current_theme_supports( 'wp-block-styles' ) && ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) ) {
220 // Include opinionated block styles if the theme supports block styles and no $editor_styles are declared, so the editor never appears broken.
221 $wp_edit_blocks_dependencies[] = 'wp-block-library-theme';
222 }
223
224 gutenberg_override_style(
225 $styles,
226 'wp-reset-editor-styles',
227 gutenberg_url( 'build/styles/block-library/reset' . $suffix . '.css' ),
228 array( 'common', 'forms' ), // Make sure the reset is loaded after the default WP Admin styles.
229 $version
230 );
231 $styles->add_data( 'wp-reset-editor-styles', 'rtl', 'replace' );
232 $styles->add_data( 'wp-reset-editor-styles', 'suffix', $suffix );
233
234 gutenberg_override_style(
235 $styles,
236 'wp-editor-classic-layout-styles',
237 gutenberg_url( 'build/styles/edit-post/classic' . $suffix . '.css' ),
238 array(),
239 $version
240 );
241 $styles->add_data( 'wp-editor-classic-layout-styles', 'rtl', 'replace' );
242 $styles->add_data( 'wp-editor-classic-layout-styles', 'suffix', $suffix );
243
244 gutenberg_override_style(
245 $styles,
246 'wp-block-library-editor',
247 gutenberg_url( 'build/styles/block-library/editor' . $suffix . '.css' ),
248 array(),
249 $version
250 );
251 $styles->add_data( 'wp-block-library-editor', 'rtl', 'replace' );
252 $styles->add_data( 'wp-block-library-editor', 'suffix', $suffix );
253
254 gutenberg_override_style(
255 $styles,
256 'wp-edit-blocks',
257 gutenberg_url( 'build/styles/block-library/editor' . $suffix . '.css' ),
258 $wp_edit_blocks_dependencies,
259 $version
260 );
261 $styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' );
262 $styles->add_data( 'wp-edit-blocks', 'suffix', $suffix );
263
264 gutenberg_override_style(
265 $styles,
266 'wp-block-library-theme',
267 gutenberg_url( 'build/styles/block-library/theme' . $suffix . '.css' ),
268 array(),
269 $version
270 );
271 $styles->add_data( 'wp-block-library-theme', 'rtl', 'replace' );
272 $styles->add_data( 'wp-block-library-theme', 'suffix', $suffix );
273
274 gutenberg_override_style(
275 $styles,
276 'classic-theme-styles',
277 gutenberg_url( 'build/styles/block-library/classic' . $suffix . '.css' ),
278 array(),
279 $version
280 );
281 $styles->add_data( 'classic-theme-styles', 'rtl', 'replace' );
282 $styles->add_data( 'classic-theme-styles', 'suffix', $suffix );
283 $styles->add_data( 'classic-theme-styles', 'path', gutenberg_dir_path() . 'build/styles/block-library/classic' . $suffix . '.css' );
284 }
285 add_action( 'wp_default_styles', 'gutenberg_register_packages_styles', 15 );
286
287 /**
288 * Fetches, processes and compiles stored core styles, then combines and renders them to the page.
289 * Styles are stored via the Style Engine API.
290 *
291 * This hook also exists, and should be backported to Core in future versions.
292 * However, it is envisaged that Gutenberg will continue to use the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes to aid continuous development.
293 *
294 * @since 6.1
295 *
296 * @link https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/
297 *
298 * @param array $options {
299 * Optional. An array of options to pass to gutenberg_style_engine_get_stylesheet_from_context(). Default empty array.
300 *
301 * @type bool $optimize Whether to optimize the CSS output, e.g., combine rules. Default is `false`.
302 * @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.
303 * }
304 *
305 * @return void
306 */
307 function gutenberg_enqueue_stored_styles( $options = array() ) {
308 $is_block_theme = wp_is_block_theme();
309 $is_classic_theme = ! $is_block_theme;
310
311 /*
312 * For block themes, print stored styles in the header.
313 * For classic themes, in the footer.
314 */
315 if (
316 ( $is_block_theme && doing_action( 'wp_footer' ) ) ||
317 ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) )
318 ) {
319 return;
320 }
321
322 $core_styles_keys = array( 'block-supports' );
323 $compiled_core_stylesheet = '';
324 $style_tag_id = 'core';
325 foreach ( $core_styles_keys as $style_key ) {
326 // Adds comment if code is prettified to identify core styles sections in debugging.
327 $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : SCRIPT_DEBUG;
328 if ( $should_prettify ) {
329 $compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n";
330 }
331 // Chains core store ids to signify what the styles contain.
332 $style_tag_id .= '-' . $style_key;
333 $compiled_core_stylesheet .= gutenberg_style_engine_get_stylesheet_from_context( $style_key, $options );
334 }
335
336 // Combines Core styles.
337 if ( ! empty( $compiled_core_stylesheet ) ) {
338 wp_register_style( $style_tag_id, false, array(), true );
339 wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet );
340 wp_enqueue_style( $style_tag_id );
341 }
342
343 // If there are any other stores registered by themes etc., print them out.
344 $additional_stores = WP_Style_Engine_CSS_Rules_Store_Gutenberg::get_stores();
345
346 /*
347 * Since the corresponding action hook in Core is removed below,
348 * this function should still honour any styles stored using the Core Style Engine store.
349 */
350 if ( class_exists( 'WP_Style_Engine_CSS_Rules_Store' ) ) {
351 $additional_stores = array_merge( $additional_stores, WP_Style_Engine_CSS_Rules_Store::get_stores() );
352 }
353
354 foreach ( array_keys( $additional_stores ) as $store_name ) {
355 if ( in_array( $store_name, $core_styles_keys, true ) ) {
356 continue;
357 }
358 $styles = gutenberg_style_engine_get_stylesheet_from_context( $store_name, $options );
359 if ( ! empty( $styles ) ) {
360 $key = "wp-style-engine-$store_name";
361 wp_register_style( $key, false, array(), true );
362 wp_add_inline_style( $key, $styles );
363 wp_enqueue_style( $key );
364 }
365 }
366 }
367
368 /**
369 * Registers vendor JavaScript files to be used as dependencies of the editor
370 * and plugins.
371 *
372 * This function is called from a script during the plugin build process, so it
373 * should not call any WordPress PHP functions.
374 *
375 * @since 13.0
376 *
377 * @param WP_Scripts $scripts WP_Scripts instance.
378 */
379 function gutenberg_register_vendor_scripts( $scripts ) {
380 $extension = SCRIPT_DEBUG ? '.js' : '.min.js';
381 $vendors_dir = gutenberg_dir_path() . 'build/scripts/vendors/';
382
383 // When the React 19 experiment is enabled, register React 19 vendor
384 // scripts under the `react`, `react-dom`, and `react-jsx-runtime` handles.
385 $use_react_19 = gutenberg_is_experiment_enabled( 'gutenberg-react-19' );
386
387 $vendor_handles = array( 'react', 'react-dom', 'react-jsx-runtime' );
388
389 foreach ( $vendor_handles as $handle ) {
390 $source = $use_react_19 ? $handle . '-19' : $handle;
391 $asset_file = $vendors_dir . $source . '.min.asset.php';
392 $asset = file_exists( $asset_file ) ? require $asset_file : array();
393 $dependencies = $asset['dependencies'] ?? array();
394 $version = $asset['version'] ?? '0';
395
396 gutenberg_override_script(
397 $scripts,
398 $handle,
399 gutenberg_url( 'build/scripts/vendors/' . $source . $extension ),
400 $dependencies,
401 $version
402 );
403 }
404
405 // WordPress Core in `wp_register_development_scripts` sets `wp-react-refresh-entry`
406 // as a dependency to `react` when `SCRIPT_DEBUG` is true. Preserve that here.
407 if ( SCRIPT_DEBUG ) {
408 $react = $scripts->query( 'react', 'registered' );
409 if ( $react && ! in_array( 'wp-react-refresh-entry', $react->deps, true ) ) {
410 $react->deps[] = 'wp-react-refresh-entry';
411 }
412 }
413 }
414 add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts' );
415
416 /**
417 * Registers or re-registers Gutenberg Script Modules.
418 *
419 * Script modules that are registered by Core will be re-registered by Gutenberg.
420 *
421 * @since 19.3.0
422 */
423 function gutenberg_define_interactivity_modules_support() {
424 // Load the auto-generated module registry.
425 $modules_registry_file = gutenberg_dir_path() . 'build/modules/index.php';
426 if ( ! file_exists( $modules_registry_file ) ) {
427 return;
428 }
429
430 $modules = require $modules_registry_file;
431
432 // Add client navigation support to block library modules.
433 foreach ( $modules as $module ) {
434 if ( str_starts_with( $module['id'], '@wordpress/block-library' ) && method_exists( 'WP_Interactivity_API', 'add_client_navigation_support_to_script_module' ) ) {
435 wp_interactivity()->add_client_navigation_support_to_script_module( $module['id'] );
436 }
437 }
438 }
439 remove_action( 'wp_default_scripts', 'wp_define_interactivity_modules_support' );
440 add_action( 'wp_default_scripts', 'gutenberg_define_interactivity_modules_support' );
441
442 /**
443 * Always remove the Core action hook while gutenberg_enqueue_stored_styles() exists to avoid styles being printed twice.
444 * This is also because gutenberg_enqueue_stored_styles uses the Style Engine's `gutenberg_*` functions and `_Gutenberg` classes,
445 * which are in continuous development and generally ahead of Core.
446 */
447 remove_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' );
448 remove_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 );
449
450 // Enqueue stored styles.
451 add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_stored_styles' );
452 add_action( 'wp_footer', 'gutenberg_enqueue_stored_styles', 1 );
453
454 add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_latex_to_mathml_loader' );
455 function gutenberg_enqueue_latex_to_mathml_loader() {
456 wp_enqueue_script_module( '@wordpress/latex-to-mathml/loader' );
457 }
458
459 /**
460 * Enqueue the vips loader script module in the block editor.
461 *
462 * This registers @wordpress/vips/worker as a dynamic dependency in the import map,
463 * enabling on-demand loading of the ~3.8MB WASM-based image processing module
464 * when client-side media processing is triggered via @wordpress/upload-media.
465 *
466 * @see packages/vips/src/loader.ts
467 */
468 add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_vips_loader' );
469 function gutenberg_enqueue_vips_loader() {
470 wp_enqueue_script_module( '@wordpress/vips/loader' );
471 }
472
473 add_action( 'admin_enqueue_scripts', 'gutenberg_enqueue_core_abilities' );
474 function gutenberg_enqueue_core_abilities() {
475 wp_enqueue_script_module( '@wordpress/core-abilities' );
476 }
477