Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global Styles: allow read access to users with edit_posts capabilities #65071

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/6.7/7336.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/7336

* https://github.com/WordPress/gutenberg/pull/65071
51 changes: 24 additions & 27 deletions lib/class-wp-rest-global-styles-controller-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -532,27 +532,39 @@ 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.
*/
public function get_theme_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/*
* Verify if the current user has edit_posts capability.
*/
if ( current_user_can( 'edit_posts' ) ) {
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;
}
}

/*
* 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(),
)
);
if ( current_user_can( 'edit_theme_options' ) ) {
return true;
}

return true;
return new WP_Error(
'rest_cannot_read_global_styles',
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
__( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ),
array(
'status' => rest_authorization_required_code(),
)
);
}

/**
Expand Down Expand Up @@ -616,23 +628,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 );
}

/**
Expand Down
23 changes: 23 additions & 0 deletions lib/compat/wordpress-6.7/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,26 @@ 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.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 ) {
$args['capabilities']['read'] = 'edit_posts';
}

return $args;
}

add_filter( 'register_post_type_args', 'gutenberg_register_post_type_args_for_wp_global_styles', 10, 2 );
4 changes: 2 additions & 2 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
86 changes: 50 additions & 36 deletions packages/editor/src/components/global-styles-provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,38 +46,61 @@ 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( 'update', {
kind: 'root',
name: 'globalStyles',
id: _globalStylesId,
} );

if ( _globalStylesId ) {
if ( userCanEditGlobalStyles ) {
record = getEditedEntityRecord(
'root',
'globalStyles',
_globalStylesId
);
} else {
record = getEntityRecord(
'root',
'globalStyles',
_globalStylesId,
{ context: 'view' }
);
}
}
ramonjd marked this conversation as resolved.
Show resolved Hide resolved

let hasResolved = false;
if (
hasFinishedResolution(
'__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 {
Expand Down Expand Up @@ -145,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 ];
}

Expand Down
69 changes: 66 additions & 3 deletions phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ 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
*/
protected static $subscriber_id;

/**
* @var int
*/
protected static $theme_manager_id;

/**
* @var int
*/
Expand Down Expand Up @@ -44,12 +54,30 @@ public static function wpSetupBeforeClass( $factory ) {
)
);

self::$editor_id = $factory->user->create(
array(
'role' => 'editor',
)
);

self::$subscriber_id = $factory->user->create(
array(
'role' => 'subscriber',
)
);

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(
Expand All @@ -72,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 );
}

/**
Expand Down Expand Up @@ -195,17 +225,50 @@ 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 );
}

/**
* @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_read_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 );
$this->assertErrorResponse( 'rest_cannot_manage_global_styles', $response, 403 );
// 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
*/
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' );
}

/**
Expand Down
Loading