PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.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 All 404 releases
gutenberg / lib / compat / wordpress-7.1 / view-config-api.php

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

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