mirror of
git://develop.git.wordpress.org/
synced 2025-02-07 16:10:43 +01:00
Role/Capability: Add a new update_role
function.
Until now, changing a user's role involved deleting a user's role then re-adding. This change creates a new `update_role` function and associated method in `WP_Roles` to consolidate this process. This commit also introduces new unit tests around `update_role` and adds additional "unhappy path" tests for roles and capabilities in general. Props maksimkuzmin, peterwilsoncc, NomNom99, costdev, SergeyBiryukov. Fixes #54572. git-svn-id: https://develop.svn.wordpress.org/trunk@54213 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
906ec63c78
commit
a647192d7c
@ -1030,6 +1030,30 @@ function add_role( $role, $display_name, $capabilities = array() ) {
|
||||
return wp_roles()->add_role( $role, $display_name, $capabilities );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing role. Creates a new role if it doesn't exist.
|
||||
*
|
||||
* Modifies the display name and/or capabilities for an existing role.
|
||||
* If the role does not exist then a new role is created.
|
||||
*
|
||||
* The capabilities are defined in the following format: `array( 'read' => true )`.
|
||||
* To explicitly deny the role a capability, set the value for that capability to false.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param string $role Role name.
|
||||
* @param string|null $display_name Optional. Role display name. If null, the display name
|
||||
* is not modified. Default null.
|
||||
* @param bool[]|null $capabilities Optional. List of capabilities keyed by the capability name,
|
||||
* e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`.
|
||||
* If null, don't alter capabilities for the existing role and make
|
||||
* empty capabilities for the new one. Default null.
|
||||
* @return WP_Role|void WP_Role object, if the role is updated.
|
||||
*/
|
||||
function update_role( $role, $display_name = null, $capabilities = null ) {
|
||||
return wp_roles()->update_role( $role, $display_name, $capabilities );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a role, if it exists.
|
||||
*
|
||||
|
@ -172,6 +172,76 @@ class WP_Roles {
|
||||
return $this->role_objects[ $role ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing role. Creates a new role if it doesn't exist.
|
||||
*
|
||||
* Modifies the display name and/or capabilities for an existing role.
|
||||
* If the role does not exist then a new role is created.
|
||||
*
|
||||
* The capabilities are defined in the following format: `array( 'read' => true )`.
|
||||
* To explicitly deny the role a capability, set the value for that capability to false.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param string $role Role name.
|
||||
* @param string|null $display_name Optional. Role display name. If null, the display name
|
||||
* is not modified. Default null.
|
||||
* @param bool[]|null $capabilities Optional. List of capabilities keyed by the capability name,
|
||||
* e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`.
|
||||
* If null, don't alter capabilities for the existing role and make
|
||||
* empty capabilities for the new one. Default null.
|
||||
* @return WP_Role|void WP_Role object, if the role is updated.
|
||||
*/
|
||||
public function update_role( $role, $display_name = null, $capabilities = null ) {
|
||||
if ( ! is_string( $role ) || '' === trim( $role ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null !== $display_name && ( ! is_string( $display_name ) || '' === trim( $display_name ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null !== $capabilities && ! is_array( $capabilities ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null === $display_name && null === $capabilities ) {
|
||||
if ( isset( $this->role_objects[ $role ] ) ) {
|
||||
return $this->role_objects[ $role ];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null === $display_name ) {
|
||||
if ( ! isset( $this->role_objects[ $role ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$display_name = $this->roles[ $role ]['name'];
|
||||
}
|
||||
|
||||
if ( null === $capabilities ) {
|
||||
if ( isset( $this->role_objects[ $role ] ) ) {
|
||||
$capabilities = $this->role_objects[ $role ]->capabilities;
|
||||
} else {
|
||||
$capabilities = array();
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $this->roles[ $role ] ) ) {
|
||||
if ( null === $capabilities ) {
|
||||
$capabilities = $this->role_objects[ $role ]->capabilities;
|
||||
}
|
||||
|
||||
unset( $this->role_objects[ $role ] );
|
||||
unset( $this->role_names[ $role ] );
|
||||
unset( $this->roles[ $role ] );
|
||||
}
|
||||
|
||||
// The roles database option will be updated in ::add_role().
|
||||
return $this->add_role( $role, $display_name, $capabilities );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a role by name.
|
||||
*
|
||||
|
@ -998,6 +998,461 @@ class Tests_User_Capabilities extends WP_UnitTestCase {
|
||||
$this->assertFalse( $wp_roles->is_role( $role_name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider data_update_role_unhappy_paths
|
||||
*
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*
|
||||
* @param mixed $role The role to update.
|
||||
* @param mixed $display_name The display name for the role.
|
||||
* @param mixed $capabilities The capabilities for the role.
|
||||
*/
|
||||
public function test_update_role_unhappy_paths( $role, $display_name, $capabilities ) {
|
||||
global $wp_roles;
|
||||
|
||||
// Create role if it does not exist.
|
||||
$role_name = 'janitor';
|
||||
$expected_caps = array(
|
||||
'edit_posts' => true,
|
||||
'edit_pages' => true,
|
||||
'level_0' => true,
|
||||
'level_1' => true,
|
||||
'level_2' => true,
|
||||
);
|
||||
add_role( $role_name, 'Janitor', $expected_caps );
|
||||
$this->flush_roles();
|
||||
|
||||
$this->assertTrue(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not created"
|
||||
);
|
||||
|
||||
$this->assertNotInstanceOf(
|
||||
'WP_Role',
|
||||
update_role( $role, $display_name, $capabilities ),
|
||||
"The $role_name role was updated"
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
remove_role( $role_name );
|
||||
$this->flush_roles();
|
||||
$this->assertFalse(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not removed"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function data_update_role_unhappy_paths() {
|
||||
return array(
|
||||
'true as the role' => array(
|
||||
'role' => true,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'false as the role' => array(
|
||||
'role' => false,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'null as the role' => array(
|
||||
'role' => null,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(int) 1 as the role' => array(
|
||||
'role' => 1,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(float) 1.0 as the role' => array(
|
||||
'role' => 1.0,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(int) 0 as the role' => array(
|
||||
'role' => 0,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(float) 0.0 as the role' => array(
|
||||
'role' => 0.0,
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an empty string as the role' => array(
|
||||
'role' => '',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'a string with only a space as the role' => array(
|
||||
'role' => ' ',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an empty array as the role' => array(
|
||||
'role' => array(),
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'a non-empty array as the role' => array(
|
||||
'role' => array( 'janitor' ),
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an object as the role' => array(
|
||||
'role' => (object) array( 'janitor' ),
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'true as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => true,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'false as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => false,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(int) 1 as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 1,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(float) 1.0 as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 1.0,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(int) 0 as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 0,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'(float) 0.0 as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 0.0,
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an empty string as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => '',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'a string with only a space as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => ' ',
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an empty array as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => array(),
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'a non-empty array as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => array( 'Janitor' ),
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'an object as the display name' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => (object) array( 'Janitor' ),
|
||||
'capabilities' => array(
|
||||
'level_1' => true,
|
||||
),
|
||||
),
|
||||
'true as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => true,
|
||||
),
|
||||
'false as the capabilities' => array(
|
||||
'role' => (object) array( 'janitor' ),
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => false,
|
||||
),
|
||||
'(int) 1 as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => 1,
|
||||
),
|
||||
'(float) 1.0 as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => 1.0,
|
||||
),
|
||||
'(int) 0 as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => 0,
|
||||
),
|
||||
'(float) 0.0 as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => 0.0,
|
||||
),
|
||||
'an empty string as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => '',
|
||||
),
|
||||
'a string with only a space as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => ' ',
|
||||
),
|
||||
'an object as the capabilities' => array(
|
||||
'role' => 'janitor',
|
||||
'display_name' => 'Janitor',
|
||||
'capabilities' => (object) array( 'level_1' => true ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*/
|
||||
public function test_update_role_should_create_a_role_if_it_does_not_exist() {
|
||||
global $wp_roles;
|
||||
|
||||
// Create role if it does not exist.
|
||||
$role_name = 'janitor';
|
||||
$expected_caps = array(
|
||||
'edit_posts' => true,
|
||||
'edit_pages' => true,
|
||||
'level_0' => true,
|
||||
'level_1' => true,
|
||||
'level_2' => true,
|
||||
);
|
||||
update_role( $role_name, 'Janitor', $expected_caps );
|
||||
$this->flush_roles();
|
||||
$this->assertTrue( $wp_roles->is_role( $role_name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*/
|
||||
public function test_update_role_should_change_the_display_name_of_a_role() {
|
||||
global $wp_roles;
|
||||
$role_name = 'janitor';
|
||||
|
||||
add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
|
||||
$this->flush_roles();
|
||||
|
||||
$expected_display_name = 'Janitor Executive';
|
||||
update_role( $role_name, $expected_display_name );
|
||||
$this->flush_roles();
|
||||
|
||||
$this->assertSame(
|
||||
$expected_display_name,
|
||||
$wp_roles->roles[ $role_name ]['name'],
|
||||
'The expected display name was not correct'
|
||||
);
|
||||
$this->assertSame(
|
||||
array( 'level_1' => true ),
|
||||
$wp_roles->roles[ $role_name ]['capabilities'],
|
||||
'The expected capabilities were not correct'
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
remove_role( $role_name );
|
||||
$this->flush_roles();
|
||||
$this->assertFalse(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not removed"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider data_update_role_should_change_the_capabilities_of_a_role
|
||||
*
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*
|
||||
* @param array $capabilities An array of capabilities.
|
||||
*/
|
||||
public function test_update_role_should_change_the_capabilities_of_a_role( $capabilities ) {
|
||||
global $wp_roles;
|
||||
$role_name = 'janitor';
|
||||
|
||||
add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
|
||||
$this->flush_roles();
|
||||
|
||||
update_role( $role_name, null, $capabilities );
|
||||
$this->flush_roles();
|
||||
|
||||
$this->assertSame(
|
||||
'Janitor',
|
||||
$wp_roles->roles[ $role_name ]['name'],
|
||||
'The display name was changed'
|
||||
);
|
||||
$this->assertSame(
|
||||
$capabilities,
|
||||
$wp_roles->roles[ $role_name ]['capabilities'],
|
||||
'The expected capabilities were not correct'
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
remove_role( $role_name );
|
||||
$this->flush_roles();
|
||||
$this->assertFalse(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not removed"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function data_update_role_should_change_the_capabilities_of_a_role() {
|
||||
return array(
|
||||
'an empty array' => array( 'capabilities' => array() ),
|
||||
'an array of capabilities' => array( 'capabilities' => array( 'level_2' => true ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*/
|
||||
public function test_update_role_should_change_display_name_and_capabilities_of_role() {
|
||||
global $wp_roles;
|
||||
$role_name = 'janitor';
|
||||
|
||||
add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
|
||||
$this->flush_roles();
|
||||
|
||||
$expected_display_name = 'Janitor Manager';
|
||||
$new_expected_caps = array(
|
||||
'level_3' => true,
|
||||
'level_4' => true,
|
||||
);
|
||||
update_role( $role_name, $expected_display_name, $new_expected_caps );
|
||||
$this->flush_roles();
|
||||
|
||||
$this->assertSame(
|
||||
$expected_display_name,
|
||||
$wp_roles->roles[ $role_name ]['name'],
|
||||
'The expected display name was not correct'
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
$new_expected_caps,
|
||||
$wp_roles->roles[ $role_name ]['capabilities'],
|
||||
'The expected capabilities were not correct'
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
remove_role( $role_name );
|
||||
$this->flush_roles();
|
||||
$this->assertFalse(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not removed"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 54572
|
||||
*
|
||||
* @covers WP_Roles::update_role
|
||||
* @covers ::update_role
|
||||
*/
|
||||
public function test_update_role_should_change_capabilities_of_a_user() {
|
||||
global $wp_roles;
|
||||
$role_name = 'janitor';
|
||||
|
||||
add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
|
||||
$this->flush_roles();
|
||||
|
||||
// Assign a user to the role.
|
||||
$id = self::factory()->user->create( array( 'role' => $role_name ) );
|
||||
|
||||
// Update empty capabilities.
|
||||
update_role( $role_name, null, array( 'level_2' => true ) );
|
||||
$this->flush_roles();
|
||||
|
||||
$this->assertTrue(
|
||||
user_can( $id, 'level_2' ),
|
||||
'The user does not have level_2 capabilities'
|
||||
);
|
||||
$this->assertFalse(
|
||||
user_can( $id, 'level_1' ),
|
||||
'The user has level_1 capabilities'
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
remove_role( $role_name );
|
||||
$this->flush_roles();
|
||||
$this->assertFalse(
|
||||
$wp_roles->is_role( $role_name ),
|
||||
"The $role_name role was not removed"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the capabilites associated with a role and make sure the change
|
||||
* is reflected in has_cap().
|
||||
|
Loading…
x
Reference in New Issue
Block a user