From 86a463024ba349565ea4f82821648dc0dff36c81 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 20 Nov 2023 16:46:38 +0000 Subject: [PATCH 1/3] Dataviews: All templates: Add: Sorting to template author. --- lib/compat/wordpress-6.5/rest-api.php | 56 +++++++++++++++++++ .../page-templates/dataviews-templates.js | 16 +++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index dd372eff7943b7..9ac5428b23b6b8 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -19,3 +19,59 @@ function gutenberg_register_global_styles_revisions_endpoints() { } add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); + +function gutenberg_get_wp_templates_author_text_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 ) ) + ) + ) { + $theme_name = wp_get_theme( $template_object['theme'] )->get( 'Name' ); + return empty( $theme_name ) ? $template_object['theme'] : $theme_name; + } + + // Added by plugin. + if ( $template_object['has_theme_file'] && $template_object['origin'] === 'plugin' ) { + $plugins = get_plugins(); + $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ]; + return empty( $plugin['Name'] ) ? $template_object['theme'] : $plugin['Name']; + } + + // 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'] ) && $template_object['source'] === 'custom' && empty( $template_object['author'] ) ) { + return get_bloginfo( 'name' ); + } + } + + // Added by user. + return get_user_by( 'id', $template_object['author'] ); +} + +/** + * Registers the Global Styles Revisions REST API routes. + */ +function gutenberg_register_wp_templates_author_text_field() { + register_rest_field( + 'wp_template', + 'author_text', + array( + 'get_callback' => 'gutenberg_get_wp_templates_author_text_field', + 'update_callback' => null, + 'schema' => null, + ) + ); +} + +add_action( 'rest_api_init', 'gutenberg_register_wp_templates_author_text_field' ); diff --git a/packages/edit-site/src/components/page-templates/dataviews-templates.js b/packages/edit-site/src/components/page-templates/dataviews-templates.js index 26d10614323113..e830cad3ba2106 100644 --- a/packages/edit-site/src/components/page-templates/dataviews-templates.js +++ b/packages/edit-site/src/components/page-templates/dataviews-templates.js @@ -177,6 +177,15 @@ export default function DataviewsTemplates() { : titleB.localeCompare( titleA ); } ); } + if ( view.sort.field === 'author' ) { + filteredTemplates.sort( ( a, b ) => { + const authorA = a.author_text; + const authorB = b.author_text; + return view.sort.direction === 'asc' + ? authorA.localeCompare( authorB ) + : authorB.localeCompare( authorA ); + } ); + } } // Handle pagination. const start = ( view.page - 1 ) * view.perPage; @@ -237,9 +246,12 @@ export default function DataviewsTemplates() { { header: __( 'Author' ), id: 'author', - render: ( { item } ) => , + getValue: ( { item } ) => item.author_text, + render: ( { item } ) => { + return ; + }, enableHiding: false, - enableSorting: false, + enableSorting: true, }, ], [] From 49aa4c2fbe4d42b1d842ed8dcaaf33043f724b7f Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Sun, 26 Nov 2023 12:33:44 +0000 Subject: [PATCH 2/3] Feedback --- lib/compat/wordpress-6.5/rest-api.php | 69 ++++++++-- .../page-templates/dataviews-templates.js | 120 +++++++++--------- 2 files changed, 117 insertions(+), 72 deletions(-) diff --git a/lib/compat/wordpress-6.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index 9ac5428b23b6b8..3a5d15dfbbd41e 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -20,7 +20,16 @@ function gutenberg_register_global_styles_revisions_endpoints() { add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); -function gutenberg_get_wp_templates_author_text_field( $template_object ) { +/** + * Registers additional fields for wp_template rest api. + * + * @access private + * @internal + * + * @param array $template_object Template object. + * @return string Original source of the template one of theme, plugin, site, or user. + */ +function _gutenberg_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. @@ -35,15 +44,12 @@ function gutenberg_get_wp_templates_author_text_field( $template_object ) { ), true ) ) ) ) { - $theme_name = wp_get_theme( $template_object['theme'] )->get( 'Name' ); - return empty( $theme_name ) ? $template_object['theme'] : $theme_name; + return 'theme'; } // Added by plugin. if ( $template_object['has_theme_file'] && $template_object['origin'] === 'plugin' ) { - $plugins = get_plugins(); - $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ]; - return empty( $plugin['Name'] ) ? $template_object['theme'] : $plugin['Name']; + return 'plugin'; } // Added by site. @@ -51,27 +57,66 @@ function gutenberg_get_wp_templates_author_text_field( $template_object ) { // was only added to templates in WordPress 5.9. Fallback to showing the // site logo and title. if ( empty( $template_object['has_theme_file'] ) && $template_object['source'] === 'custom' && empty( $template_object['author'] ) ) { - return get_bloginfo( 'name' ); + return 'site'; } } // Added by user. - return get_user_by( 'id', $template_object['author'] ); + return 'user'; } /** - * Registers the Global Styles Revisions REST API routes. + * Registers additional fields for wp_template rest api. + * + * @access private + * @internal + * + * @param array $template_object Template object. + * @return string Human readable text for the author. */ -function gutenberg_register_wp_templates_author_text_field() { +function _gutenberg_get_wp_templates_author_text_field( $template_object ) { + $original_source = _gutenberg_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': + $plugins = get_plugins(); + $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ]; + return empty( $plugin['Name'] ) ? $template_object['theme'] : $plugin['Name']; + case 'site': + return get_bloginfo( 'name' ); + case 'user': + get_user_by( 'id', $template_object['author'] )->get( 'display_name' ); + } +} + +/** + * Registers additional fields for wp_template rest api. + * + * @access private + * @internal + */ +function _gutenberg_register_wp_templates_additional_fields() { register_rest_field( 'wp_template', 'author_text', array( - 'get_callback' => 'gutenberg_get_wp_templates_author_text_field', + 'get_callback' => '_gutenberg_get_wp_templates_author_text_field', + 'update_callback' => null, + 'schema' => null, + ) + ); + + register_rest_field( + 'wp_template', + 'original_source', + array( + 'get_callback' => '_gutenberg_get_wp_templates_original_source_field', 'update_callback' => null, 'schema' => null, ) ); } -add_action( 'rest_api_init', 'gutenberg_register_wp_templates_author_text_field' ); +add_action( 'rest_api_init', '_gutenberg_register_wp_templates_additional_fields' ); diff --git a/packages/edit-site/src/components/page-templates/dataviews-templates.js b/packages/edit-site/src/components/page-templates/dataviews-templates.js index e830cad3ba2106..d0f54e5902f8c6 100644 --- a/packages/edit-site/src/components/page-templates/dataviews-templates.js +++ b/packages/edit-site/src/components/page-templates/dataviews-templates.js @@ -142,66 +142,7 @@ export default function DataviewsTemplates() { useEntityRecords( 'postType', TEMPLATE_POST_TYPE, { per_page: -1, } ); - const { shownTemplates, paginationInfo } = useMemo( () => { - if ( ! allTemplates ) { - return { - shownTemplates: EMPTY_ARRAY, - paginationInfo: { totalItems: 0, totalPages: 0 }, - }; - } - let filteredTemplates = [ ...allTemplates ]; - // Handle global search. - if ( view.search ) { - const normalizedSearch = normalizeSearchInput( view.search ); - filteredTemplates = filteredTemplates.filter( ( item ) => { - const title = item.title?.rendered || item.slug; - return ( - normalizeSearchInput( title ).includes( - normalizedSearch - ) || - normalizeSearchInput( item.description ).includes( - normalizedSearch - ) - ); - } ); - } - // Handle sorting. - // TODO: Explore how this can be more dynamic.. - if ( view.sort ) { - if ( view.sort.field === 'title' ) { - filteredTemplates.sort( ( a, b ) => { - const titleA = a.title?.rendered || a.slug; - const titleB = b.title?.rendered || b.slug; - return view.sort.direction === 'asc' - ? titleA.localeCompare( titleB ) - : titleB.localeCompare( titleA ); - } ); - } - if ( view.sort.field === 'author' ) { - filteredTemplates.sort( ( a, b ) => { - const authorA = a.author_text; - const authorB = b.author_text; - return view.sort.direction === 'asc' - ? authorA.localeCompare( authorB ) - : authorB.localeCompare( authorA ); - } ); - } - } - // Handle pagination. - const start = ( view.page - 1 ) * view.perPage; - const totalItems = filteredTemplates?.length || 0; - filteredTemplates = filteredTemplates?.slice( - start, - start + view.perPage - ); - return { - shownTemplates: filteredTemplates, - paginationInfo: { - totalItems, - totalPages: Math.ceil( totalItems / view.perPage ), - }, - }; - }, [ allTemplates, view ] ); + const fields = useMemo( () => [ { @@ -256,6 +197,65 @@ export default function DataviewsTemplates() { ], [] ); + + const { shownTemplates, paginationInfo } = useMemo( () => { + if ( ! allTemplates ) { + return { + shownTemplates: EMPTY_ARRAY, + paginationInfo: { totalItems: 0, totalPages: 0 }, + }; + } + let filteredTemplates = [ ...allTemplates ]; + // Handle global search. + if ( view.search ) { + const normalizedSearch = normalizeSearchInput( view.search ); + filteredTemplates = filteredTemplates.filter( ( item ) => { + const title = item.title?.rendered || item.slug; + return ( + normalizeSearchInput( title ).includes( + normalizedSearch + ) || + normalizeSearchInput( item.description ).includes( + normalizedSearch + ) + ); + } ); + } + + // Handle sorting. + if ( view.sort ) { + const stringSortingFields = [ 'title', 'author' ]; + const fieldId = view.sort.field; + if ( stringSortingFields.includes( fieldId ) ) { + const fieldToSort = fields.find( ( field ) => { + return field.id === fieldId; + } ); + filteredTemplates.sort( ( a, b ) => { + const valueA = fieldToSort.getValue( { item: a } ) ?? ''; + const valueB = fieldToSort.getValue( { item: b } ) ?? ''; + return view.sort.direction === 'asc' + ? valueA.localeCompare( valueB ) + : valueB.localeCompare( valueA ); + } ); + } + } + + // Handle pagination. + const start = ( view.page - 1 ) * view.perPage; + const totalItems = filteredTemplates?.length || 0; + filteredTemplates = filteredTemplates?.slice( + start, + start + view.perPage + ); + return { + shownTemplates: filteredTemplates, + paginationInfo: { + totalItems, + totalPages: Math.ceil( totalItems / view.perPage ), + }, + }; + }, [ allTemplates, view, fields ] ); + const resetTemplateAction = useResetTemplateAction(); const actions = useMemo( () => [ From 71eb4de0054c52e49989e6313b67792c7021fbc0 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 27 Nov 2023 13:42:57 +0000 Subject: [PATCH 3/3] Lint and test fixes --- lib/compat/wordpress-6.5/rest-api.php | 50 +++++++--- .../page-templates/dataviews-templates.js | 1 - ...tenberg-rest-templates-controller-test.php | 94 ++++++++++--------- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/lib/compat/wordpress-6.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index 3a5d15dfbbd41e..2e1321dee0f7ef 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -25,8 +25,8 @@ function gutenberg_register_global_styles_revisions_endpoints() { * * @access private * @internal - * - * @param array $template_object Template object. + * + * @param array $template_object Template object. * @return string Original source of the template one of theme, plugin, site, or user. */ function _gutenberg_get_wp_templates_original_source_field( $template_object ) { @@ -38,17 +38,21 @@ function _gutenberg_get_wp_templates_original_source_field( $template_object ) { // 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 ) ) + empty( $template_object['origin'] ) && in_array( + $template_object['source'], + array( + 'theme', + 'custom', + ), + true + ) ) ) ) { return 'theme'; } // Added by plugin. - if ( $template_object['has_theme_file'] && $template_object['origin'] === 'plugin' ) { + if ( $template_object['has_theme_file'] && 'plugin' === $template_object['origin'] ) { return 'plugin'; } @@ -56,7 +60,7 @@ function _gutenberg_get_wp_templates_original_source_field( $template_object ) { // 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'] ) && $template_object['source'] === 'custom' && empty( $template_object['author'] ) ) { + if ( empty( $template_object['has_theme_file'] ) && 'custom' === $template_object['source'] && empty( $template_object['author'] ) ) { return 'site'; } } @@ -70,24 +74,24 @@ function _gutenberg_get_wp_templates_original_source_field( $template_object ) { * * @access private * @internal - * - * @param array $template_object Template object. + * + * @param array $template_object Template object. * @return string Human readable text for the author. */ function _gutenberg_get_wp_templates_author_text_field( $template_object ) { $original_source = _gutenberg_get_wp_templates_original_source_field( $template_object ); - switch( $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': $plugins = get_plugins(); - $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ]; + $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ]; return empty( $plugin['Name'] ) ? $template_object['theme'] : $plugin['Name']; case 'site': return get_bloginfo( 'name' ); case 'user': - get_user_by( 'id', $template_object['author'] )->get( 'display_name' ); + return get_user_by( 'id', $template_object['author'] )->get( 'display_name' ); } } @@ -104,7 +108,12 @@ function _gutenberg_register_wp_templates_additional_fields() { array( 'get_callback' => '_gutenberg_get_wp_templates_author_text_field', 'update_callback' => null, - 'schema' => null, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'Human readable text for the author.', 'gutenberg' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), ) ); @@ -114,7 +123,18 @@ function _gutenberg_register_wp_templates_additional_fields() { array( 'get_callback' => '_gutenberg_get_wp_templates_original_source_field', 'update_callback' => null, - 'schema' => null, + 'schema' => array( + 'description' => __( 'Where the template originally comes from e.g. \'theme\'', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + 'enum' => array( + 'theme', + 'plugin', + 'site', + 'user', + ), + ), ) ); } diff --git a/packages/edit-site/src/components/page-templates/dataviews-templates.js b/packages/edit-site/src/components/page-templates/dataviews-templates.js index d0f54e5902f8c6..1b342b414db765 100644 --- a/packages/edit-site/src/components/page-templates/dataviews-templates.js +++ b/packages/edit-site/src/components/page-templates/dataviews-templates.js @@ -192,7 +192,6 @@ export default function DataviewsTemplates() { return ; }, enableHiding: false, - enableSorting: true, }, ], [] diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index 0992399464c5c8..bbb588fd583ea1 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -99,7 +99,7 @@ public function test_get_item_schema() { $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertCount( 15, $properties ); + $this->assertCount( 17, $properties ); $this->assertArrayHasKey( 'id', $properties ); $this->assertArrayHasKey( 'description', $properties ); $this->assertArrayHasKey( 'slug', $properties ); @@ -131,23 +131,25 @@ public function test_get_item() { $this->assertSame( array( - 'id' => 'emptytheme//my_template', - 'theme' => 'emptytheme', - 'slug' => 'my_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Description of my template.', - 'title' => array( + 'id' => 'emptytheme//my_template', + 'theme' => 'emptytheme', + 'slug' => 'my_template', + 'source' => 'custom', + 'origin' => null, + 'type' => 'wp_template', + 'description' => 'Description of my template.', + 'title' => array( 'raw' => 'My Template', 'rendered' => 'My Template', ), - 'status' => 'publish', - 'wp_id' => self::$post->ID, - 'has_theme_file' => false, - 'is_custom' => true, - 'author' => 0, - 'modified' => mysql_to_rfc3339( self::$post->post_modified ), + 'status' => 'publish', + 'wp_id' => self::$post->ID, + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => 0, + 'modified' => mysql_to_rfc3339( self::$post->post_modified ), + 'author_text' => 'Test Blog', + 'original_source' => 'site', ), $data ); @@ -164,23 +166,25 @@ public function test_get_items() { $this->assertSame( array( - 'id' => 'emptytheme//my_template', - 'theme' => 'emptytheme', - 'slug' => 'my_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Description of my template.', - 'title' => array( + 'id' => 'emptytheme//my_template', + 'theme' => 'emptytheme', + 'slug' => 'my_template', + 'source' => 'custom', + 'origin' => null, + 'type' => 'wp_template', + 'description' => 'Description of my template.', + 'title' => array( 'raw' => 'My Template', 'rendered' => 'My Template', ), - 'status' => 'publish', - 'wp_id' => self::$post->ID, - 'has_theme_file' => false, - 'is_custom' => true, - 'author' => 0, - 'modified' => mysql_to_rfc3339( self::$post->post_modified ), + 'status' => 'publish', + 'wp_id' => self::$post->ID, + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => 0, + 'modified' => mysql_to_rfc3339( self::$post->post_modified ), + 'author_text' => 'Test Blog', + 'original_source' => 'site', ), $this->find_and_normalize_template_by_id( $data, 'emptytheme//my_template' ) ); @@ -225,27 +229,31 @@ public function test_create_item() { unset( $data['_links'] ); unset( $data['wp_id'] ); + $author_name = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); + $this->assertSame( array( - 'id' => 'emptytheme//my_custom_template', - 'theme' => 'emptytheme', - 'content' => array( + 'id' => 'emptytheme//my_custom_template', + 'theme' => 'emptytheme', + 'content' => array( 'raw' => 'Content', ), - 'slug' => 'my_custom_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Just a description', - 'title' => array( + 'slug' => 'my_custom_template', + 'source' => 'custom', + 'origin' => null, + 'type' => 'wp_template', + 'description' => 'Just a description', + 'title' => array( 'raw' => 'My Template', 'rendered' => 'My Template', ), - 'status' => 'publish', - 'has_theme_file' => false, - 'is_custom' => true, - 'author' => self::$admin_id, - 'modified' => $data['modified'], + 'status' => 'publish', + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => self::$admin_id, + 'modified' => $data['modified'], + 'author_text' => $author_name, + 'original_source' => 'user', ), $data );