PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
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
← All changes | lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php +160 -621 23.2.2 → trunk View file →
@@ -46,9 +46,10 @@
46 46 ),
47 47 ),
48 48 ),
49 49 'schema' => array( $this, 'get_public_item_schema' ),
50 - )
50 + ),
51 + true // override existing route defined by core, if it exists
51 52 );
52 53 }
53 54
54 55 /**
@@ -56,14 +57,24 @@
56 57 *
57 58 * @param WP_REST_Request $request Full details about the request.
58 59 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
59 60 */
60 - public function get_items_permissions_check(
61 - // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
62 - $request
63 - ) {
64 - if ( ! current_user_can( 'edit_posts' ) ) {
61 + public function get_items_permissions_check( $request ) {
62 + $kind = $request->get_param( 'kind' );
63 + $name = $request->get_param( 'name' );
64 +
65 + $capability = $this->get_required_capability( $kind, $name );
66 +
67 + if ( null === $capability ) {
65 68 return new WP_Error(
69 + 'rest_view_config_invalid_entity',
70 + __( 'Invalid entity kind or name.', 'gutenberg' ),
71 + array( 'status' => 404 )
72 + );
73 + }
74 +
75 + if ( ! current_user_can( $capability ) ) {
76 + return new WP_Error(
66 77 'rest_cannot_read',
67 78 __( 'Sorry, you are not allowed to read view config.', 'gutenberg' ),
68 79 array( 'status' => rest_authorization_required_code() )
69 80 );
@@ -72,8 +83,50 @@
72 83 return true;
73 84 }
74 85
75 86 /**
87 + * Resolves the capability required to read the view config for an entity.
88 + *
89 + * Known kinds map to the capability that gates managing that entity's list:
90 + * post types use their own `edit_posts` capability (which honors custom
91 + * `capability_type` registrations), taxonomies use `manage_terms`, and
92 + * root-level entities use `manage_options`. A post type or taxonomy that is
93 + * not registered, or not exposed to the REST API, resolves to `null` so the
94 + * request is treated as referencing an unknown entity.
95 + *
96 + * Any other kind falls back to `edit_posts`. This keeps entities registered
97 + * through the `get_entity_view_config_{$kind}_{$name}` filter readable behind
98 + * a baseline capability.
99 + *
100 + * @param string $kind The entity kind (e.g. `postType`).
101 + * @param string $name The entity name (e.g. `page`).
102 + * @return string|null Capability required to read the config, or null if the
103 + * entity is not registered.
104 + */
105 + protected function get_required_capability( $kind, $name ) {
106 + switch ( $kind ) {
107 + case 'postType':
108 + $post_type = get_post_type_object( $name );
109 + if ( $post_type && $post_type->show_in_rest ) {
110 + return $post_type->cap->edit_posts;
111 + }
112 + return null;
113 +
114 + case 'taxonomy':
115 + $taxonomy = get_taxonomy( $name );
116 + if ( $taxonomy && $taxonomy->show_in_rest ) {
117 + return $taxonomy->cap->manage_terms;
118 + }
119 + return null;
120 +
121 + case 'root':
122 + return 'manage_options';
123 + }
124 +
125 + return 'edit_posts';
126 + }
127 +
128 + /**
76 129 * Returns the default view configuration for the given entity type.
77 130 *
78 131 * @param WP_REST_Request $request Full details about the request.
79 132 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
@@ -81,67 +134,19 @@
81 134 public function get_items( $request ) {
82 135 $kind = $request->get_param( 'kind' );
83 136 $name = $request->get_param( 'name' );
84 137
85 - // TODO: this data will come from a registry of view configs per entity.
86 - $form = array();
87 - $default_view = array(
88 - 'type' => 'table',
89 - 'filters' => array(),
90 - 'perPage' => 20,
91 - 'sort' => array(
92 - 'field' => 'title',
93 - 'direction' => 'asc',
94 - ),
95 - 'titleField' => 'title',
96 - 'fields' => array( 'author', 'status' ),
97 - );
98 - $default_layouts = array(
99 - 'table' => array(),
100 - 'grid' => array(),
101 - 'list' => array(),
102 - );
103 - $all_items_title = __( 'All items', 'gutenberg' );
104 - if ( 'postType' === $kind ) {
105 - $post_type_object = get_post_type_object( $name );
106 - if ( $post_type_object && ! empty( $post_type_object->labels->all_items ) ) {
107 - $all_items_title = $post_type_object->labels->all_items;
108 - }
109 - }
110 - $view_list = array(
111 - array(
112 - 'title' => $all_items_title,
113 - 'slug' => 'all',
114 - ),
115 - );
116 - if ( 'postType' === $kind && 'page' === $name ) {
117 - $default_layouts = $this->get_default_layouts_for_page();
118 - $default_view = $this->get_default_view_for_page();
119 - $view_list = $this->get_view_list_for_page( $all_items_title, $default_layouts );
120 - $form = $this->get_form_for_page();
121 - } elseif ( 'postType' === $kind && 'post' === $name ) {
122 - $form = $this->get_form_for_page();
123 - } elseif ( 'postType' === $kind && 'wp_block' === $name ) {
124 - $default_layouts = $this->get_default_layouts_for_wp_block();
125 - $default_view = $this->get_default_view_for_wp_block( $default_layouts );
126 - $view_list = $this->get_view_list_for_wp_block();
127 - } elseif ( 'postType' === $kind && 'wp_template_part' === $name ) {
128 - $default_layouts = $this->get_default_layouts_for_wp_template_part();
129 - $default_view = $this->get_default_view_for_wp_template_part( $default_layouts );
130 - $view_list = $this->get_view_list_for_wp_template_part();
131 - } elseif ( 'postType' === $kind && 'wp_template' === $name ) {
132 - $default_view = $this->get_default_view_for_wp_template();
133 - $default_layouts = $this->get_default_layouts_for_wp_template();
134 - $view_list = $this->get_view_list_for_wp_template();
135 - }
138 + $config = gutenberg_get_entity_view_config( $kind, $name );
139 + $schema = $this->get_item_schema();
136 140
137 141 $response = array(
138 142 'kind' => $kind,
139 143 'name' => $name,
140 - 'default_view' => $default_view,
141 - 'default_layouts' => $default_layouts,
142 - 'view_list' => $view_list,
143 - 'form' => $form,
144 + 'version' => Gutenberg_View_Config_Data::LATEST_VERSION,
145 + 'default_view' => $this->cast_empty_objects( $config['default_view'], $schema['properties']['default_view'] ),
146 + 'default_layouts' => $this->cast_empty_objects( $config['default_layouts'], $schema['properties']['default_layouts'] ),
147 + 'view_list' => $this->cast_empty_objects( $config['view_list'], $schema['properties']['view_list'] ),
148 + 'form' => $this->cast_empty_objects( $config['form'], $schema['properties']['form'] ),
144 149 );
145 150
146 151 return rest_ensure_response( $response );
147 152 }
@@ -146,8 +151,81 @@
146 151 return rest_ensure_response( $response );
147 152 }
148 153
149 154 /**
155 + * Recursively casts empty arrays to objects where the schema types them as
156 + * objects.
157 + *
158 + * PHP cannot distinguish an empty associative array from an empty list, so
159 + * `json_encode()` always serializes `array()` as a JSON array (`[]`). The
160 + * REST schema, however, types several values as objects, which must encode
161 + * as `{}`. This walks the value against its schema and casts any empty,
162 + * object-typed array to an object. Non-empty associative arrays already
163 + * encode as objects, so they are left as arrays and only recursed into to
164 + * fix any nested empty objects.
165 + *
166 + * Union schemas (`oneOf`/`anyOf`) are handled only for the empty-array case:
167 + * an empty value is cast to an object when any branch allows an object. Such
168 + * values are not recursed into, which is sufficient for the form schema
169 + * where they never contain empty nested objects.
170 + *
171 + * @param mixed $value The value to normalize.
172 + * @param array $schema The schema node describing the value.
173 + * @return mixed The normalized value, with empty object-typed arrays cast to objects.
174 + */
175 + protected function cast_empty_objects( $value, $schema ) {
176 + if ( ! is_array( $value ) || ! is_array( $schema ) ) {
177 + return $value;
178 + }
179 +
180 + if ( isset( $schema['oneOf'] ) || isset( $schema['anyOf'] ) ) {
181 + $branches = isset( $schema['oneOf'] ) ? $schema['oneOf'] : $schema['anyOf'];
182 + if ( array() === $value ) {
183 + foreach ( $branches as $branch ) {
184 + if ( is_array( $branch ) && in_array( 'object', (array) ( isset( $branch['type'] ) ? $branch['type'] : array() ), true ) ) {
185 + return (object) array();
186 + }
187 + }
188 + }
189 + return $value;
190 + }
191 +
192 + $types = (array) ( isset( $schema['type'] ) ? $schema['type'] : array() );
193 +
194 + if ( in_array( 'array', $types, true ) && isset( $schema['items'] ) ) {
195 + foreach ( $value as $index => $item ) {
196 + $value[ $index ] = $this->cast_empty_objects( $item, $schema['items'] );
197 + }
198 + return $value;
199 + }
200 +
201 + if ( in_array( 'object', $types, true ) ) {
202 + if ( isset( $schema['properties'] ) ) {
203 + foreach ( $schema['properties'] as $property => $property_schema ) {
204 + if ( array_key_exists( $property, $value ) ) {
205 + $value[ $property ] = $this->cast_empty_objects( $value[ $property ], $property_schema );
206 + }
207 + }
208 + }
209 + if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) {
210 + foreach ( $value as $key => $item ) {
211 + if ( isset( $schema['properties'][ $key ] ) ) {
212 + continue;
213 + }
214 + $value[ $key ] = $this->cast_empty_objects( $item, $schema['additionalProperties'] );
215 + }
216 + }
217 +
218 + // Empty object-typed arrays must serialize as {} to match the schema.
219 + if ( array() === $value ) {
220 + return (object) array();
221 + }
222 + }
223 +
224 + return $value;
225 + }
226 +
227 + /**
150 228 * Retrieves the item's schema, conforming to JSON Schema.
151 229 *
152 230 * @return array Item schema data.
153 231 */
@@ -172,8 +250,13 @@
172 250 'description' => __( 'Entity name.', 'gutenberg' ),
173 251 'type' => 'string',
174 252 'readonly' => true,
175 253 ),
254 + 'version' => array(
255 + 'description' => __( 'The schema version of the configuration.', 'gutenberg' ),
256 + 'type' => 'integer',
257 + 'readonly' => true,
258 + ),
176 259 'default_view' => array(
177 260 'description' => __( 'Default view configuration.', 'gutenberg' ),
178 261 'type' => 'object',
179 262 'readonly' => true,
@@ -178,11 +261,12 @@
178 261 'type' => 'object',
179 262 'readonly' => true,
180 263 'properties' => array_merge(
181 264 array(
182 - 'type' => array(
265 + 'type' => array(
183 266 'type' => 'string',
184 267 ),
268 + 'layout' => $this->get_combined_layout_schema(),
185 269 ),
186 270 $view_base_properties
187 271 ),
188 272 ),
@@ -289,15 +373,15 @@
289 373
290 374 /**
291 375 * Returns the schema properties shared by all view types (ViewBase), excluding 'type'.
292 376 *
377 + * Note that `search` and `page` are not part of the schema: they are managed
378 + * via the URL, which is their only source of truth.
379 + *
293 380 * @return array Schema properties for the base view configuration.
294 381 */
295 - private function get_view_base_schema() {
382 + protected function get_view_base_schema() {
296 383 return array(
297 - 'search' => array(
298 - 'type' => 'string',
299 - ),
300 384 'filters' => array(
301 385 'type' => 'array',
302 386 'items' => array(
303 387 'type' => 'object',
@@ -340,11 +424,8 @@
340 424 'enum' => array( 'asc', 'desc' ),
341 425 ),
342 426 ),
343 427 ),
344 - 'page' => array(
345 - 'type' => 'integer',
346 - ),
347 428 'perPage' => array(
348 429 'type' => 'integer',
349 430 ),
350 431 'fields' => array(
@@ -400,9 +481,9 @@
400 481 * Returns the schema for the ColumnStyle type.
401 482 *
402 483 * @return array Schema for a column style object.
403 484 */
404 - private function get_column_style_schema() {
485 + protected function get_column_style_schema() {
405 486 return array(
406 487 'type' => 'object',
407 488 'properties' => array(
408 489 'width' => array(
@@ -426,9 +507,9 @@
426 507 * Returns the layout schema for table-type views (ViewTable, ViewPickerTable).
427 508 *
428 509 * @return array Schema for a table layout object.
429 510 */
430 - private function get_table_layout_schema() {
511 + protected function get_table_layout_schema() {
431 512 return array(
432 513 'type' => 'object',
433 514 'properties' => array(
434 515 'styles' => array(
@@ -450,9 +531,9 @@
450 531 * Returns the layout schema for list-type views (ViewList, ViewActivity).
451 532 *
452 533 * @return array Schema for a list layout object.
453 534 */
454 - private function get_list_layout_schema() {
535 + protected function get_list_layout_schema() {
455 536 return array(
456 537 'type' => 'object',
457 538 'properties' => array(
458 539 'density' => array(
@@ -471,14 +552,15 @@
471 552 * properties must be accepted.
472 553 *
473 554 * @return array Schema for a combined layout object.
474 555 */
475 - private function get_combined_layout_schema() {
556 + protected function get_combined_layout_schema() {
476 557 return array(
477 558 'type' => 'object',
478 559 'properties' => array_merge(
479 560 $this->get_table_layout_schema()['properties'],
480 - $this->get_grid_layout_schema()['properties']
561 + $this->get_grid_layout_schema()['properties'],
562 + $this->get_list_layout_schema()['properties']
481 563 ),
482 564 );
483 565 }
484 566
@@ -486,9 +568,9 @@
486 568 * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid).
487 569 *
488 570 * @return array Schema for a grid layout object.
489 571 */
490 - private function get_grid_layout_schema() {
572 + protected function get_grid_layout_schema() {
491 573 return array(
492 574 'type' => 'object',
493 575 'properties' => array(
494 576 'badgeFields' => array(
@@ -515,9 +597,9 @@
515 597 * matching the TypeScript Layout union in dataviews/src/types/dataform.ts.
516 598 *
517 599 * @return array Schema for a form layout object.
518 600 */
519 - private function get_form_layout_schema() {
601 + protected function get_form_layout_schema() {
520 602 return array(
521 603 'oneOf' => array(
522 604 // RegularLayout.
523 605 array(
@@ -675,9 +757,9 @@
675 757 * Returns the schema for a form field item (string or object).
676 758 *
677 759 * @return array Schema for a form field.
678 760 */
679 - private function get_form_field_schema() {
761 + protected function get_form_field_schema() {
680 762 return array(
681 763 'oneOf' => array(
682 764 array( 'type' => 'string' ),
683 765 array(
@@ -716,9 +798,9 @@
716 798 * Returns the schema for the form configuration object.
717 799 *
718 800 * @return array Schema properties for the form configuration.
719 801 */
720 - private function get_form_schema() {
802 + protected function get_form_schema() {
721 803 return array(
722 804 'layout' => $this->get_form_layout_schema(),
723 805 'fields' => array(
724 806 'type' => 'array',
@@ -724,549 +806,6 @@
724 806 'type' => 'array',
725 807 'items' => $this->get_form_field_schema(),
726 808 ),
727 809 );
728 - }
729 -
730 - private function get_default_view_for_page() {
731 - return array(
732 - 'type' => 'list',
733 - 'filters' => array(),
734 - 'perPage' => 20,
735 - 'sort' => array(
736 - 'field' => 'title',
737 - 'direction' => 'asc',
738 - ),
739 - 'showLevels' => true,
740 - 'titleField' => 'title',
741 - 'mediaField' => 'featured_media',
742 - 'fields' => array( 'author', 'status' ),
743 - );
744 - }
745 -
746 - private function get_default_layouts_for_page() {
747 - return array(
748 - 'table' => array(
749 - 'layout' => array(
750 - 'styles' => array(
751 - 'author' => array(
752 - 'align' => 'start',
753 - ),
754 - ),
755 - ),
756 - ),
757 - 'grid' => array(),
758 - 'list' => array(),
759 - );
760 - }
761 -
762 - private function get_form_for_page() {
763 - return array(
764 - 'layout' => array( 'type' => 'panel' ),
765 - 'fields' => array(
766 - array(
767 - 'id' => 'featured_media',
768 - 'layout' => array(
769 - 'type' => 'regular',
770 - 'labelPosition' => 'none',
771 - ),
772 - ),
773 - array(
774 - 'id' => 'post-content-info',
775 - 'layout' => array(
776 - 'type' => 'regular',
777 - 'labelPosition' => 'none',
778 - ),
779 - ),
780 - array(
781 - 'id' => 'excerpt',
782 - 'layout' => array(
783 - 'type' => 'panel',
784 - 'labelPosition' => 'top',
785 - ),
786 - ),
787 - array(
788 - 'id' => 'status',
789 - 'label' => __( 'Status', 'gutenberg' ),
790 - 'children' => array(
791 - array(
792 - 'id' => 'status',
793 - 'layout' => array(
794 - 'type' => 'regular',
795 - 'labelPosition' => 'none',
796 - ),
797 - ),
798 - 'scheduled_date',
799 - 'password',
800 - 'sticky',
801 - ),
802 - ),
803 - 'date',
804 - 'slug',
805 - 'author',
806 - 'template',
807 - array(
808 - 'id' => 'discussion',
809 - 'label' => __( 'Discussion', 'gutenberg' ),
810 - 'children' => array(
811 - array(
812 - 'id' => 'comment_status',
813 - 'layout' => array(
814 - 'type' => 'regular',
815 - 'labelPosition' => 'none',
816 - ),
817 - ),
818 - 'ping_status',
819 - ),
820 - ),
821 - 'parent',
822 - 'format',
823 - ),
824 - );
825 - }
826 -
827 - private function get_view_list_for_page( $all_items_title, $default_layouts ) {
828 - return array(
829 - array(
830 - 'title' => $all_items_title,
831 - 'slug' => 'all',
832 - ),
833 - array(
834 - 'title' => __( 'Published', 'gutenberg' ),
835 - 'slug' => 'published',
836 - 'view' => array(
837 - 'filters' => array(
838 - array(
839 - 'field' => 'status',
840 - 'operator' => 'isAny',
841 - 'value' => 'publish',
842 - 'isLocked' => true,
843 - ),
844 - ),
845 - ),
846 - ),
847 - array(
848 - 'title' => __( 'Scheduled', 'gutenberg' ),
849 - 'slug' => 'future',
850 - 'view' => array(
851 - 'filters' => array(
852 - array(
853 - 'field' => 'status',
854 - 'operator' => 'isAny',
855 - 'value' => 'future',
856 - 'isLocked' => true,
857 - ),
858 - ),
859 - ),
860 - ),
861 - array(
862 - 'title' => __( 'Drafts', 'gutenberg' ),
863 - 'slug' => 'drafts',
864 - 'view' => array(
865 - 'filters' => array(
866 - array(
867 - 'field' => 'status',
868 - 'operator' => 'isAny',
869 - 'value' => 'draft',
870 - 'isLocked' => true,
871 - ),
872 - ),
873 - ),
874 - ),
875 - array(
876 - 'title' => __( 'Pending', 'gutenberg' ),
877 - 'slug' => 'pending',
878 - 'view' => array(
879 - 'filters' => array(
880 - array(
881 - 'field' => 'status',
882 - 'operator' => 'isAny',
883 - 'value' => 'pending',
884 - 'isLocked' => true,
885 - ),
886 - ),
887 - ),
888 - ),
889 - array(
890 - 'title' => __( 'Private', 'gutenberg' ),
891 - 'slug' => 'private',
892 - 'view' => array(
893 - 'filters' => array(
894 - array(
895 - 'field' => 'status',
896 - 'operator' => 'isAny',
897 - 'value' => 'private',
898 - 'isLocked' => true,
899 - ),
900 - ),
901 - ),
902 - ),
903 - array(
904 - 'title' => __( 'Trash', 'gutenberg' ),
905 - 'slug' => 'trash',
906 - 'view' => array(
907 - 'type' => 'table',
908 - 'layout' => $default_layouts['table']['layout'],
909 - 'filters' => array(
910 - array(
911 - 'field' => 'status',
912 - 'operator' => 'isAny',
913 - 'value' => 'trash',
914 - 'isLocked' => true,
915 - ),
916 - ),
917 - ),
918 - ),
919 - );
920 - }
921 -
922 - private function get_default_layouts_for_wp_block() {
923 - return array(
924 - 'table' => array(
925 - 'layout' => array(
926 - 'styles' => array(
927 - 'author' => array(
928 - 'width' => '1%',
929 - ),
930 - ),
931 - ),
932 - ),
933 - 'grid' => array(
934 - 'layout' => array(
935 - 'badgeFields' => array( 'sync-status' ),
936 - ),
937 - ),
938 - );
939 - }
940 -
941 - private function get_default_view_for_wp_block( $default_layouts ) {
942 - return array(
943 - 'type' => 'grid',
944 - 'perPage' => 20,
945 - 'titleField' => 'title',
946 - 'mediaField' => 'preview',
947 - 'fields' => array( 'sync-status' ),
948 - 'filters' => array(),
949 - 'layout' => $default_layouts['grid']['layout'],
950 - );
951 - }
952 -
953 - private function get_default_layouts_for_wp_template_part() {
954 - return array(
955 - 'table' => array(
956 - 'layout' => array(
957 - 'styles' => array(
958 - 'author' => array(
959 - 'width' => '1%',
960 - ),
961 - ),
962 - ),
963 - ),
964 - 'grid' => array(
965 - 'layout' => array(),
966 - ),
967 - );
968 - }
969 -
970 - private function get_default_view_for_wp_template_part( $default_layouts ) {
971 - return array(
972 - 'type' => 'grid',
973 - 'perPage' => 20,
974 - 'titleField' => 'title',
975 - 'mediaField' => 'preview',
976 - 'fields' => array( 'author' ),
977 - 'filters' => array(),
978 - 'layout' => $default_layouts['grid']['layout'],
979 - );
980 - }
981 -
982 - /**
983 - * Returns the original source of a template.
984 - *
985 - * @param WP_Block_Template $template_object Template instance.
986 - * @return string The original source ('theme', 'plugin', 'site', or 'user').
987 - */
988 - private static function get_wp_templates_original_source_field( $template_object ) {
989 - if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
990 - /*
991 - * Added by theme.
992 - * Template originally provided by a theme, but customized by a user.
993 - * Templates originally didn't have the 'origin' field so identify
994 - * older customized templates by checking for no origin and a 'theme'
995 - * or 'custom' source.
996 - */
997 - if ( $template_object->has_theme_file &&
998 - ( 'theme' === $template_object->origin || (
999 - empty( $template_object->origin ) && in_array(
1000 - $template_object->source,
1001 - array(
1002 - 'theme',
1003 - 'custom',
1004 - ),
1005 - true
1006 - ) )
1007 - )
1008 - ) {
1009 - return 'theme';
1010 - }
1011 -
1012 - // Added by plugin.
1013 - if ( 'plugin' === $template_object->origin ) {
1014 - return 'plugin';
1015 - }
1016 -
1017 - /*
1018 - * Added by site.
1019 - * Template was created from scratch, but has no author. Author support
1020 - * was only added to templates in WordPress 5.9. Fallback to showing the
1021 - * site logo and title.
1022 - */
1023 - if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
1024 - return 'site';
1025 - }
1026 - }
1027 -
1028 - // Added by user.
1029 - return 'user';
1030 - }
1031 -
1032 - /**
1033 - * Returns a human readable text for the author of a template.
1034 - *
1035 - * @param WP_Block_Template $template_object Template instance.
1036 - * @return string Human readable text for the author.
1037 - */
1038 - private static function get_wp_templates_author_text_field( $template_object ) {
1039 - $original_source = self::get_wp_templates_original_source_field( $template_object );
1040 - switch ( $original_source ) {
1041 - case 'theme':
1042 - $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
1043 - return empty( $theme_name ) ? $template_object->theme : $theme_name;
1044 - case 'plugin':
1045 - if ( ! function_exists( 'get_plugins' ) ) {
1046 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
1047 - }
1048 - if ( isset( $template_object->plugin ) ) {
1049 - $plugins = wp_get_active_and_valid_plugins();
1050 -
1051 - foreach ( $plugins as $plugin_file ) {
1052 - $plugin_basename = plugin_basename( $plugin_file );
1053 - list( $plugin_slug, ) = explode( '/', $plugin_basename );
1054 -
1055 - if ( $plugin_slug === $template_object->plugin ) {
1056 - $plugin_data = get_plugin_data( $plugin_file );
1057 -
1058 - if ( ! empty( $plugin_data['Name'] ) ) {
1059 - return $plugin_data['Name'];
1060 - }
1061 -
1062 - break;
1063 - }
1064 - }
1065 - }
1066 -
1067 - /*
1068 - * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
1069 - * compatibility with templates that were registered before the plugin attribute was added.
1070 - */
1071 - $plugins = get_plugins();
1072 - $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
1073 - if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
1074 - return $plugins[ $plugin_basename ]['Name'];
1075 - }
1076 - return $template_object->plugin ?? $template_object->theme;
1077 - case 'site':
1078 - return get_bloginfo( 'name' );
1079 - case 'user':
1080 - $author = get_user_by( 'id', $template_object->author );
1081 - if ( ! $author ) {
1082 - return __( 'Unknown author', 'gutenberg' );
1083 - }
1084 - return $author->get( 'display_name' );
1085 - }
1086 -
1087 - // Fail-safe to return a string should the original source ever fall through.
1088 - return '';
1089 - }
1090 -
1091 - /**
1092 - * Returns the view list for the wp_template_part post type.
1093 - *
1094 - * Builds entries from the registered template part areas (header, footer, etc.).
1095 - *
1096 - * @return array View list entries.
1097 - */
1098 - private function get_view_list_for_wp_template_part() {
1099 - $view_list = array(
1100 - array(
1101 - 'title' => __( 'All template parts', 'gutenberg' ),
1102 - 'slug' => 'all-parts',
1103 - ),
1104 - );
1105 -
1106 - $areas = get_allowed_block_template_part_areas();
1107 -
1108 - // Ensure default areas appear in a consistent order.
1109 - $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' );
1110 - $ordered_areas = array();
1111 - $remaining_areas = array();
1112 - foreach ( $areas as $area ) {
1113 - $position = array_search( $area['area'], $preferred_order, true );
1114 - if ( false !== $position ) {
1115 - $ordered_areas[ $position ] = $area;
1116 - } else {
1117 - $remaining_areas[] = $area;
1118 - }
1119 - }
1120 - ksort( $ordered_areas );
1121 - $areas = array_merge( array_values( $ordered_areas ), $remaining_areas );
1122 -
1123 - foreach ( $areas as $area ) {
1124 - $view_list[] = array(
1125 - 'title' => $area['label'],
1126 - 'slug' => $area['area'],
1127 - 'view' => array(
1128 - 'filters' => array(
1129 - array(
1130 - 'field' => 'area',
1131 - 'operator' => 'is',
1132 - 'value' => $area['area'],
1133 - 'isLocked' => true,
1134 - ),
1135 - ),
1136 - ),
1137 - );
1138 - }
1139 -
1140 - return $view_list;
1141 - }
1142 -
1143 - /**
1144 - * Returns the view list for the wp_block (patterns) post type.
1145 - *
1146 - * Builds entries from registered block pattern categories and user pattern categories.
1147 - *
1148 - * @return array View list entries.
1149 - */
1150 - private function get_view_list_for_wp_block() {
1151 - $view_list = array(
1152 - array(
1153 - 'title' => __( 'All patterns', 'gutenberg' ),
1154 - 'slug' => 'all-patterns',
1155 - ),
1156 - array(
1157 - 'title' => __( 'My patterns', 'gutenberg' ),
1158 - 'slug' => 'my-patterns',
1159 - ),
1160 - );
1161 -
1162 - // Gather categories from the block pattern categories registry.
1163 - $registry = WP_Block_Pattern_Categories_Registry::get_instance();
1164 - $categories = array();
1165 -
1166 - foreach ( $registry->get_all_registered() as $category ) {
1167 - $categories[ $category['name'] ] = $category['label'];
1168 - }
1169 -
1170 - // Ensure "Uncategorized" is always included for patterns
1171 - // that have no category assigned.
1172 - $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' );
1173 -
1174 - // Also gather user-created pattern categories (wp_pattern_category taxonomy).
1175 - $user_terms = get_terms(
1176 - array(
1177 - 'taxonomy' => 'wp_pattern_category',
1178 - 'hide_empty' => false,
1179 - )
1180 - );
1181 -
1182 - if ( ! is_wp_error( $user_terms ) ) {
1183 - foreach ( $user_terms as $term ) {
1184 - $categories[ $term->slug ] = $term->name;
1185 - }
1186 - }
1187 -
1188 - // Sort categories alphabetically by label.
1189 - asort( $categories, SORT_NATURAL | SORT_FLAG_CASE );
1190 -
1191 - foreach ( $categories as $name => $label ) {
1192 - $view_list[] = array(
1193 - 'title' => $label,
1194 - 'slug' => $name,
1195 - );
1196 - }
1197 -
1198 - return $view_list;
1199 - }
1200 -
1201 - private function get_default_view_for_wp_template() {
1202 - return array(
1203 - 'type' => 'grid',
1204 - 'perPage' => 20,
1205 - 'sort' => array(
1206 - 'field' => 'title',
1207 - 'direction' => 'asc',
1208 - ),
1209 - 'titleField' => 'title',
1210 - 'descriptionField' => 'description',
1211 - 'mediaField' => 'preview',
1212 - 'fields' => array( 'author', 'active', 'slug', 'theme' ),
1213 - 'filters' => array(),
1214 - 'showMedia' => true,
1215 - );
1216 - }
1217 -
1218 - private function get_default_layouts_for_wp_template() {
1219 - return array(
1220 - 'table' => array( 'showMedia' => false ),
1221 - 'grid' => array( 'showMedia' => true ),
1222 - 'list' => array( 'showMedia' => false ),
1223 - );
1224 - }
1225 -
1226 - private function get_view_list_for_wp_template() {
1227 - $view_list = array(
1228 - array(
1229 - 'title' => __( 'All templates', 'gutenberg' ),
1230 - 'slug' => 'all',
1231 - ),
1232 - );
1233 -
1234 - $templates = get_block_templates( array(), 'wp_template' );
1235 -
1236 - // Collect unique authors, tracking whether they come from a registered
1237 - // source (theme, plugin, site) so we can sort those before user ones.
1238 - $seen_authors = array();
1239 - $registered_authors = array();
1240 - $user_authors = array();
1241 - foreach ( $templates as $template ) {
1242 - $original_source = self::get_wp_templates_original_source_field( $template );
1243 - $author_text = self::get_wp_templates_author_text_field( $template );
1244 - if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
1245 - $seen_authors[ $author_text ] = true;
1246 - $entry = array(
1247 - 'title' => $author_text,
1248 - 'slug' => $author_text,
1249 - 'view' => array(
1250 - 'filters' => array(
1251 - array(
1252 - 'field' => 'author',
1253 - 'operator' => 'is',
1254 - 'value' => $author_text,
1255 - 'isLocked' => true,
1256 - ),
1257 - ),
1258 - ),
1259 - );
1260 - if ( 'user' === $original_source ) {
1261 - $user_authors[] = $entry;
1262 - } else {
1263 - $registered_authors[] = $entry;
1264 - }
1265 - }
1266 - }
1267 -
1268 - $view_list = array_merge( $view_list, $registered_authors, $user_authors );
1269 -
1270 - return $view_list;
1271 810 }
1272 811 }