PluginProbe
Gutenberg / 4.0.1
Gutenberg v4.0.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / register.php

register.php in Gutenberg 4.0.1, at lib/register.php

600 lines 18.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Initialization and wp-admin integration for the Gutenberg editor plugin.
4 *
5 * @package gutenberg
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 die( 'Silence is golden.' );
10 }
11
12 /**
13 * Set up global variables so that plugins will add meta boxes as if we were
14 * using the main editor.
15 *
16 * @since 1.5.0
17 */
18 function gutenberg_trick_plugins_into_registering_meta_boxes() {
19 global $pagenow;
20
21 if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) && ! isset( $_REQUEST['classic-editor'] ) ) {
22 // As early as possible, but after any plugins ( ACF ) that adds meta boxes.
23 add_action( 'admin_head', 'gutenberg_collect_meta_box_data', 99 );
24 }
25 }
26 // As late as possible, but before any logic that adds meta boxes.
27 add_action(
28 'plugins_loaded',
29 'gutenberg_trick_plugins_into_registering_meta_boxes'
30 );
31
32 /**
33 * Collect information about meta_boxes registered for the current post.
34 *
35 * Redirects to classic editor if a meta box is incompatible.
36 *
37 * @since 1.5.0
38 */
39 function gutenberg_collect_meta_box_data() {
40 global $current_screen, $wp_meta_boxes, $post, $typenow;
41
42 $screen = $current_screen;
43
44 // If we are working with an already predetermined post.
45 if ( isset( $_REQUEST['post'] ) ) {
46 $post = get_post( absint( $_REQUEST['post'] ) );
47 $typenow = $post->post_type;
48
49 if ( ! gutenberg_can_edit_post( $post->ID ) ) {
50 return;
51 }
52 } else {
53 // Eventually add handling for creating new posts of different types in Gutenberg.
54 }
55 $post_type = $post->post_type;
56 $post_type_object = get_post_type_object( $post_type );
57
58 if ( ! gutenberg_can_edit_post_type( $post_type ) ) {
59 return;
60 }
61
62 // Disable hidden metaboxes because there's no UI to toggle visibility.
63 add_filter( 'hidden_meta_boxes', '__return_empty_array' );
64
65 $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' );
66 if ( ! $thumbnail_support && 'attachment' === $post_type && $post->post_mime_type ) {
67 if ( wp_attachment_is( 'audio', $post ) ) {
68 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
69 } elseif ( wp_attachment_is( 'video', $post ) ) {
70 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
71 }
72 }
73
74 /*
75 * WIP: Collect and send information needed to render meta boxes.
76 * From wp-admin/edit-form-advanced.php
77 * Relevant code there:
78 * do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post );
79 * do_meta_boxes( $post_type, 'side', $post );
80 * do_meta_boxes( null, 'normal', $post );
81 * do_meta_boxes( null, 'advanced', $post );
82 */
83 $publish_callback_args = null;
84 if ( post_type_supports( $post_type, 'revisions' ) && 'auto-draft' !== $post->post_status ) {
85 $revisions = wp_get_post_revisions( $post->ID );
86
87 // We should aim to show the revisions meta box only when there are revisions.
88 if ( count( $revisions ) > 1 ) {
89 reset( $revisions ); // Reset pointer for key().
90 $publish_callback_args = array(
91 'revisions_count' => count( $revisions ),
92 'revision_id' => key( $revisions ),
93 );
94 add_meta_box( 'revisionsdiv', __( 'Revisions', 'gutenberg' ), 'post_revisions_meta_box', $screen, 'normal', 'core' );
95 }
96 }
97
98 if ( 'attachment' == $post_type ) {
99 wp_enqueue_script( 'image-edit' );
100 wp_enqueue_style( 'imgareaselect' );
101 add_meta_box( 'submitdiv', __( 'Save', 'gutenberg' ), 'attachment_submit_meta_box', $screen, 'side', 'core' );
102 add_action( 'edit_form_after_title', 'edit_form_image_editor' );
103
104 if ( wp_attachment_is( 'audio', $post ) ) {
105 add_meta_box( 'attachment-id3', __( 'Metadata', 'gutenberg' ), 'attachment_id3_data_meta_box', $screen, 'normal', 'core' );
106 }
107 } else {
108 add_meta_box( 'submitdiv', __( 'Publish', 'gutenberg' ), 'post_submit_meta_box', $screen, 'side', 'core', $publish_callback_args );
109 }
110
111 if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post_type, 'post-formats' ) ) {
112 add_meta_box( 'formatdiv', _x( 'Format', 'post format', 'gutenberg' ), 'post_format_meta_box', $screen, 'side', 'core' );
113 }
114
115 // All taxonomies.
116 foreach ( get_object_taxonomies( $post ) as $tax_name ) {
117 $taxonomy = get_taxonomy( $tax_name );
118 if ( ! $taxonomy->show_ui || false === $taxonomy->meta_box_cb ) {
119 continue;
120 }
121
122 $label = $taxonomy->labels->name;
123
124 if ( ! is_taxonomy_hierarchical( $tax_name ) ) {
125 $tax_meta_box_id = 'tagsdiv-' . $tax_name;
126 } else {
127 $tax_meta_box_id = $tax_name . 'div';
128 }
129
130 add_meta_box( $tax_meta_box_id, $label, $taxonomy->meta_box_cb, $screen, 'side', 'core', array( 'taxonomy' => $tax_name ) );
131 }
132
133 if ( post_type_supports( $post_type, 'page-attributes' ) || count( get_page_templates( $post ) ) > 0 ) {
134 add_meta_box( 'pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $screen, 'side', 'core' );
135 }
136
137 if ( $thumbnail_support && current_user_can( 'upload_files' ) ) {
138 add_meta_box( 'postimagediv', esc_html( $post_type_object->labels->featured_image ), 'post_thumbnail_meta_box', $screen, 'side', 'low' );
139 }
140
141 if ( post_type_supports( $post_type, 'excerpt' ) ) {
142 add_meta_box( 'postexcerpt', __( 'Excerpt', 'gutenberg' ), 'post_excerpt_meta_box', $screen, 'normal', 'core' );
143 }
144
145 if ( post_type_supports( $post_type, 'trackbacks' ) ) {
146 add_meta_box( 'trackbacksdiv', __( 'Send Trackbacks', 'gutenberg' ), 'post_trackback_meta_box', $screen, 'normal', 'core' );
147 }
148
149 if ( post_type_supports( $post_type, 'custom-fields' ) ) {
150 add_meta_box( 'postcustom', __( 'Custom Fields', 'gutenberg' ), 'post_custom_meta_box', $screen, 'normal', 'core' );
151 }
152
153 /**
154 * Fires in the middle of built-in meta box registration.
155 *
156 * @since 2.1.0
157 * @deprecated 3.7.0 Use 'add_meta_boxes' instead.
158 *
159 * @param WP_Post $post Post object.
160 */
161 do_action( 'dbx_post_advanced', $post );
162
163 // Allow the Discussion meta box to show up if the post type supports comments,
164 // or if comments or pings are open.
165 if ( comments_open( $post ) || pings_open( $post ) || post_type_supports( $post_type, 'comments' ) ) {
166 add_meta_box( 'commentstatusdiv', __( 'Discussion', 'gutenberg' ), 'post_comment_status_meta_box', $screen, 'normal', 'core' );
167 }
168
169 $stati = get_post_stati( array( 'public' => true ) );
170 if ( empty( $stati ) ) {
171 $stati = array( 'publish' );
172 }
173 $stati[] = 'private';
174
175 if ( in_array( get_post_status( $post ), $stati ) ) {
176 // If the post type support comments, or the post has comments, allow the
177 // Comments meta box.
178 if ( comments_open( $post ) || pings_open( $post ) || $post->comment_count > 0 || post_type_supports( $post_type, 'comments' ) ) {
179 add_meta_box( 'commentsdiv', __( 'Comments', 'gutenberg' ), 'post_comment_meta_box', $screen, 'normal', 'core' );
180 }
181 }
182
183 if ( ! ( 'pending' == get_post_status( $post ) && ! current_user_can( $post_type_object->cap->publish_posts ) ) ) {
184 add_meta_box( 'slugdiv', __( 'Slug', 'gutenberg' ), 'post_slug_meta_box', $screen, 'normal', 'core' );
185 }
186
187 if ( post_type_supports( $post_type, 'author' ) && current_user_can( $post_type_object->cap->edit_others_posts ) ) {
188 add_meta_box( 'authordiv', __( 'Author', 'gutenberg' ), 'post_author_meta_box', $screen, 'normal', 'core' );
189 }
190
191 // Run the hooks for adding meta boxes for a specific post type.
192 do_action( 'add_meta_boxes', $post_type, $post );
193 do_action( "add_meta_boxes_{$post_type}", $post );
194
195 // Set up meta box locations.
196 $locations = array( 'normal', 'advanced', 'side' );
197
198 // Foreach location run the hooks meta boxes are potentially registered on.
199 foreach ( $locations as $location ) {
200 do_action(
201 'do_meta_boxes',
202 $screen,
203 $location,
204 $post
205 );
206 }
207 do_action( 'edit_form_advanced', $post );
208
209 // Copy meta box state.
210 $_meta_boxes_copy = $wp_meta_boxes;
211
212 /**
213 * Documented in lib/meta-box-partial-page.php
214 *
215 * @param array $wp_meta_boxes Global meta box state.
216 */
217 $_meta_boxes_copy = apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy );
218
219 // Redirect to classic editor if a meta box is incompatible.
220 foreach ( $locations as $location ) {
221 if ( ! isset( $_meta_boxes_copy[ $post->post_type ][ $location ] ) ) {
222 continue;
223 }
224 // Check if we have a meta box that has declared itself incompatible with the block editor.
225 foreach ( $_meta_boxes_copy[ $post->post_type ][ $location ] as $boxes ) {
226 foreach ( $boxes as $box ) {
227 /*
228 * If __block_editor_compatible_meta_box is declared as a false-y value,
229 * the meta box is not compatible with the block editor.
230 */
231 if ( is_array( $box['args'] )
232 && isset( $box['args']['__block_editor_compatible_meta_box'] )
233 && ! $box['args']['__block_editor_compatible_meta_box'] ) {
234 $incompatible_meta_box = true;
235 ?>
236 <script type="text/javascript">
237 var joiner = '?';
238 if ( window.location.search ) {
239 joiner = '&';
240 }
241 window.location.href += joiner + 'classic-editor';
242 </script>
243 <?php
244 exit;
245 }
246 }
247 }
248 }
249 }
250
251 /**
252 * Return whether the post can be edited in Gutenberg and by the current user.
253 *
254 * @since 0.5.0
255 *
256 * @param int|WP_Post $post Post ID or WP_Post object.
257 * @return bool Whether the post can be edited with Gutenberg.
258 */
259 function gutenberg_can_edit_post( $post ) {
260 $post = get_post( $post );
261 $can_edit = true;
262
263 if ( ! $post ) {
264 $can_edit = false;
265 }
266
267 if ( $can_edit && 'trash' === $post->post_status ) {
268 $can_edit = false;
269 }
270
271 if ( $can_edit && ! gutenberg_can_edit_post_type( $post->post_type ) ) {
272 $can_edit = false;
273 }
274
275 if ( $can_edit && ! current_user_can( 'edit_post', $post->ID ) ) {
276 $can_edit = false;
277 }
278
279 // Disable the editor if on the blog page and there is no content.
280 if ( $can_edit && absint( get_option( 'page_for_posts' ) ) === $post->ID && empty( $post->post_content ) ) {
281 $can_edit = false;
282 }
283
284 /**
285 * Filter to allow plugins to enable/disable Gutenberg for particular post.
286 *
287 * @since 3.5
288 *
289 * @param bool $can_edit Whether the post can be edited or not.
290 * @param WP_Post $post The post being checked.
291 */
292 return apply_filters( 'gutenberg_can_edit_post', $can_edit, $post );
293
294 }
295
296 /**
297 * Return whether the post type can be edited in Gutenberg.
298 *
299 * Gutenberg depends on the REST API, and if the post type is not shown in the
300 * REST API, then the post cannot be edited in Gutenberg.
301 *
302 * @since 1.5.2
303 *
304 * @param string $post_type The post type.
305 * @return bool Whether the post type can be edited with Gutenberg.
306 */
307 function gutenberg_can_edit_post_type( $post_type ) {
308 $can_edit = true;
309 if ( ! post_type_exists( $post_type ) ) {
310 $can_edit = false;
311 }
312
313 if ( ! post_type_supports( $post_type, 'editor' ) ) {
314 $can_edit = false;
315 }
316
317 $post_type_object = get_post_type_object( $post_type );
318 if ( $post_type_object && ! $post_type_object->show_in_rest ) {
319 $can_edit = false;
320 }
321
322 /**
323 * Filter to allow plugins to enable/disable Gutenberg for particular post types.
324 *
325 * @since 1.5.2
326 *
327 * @param bool $can_edit Whether the post type can be edited or not.
328 * @param string $post_type The post type being checked.
329 */
330 return apply_filters( 'gutenberg_can_edit_post_type', $can_edit, $post_type );
331 }
332
333 /**
334 * Determine whether a post or content string has blocks.
335 *
336 * This test optimizes for performance rather than strict accuracy, detecting
337 * the pattern of a block but not validating its structure. For strict accuracy
338 * you should use the block parser on post content.
339 *
340 * @since 3.6.0
341 * @see gutenberg_parse_blocks()
342 *
343 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
344 * @return bool Whether the post has blocks.
345 */
346 function has_blocks( $post = null ) {
347 if ( ! is_string( $post ) ) {
348 $wp_post = get_post( $post );
349 if ( $wp_post instanceof WP_Post ) {
350 $post = $wp_post->post_content;
351 }
352 }
353
354 return false !== strpos( (string) $post, '<!-- wp:' );
355 }
356
357 /**
358 * Determine whether a post has blocks. This test optimizes for performance
359 * rather than strict accuracy, detecting the pattern of a block but not
360 * validating its structure. For strict accuracy, you should use the block
361 * parser on post content.
362 *
363 * @see gutenberg_parse_blocks()
364 *
365 * @since 0.5.0
366 * @deprecated 3.6.0 Use has_blocks()
367 *
368 * @param object $post Post.
369 * @return bool Whether the post has blocks.
370 */
371 function gutenberg_post_has_blocks( $post ) {
372 _deprecated_function( __FUNCTION__, '3.6.0', 'has_blocks()' );
373 return has_blocks( $post );
374 }
375
376 /**
377 * Determine whether a content string contains blocks. This test optimizes for
378 * performance rather than strict accuracy, detecting the pattern of a block
379 * but not validating its structure. For strict accuracy, you should use the
380 * block parser on post content.
381 *
382 * @see gutenberg_parse_blocks()
383 *
384 * @since 1.6.0
385 * @deprecated 3.6.0 Use has_blocks()
386 *
387 * @param string $content Content to test.
388 * @return bool Whether the content contains blocks.
389 */
390 function gutenberg_content_has_blocks( $content ) {
391 _deprecated_function( __FUNCTION__, '3.6.0', 'has_blocks()' );
392 return has_blocks( $content );
393 }
394
395 /**
396 * Determine whether a $post or a string contains a specific block type.
397 * This test optimizes for performance rather than strict accuracy, detecting
398 * the block type exists but not validating its structure.
399 * For strict accuracy, you should use the block parser on post content.
400 *
401 * @since 3.6.0
402 *
403 * @param string $block_type Full Block type to look for.
404 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
405 * @return bool Whether the post content contains the specified block.
406 */
407 function has_block( $block_type, $post = null ) {
408 if ( ! has_blocks( $post ) ) {
409 return false;
410 }
411
412 if ( ! is_string( $post ) ) {
413 $wp_post = get_post( $post );
414 if ( $wp_post instanceof WP_Post ) {
415 $post = $wp_post->post_content;
416 }
417 }
418
419 return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' );
420 }
421
422 /**
423 * Returns the current version of the block format that the content string is using.
424 *
425 * If the string doesn't contain blocks, it returns 0.
426 *
427 * @since 2.8.0
428 * @see gutenberg_content_has_blocks()
429 *
430 * @param string $content Content to test.
431 * @return int The block format version.
432 */
433 function gutenberg_content_block_version( $content ) {
434 return has_blocks( $content ) ? 1 : 0;
435 }
436
437 /**
438 * Adds a "Gutenberg" post state for post tables, if the post contains blocks.
439 *
440 * @param array $post_states An array of post display states.
441 * @param WP_Post $post The current post object.
442 * @return array A filtered array of post display states.
443 */
444 function gutenberg_add_gutenberg_post_state( $post_states, $post ) {
445 if ( has_blocks( $post ) ) {
446 $post_states[] = 'Gutenberg';
447 }
448
449 return $post_states;
450 }
451 add_filter( 'display_post_states', 'gutenberg_add_gutenberg_post_state', 10, 2 );
452
453 /**
454 * Registers custom post types required by the Gutenberg editor.
455 *
456 * @since 0.10.0
457 */
458 function gutenberg_register_post_types() {
459 register_post_type(
460 'wp_block',
461 array(
462 'labels' => array(
463 'name' => __( 'Blocks', 'gutenberg' ),
464 'singular_name' => __( 'Block', 'gutenberg' ),
465 'search_items' => __( 'Search Blocks', 'gutenberg' ),
466 ),
467 'public' => false,
468 'show_ui' => true,
469 'show_in_menu' => false,
470 'rewrite' => false,
471 'show_in_rest' => true,
472 'rest_base' => 'blocks',
473 'rest_controller_class' => 'WP_REST_Blocks_Controller',
474 'capability_type' => 'block',
475 'capabilities' => array(
476 'read' => 'read_blocks',
477 'create_posts' => 'create_blocks',
478 ),
479 'map_meta_cap' => true,
480 'supports' => false,
481 )
482 );
483
484 $editor_caps = array(
485 'edit_blocks',
486 'edit_others_blocks',
487 'publish_blocks',
488 'read_private_blocks',
489 'read_blocks',
490 'delete_blocks',
491 'delete_private_blocks',
492 'delete_published_blocks',
493 'delete_others_blocks',
494 'edit_private_blocks',
495 'edit_published_blocks',
496 'create_blocks',
497 );
498
499 $caps_map = array(
500 'administrator' => $editor_caps,
501 'editor' => $editor_caps,
502 'author' => array(
503 'edit_blocks',
504 'publish_blocks',
505 'read_blocks',
506 'delete_blocks',
507 'delete_published_blocks',
508 'edit_published_blocks',
509 'create_blocks',
510 ),
511 'contributor' => array(
512 'read_blocks',
513 ),
514 );
515
516 foreach ( $caps_map as $role_name => $caps ) {
517 $role = get_role( $role_name );
518
519 if ( empty( $role ) ) {
520 continue;
521 }
522
523 foreach ( $caps as $cap ) {
524 if ( ! $role->has_cap( $cap ) ) {
525 $role->add_cap( $cap );
526 }
527 }
528 }
529 }
530 add_action( 'init', 'gutenberg_register_post_types' );
531
532 /**
533 * Injects a hidden input in the edit form to propagate the information that classic editor is selected.
534 *
535 * @since 1.5.2
536 */
537 function gutenberg_remember_classic_editor_when_saving_posts() {
538 ?>
539 <input type="hidden" name="classic-editor" />
540 <?php
541 }
542 add_action( 'edit_form_top', 'gutenberg_remember_classic_editor_when_saving_posts' );
543
544 /**
545 * Appends a query argument to the redirect url to make sure it gets redirected to the classic editor.
546 *
547 * @since 1.5.2
548 *
549 * @param string $url Redirect url.
550 * @return string Redirect url.
551 */
552 function gutenberg_redirect_to_classic_editor_when_saving_posts( $url ) {
553 if ( isset( $_REQUEST['classic-editor'] ) ) {
554 $url = add_query_arg( 'classic-editor', '', $url );
555 }
556 return $url;
557 }
558 add_filter( 'redirect_post_location', 'gutenberg_redirect_to_classic_editor_when_saving_posts', 10, 1 );
559
560 /**
561 * Appends a query argument to the edit url to make sure it is redirected to
562 * the editor from which the user navigated.
563 *
564 * @since 1.5.2
565 *
566 * @param string $url Edit url.
567 * @return string Edit url.
568 */
569 function gutenberg_revisions_link_to_editor( $url ) {
570 global $pagenow;
571 if ( 'revision.php' !== $pagenow || isset( $_REQUEST['gutenberg'] ) ) {
572 return $url;
573 }
574
575 return add_query_arg( 'classic-editor', '', $url );
576 }
577 add_filter( 'get_edit_post_link', 'gutenberg_revisions_link_to_editor' );
578
579 /**
580 * Modifies revisions data to preserve Gutenberg argument used in determining
581 * where to redirect user returning to editor.
582 *
583 * @since 1.9.0
584 *
585 * @param array $revisions_data The bootstrapped data for the revisions screen.
586 * @return array Modified bootstrapped data for the revisions screen.
587 */
588 function gutenberg_revisions_restore( $revisions_data ) {
589 if ( isset( $_REQUEST['gutenberg'] ) ) {
590 $revisions_data['restoreUrl'] = add_query_arg(
591 'gutenberg',
592 $_REQUEST['gutenberg'],
593 $revisions_data['restoreUrl']
594 );
595 }
596
597 return $revisions_data;
598 }
599 add_filter( 'wp_prepare_revision_for_js', 'gutenberg_revisions_restore' );
600