PluginProbe
Gutenberg / 5.1.0
Gutenberg v5.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
gutenberg / lib / client-assets.php

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

1,285 lines 39.2 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) for the
4 * 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 * @return string Root path to the gutenberg plugin.
17 *
18 * @since 0.1.0
19 */
20 function gutenberg_dir_path() {
21 return plugin_dir_path( dirname( __FILE__ ) );
22 }
23
24 /**
25 * Retrieves a URL to a file in the gutenberg plugin.
26 *
27 * @param string $path Relative path of the desired file.
28 *
29 * @return string Fully qualified URL pointing to the desired file.
30 *
31 * @since 0.1.0
32 */
33 function gutenberg_url( $path ) {
34 return plugins_url( $path, dirname( __FILE__ ) );
35 }
36
37 /**
38 * Returns contents of an inline script used in appending polyfill scripts for
39 * browsers which fail the provided tests. The provided array is a mapping from
40 * a condition to verify feature support to its polyfill script handle.
41 *
42 * @param array $tests Features to detect.
43 * @return string Conditional polyfill inline script.
44 */
45 function gutenberg_get_script_polyfill( $tests ) {
46 _deprecated_function( __FUNCTION__, '5.0.0', 'wp_get_script_polyfill' );
47
48 global $wp_scripts;
49 return wp_get_script_polyfill( $wp_scripts, $tests );
50 }
51
52 if ( ! function_exists( 'register_tinymce_scripts' ) ) {
53 /**
54 * Registers the main TinyMCE scripts.
55 *
56 * @deprecated 5.0.0 wp_register_tinymce_scripts
57 */
58 function register_tinymce_scripts() {
59 _deprecated_function( __FUNCTION__, '5.0.0', 'wp_register_tinymce_scripts' );
60
61 global $wp_scripts;
62 return wp_register_tinymce_scripts( $wp_scripts );
63 }
64 }
65
66 /**
67 * Registers a script according to `wp_register_script`. Honors this request by
68 * deregistering any script by the same handler before registration.
69 *
70 * @since 4.1.0
71 *
72 * @param string $handle Name of the script. Should be unique.
73 * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
74 * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
75 * @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
76 * as a query string for cache busting purposes. If version is set to false, a version
77 * number is automatically added equal to current installed WordPress version.
78 * If set to null, no version is added.
79 * @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
80 * Default 'false'.
81 */
82 function gutenberg_override_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
83 wp_deregister_script( $handle );
84 wp_register_script( $handle, $src, $deps, $ver, $in_footer );
85 }
86
87 /**
88 * Registers a style according to `wp_register_style`. Honors this request by
89 * deregistering any style by the same handler before registration.
90 *
91 * @since 4.1.0
92 *
93 * @param string $handle Name of the stylesheet. Should be unique.
94 * @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
95 * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
96 * @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
97 * as a query string for cache busting purposes. If version is set to false, a version
98 * number is automatically added equal to current installed WordPress version.
99 * If set to null, no version is added.
100 * @param string $media Optional. The media for which this stylesheet has been defined.
101 * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
102 * '(orientation: portrait)' and '(max-width: 640px)'.
103 */
104 function gutenberg_override_style( $handle, $src, $deps = array(), $ver = false, $media = 'all' ) {
105 wp_deregister_style( $handle );
106 wp_register_style( $handle, $src, $deps, $ver, $media );
107 }
108
109 /**
110 * Registers all the WordPress packages scripts that are in the standardized
111 * `build/` location.
112 *
113 * @since 4.5.0
114 */
115 function gutenberg_register_packages_scripts() {
116 $packages_dependencies = include dirname( __FILE__ ) . '/packages-dependencies.php';
117
118 foreach ( $packages_dependencies as $handle => $dependencies ) {
119 // Remove `wp-` prefix from the handle to get the package's name.
120 $package_name = strpos( $handle, 'wp-' ) === 0 ? substr( $handle, 3 ) : $handle;
121 $path = "build/$package_name/index.js";
122 gutenberg_override_script(
123 $handle,
124 gutenberg_url( $path ),
125 array_merge( $dependencies, array( 'wp-polyfill' ) ),
126 filemtime( gutenberg_dir_path() . $path ),
127 true
128 );
129 }
130 }
131
132 /**
133 * Registers common scripts and styles to be used as dependencies of the editor
134 * and plugins.
135 *
136 * @since 0.1.0
137 */
138 function gutenberg_register_scripts_and_styles() {
139 global $wp_scripts;
140
141 gutenberg_register_vendor_scripts();
142
143 wp_add_inline_script(
144 'wp-polyfill',
145 wp_get_script_polyfill(
146 $wp_scripts,
147 array(
148 '\'fetch\' in window' => 'wp-polyfill-fetch',
149 'document.contains' => 'wp-polyfill-node-contains',
150 'window.FormData && window.FormData.prototype.keys' => 'wp-polyfill-formdata',
151 'Element.prototype.matches && Element.prototype.closest' => 'wp-polyfill-element-closest',
152 ),
153 'after'
154 )
155 );
156
157 gutenberg_register_packages_scripts();
158
159 // Inline scripts.
160 global $wp_scripts;
161 if ( isset( $wp_scripts->registered['wp-api-fetch'] ) ) {
162 $wp_scripts->registered['wp-api-fetch']->deps[] = 'wp-hooks';
163 }
164 wp_add_inline_script(
165 'wp-api-fetch',
166 sprintf(
167 implode(
168 "\n",
169 array(
170 '( function() {',
171 ' var nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );',
172 ' wp.apiFetch.use( nonceMiddleware );',
173 ' wp.hooks.addAction(',
174 ' "heartbeat.tick",',
175 ' "core/api-fetch/create-nonce-middleware",',
176 ' function( response ) {',
177 ' if ( response[ "rest_nonce" ] ) {',
178 ' nonceMiddleware.nonce = response[ "rest_nonce" ];',
179 ' }',
180 ' }',
181 ' )',
182 '} )()',
183 )
184 ),
185 ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' )
186 ),
187 'after'
188 );
189 wp_add_inline_script(
190 'wp-api-fetch',
191 sprintf(
192 'wp.apiFetch.use( wp.apiFetch.createRootURLMiddleware( "%s" ) );',
193 esc_url_raw( get_rest_url() )
194 ),
195 'after'
196 );
197 wp_add_inline_script(
198 'wp-data',
199 implode(
200 "\n",
201 array(
202 '( function() {',
203 ' var userId = ' . get_current_user_ID() . ';',
204 ' var storageKey = "WP_DATA_USER_" + userId;',
205 ' wp.data',
206 ' .use( wp.data.plugins.persistence, { storageKey: storageKey } )',
207 ' .use( wp.data.plugins.controls );',
208 '} )()',
209 )
210 )
211 );
212 global $wp_locale;
213 wp_add_inline_script(
214 'wp-date',
215 sprintf(
216 'wp.date.setSettings( %s );',
217 wp_json_encode(
218 array(
219 'l10n' => array(
220 'locale' => get_user_locale(),
221 'months' => array_values( $wp_locale->month ),
222 'monthsShort' => array_values( $wp_locale->month_abbrev ),
223 'weekdays' => array_values( $wp_locale->weekday ),
224 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
225 'meridiem' => (object) $wp_locale->meridiem,
226 'relative' => array(
227 /* translators: %s: duration */
228 'future' => __( '%s from now', 'default' ),
229 /* translators: %s: duration */
230 'past' => __( '%s ago', 'default' ),
231 ),
232 ),
233 'formats' => array(
234 'time' => get_option( 'time_format', __( 'g:i a', 'default' ) ),
235 'date' => get_option( 'date_format', __( 'F j, Y', 'default' ) ),
236 'datetime' => __( 'F j, Y g:i a', 'default' ),
237 'datetimeAbbreviated' => __( 'M j, Y g:i a', 'default' ),
238 ),
239 'timezone' => array(
240 'offset' => get_option( 'gmt_offset', 0 ),
241 'string' => get_option( 'timezone_string', 'UTC' ),
242 ),
243 )
244 )
245 ),
246 'after'
247 );
248 wp_add_inline_script(
249 'moment',
250 sprintf(
251 "moment.locale( '%s', %s );",
252 get_user_locale(),
253 wp_json_encode(
254 array(
255 'months' => array_values( $wp_locale->month ),
256 'monthsShort' => array_values( $wp_locale->month_abbrev ),
257 'weekdays' => array_values( $wp_locale->weekday ),
258 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
259 'week' => array(
260 'dow' => (int) get_option( 'start_of_week', 0 ),
261 ),
262 'longDateFormat' => array(
263 'LT' => get_option( 'time_format', __( 'g:i a', 'default' ) ),
264 'LTS' => null,
265 'L' => null,
266 'LL' => get_option( 'date_format', __( 'F j, Y', 'default' ) ),
267 'LLL' => __( 'F j, Y g:i a', 'default' ),
268 'LLLL' => null,
269 ),
270 )
271 )
272 ),
273 'after'
274 );
275 // Loading the old editor and its config to ensure the classic block works as expected.
276 wp_add_inline_script(
277 'editor',
278 'window.wp.oldEditor = window.wp.editor;',
279 'after'
280 );
281
282 $tinymce_plugins = array(
283 'charmap',
284 'colorpicker',
285 'hr',
286 'lists',
287 'media',
288 'paste',
289 'tabfocus',
290 'textcolor',
291 'fullscreen',
292 'wordpress',
293 'wpautoresize',
294 'wpeditimage',
295 'wpemoji',
296 'wpgallery',
297 'wplink',
298 'wpdialogs',
299 'wptextpattern',
300 'wpview',
301 );
302 $tinymce_plugins = apply_filters( 'tiny_mce_plugins', $tinymce_plugins, 'classic-block' );
303 $tinymce_plugins = array_unique( $tinymce_plugins );
304
305 $toolbar1 = array(
306 'formatselect',
307 'bold',
308 'italic',
309 'bullist',
310 'numlist',
311 'blockquote',
312 'alignleft',
313 'aligncenter',
314 'alignright',
315 'link',
316 'unlink',
317 'wp_more',
318 'spellchecker',
319 'wp_add_media',
320 'kitchensink',
321 );
322 $toolbar1 = apply_filters( 'mce_buttons', $toolbar1, 'classic-block' );
323
324 $toolbar2 = array(
325 'strikethrough',
326 'hr',
327 'forecolor',
328 'pastetext',
329 'removeformat',
330 'charmap',
331 'outdent',
332 'indent',
333 'undo',
334 'redo',
335 'wp_help',
336 );
337 $toolbar2 = apply_filters( 'mce_buttons_2', $toolbar2, 'classic-block' );
338
339 $toolbar3 = apply_filters( 'mce_buttons_3', array(), 'classic-block' );
340 $toolbar4 = apply_filters( 'mce_buttons_4', array(), 'classic-block' );
341
342 $external_plugins = apply_filters( 'mce_external_plugins', array(), 'classic-block' );
343
344 $tinymce_settings = array(
345 'plugins' => implode( ',', $tinymce_plugins ),
346 'toolbar1' => implode( ',', $toolbar1 ),
347 'toolbar2' => implode( ',', $toolbar2 ),
348 'toolbar3' => implode( ',', $toolbar3 ),
349 'toolbar4' => implode( ',', $toolbar4 ),
350 'external_plugins' => wp_json_encode( $external_plugins ),
351 'classic_block_editor' => true,
352 );
353 $tinymce_settings = apply_filters( 'tiny_mce_before_init', $tinymce_settings, 'classic-block' );
354
355 // Do "by hand" translation from PHP array to js object.
356 // Prevents breakage in some custom settings.
357 $init_obj = '';
358 foreach ( $tinymce_settings as $key => $value ) {
359 if ( is_bool( $value ) ) {
360 $val = $value ? 'true' : 'false';
361 $init_obj .= $key . ':' . $val . ',';
362 continue;
363 } elseif ( ! empty( $value ) && is_string( $value ) && (
364 ( '{' == $value{0} && '}' == $value{strlen( $value ) - 1} ) ||
365 ( '[' == $value{0} && ']' == $value{strlen( $value ) - 1} ) ||
366 preg_match( '/^\(?function ?\(/', $value ) ) ) {
367
368 $init_obj .= $key . ':' . $value . ',';
369 continue;
370 }
371 $init_obj .= $key . ':"' . $value . '",';
372 }
373
374 $init_obj = '{' . trim( $init_obj, ' ,' ) . '}';
375
376 $script = 'window.wpEditorL10n = {
377 tinymce: {
378 baseURL: ' . wp_json_encode( includes_url( 'js/tinymce' ) ) . ',
379 suffix: ' . ( SCRIPT_DEBUG ? '""' : '".min"' ) . ',
380 settings: ' . $init_obj . ',
381 }
382 }';
383
384 wp_add_inline_script( 'wp-block-library', $script, 'before' );
385
386 // Editor Styles.
387 // This empty stylesheet is defined to ensure backward compatibility.
388 gutenberg_override_style( 'wp-blocks', false );
389 $fonts_url = '';
390
391 /*
392 * Translators: Use this to specify the proper Google Font name and variants
393 * to load that is supported by your language. Do not translate.
394 * Set to 'off' to disable loading.
395 */
396 $font_family = _x( 'Noto Serif:400,400i,700,700i', 'Google Font Name and Variants', 'gutenberg' );
397 if ( 'off' !== $font_family ) {
398 $query_args = array(
399 'family' => urlencode( $font_family ),
400 );
401 $fonts_url = esc_url_raw( add_query_arg( $query_args, 'https://fonts.googleapis.com/css' ) );
402 }
403
404 gutenberg_override_style(
405 'wp-editor-font',
406 $fonts_url,
407 array(),
408 null
409 );
410
411 gutenberg_override_style(
412 'wp-editor',
413 gutenberg_url( 'build/editor/style.css' ),
414 array( 'wp-components', 'wp-editor-font', 'wp-nux' ),
415 filemtime( gutenberg_dir_path() . 'build/editor/style.css' )
416 );
417 wp_style_add_data( 'wp-editor', 'rtl', 'replace' );
418
419 gutenberg_override_style(
420 'wp-edit-post',
421 gutenberg_url( 'build/edit-post/style.css' ),
422 array( 'wp-components', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ),
423 filemtime( gutenberg_dir_path() . 'build/edit-post/style.css' )
424 );
425 wp_style_add_data( 'wp-edit-post', 'rtl', 'replace' );
426
427 gutenberg_override_style(
428 'wp-components',
429 gutenberg_url( 'build/components/style.css' ),
430 array(),
431 filemtime( gutenberg_dir_path() . 'build/components/style.css' )
432 );
433 wp_style_add_data( 'wp-components', 'rtl', 'replace' );
434
435 gutenberg_override_style(
436 'wp-block-library',
437 gutenberg_url( 'build/block-library/style.css' ),
438 current_theme_supports( 'wp-block-styles' ) ? array( 'wp-block-library-theme' ) : array(),
439 filemtime( gutenberg_dir_path() . 'build/block-library/style.css' )
440 );
441 wp_style_add_data( 'wp-block-library', 'rtl', 'replace' );
442
443 gutenberg_override_style(
444 'wp-format-library',
445 gutenberg_url( 'build/format-library/style.css' ),
446 array(),
447 filemtime( gutenberg_dir_path() . 'build/format-library/style.css' )
448 );
449 wp_style_add_data( 'wp-format-library', 'rtl', 'replace' );
450
451 gutenberg_override_style(
452 'wp-edit-blocks',
453 gutenberg_url( 'build/block-library/editor.css' ),
454 array(
455 'wp-components',
456 'wp-editor',
457 'wp-block-library',
458 // Always include visual styles so the editor never appears broken.
459 'wp-block-library-theme',
460 ),
461 filemtime( gutenberg_dir_path() . 'build/block-library/editor.css' )
462 );
463 wp_style_add_data( 'wp-edit-blocks', 'rtl', 'replace' );
464
465 gutenberg_override_style(
466 'wp-nux',
467 gutenberg_url( 'build/nux/style.css' ),
468 array( 'wp-components' ),
469 filemtime( gutenberg_dir_path() . 'build/nux/style.css' )
470 );
471 wp_style_add_data( 'wp-nux', 'rtl', 'replace' );
472
473 gutenberg_override_style(
474 'wp-block-library-theme',
475 gutenberg_url( 'build/block-library/theme.css' ),
476 array(),
477 filemtime( gutenberg_dir_path() . 'build/block-library/theme.css' )
478 );
479 wp_style_add_data( 'wp-block-library-theme', 'rtl', 'replace' );
480
481 gutenberg_override_style(
482 'wp-list-reusable-blocks',
483 gutenberg_url( 'build/list-reusable-blocks/style.css' ),
484 array( 'wp-components' ),
485 filemtime( gutenberg_dir_path() . 'build/list-reusable-blocks/style.css' )
486 );
487 wp_style_add_data( 'wp-list-reusable-block', 'rtl', 'replace' );
488
489 if ( defined( 'GUTENBERG_LIVE_RELOAD' ) && GUTENBERG_LIVE_RELOAD ) {
490 $live_reload_url = ( GUTENBERG_LIVE_RELOAD === true ) ? 'http://localhost:35729/livereload.js' : GUTENBERG_LIVE_RELOAD;
491
492 wp_enqueue_script(
493 'gutenberg-live-reload',
494 $live_reload_url
495 );
496 }
497 }
498 add_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 );
499 add_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 );
500
501 /**
502 * Append result of internal request to REST API for purpose of preloading
503 * data to be attached to the page. Expected to be called in the context of
504 * `array_reduce`.
505 *
506 * @deprecated 5.0.0 rest_preload_api_request
507 *
508 * @param array $memo Reduce accumulator.
509 * @param string $path REST API path to preload.
510 * @return array Modified reduce accumulator.
511 */
512 function gutenberg_preload_api_request( $memo, $path ) {
513 _deprecated_function( __FUNCTION__, '5.0.0', 'rest_preload_api_request' );
514
515 return rest_preload_api_request( $memo, $path );
516 }
517
518 /**
519 * Registers vendor JavaScript files to be used as dependencies of the editor
520 * and plugins.
521 *
522 * This function is called from a script during the plugin build process, so it
523 * should not call any WordPress PHP functions.
524 *
525 * @since 0.1.0
526 */
527 function gutenberg_register_vendor_scripts() {
528 $suffix = SCRIPT_DEBUG ? '' : '.min';
529
530 // Vendor Scripts.
531 $react_suffix = ( SCRIPT_DEBUG ? '.development' : '.production' ) . $suffix;
532
533 gutenberg_register_vendor_script(
534 'react',
535 'https://unpkg.com/react@16.6.3/umd/react' . $react_suffix . '.js',
536 array( 'wp-polyfill' )
537 );
538 gutenberg_register_vendor_script(
539 'react-dom',
540 'https://unpkg.com/react-dom@16.6.3/umd/react-dom' . $react_suffix . '.js',
541 array( 'react' )
542 );
543 $moment_script = SCRIPT_DEBUG ? 'moment.js' : 'min/moment.min.js';
544 gutenberg_register_vendor_script(
545 'moment',
546 'https://unpkg.com/moment@2.22.1/' . $moment_script,
547 array()
548 );
549 gutenberg_register_vendor_script(
550 'lodash',
551 'https://unpkg.com/lodash@4.17.11/lodash' . $suffix . '.js'
552 );
553 wp_add_inline_script( 'lodash', 'window.lodash = _.noConflict();' );
554 gutenberg_register_vendor_script(
555 'wp-polyfill-fetch',
556 'https://unpkg.com/whatwg-fetch@3.0.0/dist/fetch.umd.js'
557 );
558 gutenberg_register_vendor_script(
559 'wp-polyfill-formdata',
560 'https://unpkg.com/formdata-polyfill@3.0.9/formdata.min.js'
561 );
562 gutenberg_register_vendor_script(
563 'wp-polyfill-node-contains',
564 'https://unpkg.com/polyfill-library@3.26.0-0/polyfills/Node/prototype/contains/polyfill.js'
565 );
566 gutenberg_register_vendor_script(
567 'wp-polyfill-element-closest',
568 'https://unpkg.com/element-closest@2.0.2/element-closest.js'
569 );
570 gutenberg_register_vendor_script(
571 'wp-polyfill',
572 'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.0.0/polyfill' . $suffix . '.js'
573 );
574 }
575
576 /**
577 * Retrieves a unique and reasonably short and human-friendly filename for a
578 * vendor script based on a URL and the script handle.
579 *
580 * @param string $handle The name of the script.
581 * @param string $src Full URL of the external script.
582 *
583 * @return string Script filename suitable for local caching.
584 *
585 * @since 0.1.0
586 */
587 function gutenberg_vendor_script_filename( $handle, $src ) {
588 $filename = basename( $src );
589 $match = preg_match(
590 '/^'
591 . '(?P<ignore>.*?)'
592 . '(?P<suffix>\.min)?'
593 . '(?P<extension>\.js)'
594 . '(?P<extra>.*)'
595 . '$/',
596 $filename,
597 $filename_pieces
598 );
599
600 $prefix = $handle;
601 $suffix = $match ? $filename_pieces['suffix'] : '';
602 $hash = substr( md5( $src ), 0, 8 );
603
604 return "${prefix}${suffix}.${hash}.js";
605 }
606
607 /**
608 * Registers a vendor script from a URL, preferring a locally cached version if
609 * possible, or downloading it if the cached version is unavailable or
610 * outdated.
611 *
612 * @param string $handle Name of the script.
613 * @param string $src Full URL of the external script.
614 * @param array $deps Optional. An array of registered script handles this
615 * script depends on.
616 *
617 * @since 0.1.0
618 */
619 function gutenberg_register_vendor_script( $handle, $src, $deps = array() ) {
620 if ( defined( 'GUTENBERG_LOAD_VENDOR_SCRIPTS' ) && ! GUTENBERG_LOAD_VENDOR_SCRIPTS ) {
621 return;
622 }
623
624 $filename = gutenberg_vendor_script_filename( $handle, $src );
625
626 if ( defined( 'GUTENBERG_LIST_VENDOR_ASSETS' ) && GUTENBERG_LIST_VENDOR_ASSETS ) {
627 echo "$src|$filename\n";
628 return;
629 }
630
631 $full_path = gutenberg_dir_path() . 'vendor/' . $filename;
632
633 $needs_fetch = (
634 defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && (
635 ! file_exists( $full_path ) ||
636 time() - filemtime( $full_path ) >= DAY_IN_SECONDS
637 )
638 );
639
640 if ( $needs_fetch ) {
641 // Determine whether we can write to this file. If not, don't waste
642 // time doing a network request.
643 // @codingStandardsIgnoreStart
644 $f = @fopen( $full_path, 'a' );
645 // @codingStandardsIgnoreEnd
646 if ( ! $f ) {
647 // Failed to open the file for writing, probably due to server
648 // permissions. Enqueue the script directly from the URL instead.
649 gutenberg_override_script( $handle, $src, $deps, null );
650 return;
651 }
652 fclose( $f );
653 $response = wp_remote_get( $src );
654 if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
655 $f = fopen( $full_path, 'w' );
656 fwrite( $f, wp_remote_retrieve_body( $response ) );
657 fclose( $f );
658 } elseif ( ! filesize( $full_path ) ) {
659 // The request failed. If the file is already cached, continue to
660 // use this file. If not, then unlink the 0 byte file, and enqueue
661 // the script directly from the URL.
662 gutenberg_override_script( $handle, $src, $deps, null );
663 unlink( $full_path );
664 return;
665 }
666 }
667 gutenberg_override_script(
668 $handle,
669 gutenberg_url( 'vendor/' . $filename ),
670 $deps,
671 null
672 );
673 }
674
675 /**
676 * Prepares server-registered blocks for JavaScript, returning an associative
677 * array of registered block data keyed by block name. Data includes properties
678 * of a block relevant for client registration.
679 *
680 * @deprecated 5.0.0 get_block_editor_server_block_settings
681 *
682 * @return array An associative array of registered block data.
683 */
684 function gutenberg_prepare_blocks_for_js() {
685 _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_editor_server_block_settings' );
686
687 return get_block_editor_server_block_settings();
688 }
689
690 /**
691 * Handles the enqueueing of block scripts and styles that are common to both
692 * the editor and the front-end.
693 *
694 * Note: This function must remain *before*
695 * `gutenberg_editor_scripts_and_styles` so that editor-specific stylesheets
696 * are loaded last.
697 *
698 * @since 0.4.0
699 * @deprecated 5.0.0 wp_common_block_scripts_and_styles
700 */
701 function gutenberg_common_scripts_and_styles() {
702 _deprecated_function( __FUNCTION__, '5.0.0', 'wp_common_block_scripts_and_styles' );
703
704 wp_common_block_scripts_and_styles();
705 }
706
707 /**
708 * Enqueues registered block scripts and styles, depending on current rendered
709 * context (only enqueuing editor scripts while in context of the editor).
710 *
711 * @since 2.0.0
712 * @deprecated 5.0.0 wp_enqueue_registered_block_scripts_and_styles
713 */
714 function gutenberg_enqueue_registered_block_scripts_and_styles() {
715 _deprecated_function( __FUNCTION__, '5.0.0', 'wp_enqueue_registered_block_scripts_and_styles' );
716
717 wp_enqueue_registered_block_scripts_and_styles();
718 }
719
720 /**
721 * Assigns a default editor template with a default block by post format, if
722 * not otherwise assigned for a new post of type "post".
723 *
724 * @param array $settings Default editor settings.
725 * @param WP_Post $post Post being edited.
726 *
727 * @return array Filtered block editor settings.
728 */
729 function gutenberg_default_post_format_template( $settings, $post ) {
730 // Only assign template for new posts without explicitly assigned template.
731 $is_new_post = 'auto-draft' === $post->post_status;
732 if ( $is_new_post && ! isset( $settings['template'] ) && 'post' === $post->post_type ) {
733 switch ( get_post_format() ) {
734 case 'audio':
735 $default_block_name = 'core/audio';
736 break;
737 case 'gallery':
738 $default_block_name = 'core/gallery';
739 break;
740 case 'image':
741 $default_block_name = 'core/image';
742 break;
743 case 'quote':
744 $default_block_name = 'core/quote';
745 break;
746 case 'video':
747 $default_block_name = 'core/video';
748 break;
749 }
750
751 if ( isset( $default_block_name ) ) {
752 $settings['template'] = array( array( $default_block_name ) );
753 }
754 }
755
756 return $settings;
757 }
758 add_filter( 'block_editor_settings', 'gutenberg_default_post_format_template', 10, 2 );
759
760 /**
761 * Retrieve a stored autosave that is newer than the post save.
762 *
763 * Deletes autosaves that are older than the post save.
764 *
765 * @param WP_Post $post Post object.
766 * @return WP_Post|boolean The post autosave. False if none found.
767 */
768 function gutenberg_get_autosave_newer_than_post_save( $post ) {
769 // Add autosave data if it is newer and changed.
770 $autosave = wp_get_post_autosave( $post->ID );
771
772 if ( ! $autosave ) {
773 return false;
774 }
775
776 // Check if the autosave is newer than the current post.
777 if (
778 mysql2date( 'U', $autosave->post_modified_gmt, false ) > mysql2date( 'U', $post->post_modified_gmt, false )
779 ) {
780 return $autosave;
781 }
782
783 // If the autosave isn't newer, remove it.
784 wp_delete_post_revision( $autosave->ID );
785
786 return false;
787 }
788
789 /**
790 * Returns all the block categories.
791 *
792 * @since 2.2.0
793 * @deprecated 5.0.0 get_block_categories
794 *
795 * @param WP_Post $post Post object.
796 * @return Object[] Block categories.
797 */
798 function gutenberg_get_block_categories( $post ) {
799 _deprecated_function( __FUNCTION__, '5.0.0', 'get_block_categories' );
800
801 return get_block_categories( $post );
802 }
803
804 /**
805 * Loads Gutenberg Locale Data.
806 */
807 function gutenberg_load_locale_data() {
808 // Prepare Jed locale data.
809 $locale_data = gutenberg_get_jed_locale_data( 'gutenberg' );
810 wp_add_inline_script(
811 'wp-i18n',
812 'wp.i18n.setLocaleData( ' . json_encode( $locale_data ) . ' );'
813 );
814 }
815
816 /**
817 * Retrieve The available image sizes for a post
818 *
819 * @return array
820 */
821 function gutenberg_get_available_image_sizes() {
822 $size_names = apply_filters(
823 'image_size_names_choose',
824 array(
825 'thumbnail' => __( 'Thumbnail', 'gutenberg' ),
826 'medium' => __( 'Medium', 'gutenberg' ),
827 'large' => __( 'Large', 'gutenberg' ),
828 'full' => __( 'Full Size', 'gutenberg' ),
829 )
830 );
831
832 $all_sizes = array();
833 foreach ( $size_names as $size_slug => $size_name ) {
834 $all_sizes[] = array(
835 'slug' => $size_slug,
836 'name' => $size_name,
837 );
838 }
839
840 return $all_sizes;
841 }
842
843 /**
844 * Extends block editor settings to include Gutenberg's `editor-styles.css` as
845 * taking precedent those styles shipped with core.
846 *
847 * @param array $settings Default editor settings.
848 *
849 * @return array Filtered editor settings.
850 */
851 function gutenberg_extend_block_editor_styles( $settings ) {
852 $editor_styles_file = gutenberg_dir_path() . 'build/editor/editor-styles.css';
853
854 /*
855 * If, for whatever reason, the built editor styles do not exist, avoid
856 * override and fall back to the default.
857 */
858 if ( ! file_exists( $editor_styles_file ) ) {
859 return $settings;
860 }
861
862 if ( empty( $settings['styles'] ) ) {
863 $settings['styles'] = array();
864 } else {
865 /*
866 * The styles setting is an array of CSS strings, so there is no direct
867 * way to find the default styles. To maximize stability, load (again)
868 * the default styles from disk and find its place in the array.
869 *
870 * See: https://github.com/WordPress/wordpress-develop/blob/5.0.3/src/wp-admin/edit-form-blocks.php#L168-L175
871 */
872
873 $default_styles = file_get_contents(
874 ABSPATH . WPINC . '/css/dist/editor/editor-styles.css'
875 );
876
877 /*
878 * Iterate backwards from the end of the array since the preferred
879 * insertion point in case not found is prepended as first entry.
880 */
881 for ( $i = count( $settings['styles'] ) - 1; $i >= 0; $i-- ) {
882 if ( isset( $settings['styles'][ $i ]['css'] ) &&
883 $default_styles === $settings['styles'][ $i ]['css'] ) {
884 break;
885 }
886 }
887 }
888
889 $editor_styles = array(
890 'css' => file_get_contents( $editor_styles_file ),
891 );
892
893 // Substitute default styles if found. Otherwise, prepend to setting array.
894 if ( isset( $i ) && $i >= 0 ) {
895 $settings['styles'][ $i ] = $editor_styles;
896 } else {
897 array_unshift( $settings['styles'], $editor_styles );
898 }
899
900 return $settings;
901 }
902 add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' );
903
904 /**
905 * Scripts & Styles.
906 *
907 * Enqueues the needed scripts and styles when visiting the top-level page of
908 * the Gutenberg editor.
909 *
910 * @since 0.1.0
911 *
912 * @param string $hook Screen name.
913 */
914 function gutenberg_editor_scripts_and_styles( $hook ) {
915 global $wp_meta_boxes;
916
917 // Enqueue heartbeat separately as an "optional" dependency of the editor.
918 // Heartbeat is used for automatic nonce refreshing, but some hosts choose
919 // to disable it outright.
920 wp_enqueue_script( 'heartbeat' );
921
922 wp_enqueue_script( 'wp-edit-post' );
923 wp_enqueue_script( 'wp-format-library' );
924 wp_enqueue_style( 'wp-format-library' );
925
926 global $post;
927
928 // Set initial title to empty string for auto draft for duration of edit.
929 // Otherwise, title defaults to and displays as "Auto Draft".
930 $is_new_post = 'auto-draft' === $post->post_status;
931
932 // Set the post type name.
933 $post_type = get_post_type( $post );
934 $post_type_object = get_post_type_object( $post_type );
935 $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
936
937 $preload_paths = array(
938 '/',
939 '/wp/v2/types?context=edit',
940 '/wp/v2/taxonomies?per_page=-1&context=edit',
941 '/wp/v2/themes?status=active',
942 sprintf( '/wp/v2/%s/%s?context=edit', $rest_base, $post->ID ),
943 sprintf( '/wp/v2/types/%s?context=edit', $post_type ),
944 sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ),
945 array( '/wp/v2/media', 'OPTIONS' ),
946 array( '/wp/v2/blocks', 'OPTIONS' ),
947 );
948
949 /**
950 * Preload common data by specifying an array of REST API paths that will be preloaded.
951 *
952 * Filters the array of paths that will be preloaded.
953 *
954 * @param array $preload_paths Array of paths to preload
955 * @param object $post The post resource data.
956 */
957 $preload_paths = apply_filters( 'block_editor_preload_paths', $preload_paths, $post );
958
959 // Ensure the global $post remains the same after
960 // API data is preloaded. Because API preloading
961 // can call the_content and other filters, callbacks
962 // can unexpectedly modify $post resulting in issues
963 // like https://github.com/WordPress/gutenberg/issues/7468.
964 $backup_global_post = $post;
965
966 $preload_data = array_reduce(
967 $preload_paths,
968 'rest_preload_api_request',
969 array()
970 );
971
972 // Restore the global $post as it was before API preloading.
973 $post = $backup_global_post;
974
975 wp_add_inline_script(
976 'wp-api-fetch',
977 sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ),
978 'after'
979 );
980
981 wp_add_inline_script(
982 'wp-blocks',
983 sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ),
984 'after'
985 );
986
987 // Assign initial edits, if applicable. These are not initially assigned
988 // to the persisted post, but should be included in its save payload.
989 if ( $is_new_post ) {
990 // Override "(Auto Draft)" new post default title with empty string,
991 // or filtered value.
992 $initial_edits = array(
993 'title' => $post->post_title,
994 'content' => $post->post_content,
995 'excerpt' => $post->post_excerpt,
996 );
997 } else {
998 $initial_edits = null;
999 }
1000
1001 gutenberg_load_locale_data();
1002
1003 // Preload server-registered block schemas.
1004 wp_add_inline_script(
1005 'wp-blocks',
1006 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( get_block_editor_server_block_settings() ) . ');'
1007 );
1008
1009 // Get admin url for handling meta boxes.
1010 $meta_box_url = admin_url( 'post.php' );
1011 $meta_box_url = add_query_arg(
1012 array(
1013 'post' => $post->ID,
1014 'action' => 'edit',
1015 'meta-box-loader' => true,
1016 '_wpnonce' => wp_create_nonce( 'meta-box-loader' ),
1017 ),
1018 $meta_box_url
1019 );
1020 wp_localize_script( 'wp-editor', '_wpMetaBoxUrl', $meta_box_url );
1021
1022 // Initialize the editor.
1023 $gutenberg_theme_support = get_theme_support( 'gutenberg' );
1024 $align_wide = get_theme_support( 'align-wide' );
1025 $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
1026 $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
1027
1028 if ( ! empty( $gutenberg_theme_support ) ) {
1029 wp_enqueue_script( 'wp-deprecated' );
1030 wp_add_inline_script( 'wp-deprecated', 'wp.deprecated( "`gutenberg` theme support", { plugin: "Gutenberg", version: "5.2", alternative: "`align-wide` theme support" } );' );
1031 }
1032
1033 /**
1034 * Filters the allowed block types for the editor, defaulting to true (all
1035 * block types supported).
1036 *
1037 * @param bool|array $allowed_block_types Array of block type slugs, or
1038 * boolean to enable/disable all.
1039 * @param object $post The post resource data.
1040 */
1041 $allowed_block_types = apply_filters( 'allowed_block_types', true, $post );
1042
1043 // Get all available templates for the post/page attributes meta-box.
1044 // The "Default template" array element should only be added if the array is
1045 // not empty so we do not trigger the template select element without any options
1046 // besides the default value.
1047 $available_templates = wp_get_theme()->get_page_templates( get_post( $post->ID ) );
1048 $available_templates = ! empty( $available_templates ) ? array_merge(
1049 array(
1050 '' => apply_filters( 'default_page_template_title', __( 'Default template', 'gutenberg' ), 'rest-api' ),
1051 ),
1052 $available_templates
1053 ) : $available_templates;
1054
1055 // Media settings.
1056 $max_upload_size = wp_max_upload_size();
1057 if ( ! $max_upload_size ) {
1058 $max_upload_size = 0;
1059 }
1060
1061 // Editor Styles.
1062 global $editor_styles;
1063 $styles = array(
1064 array(
1065 'css' => file_get_contents(
1066 ABSPATH . WPINC . '/css/dist/editor/editor-styles.css'
1067 ),
1068 ),
1069 );
1070
1071 /* Translators: Use this to specify the CSS font family for the default font */
1072 $locale_font_family = esc_html_x( 'Noto Serif', 'CSS Font Family for Editor Font', 'gutenberg' );
1073 $styles[] = array(
1074 'css' => "body { font-family: '$locale_font_family' }",
1075 );
1076
1077 if ( $editor_styles && current_theme_supports( 'editor-styles' ) ) {
1078 foreach ( $editor_styles as $style ) {
1079 if ( filter_var( $style, FILTER_VALIDATE_URL ) ) {
1080 $styles[] = array(
1081 'css' => file_get_contents( $style ),
1082 );
1083 } else {
1084 $file = get_theme_file_path( $style );
1085 if ( file_exists( $file ) ) {
1086 $styles[] = array(
1087 'css' => file_get_contents( $file ),
1088 'baseURL' => get_theme_file_uri( $style ),
1089 );
1090 }
1091 }
1092 }
1093 }
1094
1095 // Lock settings.
1096 $user_id = wp_check_post_lock( $post->ID );
1097 if ( $user_id ) {
1098 /**
1099 * Filters whether to show the post locked dialog.
1100 *
1101 * Returning a falsey value to the filter will short-circuit displaying the dialog.
1102 *
1103 * @since 3.6.0
1104 *
1105 * @param bool $display Whether to display the dialog. Default true.
1106 * @param WP_Post $post Post object.
1107 * @param WP_User|bool $user The user id currently editing the post.
1108 */
1109 if ( apply_filters( 'show_post_locked_dialog', true, $post, $user_id ) ) {
1110 $locked = true;
1111 }
1112
1113 $user_details = null;
1114 if ( $locked ) {
1115 $user = get_userdata( $user_id );
1116 $user_details = array(
1117 'name' => $user->display_name,
1118 );
1119 $avatar = get_avatar( $user_id, 64 );
1120 if ( $avatar ) {
1121 if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
1122 $user_details['avatar'] = $matches[1];
1123 }
1124 }
1125 }
1126
1127 $lock_details = array(
1128 'isLocked' => $locked,
1129 'user' => $user_details,
1130 );
1131 } else {
1132
1133 // Lock the post.
1134 $active_post_lock = wp_set_post_lock( $post->ID );
1135 $lock_details = array(
1136 'isLocked' => false,
1137 'activePostLock' => esc_attr( implode( ':', $active_post_lock ) ),
1138 );
1139 }
1140
1141 $editor_settings = array(
1142 'alignWide' => $align_wide || ! empty( $gutenberg_theme_support[0]['wide-images'] ), // Backcompat. Use `align-wide` outside of `gutenberg` array.
1143 'availableTemplates' => $available_templates,
1144 'allowedBlockTypes' => $allowed_block_types,
1145 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ),
1146 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
1147 'disablePostFormats' => ! current_theme_supports( 'post-formats' ),
1148 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ),
1149 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ),
1150 'isRTL' => is_rtl(),
1151 'autosaveInterval' => 10,
1152 'maxUploadFileSize' => $max_upload_size,
1153 'allowedMimeTypes' => get_allowed_mime_types(),
1154 'styles' => $styles,
1155 'imageSizes' => gutenberg_get_available_image_sizes(),
1156 'richEditingEnabled' => user_can_richedit(),
1157
1158 // Ideally, we'd remove this and rely on a REST API endpoint.
1159 'postLock' => $lock_details,
1160 'postLockUtils' => array(
1161 'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ),
1162 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ),
1163 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
1164 ),
1165
1166 // Whether or not to load the 'postcustom' meta box is stored as a user meta
1167 // field so that we're not always loading its assets.
1168 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ),
1169 );
1170
1171 $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post );
1172 if ( $post_autosave ) {
1173 $editor_settings['autosave'] = array(
1174 'editLink' => get_edit_post_link( $post_autosave->ID ),
1175 );
1176 }
1177
1178 if ( false !== $color_palette ) {
1179 $editor_settings['colors'] = $color_palette;
1180 }
1181
1182 if ( false !== $font_sizes ) {
1183 $editor_settings['fontSizes'] = $font_sizes;
1184 }
1185
1186 if ( ! empty( $post_type_object->template ) ) {
1187 $editor_settings['template'] = $post_type_object->template;
1188 $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
1189 }
1190
1191 $current_screen = get_current_screen();
1192 $core_meta_boxes = array();
1193
1194 // Make sure the current screen is set as well as the normal core metaboxes.
1195 if ( isset( $current_screen->id ) && isset( $wp_meta_boxes[ $current_screen->id ]['normal']['core'] ) ) {
1196 $core_meta_boxes = $wp_meta_boxes[ $current_screen->id ]['normal']['core'];
1197 }
1198
1199 // Check if the Custom Fields meta box has been removed at some point.
1200 if ( ! isset( $core_meta_boxes['postcustom'] ) || ! $core_meta_boxes['postcustom'] ) {
1201 unset( $editor_settings['enableCustomFields'] );
1202 }
1203
1204 /**
1205 * Filters the settings to pass to the block editor.
1206 *
1207 * @since 3.7.0
1208 *
1209 * @param array $editor_settings Default editor settings.
1210 * @param WP_Post $post Post being edited.
1211 */
1212 $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post );
1213
1214 $init_script = <<<JS
1215 ( function() {
1216 window._wpLoadBlockEditor = new Promise( function( resolve ) {
1217 wp.domReady( function() {
1218 resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) );
1219 } );
1220 } );
1221
1222 Object.defineProperty( window, '_wpLoadGutenbergEditor', {
1223 get: function() {
1224 // TODO: Hello future maintainer. In removing this deprecation,
1225 // ensure also to check whether `wp-editor`'s dependencies in
1226 // `package-dependencies.php` still require `wp-deprecated`.
1227 wp.deprecated( '`window._wpLoadGutenbergEditor`', {
1228 plugin: 'Gutenberg',
1229 version: '5.2',
1230 alternative: '`window._wpLoadBlockEditor`',
1231 hint: 'This is a private API, not intended for public use. It may be removed in the future.'
1232 } );
1233
1234 return window._wpLoadBlockEditor;
1235 }
1236 } );
1237 } )();
1238 JS;
1239
1240 $script = sprintf(
1241 $init_script,
1242 $post->post_type,
1243 $post->ID,
1244 wp_json_encode( $editor_settings ),
1245 wp_json_encode( $initial_edits )
1246 );
1247 wp_add_inline_script( 'wp-edit-post', $script );
1248
1249 /**
1250 * Scripts
1251 */
1252 wp_enqueue_media(
1253 array(
1254 'post' => $post->ID,
1255 )
1256 );
1257 wp_enqueue_editor();
1258
1259 /**
1260 * Styles
1261 */
1262 wp_enqueue_style( 'wp-edit-post' );
1263
1264 /**
1265 * Fires after block assets have been enqueued for the editing interface.
1266 *
1267 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
1268 *
1269 * In the function call you supply, simply use `wp_enqueue_script` and
1270 * `wp_enqueue_style` to add your functionality to the Gutenberg editor.
1271 *
1272 * @since 0.4.0
1273 */
1274 do_action( 'enqueue_block_editor_assets' );
1275 }
1276
1277 /**
1278 * Enqueue the reusable blocks listing page's script
1279 *
1280 * @deprecated 5.0.0
1281 */
1282 function gutenberg_load_list_reusable_blocks() {
1283 _deprecated_function( __FUNCTION__, '5.0.0' );
1284 }
1285