PluginProbe
Gutenberg / 5.0.0
Gutenberg v5.0.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / client-assets.php

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

1,259 lines 38.5 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.5/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 * Scripts & Styles.
845 *
846 * Enqueues the needed scripts and styles when visiting the top-level page of
847 * the Gutenberg editor.
848 *
849 * @since 0.1.0
850 *
851 * @param string $hook Screen name.
852 */
853 function gutenberg_editor_scripts_and_styles( $hook ) {
854 global $wp_scripts, $wp_meta_boxes;
855
856 // Add "wp-hooks" as dependency of "heartbeat".
857 $heartbeat_script = $wp_scripts->query( 'heartbeat', 'registered' );
858 if ( $heartbeat_script && ! in_array( 'wp-hooks', $heartbeat_script->deps ) ) {
859 $heartbeat_script->deps[] = 'wp-hooks';
860 }
861
862 // Enqueue heartbeat separately as an "optional" dependency of the editor.
863 // Heartbeat is used for automatic nonce refreshing, but some hosts choose
864 // to disable it outright.
865 wp_enqueue_script( 'heartbeat' );
866
867 // Transforms heartbeat jQuery events into equivalent hook actions. This
868 // avoids a dependency on jQuery for listening to the event.
869 $heartbeat_hooks = <<<JS
870 ( function() {
871 jQuery( document ).on( [
872 'heartbeat-send',
873 'heartbeat-tick',
874 'heartbeat-error',
875 'heartbeat-connection-lost',
876 'heartbeat-connection-restored',
877 'heartbeat-nonces-expired',
878 ].join( ' ' ), function( event ) {
879 var actionName = event.type.replace( /-/g, '.' ),
880 args;
881
882 // Omit the event argument in applying arguments to the hook callback.
883 // The remaining arguments are passed to the hook.
884 args = Array.prototype.slice.call( arguments, 1 );
885
886 wp.hooks.doAction.apply( null, [ actionName ].concat( args ) );
887 } );
888 } )();
889 JS;
890 wp_add_inline_script(
891 'heartbeat',
892 $heartbeat_hooks,
893 'after'
894 );
895
896 wp_enqueue_script( 'wp-edit-post' );
897 wp_enqueue_script( 'wp-format-library' );
898 wp_enqueue_style( 'wp-format-library' );
899
900 global $post;
901
902 // Set initial title to empty string for auto draft for duration of edit.
903 // Otherwise, title defaults to and displays as "Auto Draft".
904 $is_new_post = 'auto-draft' === $post->post_status;
905
906 // Set the post type name.
907 $post_type = get_post_type( $post );
908 $post_type_object = get_post_type_object( $post_type );
909 $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
910
911 $preload_paths = array(
912 '/',
913 '/wp/v2/types?context=edit',
914 '/wp/v2/taxonomies?per_page=-1&context=edit',
915 '/wp/v2/themes?status=active',
916 sprintf( '/wp/v2/%s/%s?context=edit', $rest_base, $post->ID ),
917 sprintf( '/wp/v2/types/%s?context=edit', $post_type ),
918 sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ),
919 array( '/wp/v2/media', 'OPTIONS' ),
920 array( '/wp/v2/blocks', 'OPTIONS' ),
921 );
922
923 /**
924 * Preload common data by specifying an array of REST API paths that will be preloaded.
925 *
926 * Filters the array of paths that will be preloaded.
927 *
928 * @param array $preload_paths Array of paths to preload
929 * @param object $post The post resource data.
930 */
931 $preload_paths = apply_filters( 'block_editor_preload_paths', $preload_paths, $post );
932
933 // Ensure the global $post remains the same after
934 // API data is preloaded. Because API preloading
935 // can call the_content and other filters, callbacks
936 // can unexpectedly modify $post resulting in issues
937 // like https://github.com/WordPress/gutenberg/issues/7468.
938 $backup_global_post = $post;
939
940 $preload_data = array_reduce(
941 $preload_paths,
942 'rest_preload_api_request',
943 array()
944 );
945
946 // Restore the global $post as it was before API preloading.
947 $post = $backup_global_post;
948
949 wp_add_inline_script(
950 'wp-api-fetch',
951 sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ),
952 'after'
953 );
954
955 wp_add_inline_script(
956 'wp-blocks',
957 sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ),
958 'after'
959 );
960
961 // Assign initial edits, if applicable. These are not initially assigned
962 // to the persisted post, but should be included in its save payload.
963 if ( $is_new_post ) {
964 // Override "(Auto Draft)" new post default title with empty string,
965 // or filtered value.
966 $initial_edits = array(
967 'title' => $post->post_title,
968 'content' => $post->post_content,
969 'excerpt' => $post->post_excerpt,
970 );
971 } else {
972 $initial_edits = null;
973 }
974
975 gutenberg_load_locale_data();
976
977 // Preload server-registered block schemas.
978 wp_add_inline_script(
979 'wp-blocks',
980 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( get_block_editor_server_block_settings() ) . ');'
981 );
982
983 // Get admin url for handling meta boxes.
984 $meta_box_url = admin_url( 'post.php' );
985 $meta_box_url = add_query_arg(
986 array(
987 'post' => $post->ID,
988 'action' => 'edit',
989 'meta-box-loader' => true,
990 '_wpnonce' => wp_create_nonce( 'meta-box-loader' ),
991 ),
992 $meta_box_url
993 );
994 wp_localize_script( 'wp-editor', '_wpMetaBoxUrl', $meta_box_url );
995
996 // Initialize the editor.
997 $gutenberg_theme_support = get_theme_support( 'gutenberg' );
998 $align_wide = get_theme_support( 'align-wide' );
999 $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
1000 $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
1001
1002 if ( ! empty( $gutenberg_theme_support ) ) {
1003 wp_enqueue_script( 'wp-deprecated' );
1004 wp_add_inline_script( 'wp-deprecated', 'wp.deprecated( "`gutenberg` theme support", { plugin: "Gutenberg", version: "5.2", alternative: "`align-wide` theme support" } );' );
1005 }
1006
1007 /**
1008 * Filters the allowed block types for the editor, defaulting to true (all
1009 * block types supported).
1010 *
1011 * @param bool|array $allowed_block_types Array of block type slugs, or
1012 * boolean to enable/disable all.
1013 * @param object $post The post resource data.
1014 */
1015 $allowed_block_types = apply_filters( 'allowed_block_types', true, $post );
1016
1017 // Get all available templates for the post/page attributes meta-box.
1018 // The "Default template" array element should only be added if the array is
1019 // not empty so we do not trigger the template select element without any options
1020 // besides the default value.
1021 $available_templates = wp_get_theme()->get_page_templates( get_post( $post->ID ) );
1022 $available_templates = ! empty( $available_templates ) ? array_merge(
1023 array(
1024 '' => apply_filters( 'default_page_template_title', __( 'Default template', 'gutenberg' ), 'rest-api' ),
1025 ),
1026 $available_templates
1027 ) : $available_templates;
1028
1029 // Media settings.
1030 $max_upload_size = wp_max_upload_size();
1031 if ( ! $max_upload_size ) {
1032 $max_upload_size = 0;
1033 }
1034
1035 // Editor Styles.
1036 global $editor_styles;
1037 $styles = array(
1038 array(
1039 'css' => file_get_contents(
1040 gutenberg_dir_path() . 'build/editor/editor-styles.css'
1041 ),
1042 ),
1043 );
1044
1045 /* Translators: Use this to specify the CSS font family for the default font */
1046 $locale_font_family = esc_html_x( 'Noto Serif', 'CSS Font Family for Editor Font', 'gutenberg' );
1047 $styles[] = array(
1048 'css' => "body { font-family: '$locale_font_family' }",
1049 );
1050
1051 if ( $editor_styles && current_theme_supports( 'editor-styles' ) ) {
1052 foreach ( $editor_styles as $style ) {
1053 if ( filter_var( $style, FILTER_VALIDATE_URL ) ) {
1054 $styles[] = array(
1055 'css' => file_get_contents( $style ),
1056 );
1057 } else {
1058 $file = get_theme_file_path( $style );
1059 if ( file_exists( $file ) ) {
1060 $styles[] = array(
1061 'css' => file_get_contents( $file ),
1062 'baseURL' => get_theme_file_uri( $style ),
1063 );
1064 }
1065 }
1066 }
1067 }
1068
1069 // Lock settings.
1070 $user_id = wp_check_post_lock( $post->ID );
1071 if ( $user_id ) {
1072 /**
1073 * Filters whether to show the post locked dialog.
1074 *
1075 * Returning a falsey value to the filter will short-circuit displaying the dialog.
1076 *
1077 * @since 3.6.0
1078 *
1079 * @param bool $display Whether to display the dialog. Default true.
1080 * @param WP_Post $post Post object.
1081 * @param WP_User|bool $user The user id currently editing the post.
1082 */
1083 if ( apply_filters( 'show_post_locked_dialog', true, $post, $user_id ) ) {
1084 $locked = true;
1085 }
1086
1087 $user_details = null;
1088 if ( $locked ) {
1089 $user = get_userdata( $user_id );
1090 $user_details = array(
1091 'name' => $user->display_name,
1092 );
1093 $avatar = get_avatar( $user_id, 64 );
1094 if ( $avatar ) {
1095 if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
1096 $user_details['avatar'] = $matches[1];
1097 }
1098 }
1099 }
1100
1101 $lock_details = array(
1102 'isLocked' => $locked,
1103 'user' => $user_details,
1104 );
1105 } else {
1106
1107 // Lock the post.
1108 $active_post_lock = wp_set_post_lock( $post->ID );
1109 $lock_details = array(
1110 'isLocked' => false,
1111 'activePostLock' => esc_attr( implode( ':', $active_post_lock ) ),
1112 );
1113 }
1114
1115 $editor_settings = array(
1116 'alignWide' => $align_wide || ! empty( $gutenberg_theme_support[0]['wide-images'] ), // Backcompat. Use `align-wide` outside of `gutenberg` array.
1117 'availableTemplates' => $available_templates,
1118 'allowedBlockTypes' => $allowed_block_types,
1119 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ),
1120 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
1121 'disablePostFormats' => ! current_theme_supports( 'post-formats' ),
1122 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ),
1123 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ),
1124 'isRTL' => is_rtl(),
1125 'autosaveInterval' => 10,
1126 'maxUploadFileSize' => $max_upload_size,
1127 'allowedMimeTypes' => get_allowed_mime_types(),
1128 'styles' => $styles,
1129 'imageSizes' => gutenberg_get_available_image_sizes(),
1130 'richEditingEnabled' => user_can_richedit(),
1131
1132 // Ideally, we'd remove this and rely on a REST API endpoint.
1133 'postLock' => $lock_details,
1134 'postLockUtils' => array(
1135 'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ),
1136 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ),
1137 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
1138 ),
1139
1140 // Whether or not to load the 'postcustom' meta box is stored as a user meta
1141 // field so that we're not always loading its assets.
1142 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ),
1143 );
1144
1145 $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post );
1146 if ( $post_autosave ) {
1147 $editor_settings['autosave'] = array(
1148 'editLink' => add_query_arg( 'gutenberg', true, get_edit_post_link( $post_autosave->ID ) ),
1149 );
1150 }
1151
1152 if ( false !== $color_palette ) {
1153 $editor_settings['colors'] = $color_palette;
1154 }
1155
1156 if ( ! empty( $font_sizes ) ) {
1157 $editor_settings['fontSizes'] = $font_sizes;
1158 }
1159
1160 if ( ! empty( $post_type_object->template ) ) {
1161 $editor_settings['template'] = $post_type_object->template;
1162 $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
1163 }
1164
1165 $current_screen = get_current_screen();
1166 $core_meta_boxes = array();
1167
1168 // Make sure the current screen is set as well as the normal core metaboxes.
1169 if ( isset( $current_screen->id ) && isset( $wp_meta_boxes[ $current_screen->id ]['normal']['core'] ) ) {
1170 $core_meta_boxes = $wp_meta_boxes[ $current_screen->id ]['normal']['core'];
1171 }
1172
1173 // Check if the Custom Fields meta box has been removed at some point.
1174 if ( ! isset( $core_meta_boxes['postcustom'] ) || ! $core_meta_boxes['postcustom'] ) {
1175 unset( $editor_settings['enableCustomFields'] );
1176 }
1177
1178 /**
1179 * Filters the settings to pass to the block editor.
1180 *
1181 * @since 3.7.0
1182 *
1183 * @param array $editor_settings Default editor settings.
1184 * @param WP_Post $post Post being edited.
1185 */
1186 $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post );
1187
1188 $init_script = <<<JS
1189 ( function() {
1190 window._wpLoadBlockEditor = new Promise( function( resolve ) {
1191 wp.domReady( function() {
1192 resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) );
1193 } );
1194 } );
1195
1196 Object.defineProperty( window, '_wpLoadGutenbergEditor', {
1197 get: function() {
1198 // TODO: Hello future maintainer. In removing this deprecation,
1199 // ensure also to check whether `wp-editor`'s dependencies in
1200 // `package-dependencies.php` still require `wp-deprecated`.
1201 wp.deprecated( '`window._wpLoadGutenbergEditor`', {
1202 plugin: 'Gutenberg',
1203 version: '5.2',
1204 alternative: '`window._wpLoadBlockEditor`',
1205 hint: 'This is a private API, not intended for public use. It may be removed in the future.'
1206 } );
1207
1208 return window._wpLoadBlockEditor;
1209 }
1210 } );
1211 } )();
1212 JS;
1213
1214 $script = sprintf(
1215 $init_script,
1216 $post->post_type,
1217 $post->ID,
1218 wp_json_encode( $editor_settings ),
1219 wp_json_encode( $initial_edits )
1220 );
1221 wp_add_inline_script( 'wp-edit-post', $script );
1222
1223 /**
1224 * Scripts
1225 */
1226 wp_enqueue_media(
1227 array(
1228 'post' => $post->ID,
1229 )
1230 );
1231 wp_enqueue_editor();
1232
1233 /**
1234 * Styles
1235 */
1236 wp_enqueue_style( 'wp-edit-post' );
1237
1238 /**
1239 * Fires after block assets have been enqueued for the editing interface.
1240 *
1241 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
1242 *
1243 * In the function call you supply, simply use `wp_enqueue_script` and
1244 * `wp_enqueue_style` to add your functionality to the Gutenberg editor.
1245 *
1246 * @since 0.4.0
1247 */
1248 do_action( 'enqueue_block_editor_assets' );
1249 }
1250
1251 /**
1252 * Enqueue the reusable blocks listing page's script
1253 *
1254 * @deprecated 5.0.0
1255 */
1256 function gutenberg_load_list_reusable_blocks() {
1257 _deprecated_function( __FUNCTION__, '5.0.0' );
1258 }
1259