From ed1f411c564f79d003de8babb485884d0a71caa2 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 5 Apr 2022 09:50:13 +0000 Subject: [PATCH] Block Editor: Backport the Global Styles Variations endpoint. This include the /global-styles/themes/{theme}/variations rest endpoint into core. The endpoint will be used by the site editor to display alternative theme styles to the user. Props gziolo, oandregal. See #55505. git-svn-id: https://develop.svn.wordpress.org/trunk@53072 602fd350-edb4-49c9-b593-d223f7449a82 --- package-lock.json | 2 +- .../class-wp-theme-json-resolver.php | 28 ++++++++ ...class-wp-rest-global-styles-controller.php | 67 +++++++++++++++++++ .../block-theme/styles/variation.json | 23 +++++++ .../rest-global-styles-controller.php | 43 ++++++++++++ .../tests/rest-api/rest-schema-setup.php | 1 + tests/qunit/fixtures/wp-api-generated.js | 20 ++++++ 7 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/data/themedir1/block-theme/styles/variation.json diff --git a/package-lock.json b/package-lock.json index 0f66fb3ed9..268f3138f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7750,7 +7750,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index d7828d727d..ac3d28be6c 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -449,4 +449,32 @@ class WP_Theme_JSON_Resolver { static::$i18n_schema = null; } + /** + * Returns the style variations defined by the theme. + * + * @since 6.0.0 + * + * @return array + */ + public static function get_style_variations() { + $variations = array(); + $base_directory = get_stylesheet_directory() . '/styles'; + if ( is_dir( $base_directory ) ) { + $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); + $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); + ksort( $nested_html_files ); + foreach ( $nested_html_files as $path => $file ) { + $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); + if ( is_array( $decoded_file ) ) { + $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); + $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); + if ( empty( $variation['title'] ) ) { + $variation['title'] = basename( $path, '.json' ); + } + $variations[] = $variation; + } + } + } + return $variations; + } } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index e6771ff851..5d0c4ede2b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -38,6 +38,24 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller { * @return void */ public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_theme_items' ), + 'permission_callback' => array( $this, 'get_theme_items_permissions_check' ), + 'args' => array( + 'stylesheet' => array( + 'description' => __( 'The theme identifier' ), + 'type' => 'string', + ), + ), + ), + ) + ); + // List themes global styles. register_rest_route( $this->namespace, @@ -585,4 +603,53 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller { return $response; } + + /** + * Checks if a given request has access to read a single theme global styles config. + * + * @since 6.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + */ + public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Verify if the current user has edit_theme_options capability. + // This capability is required to edit/view/delete templates. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_manage_global_styles', + __( 'Sorry, you are not allowed to access the global styles on this site.' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + + return true; + } + + /** + * Returns the given theme global styles variations. + * + * @since 6.0.0 + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_theme_items( $request ) { + if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) { + // This endpoint only supports the active theme for now. + return new WP_Error( + 'rest_theme_not_found', + __( 'Theme not found.' ), + array( 'status' => 404 ) + ); + } + + $variations = WP_Theme_JSON_Resolver::get_style_variations(); + $response = rest_ensure_response( $variations ); + + return $response; + } } diff --git a/tests/phpunit/data/themedir1/block-theme/styles/variation.json b/tests/phpunit/data/themedir1/block-theme/styles/variation.json new file mode 100644 index 0000000000..ad3affb115 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme/styles/variation.json @@ -0,0 +1,23 @@ +{ + "version": 2, + "settings": { + "color": { + "palette": [ + { + "slug": "foreground", + "color": "#3F67C6", + "name": "Foreground" + } + ] + } + }, + "styles": { + "blocks": { + "core/post-title": { + "typography": { + "fontWeight": "700" + } + } + } + } +} \ No newline at end of file diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 60023e7c01..c8df046ca0 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -117,6 +117,11 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test $routes['/wp/v2/global-styles/themes/(?P[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)'], 'Theme global styles route does not have exactly one element' ); + $this->assertArrayHasKey( + '/wp/v2/global-styles/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations', + $routes, + 'Theme global styles variations route does not exist' + ); } public function test_context_param() { @@ -455,4 +460,42 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test $this->assertArrayHasKey( 'settings', $properties, 'Schema properties array does not have "settings" key' ); $this->assertArrayHasKey( 'title', $properties, 'Schema properties array does not have "title" key' ); } + + + public function test_get_theme_items() { + wp_set_current_user( self::$admin_id ); + switch_theme( 'block-theme' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/block-theme/variations' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $expected = array( + array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'foreground', + 'color' => '#3F67C6', + 'name' => 'Foreground', + ), + ), + ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/post-title' => array( + 'typography' => array( + 'fontWeight' => '700', + ), + ), + ), + ), + 'title' => 'variation', + ), + ); + $this->assertSameSetsWithIndex( $data, $expected ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 3bec8d45d4..1a9347b4ca 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -136,6 +136,7 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { '/wp/v2/comments', '/wp/v2/comments/(?P[\\d]+)', '/wp/v2/global-styles/(?P[\/\w-]+)', + '/wp/v2/global-styles/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations', '/wp/v2/global-styles/themes/(?P[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', '/wp/v2/search', '/wp/v2/block-renderer/(?P[a-z0-9-]+/[a-z0-9-]+)', diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 9566fb0ded..74504826d1 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -9424,6 +9424,26 @@ mockedApiResponse.Schema = { } ] }, + "/wp/v2/global-styles/themes/(?P[\\/\\s%\\w\\.\\(\\)\\[\\]\\@_\\-]+)/variations": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "stylesheet": { + "description": "The theme identifier", + "type": "string", + "required": false + } + } + } + ] + }, "/wp/v2/global-styles/themes/(?P[^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)": { "namespace": "wp/v2", "methods": [