PluginProbe
Gutenberg / 23.6.0
Gutenberg v23.6.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 / compat / wordpress-7.1 / view-config-api.php

view-config-api.php in Gutenberg 23.6.0, at lib/compat/wordpress-7.1/view-config-api.php

802 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Entity view configuration API.
4 *
5 * Builds the default view configuration for an entity and exposes it through
6 * the dynamic `get_entity_view_config_{$kind}_{$name}` filter so core and third
7 * parties can provide the configuration for a specific entity.
8 *
9 * @package gutenberg
10 */
11
12 /**
13 * Builds the default `form` configuration for post types that don't provide their own.
14 *
15 * It is a sensible default for `post`, `page`, and custom post types alike rather
16 * than being tailored per type. Post types that need a different shape can replace
17 * it entirely with a dedicated `form` through their own filter callback.
18 *
19 * It is intentionally NOT gated by `supports`. The registered fields are the
20 * single source of truth for what applies: each field is registered for a post
21 * type based on its `supports` (and related flags such as `theme_supports`), and
22 * the editor drops any form field whose definition is absent or whose `isVisible`
23 * returns `false`.
24 *
25 * @return array The default form configuration.
26 */
27 function _gutenberg_get_default_post_type_form() {
28 return array(
29 'layout' => array( 'type' => 'panel' ),
30 'fields' => array(
31 array(
32 'id' => 'featured_media',
33 'layout' => array(
34 'type' => 'regular',
35 'labelPosition' => 'none',
36 ),
37 ),
38 array(
39 'id' => 'post-content-info',
40 'layout' => array(
41 'type' => 'regular',
42 'labelPosition' => 'none',
43 ),
44 ),
45 array(
46 'id' => 'excerpt',
47 'layout' => array(
48 'type' => 'panel',
49 'labelPosition' => 'top',
50 ),
51 ),
52 array(
53 'id' => 'status',
54 'label' => __( 'Status', 'gutenberg' ),
55 'children' => array(
56 array(
57 'id' => 'status',
58 'layout' => array(
59 'type' => 'regular',
60 'labelPosition' => 'none',
61 ),
62 ),
63 'scheduled_date',
64 'password',
65 'sticky',
66 ),
67 ),
68 'date',
69 'slug',
70 'author',
71 'template',
72 array(
73 'id' => 'discussion',
74 'label' => __( 'Discussion', 'gutenberg' ),
75 'children' => array(
76 array(
77 'id' => 'comment_status',
78 'layout' => array(
79 'type' => 'regular',
80 'labelPosition' => 'none',
81 ),
82 ),
83 'ping_status',
84 ),
85 ),
86 'parent',
87 'format',
88 'revisions',
89 ),
90 );
91 }
92
93 /**
94 * Returns the view configuration for the given entity.
95 *
96 * Builds the default configuration shared by all entities and then exposes it
97 * through the dynamic `get_entity_view_config_{$kind}_{$name}` filter so that core
98 * and third parties can provide the configuration for a specific entity.
99 *
100 * @param string $kind The entity kind (e.g. `postType`).
101 * @param string $name The entity name (e.g. `page`).
102 * @return array {
103 * The view configuration for the entity.
104 *
105 * @type array $default_view Default view configuration.
106 * @type array $default_layouts Default layouts configuration.
107 * @type array $view_list List of available views.
108 * @type array $form Form configuration.
109 * }
110 */
111 function gutenberg_get_entity_view_config( $kind, $name ) {
112 $default_view = array(
113 'type' => 'table',
114 'filters' => array(),
115 'sort' => array(
116 'field' => 'title',
117 'direction' => 'asc',
118 ),
119 'perPage' => 20,
120 'fields' => array( 'author', 'status' ),
121 'titleField' => 'title',
122 );
123 $default_layouts = array(
124 'table' => array(),
125 'grid' => array(),
126 'list' => array(),
127 );
128 $all_items_title = __( 'All items', 'gutenberg' );
129 if ( 'postType' === $kind ) {
130 $post_type_object = get_post_type_object( $name );
131 if ( $post_type_object && ! empty( $post_type_object->labels->all_items ) ) {
132 $all_items_title = $post_type_object->labels->all_items;
133 }
134 }
135 $view_list = array(
136 array(
137 'title' => $all_items_title,
138 'slug' => 'all',
139 ),
140 );
141
142 $config = array(
143 'default_view' => $default_view,
144 'default_layouts' => $default_layouts,
145 'view_list' => $view_list,
146 'form' => 'postType' === $kind ? _gutenberg_get_default_post_type_form() : array(),
147 );
148
149 $data = new Gutenberg_View_Config_Data( $config );
150
151 /**
152 * Filters the view configuration for a given entity.
153 *
154 * The dynamic portions of the hook name, `$kind` and `$name`, refer to the
155 * entity kind (e.g. `postType`) and the entity name (e.g. `page`).
156 *
157 * Callbacks receive a Gutenberg_View_Config_Data object and change the
158 * configuration through its methods: the `update_*()` methods merge
159 * partial changes into the current configuration, while `set()` replaces
160 * a whole top-level key. Callbacks must return the object they were
161 * given.
162 *
163 * @param Gutenberg_View_Config_Data $data The view configuration container
164 * for the entity, exposing the
165 * `default_view`, `default_layouts`,
166 * `view_list`, and `form` keys.
167 * @param array $entity {
168 * The entity the configuration is built for.
169 *
170 * @type string $kind The entity kind.
171 * @type string $name The entity name.
172 * }
173 */
174 $filtered = apply_filters(
175 "get_entity_view_config_{$kind}_{$name}",
176 $data,
177 array(
178 'kind' => $kind,
179 'name' => $name,
180 )
181 );
182
183 // A well-behaved callback returns the object it was given. Fall back to the
184 // unfiltered config if a callback replaced it with something else.
185 if ( ! $filtered instanceof Gutenberg_View_Config_Data ) {
186 _doing_it_wrong(
187 __FUNCTION__,
188 sprintf(
189 /* translators: %s: the filter hook name. */
190 esc_html__( 'A "%s" filter callback must return the Gutenberg_View_Config_Data object it was given.', 'gutenberg' ),
191 esc_html( "get_entity_view_config_{$kind}_{$name}" )
192 ),
193 '7.1.0'
194 );
195 return $config;
196 }
197
198 // Backfill any dropped keys with their defaults, then discard any keys the
199 // filter introduced that are not part of the documented configuration shape.
200 return array_intersect_key( array_merge( $config, $filtered->get_config() ), $config );
201 }
202
203 /**
204 * Provides the view configuration for the `page` post type.
205 *
206 * @param Gutenberg_View_Config_Data $data The view configuration container for the entity.
207 * @return Gutenberg_View_Config_Data The updated view configuration container.
208 */
209 function _gutenberg_get_entity_view_config_post_type_page( $data ) {
210 $default_layouts = array(
211 'table' => array(
212 'layout' => array(
213 'styles' => array(
214 'author' => array(
215 'align' => 'start',
216 ),
217 ),
218 ),
219 ),
220 'grid' => array(),
221 'list' => array(),
222 );
223
224 $default_view = array(
225 'type' => 'list',
226 'filters' => array(),
227 'perPage' => 20,
228 'sort' => array(
229 'field' => 'title',
230 'direction' => 'asc',
231 ),
232 'showLevels' => true,
233 'titleField' => 'title',
234 'mediaField' => 'featured_media',
235 'fields' => array( 'author', 'status' ),
236 );
237
238 $view_list = array(
239 array(
240 'title' => __( 'Published', 'gutenberg' ),
241 'slug' => 'published',
242 'view' => array(
243 'filters' => array(
244 array(
245 'field' => 'status',
246 'operator' => 'isAny',
247 'value' => 'publish',
248 'isLocked' => true,
249 ),
250 ),
251 ),
252 ),
253 array(
254 'title' => __( 'Scheduled', 'gutenberg' ),
255 'slug' => 'future',
256 'view' => array(
257 'filters' => array(
258 array(
259 'field' => 'status',
260 'operator' => 'isAny',
261 'value' => 'future',
262 'isLocked' => true,
263 ),
264 ),
265 ),
266 ),
267 array(
268 'title' => __( 'Drafts', 'gutenberg' ),
269 'slug' => 'drafts',
270 'view' => array(
271 'filters' => array(
272 array(
273 'field' => 'status',
274 'operator' => 'isAny',
275 'value' => 'draft',
276 'isLocked' => true,
277 ),
278 ),
279 ),
280 ),
281 array(
282 'title' => __( 'Pending', 'gutenberg' ),
283 'slug' => 'pending',
284 'view' => array(
285 'filters' => array(
286 array(
287 'field' => 'status',
288 'operator' => 'isAny',
289 'value' => 'pending',
290 'isLocked' => true,
291 ),
292 ),
293 ),
294 ),
295 array(
296 'title' => __( 'Private', 'gutenberg' ),
297 'slug' => 'private',
298 'view' => array(
299 'filters' => array(
300 array(
301 'field' => 'status',
302 'operator' => 'isAny',
303 'value' => 'private',
304 'isLocked' => true,
305 ),
306 ),
307 ),
308 ),
309 array(
310 'title' => __( 'Trash', 'gutenberg' ),
311 'slug' => 'trash',
312 'view' => array(
313 'type' => 'table',
314 'layout' => $default_layouts['table']['layout'],
315 'filters' => array(
316 array(
317 'field' => 'status',
318 'operator' => 'isAny',
319 'value' => 'trash',
320 'isLocked' => true,
321 ),
322 ),
323 ),
324 ),
325 );
326
327 $data->set( 'default_layouts', $default_layouts, 1 );
328 $data->set( 'default_view', $default_view, 1 );
329 // Append the status views, thereby preserving the base "all items" view,
330 // so its post-type-specific title is kept.
331 $data->update_view_list_items( array_column( $view_list, null, 'slug' ), 1 );
332
333 return $data;
334 }
335
336 /**
337 * Provides the view configuration for the `wp_block` post type.
338 *
339 * @param Gutenberg_View_Config_Data $data The view configuration container for the entity.
340 * @return Gutenberg_View_Config_Data The updated view configuration container.
341 */
342 function _gutenberg_get_entity_view_config_post_type_wp_block( $data ) {
343 $default_layouts = array(
344 'table' => array(
345 'layout' => array(
346 'styles' => array(
347 'author' => array(
348 'width' => '1%',
349 ),
350 ),
351 ),
352 ),
353 'grid' => array(
354 'layout' => array(
355 'badgeFields' => array( 'sync-status' ),
356 ),
357 ),
358 );
359
360 $default_view = array(
361 'type' => 'grid',
362 'perPage' => 20,
363 'titleField' => 'title',
364 'mediaField' => 'preview',
365 'fields' => array( 'sync-status' ),
366 'filters' => array(),
367 'layout' => $default_layouts['grid']['layout'],
368 );
369
370 $data->set( 'default_layouts', $default_layouts, 1 );
371 $data->set( 'default_view', $default_view, 1 );
372
373 $view_list = array(
374 array(
375 'title' => __( 'All patterns', 'gutenberg' ),
376 'slug' => 'all-patterns',
377 ),
378 array(
379 'title' => __( 'My patterns', 'gutenberg' ),
380 'slug' => 'my-patterns',
381 ),
382 );
383
384 // Gather categories from the block pattern categories registry.
385 $registry = WP_Block_Pattern_Categories_Registry::get_instance();
386 $categories = array();
387
388 foreach ( $registry->get_all_registered() as $category ) {
389 $categories[ $category['name'] ] = $category['label'];
390 }
391
392 // Ensure "Uncategorized" is always included for patterns
393 // that have no category assigned.
394 $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' );
395
396 // Also gather user-created pattern categories (wp_pattern_category taxonomy).
397 $user_terms = get_terms(
398 array(
399 'taxonomy' => 'wp_pattern_category',
400 'hide_empty' => false,
401 )
402 );
403
404 if ( ! is_wp_error( $user_terms ) ) {
405 foreach ( $user_terms as $term ) {
406 $categories[ $term->slug ] = $term->name;
407 }
408 }
409
410 // Sort categories alphabetically by label.
411 asort( $categories, SORT_NATURAL | SORT_FLAG_CASE );
412
413 foreach ( $categories as $category_name => $label ) {
414 $view_list[] = array(
415 'title' => $label,
416 'slug' => $category_name,
417 );
418 }
419
420 $data->set( 'view_list', $view_list, 1 );
421
422 $data->set(
423 'form',
424 array(
425 'layout' => array( 'type' => 'panel' ),
426 'fields' => array(
427 array(
428 'id' => 'excerpt',
429 'layout' => array(
430 'type' => 'panel',
431 'labelPosition' => 'top',
432 ),
433 ),
434 array(
435 'id' => 'post-content-info',
436 'layout' => array(
437 'type' => 'regular',
438 'labelPosition' => 'none',
439 ),
440 ),
441 'sync-status',
442 'revisions',
443 ),
444 ),
445 1
446 );
447
448 return $data;
449 }
450
451 /**
452 * Provides the view configuration for the `wp_template_part` post type.
453 *
454 * @param Gutenberg_View_Config_Data $data The view configuration container for the entity.
455 * @return Gutenberg_View_Config_Data The updated view configuration container.
456 */
457 function _gutenberg_get_entity_view_config_post_type_wp_template_part( $data ) {
458 $default_layouts = array(
459 'table' => array(
460 'layout' => array(
461 'styles' => array(
462 'author' => array(
463 'width' => '1%',
464 ),
465 ),
466 ),
467 ),
468 'grid' => array(
469 'layout' => array(),
470 ),
471 );
472
473 $default_view = array(
474 'type' => 'grid',
475 'perPage' => 20,
476 'titleField' => 'title',
477 'mediaField' => 'preview',
478 'fields' => array( 'author' ),
479 'filters' => array(),
480 'layout' => $default_layouts['grid']['layout'],
481 );
482
483 $data->set( 'default_layouts', $default_layouts, 1 );
484 $data->set( 'default_view', $default_view, 1 );
485
486 $view_list = array(
487 array(
488 'title' => __( 'All template parts', 'gutenberg' ),
489 'slug' => 'all-parts',
490 ),
491 );
492
493 $areas = get_allowed_block_template_part_areas();
494
495 // Ensure default areas appear in a consistent order.
496 $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' );
497 $ordered_areas = array();
498 $remaining_areas = array();
499 foreach ( $areas as $area ) {
500 $position = array_search( $area['area'], $preferred_order, true );
501 if ( false !== $position ) {
502 $ordered_areas[ $position ] = $area;
503 } else {
504 $remaining_areas[] = $area;
505 }
506 }
507 ksort( $ordered_areas );
508 $areas = array_merge( array_values( $ordered_areas ), $remaining_areas );
509
510 foreach ( $areas as $area ) {
511 $view_list[] = array(
512 'title' => $area['label'],
513 'slug' => $area['area'],
514 'view' => array(
515 'filters' => array(
516 array(
517 'field' => 'area',
518 'operator' => 'is',
519 'value' => $area['area'],
520 'isLocked' => true,
521 ),
522 ),
523 ),
524 );
525 }
526
527 $data->set( 'view_list', $view_list, 1 );
528
529 $data->set(
530 'form',
531 array(
532 'layout' => array( 'type' => 'panel' ),
533 'fields' => array(
534 array(
535 'id' => 'last_edited_date',
536 'layout' => array(
537 'type' => 'panel',
538 'labelPosition' => 'none',
539 ),
540 ),
541 'revisions',
542 ),
543 ),
544 1
545 );
546
547 return $data;
548 }
549
550 /**
551 * Provides the view configuration for the `wp_template` post type.
552 *
553 * @param Gutenberg_View_Config_Data $data The view configuration container for the entity.
554 * @return Gutenberg_View_Config_Data The updated view configuration container.
555 */
556 function _gutenberg_get_entity_view_config_post_type_wp_template( $data ) {
557 $default_view = array(
558 'type' => 'grid',
559 'perPage' => 20,
560 'sort' => array(
561 'field' => 'title',
562 'direction' => 'asc',
563 ),
564 'titleField' => 'title',
565 'descriptionField' => 'description',
566 'mediaField' => 'preview',
567 'fields' => array( 'author', 'active', 'slug', 'theme' ),
568 'filters' => array(),
569 'showMedia' => true,
570 );
571
572 $default_layouts = array(
573 'table' => array( 'showMedia' => false ),
574 'grid' => array( 'showMedia' => true ),
575 'list' => array( 'showMedia' => false ),
576 );
577
578 $data->set( 'default_view', $default_view, 1 );
579 $data->set( 'default_layouts', $default_layouts, 1 );
580
581 $view_list = array(
582 array(
583 'title' => __( 'All templates', 'gutenberg' ),
584 'slug' => 'all',
585 ),
586 );
587
588 $templates = get_block_templates( array(), 'wp_template' );
589
590 // Collect unique authors, tracking whether they come from a registered
591 // source (theme, plugin, site) so we can sort those before user ones.
592 $seen_authors = array();
593 $registered_authors = array();
594 $user_authors = array();
595 foreach ( $templates as $template ) {
596 /*
597 * Determine the original source of the template ('theme', 'plugin',
598 * 'site', or 'user').
599 */
600 $original_source = 'user';
601 if ( 'wp_template' === $template->type || 'wp_template_part' === $template->type ) {
602 if ( $template->has_theme_file &&
603 ( 'theme' === $template->origin || (
604 empty( $template->origin ) && in_array(
605 $template->source,
606 array(
607 'theme',
608 'custom',
609 ),
610 true
611 ) )
612 )
613 ) {
614 /*
615 * Added by theme.
616 * Template originally provided by a theme, but customized by a user.
617 * Templates originally didn't have the 'origin' field so identify
618 * older customized templates by checking for no origin and a 'theme'
619 * or 'custom' source.
620 */
621 $original_source = 'theme';
622 } elseif ( 'plugin' === $template->origin ) {
623 // Added by plugin.
624 $original_source = 'plugin';
625 } elseif ( empty( $template->has_theme_file ) && 'custom' === $template->source && empty( $template->author ) ) {
626 /*
627 * Added by site.
628 * Template was created from scratch, but has no author. Author support
629 * was only added to templates in WordPress 5.9. Fallback to showing the
630 * site logo and title.
631 */
632 $original_source = 'site';
633 }
634 }
635
636 // Determine a human readable text for the author of the template.
637 $author_text = '';
638 switch ( $original_source ) {
639 case 'theme':
640 $theme_name = wp_get_theme( $template->theme )->get( 'Name' );
641 $author_text = empty( $theme_name ) ? $template->theme : $theme_name;
642 break;
643 case 'plugin':
644 if ( ! function_exists( 'get_plugins' ) ) {
645 require_once ABSPATH . 'wp-admin/includes/plugin.php';
646 }
647 $plugin_name = '';
648 if ( isset( $template->plugin ) ) {
649 $plugins = wp_get_active_and_valid_plugins();
650
651 foreach ( $plugins as $plugin_file ) {
652 $plugin_basename = plugin_basename( $plugin_file );
653 list( $plugin_slug, ) = explode( '/', $plugin_basename );
654
655 if ( $plugin_slug === $template->plugin ) {
656 $plugin_data = get_plugin_data( $plugin_file );
657
658 if ( ! empty( $plugin_data['Name'] ) ) {
659 $plugin_name = $plugin_data['Name'];
660 }
661
662 break;
663 }
664 }
665 }
666
667 /*
668 * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
669 * compatibility with templates that were registered before the plugin attribute was added.
670 */
671 if ( '' === $plugin_name ) {
672 $plugins = get_plugins();
673 $plugin_basename = plugin_basename( sanitize_text_field( $template->theme . '.php' ) );
674 if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
675 $plugin_name = $plugins[ $plugin_basename ]['Name'];
676 } else {
677 $plugin_name = $template->plugin ?? $template->theme;
678 }
679 }
680 $author_text = $plugin_name;
681 break;
682 case 'site':
683 $author_text = get_bloginfo( 'name' );
684 break;
685 case 'user':
686 $author = get_user_by( 'id', $template->author );
687 if ( ! $author ) {
688 $author_text = __( 'Unknown author', 'gutenberg' );
689 } else {
690 $author_text = $author->get( 'display_name' );
691 }
692 break;
693 }
694
695 if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
696 $seen_authors[ $author_text ] = true;
697 $entry = array(
698 'title' => $author_text,
699 'slug' => $author_text,
700 'view' => array(
701 'filters' => array(
702 array(
703 'field' => 'author',
704 'operator' => 'is',
705 'value' => $author_text,
706 'isLocked' => true,
707 ),
708 ),
709 ),
710 );
711 if ( 'user' === $original_source ) {
712 $user_authors[] = $entry;
713 } else {
714 $registered_authors[] = $entry;
715 }
716 }
717 }
718
719 $data->set( 'view_list', array_merge( $view_list, $registered_authors, $user_authors ), 1 );
720
721 $data->set(
722 'form',
723 array(
724 'layout' => array( 'type' => 'panel' ),
725 'fields' => array(
726 array(
727 'id' => 'description',
728 'layout' => array(
729 'type' => 'panel',
730 'labelPosition' => 'top',
731 ),
732 ),
733 array(
734 'id' => 'description_readonly',
735 'layout' => array(
736 'type' => 'regular',
737 'labelPosition' => 'none',
738 ),
739 ),
740 array(
741 'id' => 'last_edited_date',
742 'layout' => array(
743 'type' => 'panel',
744 'labelPosition' => 'none',
745 ),
746 ),
747 'revisions',
748 // The following fields are only meaningful in the `home`/`index`
749 // template summary. They edit other entities (`root/site` and the
750 // posts page); the editor merges those records into the form data
751 // under a namespace and controls when the fields are shown.
752 'posts_page_title',
753 'posts_per_page',
754 'default_comment_status',
755 ),
756 ),
757 1
758 );
759
760 return $data;
761 }
762
763 /**
764 * Registers the Gutenberg entity view configuration filters, overriding any
765 * defaults that WordPress core may have already registered.
766 *
767 * Core registers its own `_wp_get_entity_view_config_post_type_*` callbacks on
768 * the shared `get_entity_view_config_{$kind}_{$name}` hooks. The Gutenberg
769 * plugin always ships the newest configuration, so it removes the core defaults
770 * and installs its own `_gutenberg_*` callbacks instead.
771 *
772 * Post types without a dedicated `_gutenberg_*` callback (e.g. `post`) still have
773 * the core callback removed so they fall back to the default configuration
774 * built in gutenberg_get_entity_view_config().
775 *
776 * This runs on `init` rather than at file include time so that the core
777 * defaults are guaranteed to be registered first, regardless of whether core
778 * registers them at include time or lazily on a hook.
779 */
780 function gutenberg_register_entity_view_config_filters() {
781 $post_types = array( 'page', 'post', 'wp_block', 'wp_template_part', 'wp_template' );
782
783 foreach ( $post_types as $post_type ) {
784 $hook = "get_entity_view_config_postType_{$post_type}";
785 $wp_callback = "_wp_get_entity_view_config_post_type_{$post_type}";
786 $gb_callback = "_gutenberg_get_entity_view_config_post_type_{$post_type}";
787
788 // has_filter() returns the priority the callback was registered at.
789 $wp_priority = has_filter( $hook, $wp_callback );
790 if ( false !== $wp_priority ) {
791 remove_filter( $hook, $wp_callback, $wp_priority );
792 }
793 if ( function_exists( $gb_callback ) ) {
794 // Base definitions run before the default priority, so third-party
795 // callbacks registered at the default compose on top of them
796 // regardless of registration order.
797 add_filter( $hook, $gb_callback, 5, 1 );
798 }
799 }
800 }
801 add_action( 'init', 'gutenberg_register_entity_view_config_filters' );
802