From aeb2be60c494e1b607c8aa91429c667ec4291e13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 10:05:05 +0100
Subject: [PATCH 01/11] Site Editor > Templates: move view config to server
---
...enberg-rest-view-config-controller-7-1.php | 169 ++++++++++++++++++
.../components/page-templates/index-legacy.js | 25 +--
.../content-legacy.js | 86 ++++-----
.../site-editor-routes/templates.js | 19 +-
4 files changed, 231 insertions(+), 68 deletions(-)
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 35c88e46751142..6e54d12031ecfe 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
@@ -122,6 +122,83 @@ public function get_items( $request ) {
} 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 );
+ } elseif ( 'postType' === $kind && 'wp_template' === $name ) {
+ $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,
+ );
+ $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',
+ ),
+ );
+
+ // Add unique author-based items from registered templates.
+ $registered_templates = gutenberg_get_registered_block_templates( array() );
+ $seen_authors = array();
+ foreach ( $registered_templates as $template ) {
+ $original_source = self::get_template_original_source( $template );
+ $author_text = self::get_template_author_text( $template, $original_source );
+ if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
+ $seen_authors[ $author_text ] = true;
+ $view_list[] = array(
+ 'title' => $author_text,
+ 'slug' => $author_text,
+ 'icon' => $original_source,
+ 'view' => array(
+ 'filters' => array(
+ array(
+ 'field' => 'author',
+ 'operator' => 'is',
+ 'value' => $author_text,
+ 'isLocked' => true,
+ ),
+ ),
+ ),
+ );
+ }
+ }
+
+ // User-created DB templates.
+ $db_templates = get_block_templates( array(), 'wp_template' );
+ foreach ( $db_templates as $template ) {
+ $original_source = self::get_template_original_source( $template );
+ $author_text = self::get_template_author_text( $template, $original_source );
+ if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
+ $seen_authors[ $author_text ] = true;
+ $view_list[] = array(
+ 'title' => $author_text,
+ 'slug' => $author_text,
+ 'icon' => $original_source,
+ 'view' => array(
+ 'filters' => array(
+ array(
+ 'field' => 'author',
+ 'operator' => 'is',
+ 'value' => $author_text,
+ 'isLocked' => true,
+ ),
+ ),
+ ),
+ );
+ }
+ }
}
$response = array(
@@ -249,6 +326,9 @@ public function get_item_schema() {
'slug' => array(
'type' => 'string',
),
+ 'icon' => array(
+ 'type' => 'string',
+ ),
'view' => array(
'type' => 'object',
'properties' => array_merge(
@@ -675,4 +755,93 @@ private function get_default_view_for_wp_template_part( $default_layouts ) {
'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_template_original_source( $template_object ) {
+ if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
+ 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';
+ }
+
+ if ( 'plugin' === $template_object->origin ) {
+ return 'plugin';
+ }
+
+ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
+ return 'site';
+ }
+ }
+
+ return 'user';
+ }
+
+ /**
+ * Returns a human readable text for the author of a template.
+ *
+ * @param WP_Block_Template $template_object Template instance.
+ * @param string $original_source The original source of the template.
+ * @return string Human readable text for the author.
+ */
+ private static function get_template_author_text( $template_object, $original_source ) {
+ 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;
+ }
+ }
+ }
+
+ $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' );
+ }
+
+ return '';
+ }
}
diff --git a/packages/edit-site/src/components/page-templates/index-legacy.js b/packages/edit-site/src/components/page-templates/index-legacy.js
index 1eb1ffd858d717..7b407f9bee955c 100644
--- a/packages/edit-site/src/components/page-templates/index-legacy.js
+++ b/packages/edit-site/src/components/page-templates/index-legacy.js
@@ -10,7 +10,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router';
import { privateApis as editorPrivateApis } from '@wordpress/editor';
import { addQueryArgs } from '@wordpress/url';
import { useEvent } from '@wordpress/compose';
-import { useView } from '@wordpress/views';
+import { useView, useViewConfig } from '@wordpress/views';
/**
* Internal dependencies
@@ -20,11 +20,6 @@ import { TEMPLATE_POST_TYPE } from '../../utils/constants';
import { unlock } from '../../lock-unlock';
import { useEditPostAction } from '../dataviews-actions';
import { authorField, descriptionField, previewField } from './fields';
-import {
- defaultLayouts,
- DEFAULT_VIEW,
- getActiveViewOverridesForTab,
-} from './view-utils';
const { usePostActions, templateTitleField } = unlock( editorPrivateApis );
const { useHistory, useLocation } = unlock( routerPrivateApis );
@@ -32,13 +27,20 @@ const { useEntityRecordsWithPermissions } = unlock( corePrivateApis );
export default function PageTemplates() {
const { path, query } = useLocation();
- const { activeView = 'active', postId } = query;
+ const { activeView = 'all', postId } = query;
const [ selection, setSelection ] = useState( [ postId ] );
- const defaultView = DEFAULT_VIEW;
+ const {
+ default_view: defaultView,
+ default_layouts: defaultLayouts,
+ view_list: viewList,
+ } = useViewConfig( {
+ kind: 'postType',
+ name: TEMPLATE_POST_TYPE,
+ } );
const activeViewOverrides = useMemo(
- () => getActiveViewOverridesForTab( activeView ),
- [ activeView ]
+ () => viewList?.find( ( v ) => v.slug === activeView )?.view ?? {},
+ [ viewList, activeView ]
);
const { view, updateView, isModified, resetToDefault } = useView( {
kind: 'postType',
@@ -46,6 +48,7 @@ export default function PageTemplates() {
slug: 'default',
defaultView,
activeViewOverrides,
+ defaultLayouts,
queryParams: {
page: query.pageNumber,
search: query.search,
@@ -150,7 +153,7 @@ export default function PageTemplates() {
history.navigate( `/wp_template/${ id }?canvas=edit` );
} }
selection={ selection }
- defaultLayouts={ defaultLayouts }
+ defaultLayouts={ defaultLayouts ?? {} }
onReset={
isModified
? () => {
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
index 7920d49a43c8cd..e827b55986c484 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
@@ -1,79 +1,63 @@
/**
* WordPress dependencies
*/
-import { useEntityRecords } from '@wordpress/core-data';
-import { useMemo } from '@wordpress/element';
import { __experimentalItemGroup as ItemGroup } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { addQueryArgs } from '@wordpress/url';
+import { useViewConfig } from '@wordpress/views';
+import {
+ commentAuthorAvatar,
+ layout,
+ plugins as pluginIcon,
+ globe,
+} from '@wordpress/icons';
/**
* Internal dependencies
*/
import SidebarNavigationItem from '../sidebar-navigation-item';
-import { useAddedBy } from '../page-templates/hooks';
-import { layout } from '@wordpress/icons';
import { TEMPLATE_POST_TYPE } from '../../utils/constants';
import { unlock } from '../../lock-unlock';
const { useLocation } = unlock( routerPrivateApis );
-const EMPTY_ARRAY = [];
-
-function TemplateDataviewItem( { template, isActive } ) {
- const { text, icon } = useAddedBy( template.type, template.id );
-
- return (
-
- { text }
-
- );
-}
+const SLUG_TO_ICON = {
+ all: layout,
+ user: commentAuthorAvatar,
+ theme: layout,
+ plugin: pluginIcon,
+ site: globe,
+};
export default function DataviewsTemplatesSidebarContent() {
const {
query: { activeView = 'all' },
} = useLocation();
- const { records } = useEntityRecords( 'postType', TEMPLATE_POST_TYPE, {
- per_page: -1,
+ const { view_list: viewList } = useViewConfig( {
+ kind: 'postType',
+ name: TEMPLATE_POST_TYPE,
} );
- const firstItemPerAuthorText = useMemo( () => {
- const firstItemPerAuthor = records?.reduce( ( acc, template ) => {
- const author = template.author_text;
- if ( author && ! acc[ author ] ) {
- acc[ author ] = template;
- }
- return acc;
- }, {} );
- return (
- ( firstItemPerAuthor && Object.values( firstItemPerAuthor ) ) ??
- EMPTY_ARRAY
- );
- }, [ records ] );
return (
-
- { __( 'All templates' ) }
-
- { firstItemPerAuthorText.map( ( template ) => {
- return (
-
- );
- } ) }
+ { viewList?.map( ( item ) => (
+
+ { item.title }
+
+ ) ) }
);
}
diff --git a/packages/edit-site/src/components/site-editor-routes/templates.js b/packages/edit-site/src/components/site-editor-routes/templates.js
index 51636d044adda0..1225bbc59743b2 100644
--- a/packages/edit-site/src/components/site-editor-routes/templates.js
+++ b/packages/edit-site/src/components/site-editor-routes/templates.js
@@ -1,6 +1,8 @@
/**
* WordPress dependencies
*/
+import { resolveSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
import { loadView } from '@wordpress/views';
/**
@@ -11,19 +13,24 @@ import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen
import SidebarNavigationScreenUnsupported from '../sidebar-navigation-screen-unsupported';
import PageTemplates from '../page-templates';
import PageTemplatesLegacy from '../page-templates/index-legacy';
-import {
- DEFAULT_VIEW,
- getActiveViewOverridesForTab,
-} from '../page-templates/view-utils';
+import { unlock } from '../../lock-unlock';
async function isTemplateListView( query ) {
const { activeView = 'active' } = query;
+ const config = await unlock( resolveSelect( coreStore ) ).getViewConfig(
+ 'postType',
+ 'wp_template'
+ );
+ const defaultView = config?.default_view;
+ const viewEntry = config?.view_list?.find( ( v ) => v.slug === activeView );
+ // For author-based views, no overrides needed for type determination.
+ const activeViewOverrides = viewEntry?.view ?? {};
const view = await loadView( {
kind: 'postType',
name: 'wp_template',
slug: 'default',
- defaultView: DEFAULT_VIEW,
- activeViewOverrides: getActiveViewOverridesForTab( activeView ),
+ defaultView,
+ activeViewOverrides,
} );
return view.type === 'list';
}
From 979be83b374da0edf83a2831ff136c914430d52b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:14:55 +0100
Subject: [PATCH 02/11] simplify
---
.../edit-site/src/components/site-editor-routes/templates.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/edit-site/src/components/site-editor-routes/templates.js b/packages/edit-site/src/components/site-editor-routes/templates.js
index 1225bbc59743b2..eb6f49d524ef53 100644
--- a/packages/edit-site/src/components/site-editor-routes/templates.js
+++ b/packages/edit-site/src/components/site-editor-routes/templates.js
@@ -22,9 +22,8 @@ async function isTemplateListView( query ) {
'wp_template'
);
const defaultView = config?.default_view;
- const viewEntry = config?.view_list?.find( ( v ) => v.slug === activeView );
- // For author-based views, no overrides needed for type determination.
- const activeViewOverrides = viewEntry?.view ?? {};
+ const activeViewOverrides =
+ config?.view_list?.find( ( v ) => v.slug === activeView ).view ?? {};
const view = await loadView( {
kind: 'postType',
name: 'wp_template',
From 28455038c43e82e9cbf9563a6d747567c8b909e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:17:36 +0100
Subject: [PATCH 03/11] Icon
---
...enberg-rest-view-config-controller-7-1.php | 5 ----
.../content-legacy.js | 29 ++++++++++++++++---
2 files changed, 25 insertions(+), 9 deletions(-)
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 6e54d12031ecfe..4753526d7ca712 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
@@ -160,7 +160,6 @@ public function get_items( $request ) {
$view_list[] = array(
'title' => $author_text,
'slug' => $author_text,
- 'icon' => $original_source,
'view' => array(
'filters' => array(
array(
@@ -185,7 +184,6 @@ public function get_items( $request ) {
$view_list[] = array(
'title' => $author_text,
'slug' => $author_text,
- 'icon' => $original_source,
'view' => array(
'filters' => array(
array(
@@ -326,9 +324,6 @@ public function get_item_schema() {
'slug' => array(
'type' => 'string',
),
- 'icon' => array(
- 'type' => 'string',
- ),
'view' => array(
'type' => 'object',
'properties' => array_merge(
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
index e827b55986c484..60d7ff4e06f0b6 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
@@ -2,6 +2,8 @@
* WordPress dependencies
*/
import { __experimentalItemGroup as ItemGroup } from '@wordpress/components';
+import { store as coreStore } from '@wordpress/core-data';
+import { useSelect } from '@wordpress/data';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { addQueryArgs } from '@wordpress/url';
import { useViewConfig } from '@wordpress/views';
@@ -21,7 +23,7 @@ import { unlock } from '../../lock-unlock';
const { useLocation } = unlock( routerPrivateApis );
-const SLUG_TO_ICON = {
+const SOURCE_TO_ICON = {
all: layout,
user: commentAuthorAvatar,
theme: layout,
@@ -37,6 +39,27 @@ export default function DataviewsTemplatesSidebarContent() {
kind: 'postType',
name: TEMPLATE_POST_TYPE,
} );
+ const authorSourceMap = useSelect( ( select ) => {
+ const templates = select( coreStore ).getEntityRecords(
+ 'postType',
+ TEMPLATE_POST_TYPE,
+ { per_page: -1 }
+ );
+ if ( ! templates ) {
+ return {};
+ }
+ const map = {};
+ for ( const template of templates ) {
+ if (
+ template.author_text &&
+ template.original_source &&
+ ! map[ template.author_text ]
+ ) {
+ map[ template.author_text ] = template.original_source;
+ }
+ }
+ return map;
+ }, [] );
return (
@@ -50,9 +73,7 @@ export default function DataviewsTemplatesSidebarContent() {
activeView: item.slug,
} )
}
- icon={
- SLUG_TO_ICON[ item.icon ] || SLUG_TO_ICON[ item.slug ]
- }
+ icon={ SOURCE_TO_ICON[ authorSourceMap[ item.slug ] ] }
aria-current={ activeView === item.slug }
>
{ item.title }
From 5a2d4ee2f5853aab606f05a9cf937572da20d438 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:25:53 +0100
Subject: [PATCH 04/11] Fix typo
---
.../edit-site/src/components/site-editor-routes/templates.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/edit-site/src/components/site-editor-routes/templates.js b/packages/edit-site/src/components/site-editor-routes/templates.js
index eb6f49d524ef53..fdde0b7387697a 100644
--- a/packages/edit-site/src/components/site-editor-routes/templates.js
+++ b/packages/edit-site/src/components/site-editor-routes/templates.js
@@ -23,7 +23,7 @@ async function isTemplateListView( query ) {
);
const defaultView = config?.default_view;
const activeViewOverrides =
- config?.view_list?.find( ( v ) => v.slug === activeView ).view ?? {};
+ config?.view_list?.find( ( v ) => v.slug === activeView )?.view ?? {};
const view = await loadView( {
kind: 'postType',
name: 'wp_template',
From 35d4391f04045c8821637c9c6cbd10f4a2eecd72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:26:38 +0100
Subject: [PATCH 05/11] Account for unknown source/all
---
.../content-legacy.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
index 60d7ff4e06f0b6..706e05b09b2900 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content-legacy.js
@@ -24,7 +24,6 @@ import { unlock } from '../../lock-unlock';
const { useLocation } = unlock( routerPrivateApis );
const SOURCE_TO_ICON = {
- all: layout,
user: commentAuthorAvatar,
theme: layout,
plugin: pluginIcon,
@@ -73,7 +72,9 @@ export default function DataviewsTemplatesSidebarContent() {
activeView: item.slug,
} )
}
- icon={ SOURCE_TO_ICON[ authorSourceMap[ item.slug ] ] }
+ icon={
+ SOURCE_TO_ICON[ authorSourceMap[ item.slug ] ] ?? layout
+ }
aria-current={ activeView === item.slug }
>
{ item.title }
From 67cbb1000adcb561b941eff6d7f2d1adfe82f808 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:42:01 +0100
Subject: [PATCH 06/11] Add backport note
---
backport-changelog/7.1/11272.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/backport-changelog/7.1/11272.md b/backport-changelog/7.1/11272.md
index 7237a0a5a4e2da..d615b1c64a32a2 100644
--- a/backport-changelog/7.1/11272.md
+++ b/backport-changelog/7.1/11272.md
@@ -2,3 +2,4 @@ https://github.com/WordPress/wordpress-develop/pull/11272
* https://github.com/WordPress/gutenberg/pull/76573
* https://github.com/WordPress/gutenberg/pull/76734
+* https://github.com/WordPress/gutenberg/pull/76622
From d4cf7dbc4c63d6cc84bd26b3a000495e26f58ab3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Tue, 24 Mar 2026 17:25:25 +0100
Subject: [PATCH 07/11] Extract to method
---
...enberg-rest-view-config-controller-7-1.php | 162 ++++++++++--------
1 file changed, 88 insertions(+), 74 deletions(-)
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 4753526d7ca712..b3616027a7965b 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
@@ -123,80 +123,9 @@ public function get_items( $request ) {
$default_layouts = $this->get_default_layouts_for_wp_template_part();
$default_view = $this->get_default_view_for_wp_template_part( $default_layouts );
} elseif ( 'postType' === $kind && 'wp_template' === $name ) {
- $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,
- );
- $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',
- ),
- );
-
- // Add unique author-based items from registered templates.
- $registered_templates = gutenberg_get_registered_block_templates( array() );
- $seen_authors = array();
- foreach ( $registered_templates as $template ) {
- $original_source = self::get_template_original_source( $template );
- $author_text = self::get_template_author_text( $template, $original_source );
- if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
- $seen_authors[ $author_text ] = true;
- $view_list[] = array(
- 'title' => $author_text,
- 'slug' => $author_text,
- 'view' => array(
- 'filters' => array(
- array(
- 'field' => 'author',
- 'operator' => 'is',
- 'value' => $author_text,
- 'isLocked' => true,
- ),
- ),
- ),
- );
- }
- }
-
- // User-created DB templates.
- $db_templates = get_block_templates( array(), 'wp_template' );
- foreach ( $db_templates as $template ) {
- $original_source = self::get_template_original_source( $template );
- $author_text = self::get_template_author_text( $template, $original_source );
- if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
- $seen_authors[ $author_text ] = true;
- $view_list[] = array(
- 'title' => $author_text,
- 'slug' => $author_text,
- 'view' => array(
- 'filters' => array(
- array(
- 'field' => 'author',
- 'operator' => 'is',
- 'value' => $author_text,
- 'isLocked' => true,
- ),
- ),
- ),
- );
- }
- }
+ $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();
}
$response = array(
@@ -839,4 +768,89 @@ private static function get_template_author_text( $template_object, $original_so
return '';
}
+
+ 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',
+ ),
+ );
+
+ // Add unique author-based items from registered templates.
+ $registered_templates = gutenberg_get_registered_block_templates( array() );
+ $seen_authors = array();
+ foreach ( $registered_templates as $template ) {
+ $original_source = self::get_template_original_source( $template );
+ $author_text = self::get_template_author_text( $template, $original_source );
+ if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
+ $seen_authors[ $author_text ] = true;
+ $view_list[] = array(
+ 'title' => $author_text,
+ 'slug' => $author_text,
+ 'view' => array(
+ 'filters' => array(
+ array(
+ 'field' => 'author',
+ 'operator' => 'is',
+ 'value' => $author_text,
+ 'isLocked' => true,
+ ),
+ ),
+ ),
+ );
+ }
+ }
+
+ // User-created DB templates.
+ $db_templates = get_block_templates( array(), 'wp_template' );
+ foreach ( $db_templates as $template ) {
+ $original_source = self::get_template_original_source( $template );
+ $author_text = self::get_template_author_text( $template, $original_source );
+ if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
+ $seen_authors[ $author_text ] = true;
+ $view_list[] = array(
+ 'title' => $author_text,
+ 'slug' => $author_text,
+ 'view' => array(
+ 'filters' => array(
+ array(
+ 'field' => 'author',
+ 'operator' => 'is',
+ 'value' => $author_text,
+ 'isLocked' => true,
+ ),
+ ),
+ ),
+ );
+ }
+ }
+
+ return $view_list;
+ }
}
From c01e1da6a65291e570fa3e7240cd7955a8a9e7e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 25 Mar 2026 10:00:46 +0100
Subject: [PATCH 08/11] Rename for clarity: it matches original name
---
...ass-gutenberg-rest-view-config-controller-7-1.php | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
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 b3616027a7965b..1a716acaf6b63f 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
@@ -686,7 +686,7 @@ private function get_default_view_for_wp_template_part( $default_layouts ) {
* @param WP_Block_Template $template_object Template instance.
* @return string The original source ('theme', 'plugin', 'site', or 'user').
*/
- private static function get_template_original_source( $template_object ) {
+ private static function get_wp_templates_original_source_field( $template_object ) {
if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
if ( $template_object->has_theme_file &&
( 'theme' === $template_object->origin || (
@@ -722,7 +722,7 @@ private static function get_template_original_source( $template_object ) {
* @param string $original_source The original source of the template.
* @return string Human readable text for the author.
*/
- private static function get_template_author_text( $template_object, $original_source ) {
+ private static function get_wp_templates_author_text_field( $template_object, $original_source ) {
switch ( $original_source ) {
case 'theme':
$theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
@@ -806,8 +806,8 @@ private function get_view_list_for_wp_template() {
$registered_templates = gutenberg_get_registered_block_templates( array() );
$seen_authors = array();
foreach ( $registered_templates as $template ) {
- $original_source = self::get_template_original_source( $template );
- $author_text = self::get_template_author_text( $template, $original_source );
+ $original_source = self::get_wp_templates_original_source_field( $template );
+ $author_text = self::get_wp_templates_author_text_field( $template, $original_source );
if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
$seen_authors[ $author_text ] = true;
$view_list[] = array(
@@ -830,8 +830,8 @@ private function get_view_list_for_wp_template() {
// User-created DB templates.
$db_templates = get_block_templates( array(), 'wp_template' );
foreach ( $db_templates as $template ) {
- $original_source = self::get_template_original_source( $template );
- $author_text = self::get_template_author_text( $template, $original_source );
+ $original_source = self::get_wp_templates_original_source_field( $template );
+ $author_text = self::get_wp_templates_author_text_field( $template, $original_source );
if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
$seen_authors[ $author_text ] = true;
$view_list[] = array(
From 6ad2858643bcd3b72f5b45da4a022d423671ceb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 25 Mar 2026 10:20:44 +0100
Subject: [PATCH 09/11] Call REST templates endpoint
---
...enberg-rest-view-config-controller-7-1.php | 128 ++----------------
1 file changed, 11 insertions(+), 117 deletions(-)
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 1a716acaf6b63f..641bf12464c582 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
@@ -680,95 +680,6 @@ private function get_default_view_for_wp_template_part( $default_layouts ) {
);
}
- /**
- * 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 ) {
- 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';
- }
-
- if ( 'plugin' === $template_object->origin ) {
- return 'plugin';
- }
-
- if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
- return 'site';
- }
- }
-
- return 'user';
- }
-
- /**
- * Returns a human readable text for the author of a template.
- *
- * @param WP_Block_Template $template_object Template instance.
- * @param string $original_source The original source of the template.
- * @return string Human readable text for the author.
- */
- private static function get_wp_templates_author_text_field( $template_object, $original_source ) {
- 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;
- }
- }
- }
-
- $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' );
- }
-
- return '';
- }
-
private function get_default_view_for_wp_template() {
return array(
'type' => 'grid',
@@ -802,36 +713,19 @@ private function get_view_list_for_wp_template() {
),
);
- // Add unique author-based items from registered templates.
- $registered_templates = gutenberg_get_registered_block_templates( array() );
- $seen_authors = array();
- foreach ( $registered_templates as $template ) {
- $original_source = self::get_wp_templates_original_source_field( $template );
- $author_text = self::get_wp_templates_author_text_field( $template, $original_source );
- if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
- $seen_authors[ $author_text ] = true;
- $view_list[] = array(
- 'title' => $author_text,
- 'slug' => $author_text,
- 'view' => array(
- 'filters' => array(
- array(
- 'field' => 'author',
- 'operator' => 'is',
- 'value' => $author_text,
- 'isLocked' => true,
- ),
- ),
- ),
- );
- }
+ // Fetch all templates via the REST API to get computed author_text values.
+ $request = new WP_REST_Request( 'GET', '/wp/v2/templates' );
+ $request->set_param( '_fields', 'author_text' );
+ $response = rest_do_request( $request );
+
+ if ( $response->is_error() ) {
+ return $view_list;
}
- // User-created DB templates.
- $db_templates = get_block_templates( array(), 'wp_template' );
- foreach ( $db_templates as $template ) {
- $original_source = self::get_wp_templates_original_source_field( $template );
- $author_text = self::get_wp_templates_author_text_field( $template, $original_source );
+ $templates = $response->get_data();
+ $seen_authors = array();
+ foreach ( $templates as $template ) {
+ $author_text = $template['author_text'] ?? '';
if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
$seen_authors[ $author_text ] = true;
$view_list[] = array(
From 242eee5ee003e4e58ed15381c5a89029169bc44a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 25 Mar 2026 10:25:51 +0100
Subject: [PATCH 10/11] List registered sources first
---
...enberg-rest-view-config-controller-7-1.php | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
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 641bf12464c582..3f7fce3b003491 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
@@ -715,20 +715,26 @@ private function get_view_list_for_wp_template() {
// Fetch all templates via the REST API to get computed author_text values.
$request = new WP_REST_Request( 'GET', '/wp/v2/templates' );
- $request->set_param( '_fields', 'author_text' );
+ $request->set_param( '_fields', 'author_text,original_source' );
$response = rest_do_request( $request );
if ( $response->is_error() ) {
return $view_list;
}
- $templates = $response->get_data();
- $seen_authors = array();
+ $templates = $response->get_data();
+
+ // 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 ) {
- $author_text = $template['author_text'] ?? '';
+ $author_text = $template['author_text'] ?? '';
+ $original_source = $template['original_source'] ?? 'user';
if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) {
$seen_authors[ $author_text ] = true;
- $view_list[] = array(
+ $entry = array(
'title' => $author_text,
'slug' => $author_text,
'view' => array(
@@ -742,9 +748,17 @@ private function get_view_list_for_wp_template() {
),
),
);
+ if ( 'user' === $original_source ) {
+ $user_authors[] = $entry;
+ } else {
+ $registered_authors[] = $entry;
+ }
}
}
+ // Registered sources (theme, plugin, site) first, then user-created.
+ $view_list = array_merge( $view_list, $registered_authors, $user_authors );
+
return $view_list;
}
}
From 015e3ddc5e3721ac0e1f8689b3e070caff360480 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 25 Mar 2026 10:35:35 +0100
Subject: [PATCH 11/11] Use get_block_templates directly
---
...enberg-rest-view-config-controller-7-1.php | 125 ++++++++++++++++--
1 file changed, 112 insertions(+), 13 deletions(-)
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 3f7fce3b003491..6146aa42ce2854 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
@@ -680,6 +680,115 @@ private function get_default_view_for_wp_template_part( $default_layouts ) {
);
}
+ /**
+ * 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 '';
+ }
+
private function get_default_view_for_wp_template() {
return array(
'type' => 'grid',
@@ -713,16 +822,7 @@ private function get_view_list_for_wp_template() {
),
);
- // Fetch all templates via the REST API to get computed author_text values.
- $request = new WP_REST_Request( 'GET', '/wp/v2/templates' );
- $request->set_param( '_fields', 'author_text,original_source' );
- $response = rest_do_request( $request );
-
- if ( $response->is_error() ) {
- return $view_list;
- }
-
- $templates = $response->get_data();
+ $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.
@@ -730,8 +830,8 @@ private function get_view_list_for_wp_template() {
$registered_authors = array();
$user_authors = array();
foreach ( $templates as $template ) {
- $author_text = $template['author_text'] ?? '';
- $original_source = $template['original_source'] ?? 'user';
+ $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(
@@ -756,7 +856,6 @@ private function get_view_list_for_wp_template() {
}
}
- // Registered sources (theme, plugin, site) first, then user-created.
$view_list = array_merge( $view_list, $registered_authors, $user_authors );
return $view_list;