PluginProbe
Gutenberg / 5.2.0
Gutenberg v5.2.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.2.0, at lib/client-assets.php

995 lines 31.3 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 * Registers a script according to `wp_register_script`. Honors this request by
39 * reassigning internal dependency properties of any script handle already
40 * registered by that name. It does not deregister the original script, to
41 * avoid losing inline scripts which may have been attached.
42 *
43 * @since 4.1.0
44 *
45 * @param string $handle Name of the script. Should be unique.
46 * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
47 * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
48 * @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
49 * as a query string for cache busting purposes. If version is set to false, a version
50 * number is automatically added equal to current installed WordPress version.
51 * If set to null, no version is added.
52 * @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
53 * Default 'false'.
54 */
55 function gutenberg_override_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
56 global $wp_scripts;
57
58 $script = $wp_scripts->query( $handle, 'registered' );
59 if ( $script ) {
60 /*
61 * In many ways, this is a reimplementation of `wp_register_script` but
62 * bypassing consideration of whether a script by the given handle had
63 * already been registered.
64 */
65
66 // See: `_WP_Dependency::__construct` .
67 $script->src = $src;
68 $script->deps = $deps;
69 $script->ver = $ver;
70
71 /*
72 * The script's `group` designation is an indication of whether it is
73 * to be printed in the header or footer. The behavior here defers to
74 * the arguments as passed. Specifically, group data is not assigned
75 * for a script unless it is designated to be printed in the footer.
76 */
77
78 // See: `wp_register_script` .
79 unset( $script->extra['group'] );
80 if ( $in_footer ) {
81 $script->add_data( 'group', 1 );
82 }
83 } else {
84 wp_register_script( $handle, $src, $deps, $ver, $in_footer );
85 }
86
87 /*
88 * `WP_Dependencies::set_translations` will fall over on itself if setting
89 * translations on the `wp-i18n` handle, since it internally adds `wp-i18n`
90 * as a dependency of itself, exhausting memory. The same applies for the
91 * polyfill script, which is a dependency _of_ `wp-i18n`.
92 *
93 * See: https://core.trac.wordpress.org/ticket/46089
94 */
95 if ( 'wp-i18n' !== $handle && 'wp-polyfill' !== $handle ) {
96 wp_set_script_translations( $handle, 'default' );
97 }
98 }
99
100 /**
101 * Filters the default translation file load behavior to load the Gutenberg
102 * plugin translation file, if available.
103 *
104 * @param string|false $file Path to the translation file to load. False if
105 * there isn't one.
106 * @param string $handle Name of the script to register a translation
107 * domain to.
108 *
109 * @return string|false Filtered path to the Gutenberg translation file, if
110 * available.
111 */
112 function gutenberg_override_translation_file( $file, $handle ) {
113 if ( ! $file ) {
114 return $file;
115 }
116
117 // Only override script handles generated from the Gutenberg plugin.
118 $packages_dependencies = include dirname( __FILE__ ) . '/packages-dependencies.php';
119 if ( ! isset( $packages_dependencies[ $handle ] ) ) {
120 return $file;
121 }
122
123 /*
124 * The default file will be in the plugins language directory, omitting the
125 * domain since Gutenberg assigns the script translations as the default.
126 *
127 * Example: /www/wp-content/languages/plugins/de_DE-07d88e6a803e01276b9bfcc1203e862e.json
128 *
129 * The logic of `load_script_textdomain` is such that it will assume to
130 * search in the plugins language directory, since the assigned source of
131 * the overridden Gutenberg script originates in the plugins directory.
132 *
133 * The plugin translation files each begin with the slug of the plugin, so
134 * it's a simple matter of prepending the Gutenberg plugin slug.
135 */
136 $path_parts = pathinfo( $file );
137 $plugin_translation_file = (
138 $path_parts['dirname'] .
139 '/gutenberg-' .
140 $path_parts['basename']
141 );
142
143 if ( ! is_readable( $plugin_translation_file ) ) {
144 return $file;
145 }
146
147 return $plugin_translation_file;
148 }
149 add_filter( 'load_script_translation_file', 'gutenberg_override_translation_file', 10, 2 );
150
151 /**
152 * Registers a style according to `wp_register_style`. Honors this request by
153 * deregistering any style by the same handler before registration.
154 *
155 * @since 4.1.0
156 *
157 * @param string $handle Name of the stylesheet. Should be unique.
158 * @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
159 * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
160 * @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
161 * as a query string for cache busting purposes. If version is set to false, a version
162 * number is automatically added equal to current installed WordPress version.
163 * If set to null, no version is added.
164 * @param string $media Optional. The media for which this stylesheet has been defined.
165 * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
166 * '(orientation: portrait)' and '(max-width: 640px)'.
167 */
168 function gutenberg_override_style( $handle, $src, $deps = array(), $ver = false, $media = 'all' ) {
169 wp_deregister_style( $handle );
170 wp_register_style( $handle, $src, $deps, $ver, $media );
171 }
172
173 /**
174 * Registers all the WordPress packages scripts that are in the standardized
175 * `build/` location.
176 *
177 * @since 4.5.0
178 */
179 function gutenberg_register_packages_scripts() {
180 $packages_dependencies = include dirname( __FILE__ ) . '/packages-dependencies.php';
181
182 foreach ( $packages_dependencies as $handle => $dependencies ) {
183 // Remove `wp-` prefix from the handle to get the package's name.
184 $package_name = strpos( $handle, 'wp-' ) === 0 ? substr( $handle, 3 ) : $handle;
185 $path = "build/$package_name/index.js";
186 gutenberg_override_script(
187 $handle,
188 gutenberg_url( $path ),
189 array_merge( $dependencies, array( 'wp-polyfill' ) ),
190 filemtime( gutenberg_dir_path() . $path ),
191 true
192 );
193 }
194 }
195
196 /**
197 * Registers common scripts and styles to be used as dependencies of the editor
198 * and plugins.
199 *
200 * @since 0.1.0
201 */
202 function gutenberg_register_scripts_and_styles() {
203 global $wp_scripts;
204
205 gutenberg_register_vendor_scripts();
206 gutenberg_register_packages_scripts();
207
208 // Add nonce middleware which accounts for the absence of the heartbeat
209 // listener. This relies on API Fetch implementation running middlewares in
210 // order of last added, and that the original nonce middleware would defer
211 // to an X-WP-Nonce header already being present. This inline script should
212 // be removed once the following Core ticket is resolved in assigning the
213 // nonce received from heartbeat to the created middleware.
214 //
215 // See: https://core.trac.wordpress.org/ticket/46107 .
216 // See: https://github.com/WordPress/gutenberg/pull/13451 .
217 global $wp_scripts;
218 if ( isset( $wp_scripts->registered['wp-api-fetch'] ) ) {
219 $wp_scripts->registered['wp-api-fetch']->deps[] = 'wp-hooks';
220 }
221 wp_add_inline_script(
222 'wp-api-fetch',
223 sprintf(
224 implode(
225 "\n",
226 array(
227 '( function() {',
228 ' var nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );',
229 ' wp.apiFetch.use( nonceMiddleware );',
230 ' wp.hooks.addAction(',
231 ' "heartbeat.tick",',
232 ' "core/api-fetch/create-nonce-middleware",',
233 ' function( response ) {',
234 ' if ( response[ "rest_nonce" ] ) {',
235 ' nonceMiddleware.nonce = response[ "rest_nonce" ];',
236 ' }',
237 ' }',
238 ' )',
239 '} )();',
240 )
241 ),
242 ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' )
243 ),
244 'after'
245 );
246
247 // TEMPORARY: Core does not (yet) provide persistence migration from the
248 // introduction of the block editor.
249 wp_add_inline_script(
250 'wp-data',
251 implode(
252 "\n",
253 array(
254 '( function() {',
255 ' var userId = ' . get_current_user_ID() . ';',
256 ' var storageKey = "WP_DATA_USER_" + userId;',
257 ' wp.data.plugins.persistence.__unstableMigrate( { storageKey: storageKey } );',
258 '} )()',
259 )
260 )
261 );
262
263 // Editor Styles.
264 // This empty stylesheet is defined to ensure backward compatibility.
265 gutenberg_override_style( 'wp-blocks', false );
266
267 gutenberg_override_style(
268 'wp-editor',
269 gutenberg_url( 'build/editor/style.css' ),
270 array( 'wp-components', 'wp-editor-font', 'wp-nux' ),
271 filemtime( gutenberg_dir_path() . 'build/editor/style.css' )
272 );
273 wp_style_add_data( 'wp-editor', 'rtl', 'replace' );
274
275 gutenberg_override_style(
276 'wp-edit-post',
277 gutenberg_url( 'build/edit-post/style.css' ),
278 array( 'wp-components', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-nux' ),
279 filemtime( gutenberg_dir_path() . 'build/edit-post/style.css' )
280 );
281 wp_style_add_data( 'wp-edit-post', 'rtl', 'replace' );
282
283 gutenberg_override_style(
284 'wp-components',
285 gutenberg_url( 'build/components/style.css' ),
286 array(),
287 filemtime( gutenberg_dir_path() . 'build/components/style.css' )
288 );
289 wp_style_add_data( 'wp-components', 'rtl', 'replace' );
290
291 gutenberg_override_style(
292 'wp-block-library',
293 gutenberg_url( 'build/block-library/style.css' ),
294 current_theme_supports( 'wp-block-styles' ) ? array( 'wp-block-library-theme' ) : array(),
295 filemtime( gutenberg_dir_path() . 'build/block-library/style.css' )
296 );
297 wp_style_add_data( 'wp-block-library', 'rtl', 'replace' );
298
299 gutenberg_override_style(
300 'wp-format-library',
301 gutenberg_url( 'build/format-library/style.css' ),
302 array(),
303 filemtime( gutenberg_dir_path() . 'build/format-library/style.css' )
304 );
305 wp_style_add_data( 'wp-format-library', 'rtl', 'replace' );
306
307 gutenberg_override_style(
308 'wp-edit-blocks',
309 gutenberg_url( 'build/block-library/editor.css' ),
310 array(
311 'wp-components',
312 'wp-editor',
313 'wp-block-library',
314 // Always include visual styles so the editor never appears broken.
315 'wp-block-library-theme',
316 ),
317 filemtime( gutenberg_dir_path() . 'build/block-library/editor.css' )
318 );
319 wp_style_add_data( 'wp-edit-blocks', 'rtl', 'replace' );
320
321 gutenberg_override_style(
322 'wp-nux',
323 gutenberg_url( 'build/nux/style.css' ),
324 array( 'wp-components' ),
325 filemtime( gutenberg_dir_path() . 'build/nux/style.css' )
326 );
327 wp_style_add_data( 'wp-nux', 'rtl', 'replace' );
328
329 gutenberg_override_style(
330 'wp-block-library-theme',
331 gutenberg_url( 'build/block-library/theme.css' ),
332 array(),
333 filemtime( gutenberg_dir_path() . 'build/block-library/theme.css' )
334 );
335 wp_style_add_data( 'wp-block-library-theme', 'rtl', 'replace' );
336
337 gutenberg_override_style(
338 'wp-list-reusable-blocks',
339 gutenberg_url( 'build/list-reusable-blocks/style.css' ),
340 array( 'wp-components' ),
341 filemtime( gutenberg_dir_path() . 'build/list-reusable-blocks/style.css' )
342 );
343 wp_style_add_data( 'wp-list-reusable-block', 'rtl', 'replace' );
344
345 gutenberg_override_style(
346 'wp-edit-widgets',
347 gutenberg_url( 'build/edit-widgets/style.css' ),
348 array(),
349 filemtime( gutenberg_dir_path() . 'build/edit-widgets/style.css' )
350 );
351 wp_style_add_data( 'wp-edit-widgets', 'rtl', 'replace' );
352
353 if ( defined( 'GUTENBERG_LIVE_RELOAD' ) && GUTENBERG_LIVE_RELOAD ) {
354 $live_reload_url = ( GUTENBERG_LIVE_RELOAD === true ) ? 'http://localhost:35729/livereload.js' : GUTENBERG_LIVE_RELOAD;
355
356 wp_enqueue_script(
357 'gutenberg-live-reload',
358 $live_reload_url
359 );
360 }
361 }
362 add_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 );
363 add_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 );
364
365 /**
366 * Registers vendor JavaScript files to be used as dependencies of the editor
367 * and plugins.
368 *
369 * This function is called from a script during the plugin build process, so it
370 * should not call any WordPress PHP functions.
371 *
372 * @since 0.1.0
373 */
374 function gutenberg_register_vendor_scripts() {
375 /*
376 * This function is kept as an empty stub, in case Gutenberg should need to
377 * explicitly provide a version newer than that provided by core.
378 */
379 }
380
381 /**
382 * Retrieves a unique and reasonably short and human-friendly filename for a
383 * vendor script based on a URL and the script handle.
384 *
385 * @param string $handle The name of the script.
386 * @param string $src Full URL of the external script.
387 *
388 * @return string Script filename suitable for local caching.
389 *
390 * @since 0.1.0
391 */
392 function gutenberg_vendor_script_filename( $handle, $src ) {
393 $filename = basename( $src );
394 $match = preg_match(
395 '/^'
396 . '(?P<ignore>.*?)'
397 . '(?P<suffix>\.min)?'
398 . '(?P<extension>\.js)'
399 . '(?P<extra>.*)'
400 . '$/',
401 $filename,
402 $filename_pieces
403 );
404
405 $prefix = $handle;
406 $suffix = $match ? $filename_pieces['suffix'] : '';
407 $hash = substr( md5( $src ), 0, 8 );
408
409 return "${prefix}${suffix}.${hash}.js";
410 }
411
412 /**
413 * Registers a vendor script from a URL, preferring a locally cached version if
414 * possible, or downloading it if the cached version is unavailable or
415 * outdated.
416 *
417 * @param string $handle Name of the script.
418 * @param string $src Full URL of the external script.
419 * @param array $deps Optional. An array of registered script handles this
420 * script depends on.
421 *
422 * @since 0.1.0
423 */
424 function gutenberg_register_vendor_script( $handle, $src, $deps = array() ) {
425 if ( defined( 'GUTENBERG_LOAD_VENDOR_SCRIPTS' ) && ! GUTENBERG_LOAD_VENDOR_SCRIPTS ) {
426 return;
427 }
428
429 $filename = gutenberg_vendor_script_filename( $handle, $src );
430
431 if ( defined( 'GUTENBERG_LIST_VENDOR_ASSETS' ) && GUTENBERG_LIST_VENDOR_ASSETS ) {
432 echo "$src|$filename\n";
433 return;
434 }
435
436 $full_path = gutenberg_dir_path() . 'vendor/' . $filename;
437
438 $needs_fetch = (
439 defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && (
440 ! file_exists( $full_path ) ||
441 time() - filemtime( $full_path ) >= DAY_IN_SECONDS
442 )
443 );
444
445 if ( $needs_fetch ) {
446 // Determine whether we can write to this file. If not, don't waste
447 // time doing a network request.
448 // @codingStandardsIgnoreStart
449 $f = @fopen( $full_path, 'a' );
450 // @codingStandardsIgnoreEnd
451 if ( ! $f ) {
452 // Failed to open the file for writing, probably due to server
453 // permissions. Enqueue the script directly from the URL instead.
454 gutenberg_override_script( $handle, $src, $deps, null );
455 return;
456 }
457 fclose( $f );
458 $response = wp_remote_get( $src );
459 if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
460 $f = fopen( $full_path, 'w' );
461 fwrite( $f, wp_remote_retrieve_body( $response ) );
462 fclose( $f );
463 } elseif ( ! filesize( $full_path ) ) {
464 // The request failed. If the file is already cached, continue to
465 // use this file. If not, then unlink the 0 byte file, and enqueue
466 // the script directly from the URL.
467 gutenberg_override_script( $handle, $src, $deps, null );
468 unlink( $full_path );
469 return;
470 }
471 }
472 gutenberg_override_script(
473 $handle,
474 gutenberg_url( 'vendor/' . $filename ),
475 $deps,
476 null
477 );
478 }
479
480 /**
481 * Assigns a default editor template with a default block by post format, if
482 * not otherwise assigned for a new post of type "post".
483 *
484 * @param array $settings Default editor settings.
485 * @param WP_Post $post Post being edited.
486 *
487 * @return array Filtered block editor settings.
488 */
489 function gutenberg_default_post_format_template( $settings, $post ) {
490 // Only assign template for new posts without explicitly assigned template.
491 $is_new_post = 'auto-draft' === $post->post_status;
492 if ( $is_new_post && ! isset( $settings['template'] ) && 'post' === $post->post_type ) {
493 switch ( get_post_format() ) {
494 case 'audio':
495 $default_block_name = 'core/audio';
496 break;
497 case 'gallery':
498 $default_block_name = 'core/gallery';
499 break;
500 case 'image':
501 $default_block_name = 'core/image';
502 break;
503 case 'quote':
504 $default_block_name = 'core/quote';
505 break;
506 case 'video':
507 $default_block_name = 'core/video';
508 break;
509 }
510
511 if ( isset( $default_block_name ) ) {
512 $settings['template'] = array( array( $default_block_name ) );
513 }
514 }
515
516 return $settings;
517 }
518 add_filter( 'block_editor_settings', 'gutenberg_default_post_format_template', 10, 2 );
519
520 /**
521 * Retrieve a stored autosave that is newer than the post save.
522 *
523 * Deletes autosaves that are older than the post save.
524 *
525 * @param WP_Post $post Post object.
526 * @return WP_Post|boolean The post autosave. False if none found.
527 */
528 function gutenberg_get_autosave_newer_than_post_save( $post ) {
529 // Add autosave data if it is newer and changed.
530 $autosave = wp_get_post_autosave( $post->ID );
531
532 if ( ! $autosave ) {
533 return false;
534 }
535
536 // Check if the autosave is newer than the current post.
537 if (
538 mysql2date( 'U', $autosave->post_modified_gmt, false ) > mysql2date( 'U', $post->post_modified_gmt, false )
539 ) {
540 return $autosave;
541 }
542
543 // If the autosave isn't newer, remove it.
544 wp_delete_post_revision( $autosave->ID );
545
546 return false;
547 }
548
549 /**
550 * Loads Gutenberg Locale Data.
551 *
552 * @deprecated 5.2.0
553 */
554 function gutenberg_load_locale_data() {
555 _deprecated_function( __FUNCTION__, '5.2.0' );
556 }
557
558 /**
559 * Retrieve The available image sizes for a post
560 *
561 * @return array
562 */
563 function gutenberg_get_available_image_sizes() {
564 $size_names = apply_filters(
565 'image_size_names_choose',
566 array(
567 'thumbnail' => __( 'Thumbnail', 'gutenberg' ),
568 'medium' => __( 'Medium', 'gutenberg' ),
569 'large' => __( 'Large', 'gutenberg' ),
570 'full' => __( 'Full Size', 'gutenberg' ),
571 )
572 );
573
574 $all_sizes = array();
575 foreach ( $size_names as $size_slug => $size_name ) {
576 $all_sizes[] = array(
577 'slug' => $size_slug,
578 'name' => $size_name,
579 );
580 }
581
582 return $all_sizes;
583 }
584
585 /**
586 * Extends block editor settings to include Gutenberg's `editor-styles.css` as
587 * taking precedent those styles shipped with core.
588 *
589 * @param array $settings Default editor settings.
590 *
591 * @return array Filtered editor settings.
592 */
593 function gutenberg_extend_block_editor_styles( $settings ) {
594 $editor_styles_file = gutenberg_dir_path() . 'build/editor/editor-styles.css';
595
596 /*
597 * If, for whatever reason, the built editor styles do not exist, avoid
598 * override and fall back to the default.
599 */
600 if ( ! file_exists( $editor_styles_file ) ) {
601 return $settings;
602 }
603
604 if ( empty( $settings['styles'] ) ) {
605 $settings['styles'] = array();
606 } else {
607 /*
608 * The styles setting is an array of CSS strings, so there is no direct
609 * way to find the default styles. To maximize stability, load (again)
610 * the default styles from disk and find its place in the array.
611 *
612 * See: https://github.com/WordPress/wordpress-develop/blob/5.0.3/src/wp-admin/edit-form-blocks.php#L168-L175
613 */
614
615 $default_styles = file_get_contents(
616 ABSPATH . WPINC . '/css/dist/editor/editor-styles.css'
617 );
618
619 /*
620 * Iterate backwards from the end of the array since the preferred
621 * insertion point in case not found is prepended as first entry.
622 */
623 for ( $i = count( $settings['styles'] ) - 1; $i >= 0; $i-- ) {
624 if ( isset( $settings['styles'][ $i ]['css'] ) &&
625 $default_styles === $settings['styles'][ $i ]['css'] ) {
626 break;
627 }
628 }
629 }
630
631 $editor_styles = array(
632 'css' => file_get_contents( $editor_styles_file ),
633 );
634
635 // Substitute default styles if found. Otherwise, prepend to setting array.
636 if ( isset( $i ) && $i >= 0 ) {
637 $settings['styles'][ $i ] = $editor_styles;
638 } else {
639 array_unshift( $settings['styles'], $editor_styles );
640 }
641
642 return $settings;
643 }
644 add_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' );
645
646 /**
647 * Scripts & Styles.
648 *
649 * Enqueues the needed scripts and styles when visiting the top-level page of
650 * the Gutenberg editor.
651 *
652 * @since 0.1.0
653 *
654 * @param string $hook Screen name.
655 */
656 function gutenberg_editor_scripts_and_styles( $hook ) {
657 global $wp_meta_boxes;
658
659 // Enqueue heartbeat separately as an "optional" dependency of the editor.
660 // Heartbeat is used for automatic nonce refreshing, but some hosts choose
661 // to disable it outright.
662 wp_enqueue_script( 'heartbeat' );
663
664 wp_enqueue_script( 'wp-edit-post' );
665 wp_enqueue_script( 'wp-format-library' );
666 wp_enqueue_style( 'wp-format-library' );
667
668 global $post;
669
670 // Set initial title to empty string for auto draft for duration of edit.
671 // Otherwise, title defaults to and displays as "Auto Draft".
672 $is_new_post = 'auto-draft' === $post->post_status;
673
674 // Set the post type name.
675 $post_type = get_post_type( $post );
676 $post_type_object = get_post_type_object( $post_type );
677 $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
678
679 $preload_paths = array(
680 '/',
681 '/wp/v2/types?context=edit',
682 '/wp/v2/taxonomies?per_page=-1&context=edit',
683 '/wp/v2/themes?status=active',
684 sprintf( '/wp/v2/%s/%s?context=edit', $rest_base, $post->ID ),
685 sprintf( '/wp/v2/types/%s?context=edit', $post_type ),
686 sprintf( '/wp/v2/users/me?post_type=%s&context=edit', $post_type ),
687 array( '/wp/v2/media', 'OPTIONS' ),
688 array( '/wp/v2/blocks', 'OPTIONS' ),
689 );
690
691 /**
692 * Preload common data by specifying an array of REST API paths that will be preloaded.
693 *
694 * Filters the array of paths that will be preloaded.
695 *
696 * @param array $preload_paths Array of paths to preload
697 * @param object $post The post resource data.
698 */
699 $preload_paths = apply_filters( 'block_editor_preload_paths', $preload_paths, $post );
700
701 // Ensure the global $post remains the same after
702 // API data is preloaded. Because API preloading
703 // can call the_content and other filters, callbacks
704 // can unexpectedly modify $post resulting in issues
705 // like https://github.com/WordPress/gutenberg/issues/7468.
706 $backup_global_post = $post;
707
708 $preload_data = array_reduce(
709 $preload_paths,
710 'rest_preload_api_request',
711 array()
712 );
713
714 // Restore the global $post as it was before API preloading.
715 $post = $backup_global_post;
716
717 wp_add_inline_script(
718 'wp-api-fetch',
719 sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ),
720 'after'
721 );
722
723 wp_add_inline_script(
724 'wp-blocks',
725 sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $post ) ) ),
726 'after'
727 );
728
729 // Assign initial edits, if applicable. These are not initially assigned
730 // to the persisted post, but should be included in its save payload.
731 if ( $is_new_post ) {
732 // Override "(Auto Draft)" new post default title with empty string,
733 // or filtered value.
734 $initial_edits = array(
735 'title' => $post->post_title,
736 'content' => $post->post_content,
737 'excerpt' => $post->post_excerpt,
738 );
739 } else {
740 $initial_edits = null;
741 }
742
743 // Preload server-registered block schemas.
744 wp_add_inline_script(
745 'wp-blocks',
746 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . json_encode( get_block_editor_server_block_settings() ) . ');'
747 );
748
749 // Get admin url for handling meta boxes.
750 $meta_box_url = admin_url( 'post.php' );
751 $meta_box_url = add_query_arg(
752 array(
753 'post' => $post->ID,
754 'action' => 'edit',
755 'meta-box-loader' => true,
756 '_wpnonce' => wp_create_nonce( 'meta-box-loader' ),
757 ),
758 $meta_box_url
759 );
760 wp_localize_script( 'wp-editor', '_wpMetaBoxUrl', $meta_box_url );
761
762 // Initialize the editor.
763 $align_wide = get_theme_support( 'align-wide' );
764 $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
765 $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
766
767 /**
768 * Filters the allowed block types for the editor, defaulting to true (all
769 * block types supported).
770 *
771 * @param bool|array $allowed_block_types Array of block type slugs, or
772 * boolean to enable/disable all.
773 * @param object $post The post resource data.
774 */
775 $allowed_block_types = apply_filters( 'allowed_block_types', true, $post );
776
777 // Get all available templates for the post/page attributes meta-box.
778 // The "Default template" array element should only be added if the array is
779 // not empty so we do not trigger the template select element without any options
780 // besides the default value.
781 $available_templates = wp_get_theme()->get_page_templates( get_post( $post->ID ) );
782 $available_templates = ! empty( $available_templates ) ? array_merge(
783 array(
784 '' => apply_filters( 'default_page_template_title', __( 'Default template', 'gutenberg' ), 'rest-api' ),
785 ),
786 $available_templates
787 ) : $available_templates;
788
789 // Media settings.
790 $max_upload_size = wp_max_upload_size();
791 if ( ! $max_upload_size ) {
792 $max_upload_size = 0;
793 }
794
795 // Editor Styles.
796 global $editor_styles;
797 $styles = array(
798 array(
799 'css' => file_get_contents(
800 ABSPATH . WPINC . '/css/dist/editor/editor-styles.css'
801 ),
802 ),
803 );
804
805 /* Translators: Use this to specify the CSS font family for the default font */
806 $locale_font_family = esc_html_x( 'Noto Serif', 'CSS Font Family for Editor Font', 'gutenberg' );
807 $styles[] = array(
808 'css' => "body { font-family: '$locale_font_family' }",
809 );
810
811 if ( $editor_styles && current_theme_supports( 'editor-styles' ) ) {
812 foreach ( $editor_styles as $style ) {
813 if ( filter_var( $style, FILTER_VALIDATE_URL ) ) {
814 $styles[] = array(
815 'css' => file_get_contents( $style ),
816 );
817 } else {
818 $file = get_theme_file_path( $style );
819 if ( file_exists( $file ) ) {
820 $styles[] = array(
821 'css' => file_get_contents( $file ),
822 'baseURL' => get_theme_file_uri( $style ),
823 );
824 }
825 }
826 }
827 }
828
829 // Lock settings.
830 $user_id = wp_check_post_lock( $post->ID );
831 if ( $user_id ) {
832 /**
833 * Filters whether to show the post locked dialog.
834 *
835 * Returning a falsey value to the filter will short-circuit displaying the dialog.
836 *
837 * @since 3.6.0
838 *
839 * @param bool $display Whether to display the dialog. Default true.
840 * @param WP_Post $post Post object.
841 * @param WP_User|bool $user The user id currently editing the post.
842 */
843 if ( apply_filters( 'show_post_locked_dialog', true, $post, $user_id ) ) {
844 $locked = true;
845 }
846
847 $user_details = null;
848 if ( $locked ) {
849 $user = get_userdata( $user_id );
850 $user_details = array(
851 'name' => $user->display_name,
852 );
853 $avatar = get_avatar( $user_id, 64 );
854 if ( $avatar ) {
855 if ( preg_match( "|src='([^']+)'|", $avatar, $matches ) ) {
856 $user_details['avatar'] = $matches[1];
857 }
858 }
859 }
860
861 $lock_details = array(
862 'isLocked' => $locked,
863 'user' => $user_details,
864 );
865 } else {
866
867 // Lock the post.
868 $active_post_lock = wp_set_post_lock( $post->ID );
869 $lock_details = array(
870 'isLocked' => false,
871 'activePostLock' => esc_attr( implode( ':', $active_post_lock ) ),
872 );
873 }
874
875 $editor_settings = array(
876 'alignWide' => $align_wide,
877 'availableTemplates' => $available_templates,
878 'allowedBlockTypes' => $allowed_block_types,
879 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ),
880 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
881 'disablePostFormats' => ! current_theme_supports( 'post-formats' ),
882 'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ),
883 'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ),
884 'isRTL' => is_rtl(),
885 'autosaveInterval' => 10,
886 'maxUploadFileSize' => $max_upload_size,
887 'allowedMimeTypes' => get_allowed_mime_types(),
888 'styles' => $styles,
889 'imageSizes' => gutenberg_get_available_image_sizes(),
890 'richEditingEnabled' => user_can_richedit(),
891
892 // Ideally, we'd remove this and rely on a REST API endpoint.
893 'postLock' => $lock_details,
894 'postLockUtils' => array(
895 'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ),
896 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ),
897 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
898 ),
899
900 // Whether or not to load the 'postcustom' meta box is stored as a user meta
901 // field so that we're not always loading its assets.
902 'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ),
903 );
904
905 $post_autosave = gutenberg_get_autosave_newer_than_post_save( $post );
906 if ( $post_autosave ) {
907 $editor_settings['autosave'] = array(
908 'editLink' => get_edit_post_link( $post_autosave->ID ),
909 );
910 }
911
912 if ( false !== $color_palette ) {
913 $editor_settings['colors'] = $color_palette;
914 }
915
916 if ( false !== $font_sizes ) {
917 $editor_settings['fontSizes'] = $font_sizes;
918 }
919
920 if ( ! empty( $post_type_object->template ) ) {
921 $editor_settings['template'] = $post_type_object->template;
922 $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
923 }
924
925 $current_screen = get_current_screen();
926 $core_meta_boxes = array();
927
928 // Make sure the current screen is set as well as the normal core metaboxes.
929 if ( isset( $current_screen->id ) && isset( $wp_meta_boxes[ $current_screen->id ]['normal']['core'] ) ) {
930 $core_meta_boxes = $wp_meta_boxes[ $current_screen->id ]['normal']['core'];
931 }
932
933 // Check if the Custom Fields meta box has been removed at some point.
934 if ( ! isset( $core_meta_boxes['postcustom'] ) || ! $core_meta_boxes['postcustom'] ) {
935 unset( $editor_settings['enableCustomFields'] );
936 }
937
938 /**
939 * Filters the settings to pass to the block editor.
940 *
941 * @since 3.7.0
942 *
943 * @param array $editor_settings Default editor settings.
944 * @param WP_Post $post Post being edited.
945 */
946 $editor_settings = apply_filters( 'block_editor_settings', $editor_settings, $post );
947
948 $init_script = <<<JS
949 ( function() {
950 window._wpLoadBlockEditor = new Promise( function( resolve ) {
951 wp.domReady( function() {
952 resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) );
953 } );
954 } );
955 } )();
956 JS;
957
958 $script = sprintf(
959 $init_script,
960 $post->post_type,
961 $post->ID,
962 wp_json_encode( $editor_settings ),
963 wp_json_encode( $initial_edits )
964 );
965 wp_add_inline_script( 'wp-edit-post', $script );
966
967 /**
968 * Scripts
969 */
970 wp_enqueue_media(
971 array(
972 'post' => $post->ID,
973 )
974 );
975 wp_tinymce_inline_scripts();
976 wp_enqueue_editor();
977
978 /**
979 * Styles
980 */
981 wp_enqueue_style( 'wp-edit-post' );
982
983 /**
984 * Fires after block assets have been enqueued for the editing interface.
985 *
986 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
987 *
988 * In the function call you supply, simply use `wp_enqueue_script` and
989 * `wp_enqueue_style` to add your functionality to the Gutenberg editor.
990 *
991 * @since 0.4.0
992 */
993 do_action( 'enqueue_block_editor_assets' );
994 }
995