diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php index 14478786cf88d..dfc11edc084c5 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php @@ -115,10 +115,65 @@ public function prepare_item_for_response( $theme, $request ) { $data = array(); $fields = $this->get_fields_for_response( $request ); - if ( in_array( 'theme_supports', $fields, true ) ) { + if ( rest_is_field_included( 'stylesheet', $fields ) ) { + $data['stylesheet'] = $theme->get_stylesheet(); + } + + if ( rest_is_field_included( 'template', $fields ) ) { + /** + * Use the get_template() method, not the 'Template' header, for finding the template. + * The 'Template' header is only good for what was written in the style.css, while + * get_template() takes into account where WordPress actually located the theme and + * whether it is actually valid. + */ + $data['template'] = $theme->get_template(); + } + + $plain_field_mappings = array( + 'requires_php' => 'RequiresPHP', + 'requires_wp' => 'RequiresWP', + 'textdomain' => 'TextDomain', + 'version' => 'Version', + ); + + foreach ( $plain_field_mappings as $field => $header ) { + if ( rest_is_field_included( $field, $fields ) ) { + $data[ $field ] = $theme->get( $header ); + } + } + + if ( rest_is_field_included( 'screenshot', $fields ) ) { + // Using $theme->get_screenshot() with no args to get absolute URL. + $data['screenshot'] = $theme->get_screenshot() ?: ''; + } + + $rich_field_mappings = array( + 'author' => 'Author', + 'author_uri' => 'AuthorURI', + 'description' => 'Description', + 'name' => 'Name', + 'tags' => 'Tags', + 'theme_uri' => 'ThemeURI', + ); + + foreach ( $rich_field_mappings as $field => $header ) { + if ( rest_is_field_included( "{$field}.raw", $fields ) ) { + $data[ $field ]['raw'] = $theme->display( $header, false, true ); + } + + if ( rest_is_field_included( "{$field}.rendered", $fields ) ) { + $data[ $field ]['rendered'] = $theme->display( $header ); + } + } + + if ( rest_is_field_included( 'theme_supports', $fields ) ) { $item_schemas = $this->get_item_schema(); $theme_supports = $item_schemas['properties']['theme_supports']['properties']; foreach ( $theme_supports as $name => $schema ) { + if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) { + continue; + } + if ( 'formats' === $name ) { continue; } @@ -192,6 +247,117 @@ public function get_item_schema() { 'title' => 'theme', 'type' => 'object', 'properties' => array( + 'stylesheet' => array( + 'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'template' => array( + 'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'author' => array( + 'description' => __( 'The theme author.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme author\'s name, as found in the theme header.' ), + 'type' => 'string', + ), + 'rendered' => array( + 'description' => __( 'HTML for the theme author, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'author_uri' => array( + 'description' => __( 'The website of the theme author.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The website of the theme author, as found in the theme header.' ), + 'type' => 'string', + 'format' => 'uri', + ), + 'rendered' => array( + 'description' => __( 'The website of the theme author, transformed for display.' ), + 'type' => 'string', + 'format' => 'uri', + ), + ), + ), + 'description' => array( + 'description' => __( 'A description of the theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme description, as found in the theme header.' ), + 'type' => 'string', + ), + 'rendered' => array( + 'description' => __( 'The theme description, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'name' => array( + 'description' => __( 'The name of the theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme name, as found in the theme header.' ), + 'type' => 'string', + ), + 'rendered' => array( + 'description' => __( 'The theme name, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'requires_php' => array( + 'description' => __( 'The minimum PHP version required for the theme to work.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'requires_wp' => array( + 'description' => __( 'The minimum WordPress version required for the theme to work.' ), + 'type' => 'string', + 'readonly' => true, + ), + 'screenshot' => array( + 'description' => __( 'The theme\'s screenshot URL.' ), + 'type' => 'string', + 'format' => 'uri', + 'readonly' => true, + ), + 'tags' => array( + 'description' => __( 'Tags indicating styles and features of the theme.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The theme tags, as found in the theme header.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'rendered' => array( + 'description' => __( 'The theme tags, transformed for display.' ), + 'type' => 'string', + ), + ), + ), + 'textdomain' => array( + 'description' => __( 'The theme\'s textdomain.' ), + 'type' => 'string', + 'readonly' => true, + ), 'theme_supports' => array( 'description' => __( 'Features supported by this theme.' ), 'type' => 'object', @@ -458,6 +624,28 @@ public function get_item_schema() { ), ), ), + 'theme_uri' => array( + 'description' => __( 'The URI of the theme\'s webpage.' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ), + 'type' => 'string', + 'format' => 'uri', + ), + 'rendered' => array( + 'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ), + 'type' => 'string', + 'format' => 'uri', + ), + ), + ), + 'version' => array( + 'description' => __( 'The theme\'s current version.' ), + 'type' => 'string', + 'readonly' => true, + ), ), ); diff --git a/tests/phpunit/data/themedir1/rest-api/style.css b/tests/phpunit/data/themedir1/rest-api/style.css new file mode 100644 index 0000000000000..27fe7b53a0df5 --- /dev/null +++ b/tests/phpunit/data/themedir1/rest-api/style.css @@ -0,0 +1,15 @@ +/* +Theme Name: REST Theme +Theme URI: http://wordpress.org/?search=1&term=2 +Description: The 9' foot tall theme. +Version: 1.6 +Author: Michael Heilemann +Author URI: http://binarybonsai.com/?search=1&term=2 +Tags: holiday, custom-menu +Template: default +Requires at least: 5.3 +Requires PHP: 5.6 +Text Domain: rest-api +*/ + + diff --git a/tests/phpunit/tests/rest-api/rest-themes-controller.php b/tests/phpunit/tests/rest-api/rest-themes-controller.php index 0da8553ad9bb2..1f7c53b37458a 100644 --- a/tests/phpunit/tests/rest-api/rest-themes-controller.php +++ b/tests/phpunit/tests/rest-api/rest-themes-controller.php @@ -125,6 +125,7 @@ public function setUp() { parent::setUp(); wp_set_current_user( self::$contributor_id ); + switch_theme( 'rest-api' ); } /** @@ -150,7 +151,20 @@ public function test_get_items() { $this->check_get_theme_response( $response ); $fields = array( + 'author', + 'author_uri', + 'description', + 'name', + 'requires_php', + 'requires_wp', + 'screenshot', + 'stylesheet', + 'tags', + 'template', + 'textdomain', 'theme_supports', + 'theme_uri', + 'version', ); $this->assertEqualSets( $fields, array_keys( $data[0] ) ); } @@ -207,8 +221,44 @@ public function test_get_item_schema() { $response = self::perform_active_theme_request( 'OPTIONS' ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 1, count( $properties ) ); + $this->assertEquals( 14, count( $properties ) ); + + $this->assertArrayHasKey( 'author', $properties ); + $this->assertArrayHasKey( 'raw', $properties['author']['properties'] ); + $this->assertArrayHasKey( 'rendered', $properties['author']['properties'] ); + + $this->assertArrayHasKey( 'author_uri', $properties ); + $this->assertArrayHasKey( 'raw', $properties['author_uri']['properties'] ); + $this->assertArrayHasKey( 'rendered', $properties['author_uri']['properties'] ); + + $this->assertArrayHasKey( 'description', $properties ); + $this->assertArrayHasKey( 'raw', $properties['description']['properties'] ); + $this->assertArrayHasKey( 'rendered', $properties['description']['properties'] ); + + $this->assertArrayHasKey( 'name', $properties ); + $this->assertArrayHasKey( 'raw', $properties['name']['properties'] ); + $this->assertArrayHasKey( 'rendered', $properties['name']['properties'] ); + + $this->assertArrayHasKey( 'requires_php', $properties ); + $this->assertArrayHasKey( 'requires_wp', $properties ); + $this->assertArrayHasKey( 'screenshot', $properties ); + $this->assertArrayHasKey( 'stylesheet', $properties ); + + $this->assertArrayHasKey( 'tags', $properties ); + $this->assertArrayHasKey( 'raw', $properties['tags']['properties'] ); + $this->assertArrayHasKey( 'items', $properties['tags']['properties']['raw'] ); + $this->assertArrayHasKey( 'rendered', $properties['tags']['properties'] ); + + $this->assertArrayHasKey( 'template', $properties ); + $this->assertArrayHasKey( 'textdomain', $properties ); $this->assertArrayHasKey( 'theme_supports', $properties ); + + $this->assertArrayHasKey( 'theme_uri', $properties ); + $this->assertArrayHasKey( 'raw', $properties['theme_uri']['properties'] ); + $this->assertArrayHasKey( 'rendered', $properties['theme_uri']['properties'] ); + + $this->assertArrayHasKey( 'version', $properties ); + $theme_supports = $properties['theme_supports']['properties']; $this->assertEquals( 20, count( $theme_supports ) ); $this->assertArrayHasKey( 'align-wide', $theme_supports ); @@ -233,6 +283,148 @@ public function test_get_item_schema() { $this->assertArrayHasKey( 'wp-block-styles', $theme_supports ); } + /** + * @ticket 49906 + */ + public function test_theme_author() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'author', $result[0] ); + $this->assertSame( 'Michael Heilemann', $result[0]['author']['raw'] ); + $this->assertSame( + 'Michael Heilemann', + $result[0]['author']['rendered'] + ); + } + + /** + * @ticket 49906 + */ + public function test_theme_author_uri() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'author_uri', $result[0] ); + $this->assertSame( 'http://binarybonsai.com/?search=1&term=2', $result[0]['author_uri']['raw'] ); + $this->assertSame( 'http://binarybonsai.com/?search=1&term=2', $result[0]['author_uri']['rendered'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_description() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'description', $result[0] ); + $this->assertSame( + 'The 9\' foot tall theme.', + $result[0]['description']['raw'] + ); + $this->assertSame( + 'The 9′ foot tall theme.', + $result[0]['description']['rendered'] + ); + } + + /** + * @ticket 49906 + */ + public function test_theme_requires_php() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'requires_php', $result[0] ); + $this->assertSame( '5.6', $result[0]['requires_php'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_requires_wp() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'requires_wp', $result[0] ); + $this->assertSame( '5.3', $result[0]['requires_wp'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_name() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'name', $result[0] ); + $this->assertSame( 'REST Theme', $result[0]['name']['raw'] ); + $this->assertSame( 'REST Theme', $result[0]['name']['rendered'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_screenshot() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'screenshot', $result[0] ); + $this->assertSame( '', $result[0]['screenshot'] ); // No screenshot for default theme + } + + /** + * @ticket 49906 + */ + public function test_theme_stylesheet() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'stylesheet', $result[0] ); + $this->assertSame( 'rest-api', $result[0]['stylesheet'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_tags() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'tags', $result[0] ); + $this->assertSame( array( 'holiday', 'custom-menu' ), $result[0]['tags']['raw'] ); + $this->assertSame( 'holiday, custom-menu', $result[0]['tags']['rendered'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_template() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'template', $result[0] ); + $this->assertSame( 'default', $result[0]['template'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_textdomain() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'textdomain', $result[0] ); + $this->assertSame( 'rest-api', $result[0]['textdomain'] ); + } + + public function test_theme_theme_uri() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'theme_uri', $result[0] ); + $this->assertSame( 'http://wordpress.org/?search=1&term=2', $result[0]['theme_uri']['raw'] ); + $this->assertSame( 'http://wordpress.org/?search=1&term=2', $result[0]['theme_uri']['rendered'] ); + } + + /** + * @ticket 49906 + */ + public function test_theme_version() { + $response = self::perform_active_theme_request(); + $result = $response->get_data(); + $this->assertArrayHasKey( 'version', $result[0] ); + $this->assertSame( '1.6', $result[0]['version'] ); + } + /** * @ticket 49037 */ diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index 0b697ef49237e..a8cfd87f47c5a 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -163,6 +163,7 @@ function test_theme_list() { 'Theme with Spaces in the Directory', 'Internationalized Theme', 'camelCase', + 'REST Theme', ); sort( $theme_names );