diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index 53f8faa755..ab2fdce15b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -726,6 +726,14 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { $data['modified'] = mysql_to_rfc3339( $template->modified ); } + if ( rest_is_field_included( 'author_text', $fields ) ) { + $data['author_text'] = self::get_wp_templates_author_text_field( $template ); + } + + if ( rest_is_field_included( 'original_source', $fields ) ) { + $data['original_source'] = self::get_wp_templates_original_source_field( $template ); + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -748,6 +756,85 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { return $response; } + /** + * Returns the source from where the template originally comes from. + * + * @access private + * @internal + * + * @param WP_Block_Template $template_object Template instance. + * @return string Original source of the template one of 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 ( $template_object->has_theme_file && '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 the template. + * + * @access private + * @internal + * + * @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': + $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': + $author = get_user_by( 'id', $template_object->author ); + if ( ! $author ) { + return __( 'Unknown author' ); + } + return $author->get( 'display_name' ); + } + } + /** * Prepares links for the request. @@ -861,13 +948,13 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { 'title' => $this->post_type, 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'ID of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'slug' => array( + 'slug' => array( 'description' => __( 'Unique slug identifying the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), @@ -875,29 +962,29 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { 'minLength' => 1, 'pattern' => '[a-zA-Z0-9_\%-]+', ), - 'theme' => array( + 'theme' => array( 'description' => __( 'Theme identifier for the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), - 'type' => array( + 'type' => array( 'description' => __( 'Type of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), - 'source' => array( + 'source' => array( 'description' => __( 'Source of template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'origin' => array( + 'origin' => array( 'description' => __( 'Source of a customized template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'content' => array( + 'content' => array( 'description' => __( 'Content of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', @@ -916,7 +1003,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { ), ), ), - 'title' => array( + 'title' => array( 'description' => __( 'Title of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', @@ -935,43 +1022,61 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { ), ), ), - 'description' => array( + 'description' => array( 'description' => __( 'Description of template.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), - 'status' => array( + 'status' => array( 'description' => __( 'Status of template.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'default' => 'publish', 'context' => array( 'embed', 'view', 'edit' ), ), - 'wp_id' => array( + 'wp_id' => array( 'description' => __( 'Post ID.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'has_theme_file' => array( + 'has_theme_file' => array( 'description' => __( 'Theme file exists.' ), 'type' => 'bool', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'author' => array( + 'author' => array( 'description' => __( 'The ID for the author of the template.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), - 'modified' => array( + 'modified' => array( 'description' => __( "The date the template was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'author_text' => array( + 'type' => 'string', + 'description' => __( 'Human readable text for the author.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'original_source' => array( + 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + 'enum' => array( + 'theme', + 'plugin', + 'site', + 'user', + ), + ), ), ); diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php index 9452a18843..bb6824d679 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php @@ -310,7 +310,7 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll $properties = $data['schema']['properties']; - $this->assertCount( 16, $properties ); + $this->assertCount( 18, $properties ); $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' ); $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' ); $this->assertArrayHasKey( 'theme', $properties, 'Theme key should exist in properties.' ); @@ -326,6 +326,8 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll $this->assertArrayHasKey( 'modified', $properties, 'modified key should exist in properties.' ); $this->assertArrayHasKey( 'is_custom', $properties, 'is_custom key should exist in properties.' ); $this->assertArrayHasKey( 'parent', $properties, 'Parent key should exist in properties.' ); + $this->assertArrayHasKey( 'author_text', $properties, 'Parent key should exist in properties.' ); + $this->assertArrayHasKey( 'original_source', $properties, 'Parent key should exist in properties.' ); } /** diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php index fdd3570ebd..954e4977d3 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -449,7 +449,7 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertCount( 16, $properties ); + $this->assertCount( 18, $properties ); $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' ); $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' ); $this->assertArrayHasKey( 'theme', $properties, 'Theme key should exist in properties.' ); @@ -465,6 +465,8 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll $this->assertArrayHasKey( 'modified', $properties, 'modified key should exist in properties.' ); $this->assertArrayHasKey( 'is_custom', $properties, 'is_custom key should exist in properties.' ); $this->assertArrayHasKey( 'parent', $properties, 'Parent key should exist in properties.' ); + $this->assertArrayHasKey( 'author_text', $properties, 'Parent key should exist in properties.' ); + $this->assertArrayHasKey( 'original_source', $properties, 'Parent key should exist in properties.' ); } /** diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index 266ca89a33..046f358ba3 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -102,23 +102,25 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $this->assertSame( array( - 'id' => 'default//my_template', - 'theme' => 'default', - 'slug' => 'my_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Description of my template.', - 'title' => array( + 'id' => 'default//my_template', + 'theme' => 'default', + '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, 'default//my_template' ) ); @@ -147,23 +149,25 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $this->assertSame( array( - 'id' => 'default//my_template', - 'theme' => 'default', - 'slug' => 'my_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Description of my template.', - 'title' => array( + 'id' => 'default//my_template', + 'theme' => 'default', + '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 ); @@ -184,23 +188,25 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $this->assertSame( array( - 'id' => 'default//my_template', - 'theme' => 'default', - 'slug' => 'my_template', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Description of my template.', - 'title' => array( + 'id' => 'default//my_template', + 'theme' => 'default', + '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 ); @@ -241,26 +247,29 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $data = $response->get_data(); unset( $data['content'] ); unset( $data['_links'] ); + $author_name = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); $this->assertSameSetsWithIndex( array( - 'id' => "{$theme_dir}//{$template}", - 'theme' => $theme_dir, - 'slug' => $template, - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => $args['post_excerpt'], - 'title' => array( + 'id' => "{$theme_dir}//{$template}", + 'theme' => $theme_dir, + 'slug' => $template, + 'source' => 'custom', + 'origin' => null, + 'type' => 'wp_template', + 'description' => $args['post_excerpt'], + 'title' => array( 'raw' => $args['post_title'], 'rendered' => $args['post_title'], ), - 'status' => 'publish', - 'wp_id' => $post->ID, - 'has_theme_file' => false, - 'is_custom' => true, - 'author' => self::$admin_id, - 'modified' => mysql_to_rfc3339( $post->post_modified ), + 'status' => 'publish', + 'wp_id' => $post->ID, + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => self::$admin_id, + 'modified' => mysql_to_rfc3339( $post->post_modified ), + 'author_text' => $author_name, + 'original_source' => 'user', ), $data ); @@ -421,27 +430,31 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc unset( $data['_links'] ); unset( $data['wp_id'] ); + $author_name = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); + $this->assertSame( array( - 'id' => 'default//my_custom_template', - 'theme' => 'default', - 'content' => array( + 'id' => 'default//my_custom_template', + 'theme' => 'default', + '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' => mysql_to_rfc3339( $modified ), + 'status' => 'publish', + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => self::$admin_id, + 'modified' => mysql_to_rfc3339( $modified ), + 'author_text' => $author_name, + 'original_source' => 'user', ), $data ); @@ -469,27 +482,31 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc unset( $data['_links'] ); unset( $data['wp_id'] ); + $author_name = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); + $this->assertSame( array( - 'id' => 'default//404', - 'theme' => 'default', - 'content' => array( + 'id' => 'default//404', + 'theme' => 'default', + 'content' => array( 'raw' => '', ), - 'slug' => '404', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Template shown when no content is found.', - 'title' => array( + 'slug' => '404', + 'source' => 'custom', + 'origin' => null, + 'type' => 'wp_template', + 'description' => 'Template shown when no content is found.', + 'title' => array( 'raw' => '404', 'rendered' => '404', ), - 'status' => 'publish', - 'has_theme_file' => false, - 'is_custom' => false, - 'author' => self::$admin_id, - 'modified' => mysql_to_rfc3339( $modified ), + 'status' => 'publish', + 'has_theme_file' => false, + 'is_custom' => false, + 'author' => self::$admin_id, + 'modified' => mysql_to_rfc3339( $modified ), + 'author_text' => $author_name, + 'original_source' => 'user', ), $data ); @@ -521,27 +538,31 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc unset( $data['_links'] ); unset( $data['wp_id'] ); + $author_name = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); + $this->assertSame( array( - 'id' => 'default//my_custom_template_raw', - 'theme' => 'default', - 'content' => array( + 'id' => 'default//my_custom_template_raw', + 'theme' => 'default', + 'content' => array( 'raw' => 'Content', ), - 'slug' => 'my_custom_template_raw', - 'source' => 'custom', - 'origin' => null, - 'type' => 'wp_template', - 'description' => 'Just a description', - 'title' => array( + 'slug' => 'my_custom_template_raw', + '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' => mysql_to_rfc3339( $modified ), + 'status' => 'publish', + 'has_theme_file' => false, + 'is_custom' => true, + 'author' => self::$admin_id, + 'modified' => mysql_to_rfc3339( $modified ), + 'author_text' => $author_name, + 'original_source' => 'user', ), $data ); @@ -700,7 +721,7 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $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 ); @@ -717,6 +738,8 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $this->assertArrayHasKey( 'is_custom', $properties ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'modified', $properties ); + $this->assertArrayHasKey( 'author_text', $properties ); + $this->assertArrayHasKey( 'original_source', $properties ); } protected function find_and_normalize_template_by_id( $templates, $id ) { @@ -747,10 +770,13 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc $request = new WP_REST_Request( 'POST', '/wp/v2/templates' ); $request->set_body_params( $body_params ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $modified = get_post( $data['wp_id'] )->post_modified; - $expected['modified'] = mysql_to_rfc3339( $modified ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $modified = get_post( $data['wp_id'] )->post_modified; + $expected['modified'] = mysql_to_rfc3339( $modified ); + $expected['author_text'] = get_user_by( 'id', self::$admin_id )->get( 'display_name' ); + $expected['original_source'] = 'user'; + unset( $data['_links'] ); unset( $data['wp_id'] );