diff --git a/backport-changelog/7.1/11272.md b/backport-changelog/7.1/11272.md index a5a1ddab991ad0..d7be06f1f1251c 100644 --- a/backport-changelog/7.1/11272.md +++ b/backport-changelog/7.1/11272.md @@ -7,3 +7,4 @@ https://github.com/WordPress/wordpress-develop/pull/11272 * https://github.com/WordPress/gutenberg/pull/76953 * https://github.com/WordPress/gutenberg/pull/76903 * https://github.com/WordPress/gutenberg/pull/77290 +* https://github.com/WordPress/gutenberg/pull/78977 diff --git a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php index 551bc3927a3ce5..578d9ecb4135cc 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php @@ -82,65 +82,15 @@ public function get_items( $request ) { $kind = $request->get_param( 'kind' ); $name = $request->get_param( 'name' ); - // TODO: this data will come from a registry of view configs per entity. - $form = array(); - $default_view = array( - 'type' => 'table', - 'filters' => array(), - 'perPage' => 20, - 'sort' => array( - 'field' => 'title', - 'direction' => 'asc', - ), - 'titleField' => 'title', - 'fields' => array( 'author', 'status' ), - ); - $default_layouts = array( - 'table' => array(), - 'grid' => array(), - 'list' => array(), - ); - $all_items_title = __( 'All items', 'gutenberg' ); - if ( 'postType' === $kind ) { - $post_type_object = get_post_type_object( $name ); - if ( $post_type_object && ! empty( $post_type_object->labels->all_items ) ) { - $all_items_title = $post_type_object->labels->all_items; - } - } - $view_list = array( - array( - 'title' => $all_items_title, - 'slug' => 'all', - ), - ); - if ( 'postType' === $kind && 'page' === $name ) { - $default_layouts = $this->get_default_layouts_for_page(); - $default_view = $this->get_default_view_for_page(); - $view_list = $this->get_view_list_for_page( $all_items_title, $default_layouts ); - $form = $this->get_form_for_page(); - } elseif ( 'postType' === $kind && 'post' === $name ) { - $form = $this->get_form_for_page(); - } elseif ( 'postType' === $kind && 'wp_block' === $name ) { - $default_layouts = $this->get_default_layouts_for_wp_block(); - $default_view = $this->get_default_view_for_wp_block( $default_layouts ); - $view_list = $this->get_view_list_for_wp_block(); - } elseif ( 'postType' === $kind && 'wp_template_part' === $name ) { - $default_layouts = $this->get_default_layouts_for_wp_template_part(); - $default_view = $this->get_default_view_for_wp_template_part( $default_layouts ); - $view_list = $this->get_view_list_for_wp_template_part(); - } elseif ( 'postType' === $kind && 'wp_template' === $name ) { - $default_view = $this->get_default_view_for_wp_template(); - $default_layouts = $this->get_default_layouts_for_wp_template(); - $view_list = $this->get_view_list_for_wp_template(); - } + $config = gutenberg_get_entity_view_config( $kind, $name ); $response = array( 'kind' => $kind, 'name' => $name, - 'default_view' => $default_view, - 'default_layouts' => $default_layouts, - 'view_list' => $view_list, - 'form' => $form, + 'default_view' => $config['default_view'], + 'default_layouts' => $config['default_layouts'], + 'view_list' => $config['view_list'], + 'form' => $config['form'], ); return rest_ensure_response( $response ); @@ -726,547 +676,4 @@ private function get_form_schema() { ), ); } - - private function get_default_view_for_page() { - return array( - 'type' => 'list', - 'filters' => array(), - 'perPage' => 20, - 'sort' => array( - 'field' => 'title', - 'direction' => 'asc', - ), - 'showLevels' => true, - 'titleField' => 'title', - 'mediaField' => 'featured_media', - 'fields' => array( 'author', 'status' ), - ); - } - - private function get_default_layouts_for_page() { - return array( - 'table' => array( - 'layout' => array( - 'styles' => array( - 'author' => array( - 'align' => 'start', - ), - ), - ), - ), - 'grid' => array(), - 'list' => array(), - ); - } - - private function get_form_for_page() { - return array( - 'layout' => array( 'type' => 'panel' ), - 'fields' => array( - array( - 'id' => 'featured_media', - 'layout' => array( - 'type' => 'regular', - 'labelPosition' => 'none', - ), - ), - array( - 'id' => 'post-content-info', - 'layout' => array( - 'type' => 'regular', - 'labelPosition' => 'none', - ), - ), - array( - 'id' => 'excerpt', - 'layout' => array( - 'type' => 'panel', - 'labelPosition' => 'top', - ), - ), - array( - 'id' => 'status', - 'label' => __( 'Status', 'gutenberg' ), - 'children' => array( - array( - 'id' => 'status', - 'layout' => array( - 'type' => 'regular', - 'labelPosition' => 'none', - ), - ), - 'scheduled_date', - 'password', - 'sticky', - ), - ), - 'date', - 'slug', - 'author', - 'template', - array( - 'id' => 'discussion', - 'label' => __( 'Discussion', 'gutenberg' ), - 'children' => array( - array( - 'id' => 'comment_status', - 'layout' => array( - 'type' => 'regular', - 'labelPosition' => 'none', - ), - ), - 'ping_status', - ), - ), - 'parent', - 'format', - ), - ); - } - - private function get_view_list_for_page( $all_items_title, $default_layouts ) { - return array( - array( - 'title' => $all_items_title, - 'slug' => 'all', - ), - array( - 'title' => __( 'Published', 'gutenberg' ), - 'slug' => 'published', - 'view' => array( - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'publish', - 'isLocked' => true, - ), - ), - ), - ), - array( - 'title' => __( 'Scheduled', 'gutenberg' ), - 'slug' => 'future', - 'view' => array( - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'future', - 'isLocked' => true, - ), - ), - ), - ), - array( - 'title' => __( 'Drafts', 'gutenberg' ), - 'slug' => 'drafts', - 'view' => array( - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'draft', - 'isLocked' => true, - ), - ), - ), - ), - array( - 'title' => __( 'Pending', 'gutenberg' ), - 'slug' => 'pending', - 'view' => array( - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'pending', - 'isLocked' => true, - ), - ), - ), - ), - array( - 'title' => __( 'Private', 'gutenberg' ), - 'slug' => 'private', - 'view' => array( - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'private', - 'isLocked' => true, - ), - ), - ), - ), - array( - 'title' => __( 'Trash', 'gutenberg' ), - 'slug' => 'trash', - 'view' => array( - 'type' => 'table', - 'layout' => $default_layouts['table']['layout'], - 'filters' => array( - array( - 'field' => 'status', - 'operator' => 'isAny', - 'value' => 'trash', - 'isLocked' => true, - ), - ), - ), - ), - ); - } - - private function get_default_layouts_for_wp_block() { - return array( - 'table' => array( - 'layout' => array( - 'styles' => array( - 'author' => array( - 'width' => '1%', - ), - ), - ), - ), - 'grid' => array( - 'layout' => array( - 'badgeFields' => array( 'sync-status' ), - ), - ), - ); - } - - private function get_default_view_for_wp_block( $default_layouts ) { - return array( - 'type' => 'grid', - 'perPage' => 20, - 'titleField' => 'title', - 'mediaField' => 'preview', - 'fields' => array( 'sync-status' ), - 'filters' => array(), - 'layout' => $default_layouts['grid']['layout'], - ); - } - - private function get_default_layouts_for_wp_template_part() { - return array( - 'table' => array( - 'layout' => array( - 'styles' => array( - 'author' => array( - 'width' => '1%', - ), - ), - ), - ), - 'grid' => array( - 'layout' => array(), - ), - ); - } - - private function get_default_view_for_wp_template_part( $default_layouts ) { - return array( - 'type' => 'grid', - 'perPage' => 20, - 'titleField' => 'title', - 'mediaField' => 'preview', - 'fields' => array( 'author' ), - 'filters' => array(), - 'layout' => $default_layouts['grid']['layout'], - ); - } - - /** - * Returns the original source of a template. - * - * @param WP_Block_Template $template_object Template instance. - * @return string The original source ('theme', 'plugin', 'site', or 'user'). - */ - private static function get_wp_templates_original_source_field( $template_object ) { - if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { - /* - * Added by theme. - * Template originally provided by a theme, but customized by a user. - * Templates originally didn't have the 'origin' field so identify - * older customized templates by checking for no origin and a 'theme' - * or 'custom' source. - */ - if ( $template_object->has_theme_file && - ( 'theme' === $template_object->origin || ( - empty( $template_object->origin ) && in_array( - $template_object->source, - array( - 'theme', - 'custom', - ), - true - ) ) - ) - ) { - return 'theme'; - } - - // Added by plugin. - if ( 'plugin' === $template_object->origin ) { - return 'plugin'; - } - - /* - * Added by site. - * Template was created from scratch, but has no author. Author support - * was only added to templates in WordPress 5.9. Fallback to showing the - * site logo and title. - */ - if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { - return 'site'; - } - } - - // Added by user. - return 'user'; - } - - /** - * Returns a human readable text for the author of a template. - * - * @param WP_Block_Template $template_object Template instance. - * @return string Human readable text for the author. - */ - private static function get_wp_templates_author_text_field( $template_object ) { - $original_source = self::get_wp_templates_original_source_field( $template_object ); - switch ( $original_source ) { - case 'theme': - $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); - return empty( $theme_name ) ? $template_object->theme : $theme_name; - case 'plugin': - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - if ( isset( $template_object->plugin ) ) { - $plugins = wp_get_active_and_valid_plugins(); - - foreach ( $plugins as $plugin_file ) { - $plugin_basename = plugin_basename( $plugin_file ); - list( $plugin_slug, ) = explode( '/', $plugin_basename ); - - if ( $plugin_slug === $template_object->plugin ) { - $plugin_data = get_plugin_data( $plugin_file ); - - if ( ! empty( $plugin_data['Name'] ) ) { - return $plugin_data['Name']; - } - - break; - } - } - } - - /* - * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards - * compatibility with templates that were registered before the plugin attribute was added. - */ - $plugins = get_plugins(); - $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); - if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { - return $plugins[ $plugin_basename ]['Name']; - } - return $template_object->plugin ?? $template_object->theme; - case 'site': - return get_bloginfo( 'name' ); - case 'user': - $author = get_user_by( 'id', $template_object->author ); - if ( ! $author ) { - return __( 'Unknown author', 'gutenberg' ); - } - return $author->get( 'display_name' ); - } - - // Fail-safe to return a string should the original source ever fall through. - return ''; - } - - /** - * Returns the view list for the wp_template_part post type. - * - * Builds entries from the registered template part areas (header, footer, etc.). - * - * @return array View list entries. - */ - private function get_view_list_for_wp_template_part() { - $view_list = array( - array( - 'title' => __( 'All template parts', 'gutenberg' ), - 'slug' => 'all-parts', - ), - ); - - $areas = get_allowed_block_template_part_areas(); - - // Ensure default areas appear in a consistent order. - $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' ); - $ordered_areas = array(); - $remaining_areas = array(); - foreach ( $areas as $area ) { - $position = array_search( $area['area'], $preferred_order, true ); - if ( false !== $position ) { - $ordered_areas[ $position ] = $area; - } else { - $remaining_areas[] = $area; - } - } - ksort( $ordered_areas ); - $areas = array_merge( array_values( $ordered_areas ), $remaining_areas ); - - foreach ( $areas as $area ) { - $view_list[] = array( - 'title' => $area['label'], - 'slug' => $area['area'], - 'view' => array( - 'filters' => array( - array( - 'field' => 'area', - 'operator' => 'is', - 'value' => $area['area'], - 'isLocked' => true, - ), - ), - ), - ); - } - - return $view_list; - } - - /** - * Returns the view list for the wp_block (patterns) post type. - * - * Builds entries from registered block pattern categories and user pattern categories. - * - * @return array View list entries. - */ - private function get_view_list_for_wp_block() { - $view_list = array( - array( - 'title' => __( 'All patterns', 'gutenberg' ), - 'slug' => 'all-patterns', - ), - array( - 'title' => __( 'My patterns', 'gutenberg' ), - 'slug' => 'my-patterns', - ), - ); - - // Gather categories from the block pattern categories registry. - $registry = WP_Block_Pattern_Categories_Registry::get_instance(); - $categories = array(); - - foreach ( $registry->get_all_registered() as $category ) { - $categories[ $category['name'] ] = $category['label']; - } - - // Ensure "Uncategorized" is always included for patterns - // that have no category assigned. - $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' ); - - // Also gather user-created pattern categories (wp_pattern_category taxonomy). - $user_terms = get_terms( - array( - 'taxonomy' => 'wp_pattern_category', - 'hide_empty' => false, - ) - ); - - if ( ! is_wp_error( $user_terms ) ) { - foreach ( $user_terms as $term ) { - $categories[ $term->slug ] = $term->name; - } - } - - // Sort categories alphabetically by label. - asort( $categories, SORT_NATURAL | SORT_FLAG_CASE ); - - foreach ( $categories as $name => $label ) { - $view_list[] = array( - 'title' => $label, - 'slug' => $name, - ); - } - - return $view_list; - } - - private function get_default_view_for_wp_template() { - return array( - 'type' => 'grid', - 'perPage' => 20, - 'sort' => array( - 'field' => 'title', - 'direction' => 'asc', - ), - 'titleField' => 'title', - 'descriptionField' => 'description', - 'mediaField' => 'preview', - 'fields' => array( 'author', 'active', 'slug', 'theme' ), - 'filters' => array(), - 'showMedia' => true, - ); - } - - private function get_default_layouts_for_wp_template() { - return array( - 'table' => array( 'showMedia' => false ), - 'grid' => array( 'showMedia' => true ), - 'list' => array( 'showMedia' => false ), - ); - } - - private function get_view_list_for_wp_template() { - $view_list = array( - array( - 'title' => __( 'All templates', 'gutenberg' ), - 'slug' => 'all', - ), - ); - - $templates = get_block_templates( array(), 'wp_template' ); - - // Collect unique authors, tracking whether they come from a registered - // source (theme, plugin, site) so we can sort those before user ones. - $seen_authors = array(); - $registered_authors = array(); - $user_authors = array(); - foreach ( $templates as $template ) { - $original_source = self::get_wp_templates_original_source_field( $template ); - $author_text = self::get_wp_templates_author_text_field( $template ); - if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) { - $seen_authors[ $author_text ] = true; - $entry = array( - 'title' => $author_text, - 'slug' => $author_text, - 'view' => array( - 'filters' => array( - array( - 'field' => 'author', - 'operator' => 'is', - 'value' => $author_text, - 'isLocked' => true, - ), - ), - ), - ); - if ( 'user' === $original_source ) { - $user_authors[] = $entry; - } else { - $registered_authors[] = $entry; - } - } - } - - $view_list = array_merge( $view_list, $registered_authors, $user_authors ); - - return $view_list; - } } diff --git a/lib/compat/wordpress-7.1/view-config-api.php b/lib/compat/wordpress-7.1/view-config-api.php new file mode 100644 index 00000000000000..e58c803471b5b1 --- /dev/null +++ b/lib/compat/wordpress-7.1/view-config-api.php @@ -0,0 +1,737 @@ + 'table', + 'filters' => array(), + 'perPage' => 20, + 'sort' => array( + 'field' => 'title', + 'direction' => 'asc', + ), + 'titleField' => 'title', + 'fields' => array( 'author', 'status' ), + ); + $default_layouts = array( + 'table' => array(), + 'grid' => array(), + 'list' => array(), + ); + $all_items_title = __( 'All items', 'gutenberg' ); + if ( 'postType' === $kind ) { + $post_type_object = get_post_type_object( $name ); + if ( $post_type_object && ! empty( $post_type_object->labels->all_items ) ) { + $all_items_title = $post_type_object->labels->all_items; + } + } + $view_list = array( + array( + 'title' => $all_items_title, + 'slug' => 'all', + ), + ); + + $config = array( + 'default_view' => $default_view, + 'default_layouts' => $default_layouts, + 'view_list' => $view_list, + 'form' => array(), + ); + + /** + * Filters the view configuration for a given entity. + * + * The dynamic portions of the hook name, `$kind` and `$name`, refer to the + * entity kind (e.g. `postType`) and the entity name (e.g. `page`). + * + * @param array $config { + * The view configuration for the entity. + * + * @type array $default_view Default view configuration. + * @type array $default_layouts Default layouts configuration. + * @type array $view_list List of available views. + * @type array $form Form configuration. + * } + * @param array $entity { + * The entity the configuration is built for. + * + * @type string $kind The entity kind. + * @type string $name The entity name. + * } + */ + $filtered_config = apply_filters( + "get_entity_view_config_{$kind}_{$name}", + $config, + array( + 'kind' => $kind, + 'name' => $name, + ) + ); + + if ( ! is_array( $filtered_config ) ) { + return $config; + } + + // Backfill any dropped keys with their defaults, then discard any keys the + // filter introduced that are not part of the documented configuration shape. + $filtered_config = array_merge( $config, $filtered_config ); + return array_intersect_key( $filtered_config, $config ); +} + +/** + * Provides the view configuration for the `page` post type. + * + * @param array $config { + * The view configuration for the entity. + * } + * @return array The filtered view configuration. + */ +function _gutenberg_get_entity_view_config_post_type_page( $config ) { + $config['default_layouts'] = array( + 'table' => array( + 'layout' => array( + 'styles' => array( + 'author' => array( + 'align' => 'start', + ), + ), + ), + ), + 'grid' => array(), + 'list' => array(), + ); + + $config['default_view'] = array( + 'type' => 'list', + 'filters' => array(), + 'perPage' => 20, + 'sort' => array( + 'field' => 'title', + 'direction' => 'asc', + ), + 'showLevels' => true, + 'titleField' => 'title', + 'mediaField' => 'featured_media', + 'fields' => array( 'author', 'status' ), + ); + + $config['view_list'] = array( + // Reuse the base "all items" view, whose title is derived from the post + // type's `all_items` label in gutenberg_get_entity_view_config(). + $config['view_list'][0], + array( + 'title' => __( 'Published', 'gutenberg' ), + 'slug' => 'published', + 'view' => array( + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'publish', + 'isLocked' => true, + ), + ), + ), + ), + array( + 'title' => __( 'Scheduled', 'gutenberg' ), + 'slug' => 'future', + 'view' => array( + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'future', + 'isLocked' => true, + ), + ), + ), + ), + array( + 'title' => __( 'Drafts', 'gutenberg' ), + 'slug' => 'drafts', + 'view' => array( + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'draft', + 'isLocked' => true, + ), + ), + ), + ), + array( + 'title' => __( 'Pending', 'gutenberg' ), + 'slug' => 'pending', + 'view' => array( + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'pending', + 'isLocked' => true, + ), + ), + ), + ), + array( + 'title' => __( 'Private', 'gutenberg' ), + 'slug' => 'private', + 'view' => array( + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'private', + 'isLocked' => true, + ), + ), + ), + ), + array( + 'title' => __( 'Trash', 'gutenberg' ), + 'slug' => 'trash', + 'view' => array( + 'type' => 'table', + 'layout' => $config['default_layouts']['table']['layout'], + 'filters' => array( + array( + 'field' => 'status', + 'operator' => 'isAny', + 'value' => 'trash', + 'isLocked' => true, + ), + ), + ), + ), + ); + + $config['form'] = array( + 'layout' => array( 'type' => 'panel' ), + 'fields' => array( + array( + 'id' => 'featured_media', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + array( + 'id' => 'post-content-info', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + array( + 'id' => 'excerpt', + 'layout' => array( + 'type' => 'panel', + 'labelPosition' => 'top', + ), + ), + array( + 'id' => 'status', + 'label' => __( 'Status', 'gutenberg' ), + 'children' => array( + array( + 'id' => 'status', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + 'scheduled_date', + 'password', + 'sticky', + ), + ), + 'date', + 'slug', + 'author', + 'template', + array( + 'id' => 'discussion', + 'label' => __( 'Discussion', 'gutenberg' ), + 'children' => array( + array( + 'id' => 'comment_status', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + 'ping_status', + ), + ), + 'parent', + 'format', + ), + ); + + return $config; +} +if ( has_filter( 'get_entity_view_config_postType_page', '_wp_get_entity_view_config_post_type_page' ) ) { + remove_filter( 'get_entity_view_config_postType_page', '_wp_get_entity_view_config_post_type_page' ); +} +add_filter( 'get_entity_view_config_postType_page', '_gutenberg_get_entity_view_config_post_type_page', 10, 1 ); + +/** + * Provides the view configuration for the `post` post type. + * + * @param array $config { + * The view configuration for the entity. + * } + * @return array The filtered view configuration. + */ +function _gutenberg_get_entity_view_config_post_type_post( $config ) { + $config['form'] = array( + 'layout' => array( 'type' => 'panel' ), + 'fields' => array( + array( + 'id' => 'featured_media', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + array( + 'id' => 'post-content-info', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + array( + 'id' => 'excerpt', + 'layout' => array( + 'type' => 'panel', + 'labelPosition' => 'top', + ), + ), + array( + 'id' => 'status', + 'label' => __( 'Status', 'gutenberg' ), + 'children' => array( + array( + 'id' => 'status', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + 'scheduled_date', + 'password', + 'sticky', + ), + ), + 'date', + 'slug', + 'author', + 'template', + array( + 'id' => 'discussion', + 'label' => __( 'Discussion', 'gutenberg' ), + 'children' => array( + array( + 'id' => 'comment_status', + 'layout' => array( + 'type' => 'regular', + 'labelPosition' => 'none', + ), + ), + 'ping_status', + ), + ), + 'parent', + 'format', + ), + ); + + return $config; +} +if ( has_filter( 'get_entity_view_config_postType_post', '_wp_get_entity_view_config_post_type_post' ) ) { + remove_filter( 'get_entity_view_config_postType_post', '_wp_get_entity_view_config_post_type_post' ); +} +add_filter( 'get_entity_view_config_postType_post', '_gutenberg_get_entity_view_config_post_type_post', 10, 1 ); + +/** + * Provides the view configuration for the `wp_block` post type. + * + * @param array $config { + * The view configuration for the entity. + * } + * @return array The filtered view configuration. + */ +function _gutenberg_get_entity_view_config_post_type_wp_block( $config ) { + $config['default_layouts'] = array( + 'table' => array( + 'layout' => array( + 'styles' => array( + 'author' => array( + 'width' => '1%', + ), + ), + ), + ), + 'grid' => array( + 'layout' => array( + 'badgeFields' => array( 'sync-status' ), + ), + ), + ); + + $config['default_view'] = array( + 'type' => 'grid', + 'perPage' => 20, + 'titleField' => 'title', + 'mediaField' => 'preview', + 'fields' => array( 'sync-status' ), + 'filters' => array(), + 'layout' => $config['default_layouts']['grid']['layout'], + ); + + $view_list = array( + array( + 'title' => __( 'All patterns', 'gutenberg' ), + 'slug' => 'all-patterns', + ), + array( + 'title' => __( 'My patterns', 'gutenberg' ), + 'slug' => 'my-patterns', + ), + ); + + // Gather categories from the block pattern categories registry. + $registry = WP_Block_Pattern_Categories_Registry::get_instance(); + $categories = array(); + + foreach ( $registry->get_all_registered() as $category ) { + $categories[ $category['name'] ] = $category['label']; + } + + // Ensure "Uncategorized" is always included for patterns + // that have no category assigned. + $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' ); + + // Also gather user-created pattern categories (wp_pattern_category taxonomy). + $user_terms = get_terms( + array( + 'taxonomy' => 'wp_pattern_category', + 'hide_empty' => false, + ) + ); + + if ( ! is_wp_error( $user_terms ) ) { + foreach ( $user_terms as $term ) { + $categories[ $term->slug ] = $term->name; + } + } + + // Sort categories alphabetically by label. + asort( $categories, SORT_NATURAL | SORT_FLAG_CASE ); + + foreach ( $categories as $category_name => $label ) { + $view_list[] = array( + 'title' => $label, + 'slug' => $category_name, + ); + } + + $config['view_list'] = $view_list; + + return $config; +} +if ( has_filter( 'get_entity_view_config_postType_wp_block', '_wp_get_entity_view_config_post_type_wp_block' ) ) { + remove_filter( 'get_entity_view_config_postType_wp_block', '_wp_get_entity_view_config_post_type_wp_block' ); +} +add_filter( 'get_entity_view_config_postType_wp_block', '_gutenberg_get_entity_view_config_post_type_wp_block', 10, 1 ); + +/** + * Provides the view configuration for the `wp_template_part` post type. + * + * @param array $config { + * The view configuration for the entity. + * } + * @return array The filtered view configuration. + */ +function _gutenberg_get_entity_view_config_post_type_wp_template_part( $config ) { + $config['default_layouts'] = array( + 'table' => array( + 'layout' => array( + 'styles' => array( + 'author' => array( + 'width' => '1%', + ), + ), + ), + ), + 'grid' => array( + 'layout' => array(), + ), + ); + + $config['default_view'] = array( + 'type' => 'grid', + 'perPage' => 20, + 'titleField' => 'title', + 'mediaField' => 'preview', + 'fields' => array( 'author' ), + 'filters' => array(), + 'layout' => $config['default_layouts']['grid']['layout'], + ); + + $view_list = array( + array( + 'title' => __( 'All template parts', 'gutenberg' ), + 'slug' => 'all-parts', + ), + ); + + $areas = get_allowed_block_template_part_areas(); + + // Ensure default areas appear in a consistent order. + $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' ); + $ordered_areas = array(); + $remaining_areas = array(); + foreach ( $areas as $area ) { + $position = array_search( $area['area'], $preferred_order, true ); + if ( false !== $position ) { + $ordered_areas[ $position ] = $area; + } else { + $remaining_areas[] = $area; + } + } + ksort( $ordered_areas ); + $areas = array_merge( array_values( $ordered_areas ), $remaining_areas ); + + foreach ( $areas as $area ) { + $view_list[] = array( + 'title' => $area['label'], + 'slug' => $area['area'], + 'view' => array( + 'filters' => array( + array( + 'field' => 'area', + 'operator' => 'is', + 'value' => $area['area'], + 'isLocked' => true, + ), + ), + ), + ); + } + + $config['view_list'] = $view_list; + + return $config; +} +if ( has_filter( 'get_entity_view_config_postType_wp_template_part', '_wp_get_entity_view_config_post_type_wp_template_part' ) ) { + remove_filter( 'get_entity_view_config_postType_wp_template_part', '_wp_get_entity_view_config_post_type_wp_template_part' ); +} +add_filter( 'get_entity_view_config_postType_wp_template_part', '_gutenberg_get_entity_view_config_post_type_wp_template_part', 10, 1 ); + +/** + * Provides the view configuration for the `wp_template` post type. + * + * @param array $config { + * The view configuration for the entity. + * } + * @return array The filtered view configuration. + */ +function _gutenberg_get_entity_view_config_post_type_wp_template( $config ) { + $config['default_view'] = array( + 'type' => 'grid', + 'perPage' => 20, + 'sort' => array( + 'field' => 'title', + 'direction' => 'asc', + ), + 'titleField' => 'title', + 'descriptionField' => 'description', + 'mediaField' => 'preview', + 'fields' => array( 'author', 'active', 'slug', 'theme' ), + 'filters' => array(), + 'showMedia' => true, + ); + + $config['default_layouts'] = array( + 'table' => array( 'showMedia' => false ), + 'grid' => array( 'showMedia' => true ), + 'list' => array( 'showMedia' => false ), + ); + + $view_list = array( + array( + 'title' => __( 'All templates', 'gutenberg' ), + 'slug' => 'all', + ), + ); + + $templates = get_block_templates( array(), 'wp_template' ); + + // Collect unique authors, tracking whether they come from a registered + // source (theme, plugin, site) so we can sort those before user ones. + $seen_authors = array(); + $registered_authors = array(); + $user_authors = array(); + foreach ( $templates as $template ) { + /* + * Determine the original source of the template ('theme', 'plugin', + * 'site', or 'user'). + */ + $original_source = 'user'; + if ( 'wp_template' === $template->type || 'wp_template_part' === $template->type ) { + if ( $template->has_theme_file && + ( 'theme' === $template->origin || ( + empty( $template->origin ) && in_array( + $template->source, + array( + 'theme', + 'custom', + ), + true + ) ) + ) + ) { + /* + * Added by theme. + * Template originally provided by a theme, but customized by a user. + * Templates originally didn't have the 'origin' field so identify + * older customized templates by checking for no origin and a 'theme' + * or 'custom' source. + */ + $original_source = 'theme'; + } elseif ( 'plugin' === $template->origin ) { + // Added by plugin. + $original_source = 'plugin'; + } elseif ( empty( $template->has_theme_file ) && 'custom' === $template->source && empty( $template->author ) ) { + /* + * Added by site. + * Template was created from scratch, but has no author. Author support + * was only added to templates in WordPress 5.9. Fallback to showing the + * site logo and title. + */ + $original_source = 'site'; + } + } + + // Determine a human readable text for the author of the template. + $author_text = ''; + switch ( $original_source ) { + case 'theme': + $theme_name = wp_get_theme( $template->theme )->get( 'Name' ); + $author_text = empty( $theme_name ) ? $template->theme : $theme_name; + break; + case 'plugin': + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $plugin_name = ''; + if ( isset( $template->plugin ) ) { + $plugins = wp_get_active_and_valid_plugins(); + + foreach ( $plugins as $plugin_file ) { + $plugin_basename = plugin_basename( $plugin_file ); + list( $plugin_slug, ) = explode( '/', $plugin_basename ); + + if ( $plugin_slug === $template->plugin ) { + $plugin_data = get_plugin_data( $plugin_file ); + + if ( ! empty( $plugin_data['Name'] ) ) { + $plugin_name = $plugin_data['Name']; + } + + break; + } + } + } + + /* + * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards + * compatibility with templates that were registered before the plugin attribute was added. + */ + if ( '' === $plugin_name ) { + $plugins = get_plugins(); + $plugin_basename = plugin_basename( sanitize_text_field( $template->theme . '.php' ) ); + if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { + $plugin_name = $plugins[ $plugin_basename ]['Name']; + } else { + $plugin_name = $template->plugin ?? $template->theme; + } + } + $author_text = $plugin_name; + break; + case 'site': + $author_text = get_bloginfo( 'name' ); + break; + case 'user': + $author = get_user_by( 'id', $template->author ); + if ( ! $author ) { + $author_text = __( 'Unknown author', 'gutenberg' ); + } else { + $author_text = $author->get( 'display_name' ); + } + break; + } + + if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) { + $seen_authors[ $author_text ] = true; + $entry = array( + 'title' => $author_text, + 'slug' => $author_text, + 'view' => array( + 'filters' => array( + array( + 'field' => 'author', + 'operator' => 'is', + 'value' => $author_text, + 'isLocked' => true, + ), + ), + ), + ); + if ( 'user' === $original_source ) { + $user_authors[] = $entry; + } else { + $registered_authors[] = $entry; + } + } + } + + $config['view_list'] = array_merge( $view_list, $registered_authors, $user_authors ); + + return $config; +} +if ( has_filter( 'get_entity_view_config_postType_wp_template', '_wp_get_entity_view_config_post_type_wp_template' ) ) { + remove_filter( 'get_entity_view_config_postType_wp_template', '_wp_get_entity_view_config_post_type_wp_template' ); +} +add_filter( 'get_entity_view_config_postType_wp_template', '_gutenberg_get_entity_view_config_post_type_wp_template', 10, 1 ); diff --git a/lib/load.php b/lib/load.php index 6e537ca727f3a2..760e6ba5d6ea41 100644 --- a/lib/load.php +++ b/lib/load.php @@ -77,6 +77,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-7.0/global-styles.php'; // WordPress 7.1 compat. + require __DIR__ . '/compat/wordpress-7.1/view-config-api.php'; require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php'; require __DIR__ . '/compat/wordpress-7.1/rest-api.php'; require __DIR__ . '/compat/wordpress-7.1/collaboration.php';