From 317bcd919ece812f4f9287945a83c5d3a2381d64 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:42:16 +0700 Subject: [PATCH 01/17] Allow base global styles data in post editor --- lib/class-wp-rest-global-styles-controller-gutenberg.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index e33304e596e129..2b2b93ed3ebc1d 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -537,12 +537,11 @@ public function get_item_schema() { * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_theme_item_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 global styles. + * Verify if the current user has edit_posts capability. + * This capability is required to view global styles. */ - if ( ! current_user_can( 'edit_theme_options' ) ) { + if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_cannot_manage_global_styles', __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), From 817381f4163fb16750f476bcb9ae6356485ef520 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:22:55 +0700 Subject: [PATCH 02/17] Try getting user global styles working --- lib/compat/wordpress-6.6/rest-api.php | 1 + packages/core-data/src/entities.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index eadd3b1d376a72..4ae6e457310ae3 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -210,6 +210,7 @@ function gutenberg_block_editor_preload_paths_6_6( $paths, $context ) { $paths[] = '/wp/v2/global-styles/themes/' . get_stylesheet(); $paths[] = '/wp/v2/themes?context=edit&status=active'; $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; + $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); } return $paths; } diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 8d09402087cf90..0f7f7fad31de42 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -182,7 +182,7 @@ export const rootEntitiesConfig = [ name: 'globalStyles', kind: 'root', baseURL: '/wp/v2/global-styles', - baseURLParams: { context: 'edit' }, + baseURLParams: {}, plural: 'globalStylesVariations', // Should be different from name. getTitle: ( record ) => record?.title?.rendered || record?.title, getRevisionsUrl: ( parentId, revisionId ) => @@ -190,6 +190,17 @@ export const rootEntitiesConfig = [ revisionId ? '/' + revisionId : '' }`, supportsPagination: true, + getPath: ( path, query, baseURL, id ) => { + const context = + query.context || + ( query.operation === 'read' ? 'view' : 'edit' ); + const contextQuery = context ? `?context=${ context }` : ''; + return `${ baseURL }${ id ? '/' + id : '' }${ contextQuery }`; + }, + capabilities: { + read: 'edit_posts', + update: 'edit_theme_options', + }, }, { label: __( 'Themes' ), From f2ce2c769cd2eb67873d6cc98ef77ca56a208e5b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:48:39 +0700 Subject: [PATCH 03/17] Remove placeholder code --- packages/core-data/src/entities.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 0f7f7fad31de42..c8af0dc94348d4 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -190,17 +190,6 @@ export const rootEntitiesConfig = [ revisionId ? '/' + revisionId : '' }`, supportsPagination: true, - getPath: ( path, query, baseURL, id ) => { - const context = - query.context || - ( query.operation === 'read' ? 'view' : 'edit' ); - const contextQuery = context ? `?context=${ context }` : ''; - return `${ baseURL }${ id ? '/' + id : '' }${ contextQuery }`; - }, - capabilities: { - read: 'edit_posts', - update: 'edit_theme_options', - }, }, { label: __( 'Themes' ), From 935aa3efc3a34be91be4fda6d846971dc5533b71 Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 5 Sep 2024 11:14:49 +1000 Subject: [PATCH 04/17] Reinstate default context for globalStyles in entities.js Check read caps on all CPTs --- ...est-global-styles-controller-gutenberg.php | 25 ++++++++++++------- packages/core-data/src/entities.js | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 2b2b93ed3ebc1d..0db080f889778e 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -532,6 +532,7 @@ public function get_item_schema() { * Checks if a given request has access to read a single theme global styles config. * * @since 5.9.0 + * @since 6.7.0 Allow users with edit post capabilities to view theme global styles. * * @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. @@ -541,17 +542,23 @@ public function get_theme_item_permissions_check( $request ) { // phpcs:ignore V * Verify if the current user has edit_posts capability. * This capability is required to view global styles. */ - if ( ! current_user_can( 'edit_posts' ) ) { - return new WP_Error( - 'rest_cannot_manage_global_styles', - __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); + if ( current_user_can( 'edit_posts' ) ) { + return true; } - return true; + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { + if ( current_user_can( $post_type->cap->edit_posts ) ) { + return true; + } + } + + return new WP_Error( + 'rest_cannot_manage_global_styles', + __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); } /** diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index c8af0dc94348d4..8d09402087cf90 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -182,7 +182,7 @@ export const rootEntitiesConfig = [ name: 'globalStyles', kind: 'root', baseURL: '/wp/v2/global-styles', - baseURLParams: {}, + baseURLParams: { context: 'edit' }, plural: 'globalStylesVariations', // Should be different from name. getTitle: ( record ) => record?.title?.rendered || record?.title, getRevisionsUrl: ( parentId, revisionId ) => From 9ee89e30df31d8d2bf5eb7c88b841d32449de60b Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 5 Sep 2024 13:17:11 +1000 Subject: [PATCH 05/17] A test to allow view context for global styles GET request Preloads the global styles CPT without edit via filter Check canUser capabilities according to the capabilities set in the CPT wp_global_styles --- ...est-global-styles-controller-gutenberg.php | 19 +----- lib/compat/wordpress-6.6/rest-api.php | 1 - lib/compat/wordpress-6.7/rest-api.php | 37 +++++++++- packages/core-data/src/resolvers.js | 4 +- .../global-styles-provider/index.js | 68 +++++++++++++------ 5 files changed, 86 insertions(+), 43 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 0db080f889778e..504eb1712cd828 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -622,23 +622,8 @@ public function get_theme_item( $request ) { * @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 global styles. - */ - 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.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - - return true; + public function get_theme_items_permissions_check( $request ) { + return $this->get_theme_item_permissions_check( $request ); } /** diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index 4ae6e457310ae3..eadd3b1d376a72 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -210,7 +210,6 @@ function gutenberg_block_editor_preload_paths_6_6( $paths, $context ) { $paths[] = '/wp/v2/global-styles/themes/' . get_stylesheet(); $paths[] = '/wp/v2/themes?context=edit&status=active'; $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); } return $paths; } diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index c5e2927198da0c..d43894cdc27732 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -48,8 +48,22 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { if ( false !== $reusable_blocks_key ) { unset( $paths[ $reusable_blocks_key ] ); } - } + /* + * Removes the `context=edit` query parameter from the global styles preload path added in gutenberg_block_editor_preload_paths_6_6(). + * Uses array_filter because it may have been added again in WordPress Core 6.6 in src/wp-admin/edit-form-blocks.php + * Reason: because GET requests to global styles items are accessible to roles with the `edit_posts` capability. + */ + $global_styles_edit_path = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; + $paths = array_filter( + $paths, + function ( $value ) use ( $global_styles_edit_path ) { + return $value !== $global_styles_edit_path; + } + ); + + $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); + } return $paths; } add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 ); @@ -114,3 +128,24 @@ function gutenberg_override_default_rest_server() { return 'Gutenberg_REST_Server'; } add_filter( 'wp_rest_server_class', 'gutenberg_override_default_rest_server', 1 ); + + +/** + * Filters the arguments for registering a wp_global_styles post type. + * Note when syncing to Core: the capabilities should be updates for `wp_global_styles` in the wp-includes/post.php. + * + * @since 6.7.0 + * + * @param array $args Array of arguments for registering a post type. + * See the register_post_type() function for accepted arguments. + * @param string $post_type Post type key. + */ +function gutenberg_register_post_type_args_for_wp_global_styles( $args, $post_type ) { + if ( $post_type === 'wp_global_styles' ) { + $args['capabilities']['read'] = 'edit_posts'; + } + + return $args; +} + +add_filter( 'register_post_type_args', 'gutenberg_register_post_type_args_for_wp_global_styles', 10, 2 ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index ce8c2db7a53b4a..6cc7df27f48b1f 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -645,7 +645,7 @@ export const __experimentalGetCurrentThemeBaseGlobalStyles = async ( { resolveSelect, dispatch } ) => { const currentTheme = await resolveSelect.getCurrentTheme(); const themeGlobalStyles = await apiFetch( { - path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }`, + path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }?context=view`, } ); dispatch.__experimentalReceiveThemeBaseGlobalStyles( currentTheme.stylesheet, @@ -658,7 +658,7 @@ export const __experimentalGetCurrentThemeGlobalStylesVariations = async ( { resolveSelect, dispatch } ) => { const currentTheme = await resolveSelect.getCurrentTheme(); const variations = await apiFetch( { - path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }/variations`, + path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }/variations?context=view`, } ); dispatch.__experimentalReceiveThemeGlobalStyleVariations( currentTheme.stylesheet, diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 8268997d1f1def..44c35c7c80f469 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -16,6 +16,7 @@ import { useMemo, useCallback } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; const { GlobalStylesContext, cleanEmptyObject } = unlock( blockEditorPrivateApis @@ -46,24 +47,38 @@ export function mergeBaseAndUserConfigs( base, user ) { function useGlobalStylesUserConfig() { const { globalStylesId, isReady, settings, styles, _links } = useSelect( ( select ) => { - const { getEditedEntityRecord, hasFinishedResolution, canUser } = - select( coreStore ); + const { + getEntityRecord, + getEditedEntityRecord, + hasFinishedResolution, + canUser, + } = select( coreStore ); const _globalStylesId = select( coreStore ).__experimentalGetCurrentGlobalStylesId(); - const record = - _globalStylesId && - canUser( 'read', { - kind: 'root', - name: 'globalStyles', - id: _globalStylesId, - } ) - ? getEditedEntityRecord( - 'root', - 'globalStyles', - _globalStylesId - ) - : undefined; + let record; + const userCanEditGlobalStyles = canUser( 'edit', { + kind: 'root', + name: 'globalStyles', + id: _globalStylesId, + } ); + + if ( _globalStylesId ) { + if ( userCanEditGlobalStyles ) { + record = getEditedEntityRecord( + 'root', + 'globalStyles', + _globalStylesId + ); + } else { + record = getEntityRecord( + 'root', + 'globalStyles', + _globalStylesId, + { context: 'view' } + ); + } + } let hasResolved = false; if ( @@ -71,13 +86,22 @@ function useGlobalStylesUserConfig() { '__experimentalGetCurrentGlobalStylesId' ) ) { - hasResolved = _globalStylesId - ? hasFinishedResolution( 'getEditedEntityRecord', [ - 'root', - 'globalStyles', - _globalStylesId, - ] ) - : true; + if ( _globalStylesId ) { + hasResolved = userCanEditGlobalStyles + ? hasFinishedResolution( 'getEditedEntityRecord', [ + 'root', + 'globalStyles', + _globalStylesId, + ] ) + : hasFinishedResolution( 'getEntityRecord', [ + 'root', + 'globalStyles', + _globalStylesId, + { context: 'view' }, + ] ); + } else { + hasResolved = true; + } } return { From 3c8653076111ce32337effeaf6f4e2410d277227 Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 9 Sep 2024 11:01:09 +1000 Subject: [PATCH 06/17] Now that Star Wars is owned by Disney, do we still need Yoda conditions? --- lib/compat/wordpress-6.7/rest-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index d43894cdc27732..d62cde7aa7d769 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -141,7 +141,7 @@ function gutenberg_override_default_rest_server() { * @param string $post_type Post type key. */ function gutenberg_register_post_type_args_for_wp_global_styles( $args, $post_type ) { - if ( $post_type === 'wp_global_styles' ) { + if ( 'wp_global_styles' === $post_type ) { $args['capabilities']['read'] = 'edit_posts'; } From 499bf2dd9d76fdbbe34cc1caed85d926c77cd912 Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 9 Sep 2024 12:20:40 +1000 Subject: [PATCH 07/17] Added permissions test for editor role --- ...lobal-styles-controller-gutenberg-test.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 85731a7dff5206..4e5882fff9237f 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -12,6 +12,11 @@ class WP_REST_Global_Styles_Controller_Gutenberg_Test extends WP_Test_REST_Contr */ protected static $admin_id; + /** + * @var int + */ + protected static $editor_id; + /** * @var int */ @@ -44,6 +49,12 @@ public static function wpSetupBeforeClass( $factory ) { ) ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + self::$subscriber_id = $factory->user->create( array( 'role' => 'subscriber', @@ -201,13 +212,30 @@ public function test_get_theme_item_no_user() { /** * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_theme_item */ - public function test_get_theme_item_permission_check() { + public function test_get_theme_item_subscriber_permission_check() { wp_set_current_user( self::$subscriber_id ); + switch_theme( 'emptytheme' ); $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/emptytheme' ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 403 ); } + /** + * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_theme_item + */ + public function test_get_theme_item_editor_permission_check() { + wp_set_current_user( self::$editor_id ); + switch_theme( 'emptytheme' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/emptytheme' ); + $response = rest_get_server()->dispatch( $request ); + // Checks that the response has the expected keys. + $data = $response->get_data(); + $links = $response->get_links(); + $this->assertArrayHasKey( 'settings', $data, 'Data does not have "settings" key' ); + $this->assertArrayHasKey( 'styles', $data, 'Data does not have "styles" key' ); + $this->assertArrayHasKey( 'self', $links, 'Links do not have a "self" key' ); + } + /** * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_theme_item */ From 2ce6ba9058834f4e9bba07ee9bb870f4ab59a63b Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 12 Sep 2024 10:57:17 +1000 Subject: [PATCH 08/17] Allow reading base theme global styles in all instances --- .../global-styles-provider/index.js | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 44c35c7c80f469..7007e712234ad1 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -16,7 +16,6 @@ import { useMemo, useCallback } from '@wordpress/element'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import { store as editorStore } from '../../store'; const { GlobalStylesContext, cleanEmptyObject } = unlock( blockEditorPrivateApis @@ -169,20 +168,11 @@ function useGlobalStylesUserConfig() { } function useGlobalStylesBaseConfig() { - const baseConfig = useSelect( ( select ) => { - const { - __experimentalGetCurrentThemeBaseGlobalStyles, - getCurrentTheme, - canUser, - } = select( coreStore ); - const currentTheme = getCurrentTheme(); - - return currentTheme && - canUser( 'read', 'global-styles/themes', currentTheme.stylesheet ) - ? __experimentalGetCurrentThemeBaseGlobalStyles() - : undefined; - }, [] ); - + const baseConfig = useSelect( + ( select ) => + select( coreStore ).__experimentalGetCurrentThemeBaseGlobalStyles(), + [] + ); return [ !! baseConfig, baseConfig ]; } From db62b62ee1e7dd29d0dec6273341319f6b2820d3 Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 12 Sep 2024 11:29:32 +1000 Subject: [PATCH 09/17] Whoops. Should be "update" --- packages/editor/src/components/global-styles-provider/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 7007e712234ad1..6f2d8177056cb1 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -56,7 +56,7 @@ function useGlobalStylesUserConfig() { select( coreStore ).__experimentalGetCurrentGlobalStylesId(); let record; - const userCanEditGlobalStyles = canUser( 'edit', { + const userCanEditGlobalStyles = canUser( 'update', { kind: 'root', name: 'globalStyles', id: _globalStylesId, From f35d9b004cb908975c5434be5a150350baa5321a Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 12 Sep 2024 14:31:02 +1000 Subject: [PATCH 10/17] Return value on new filter Update WP_Error code and associate tests for read permissions check --- lib/class-wp-rest-global-styles-controller-gutenberg.php | 2 +- lib/compat/wordpress-6.7/rest-api.php | 2 ++ .../class-wp-rest-global-styles-controller-gutenberg-test.php | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 504eb1712cd828..6b753177b949bd 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -553,7 +553,7 @@ public function get_theme_item_permissions_check( $request ) { // phpcs:ignore V } return new WP_Error( - 'rest_cannot_manage_global_styles', + 'rest_cannot_read_global_styles', __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), array( 'status' => rest_authorization_required_code(), diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index d62cde7aa7d769..bb9569886dd1cf 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -139,6 +139,8 @@ function gutenberg_override_default_rest_server() { * @param array $args Array of arguments for registering a post type. * See the register_post_type() function for accepted arguments. * @param string $post_type Post type key. + * + * @return array|string Array or string of arguments for registering a post type. */ function gutenberg_register_post_type_args_for_wp_global_styles( $args, $post_type ) { if ( 'wp_global_styles' === $post_type ) { diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 4e5882fff9237f..8f7ccbbfb7676c 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -206,7 +206,7 @@ public function test_get_theme_item_no_user() { wp_set_current_user( 0 ); $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/emptytheme' ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 401 ); + $this->assertErrorResponse( 'rest_cannot_read_global_styles', $response, 401 ); } /** @@ -217,7 +217,7 @@ public function test_get_theme_item_subscriber_permission_check() { switch_theme( 'emptytheme' ); $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/emptytheme' ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_read_global_styles', $response, 403 ); } /** From 16772a32f437ec8bae1f6188a04360d08343d3b4 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 12 Sep 2024 14:40:24 +1000 Subject: [PATCH 11/17] Update return annotation Co-authored-by: George Mamadashvili --- lib/compat/wordpress-6.7/rest-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index bb9569886dd1cf..b58cea83ab686d 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -140,7 +140,7 @@ function gutenberg_override_default_rest_server() { * See the register_post_type() function for accepted arguments. * @param string $post_type Post type key. * - * @return array|string Array or string of arguments for registering a post type. + * @return array Array of arguments for registering a post type. */ function gutenberg_register_post_type_args_for_wp_global_styles( $args, $post_type ) { if ( 'wp_global_styles' === $post_type ) { From eac8ee2df06442041aff2a952d8347857dbd7024 Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 12 Sep 2024 15:11:39 +1000 Subject: [PATCH 12/17] Backport changelog entry --- backport-changelog/6.7/7336.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.7/7336.md diff --git a/backport-changelog/6.7/7336.md b/backport-changelog/6.7/7336.md new file mode 100644 index 00000000000000..7cb2e26d7eeb95 --- /dev/null +++ b/backport-changelog/6.7/7336.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7336 + +* https://github.com/WordPress/gutenberg/pull/65071 From 801883f7041914928b5ec2d02d9a7be145760528 Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 17 Sep 2024 15:17:07 +1000 Subject: [PATCH 13/17] remove preload path filtering --- lib/compat/wordpress-6.7/rest-api.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index b58cea83ab686d..313367594caae0 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -48,22 +48,8 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { if ( false !== $reusable_blocks_key ) { unset( $paths[ $reusable_blocks_key ] ); } - - /* - * Removes the `context=edit` query parameter from the global styles preload path added in gutenberg_block_editor_preload_paths_6_6(). - * Uses array_filter because it may have been added again in WordPress Core 6.6 in src/wp-admin/edit-form-blocks.php - * Reason: because GET requests to global styles items are accessible to roles with the `edit_posts` capability. - */ - $global_styles_edit_path = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; - $paths = array_filter( - $paths, - function ( $value ) use ( $global_styles_edit_path ) { - return $value !== $global_styles_edit_path; - } - ); - - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id(); } + return $paths; } add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 ); From 24e57863cad09f4faf039d2a7856db31cae0a89e Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 17 Sep 2024 17:20:14 +1000 Subject: [PATCH 14/17] Add view context preload fetch for global styles. --- lib/compat/wordpress-6.7/rest-api.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 313367594caae0..29703ec4b7ba96 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -48,8 +48,13 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { if ( false !== $reusable_blocks_key ) { unset( $paths[ $reusable_blocks_key ] ); } - } + /* + * Preload the global styles post data in the post editor. + * This is required for non-admin users without access to the 'view' context. + */ + $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=view'; + } return $paths; } add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 ); From 8caa9f58cc9d408d7b82b0bed68bfd42fba2df3a Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 17 Sep 2024 17:26:32 +1000 Subject: [PATCH 15/17] revert preload until we work out how to preload --- lib/compat/wordpress-6.7/rest-api.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 29703ec4b7ba96..313367594caae0 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -48,13 +48,8 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { if ( false !== $reusable_blocks_key ) { unset( $paths[ $reusable_blocks_key ] ); } - - /* - * Preload the global styles post data in the post editor. - * This is required for non-admin users without access to the 'view' context. - */ - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=view'; } + return $paths; } add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 ); From 768770e7a8bfde8b8941321d38fe43d1869512a0 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 18 Sep 2024 10:00:05 +1000 Subject: [PATCH 16/17] Reinstate `edit_theme_options` check and add test coverage --- ...est-global-styles-controller-gutenberg.php | 8 ++++- ...lobal-styles-controller-gutenberg-test.php | 35 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 6b753177b949bd..1f6543fa184283 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -540,7 +540,6 @@ public function get_item_schema() { public function get_theme_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable /* * Verify if the current user has edit_posts capability. - * This capability is required to view global styles. */ if ( current_user_can( 'edit_posts' ) ) { return true; @@ -552,6 +551,13 @@ public function get_theme_item_permissions_check( $request ) { // phpcs:ignore V } } + /* + * Verify if the current user has edit_theme_options capability. + */ + if ( current_user_can( 'edit_theme_options' ) ) { + return true; + } + return new WP_Error( 'rest_cannot_read_global_styles', __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 8f7ccbbfb7676c..b2a9c29cf77d75 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -22,6 +22,11 @@ class WP_REST_Global_Styles_Controller_Gutenberg_Test extends WP_Test_REST_Contr */ protected static $subscriber_id; + /** + * @var int + */ + protected static $theme_manager_id; + /** * @var int */ @@ -61,6 +66,18 @@ public static function wpSetupBeforeClass( $factory ) { ) ); + self::$theme_manager_id = $factory->user->create( + array( + 'role' => 'subscriber', + ) + ); + + // Add the 'edit_theme_options' capability to the theme manager (subscriber). + $theme_manager_id = get_user_by('id', self::$theme_manager_id ); + if ( $theme_manager_id instanceof WP_User ) { + $theme_manager_id->add_cap('edit_theme_options'); + } + // This creates the global styles for the current theme. self::$global_styles_id = $factory->post->create( array( @@ -83,7 +100,9 @@ public static function wpSetupBeforeClass( $factory ) { */ public static function wpTearDownAfterClass() { self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); self::delete_user( self::$subscriber_id ); + self::delete_user( self::$theme_manager_id ); } /** @@ -236,6 +255,22 @@ public function test_get_theme_item_editor_permission_check() { $this->assertArrayHasKey( 'self', $links, 'Links do not have a "self" key' ); } + /** + * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_theme_item + */ + public function test_get_theme_item_theme_options_manager_permission_check() { + wp_set_current_user( self::$theme_manager_id ); + switch_theme( 'emptytheme' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/emptytheme' ); + $response = rest_get_server()->dispatch( $request ); + // Checks that the response has the expected keys. + $data = $response->get_data(); + $links = $response->get_links(); + $this->assertArrayHasKey( 'settings', $data, 'Data does not have "settings" key' ); + $this->assertArrayHasKey( 'styles', $data, 'Data does not have "styles" key' ); + $this->assertArrayHasKey( 'self', $links, 'Links do not have a "self" key' ); + } + /** * @covers WP_REST_Global_Styles_Controller_Gutenberg::get_theme_item */ From 8d99a2631179be0b8611122ce373a2fe316a373e Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 18 Sep 2024 10:03:47 +1000 Subject: [PATCH 17/17] LINT ME LIKE YOU MEAN IT --- .../class-wp-rest-global-styles-controller-gutenberg-test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index b2a9c29cf77d75..c9453f4bd8e5c3 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -73,9 +73,9 @@ public static function wpSetupBeforeClass( $factory ) { ); // Add the 'edit_theme_options' capability to the theme manager (subscriber). - $theme_manager_id = get_user_by('id', self::$theme_manager_id ); + $theme_manager_id = get_user_by( 'id', self::$theme_manager_id ); if ( $theme_manager_id instanceof WP_User ) { - $theme_manager_id->add_cap('edit_theme_options'); + $theme_manager_id->add_cap( 'edit_theme_options' ); } // This creates the global styles for the current theme.