Make WordPress Core

Changeset 54213


Ignore:
Timestamp:
09/19/2022 08:47:22 PM (4 years ago)
Author:
davidbaumwald
Message:

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.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/capabilities.php

    r53436 r54213  
    10321032
    10331033/**
     1034 * Updates an existing role. Creates a new role if it doesn't exist.
     1035 *
     1036 * Modifies the display name and/or capabilities for an existing role.
     1037 * If the role does not exist then a new role is created.
     1038 *
     1039 * The capabilities are defined in the following format: `array( 'read' => true )`.
     1040 * To explicitly deny the role a capability, set the value for that capability to false.
     1041 *
     1042 * @since 6.1.0
     1043 *
     1044 * @param string      $role         Role name.
     1045 * @param string|null $display_name Optional. Role display name. If null, the display name
     1046 *                                  is not modified. Default null.
     1047 * @param bool[]|null $capabilities Optional. List of capabilities keyed by the capability name,
     1048 *                                  e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`.
     1049 *                                  If null, don't alter capabilities for the existing role and make
     1050 *                                  empty capabilities for the new one. Default null.
     1051 * @return WP_Role|void WP_Role object, if the role is updated.
     1052 */
     1053function update_role( $role, $display_name = null, $capabilities = null ) {
     1054        return wp_roles()->update_role( $role, $display_name, $capabilities );
     1055}
     1056
     1057/**
    10341058 * Removes a role, if it exists.
    10351059 *
  • trunk/src/wp-includes/class-wp-roles.php

    r54133 r54213  
    174174
    175175        /**
     176         * Updates an existing role. Creates a new role if it doesn't exist.
     177         *
     178         * Modifies the display name and/or capabilities for an existing role.
     179         * If the role does not exist then a new role is created.
     180         *
     181         * The capabilities are defined in the following format: `array( 'read' => true )`.
     182         * To explicitly deny the role a capability, set the value for that capability to false.
     183         *
     184         * @since 6.1.0
     185         *
     186         * @param string      $role         Role name.
     187         * @param string|null $display_name Optional. Role display name. If null, the display name
     188         *                                  is not modified. Default null.
     189         * @param bool[]|null $capabilities Optional. List of capabilities keyed by the capability name,
     190         *                                  e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`.
     191         *                                  If null, don't alter capabilities for the existing role and make
     192         *                                  empty capabilities for the new one. Default null.
     193         * @return WP_Role|void WP_Role object, if the role is updated.
     194         */
     195        public function update_role( $role, $display_name = null, $capabilities = null ) {
     196                if ( ! is_string( $role ) || '' === trim( $role ) ) {
     197                        return;
     198                }
     199
     200                if ( null !== $display_name && ( ! is_string( $display_name ) || '' === trim( $display_name ) ) ) {
     201                        return;
     202                }
     203
     204                if ( null !== $capabilities && ! is_array( $capabilities ) ) {
     205                        return;
     206                }
     207
     208                if ( null === $display_name && null === $capabilities ) {
     209                        if ( isset( $this->role_objects[ $role ] ) ) {
     210                                return $this->role_objects[ $role ];
     211                        }
     212                        return;
     213                }
     214
     215                if ( null === $display_name ) {
     216                        if ( ! isset( $this->role_objects[ $role ] ) ) {
     217                                return;
     218                        }
     219
     220                        $display_name = $this->roles[ $role ]['name'];
     221                }
     222
     223                if ( null === $capabilities ) {
     224                        if ( isset( $this->role_objects[ $role ] ) ) {
     225                                $capabilities = $this->role_objects[ $role ]->capabilities;
     226                        } else {
     227                                $capabilities = array();
     228                        }
     229                }
     230
     231                if ( isset( $this->roles[ $role ] ) ) {
     232                        if ( null === $capabilities ) {
     233                                $capabilities = $this->role_objects[ $role ]->capabilities;
     234                        }
     235
     236                        unset( $this->role_objects[ $role ] );
     237                        unset( $this->role_names[ $role ] );
     238                        unset( $this->roles[ $role ] );
     239                }
     240
     241                // The roles database option will be updated in ::add_role().
     242                return $this->add_role( $role, $display_name, $capabilities );
     243        }
     244
     245        /**
    176246         * Removes a role by name.
    177247         *
  • trunk/tests/phpunit/tests/user/capabilities.php

    r54090 r54213  
    10001000
    10011001        /**
     1002         * @dataProvider data_update_role_unhappy_paths
     1003         *
     1004         * @ticket 54572
     1005         *
     1006         * @covers WP_Roles::update_role
     1007         * @covers ::update_role
     1008         *
     1009         * @param mixed $role         The role to update.
     1010         * @param mixed $display_name The display name for the role.
     1011         * @param mixed $capabilities The capabilities for the role.
     1012         */
     1013        public function test_update_role_unhappy_paths( $role, $display_name, $capabilities ) {
     1014                global $wp_roles;
     1015
     1016                // Create role if it does not exist.
     1017                $role_name     = 'janitor';
     1018                $expected_caps = array(
     1019                        'edit_posts' => true,
     1020                        'edit_pages' => true,
     1021                        'level_0'    => true,
     1022                        'level_1'    => true,
     1023                        'level_2'    => true,
     1024                );
     1025                add_role( $role_name, 'Janitor', $expected_caps );
     1026                $this->flush_roles();
     1027
     1028                $this->assertTrue(
     1029                        $wp_roles->is_role( $role_name ),
     1030                        "The $role_name role was not created"
     1031                );
     1032
     1033                $this->assertNotInstanceOf(
     1034                        'WP_Role',
     1035                        update_role( $role, $display_name, $capabilities ),
     1036                        "The $role_name role was updated"
     1037                );
     1038
     1039                // Clean up.
     1040                remove_role( $role_name );
     1041                $this->flush_roles();
     1042                $this->assertFalse(
     1043                        $wp_roles->is_role( $role_name ),
     1044                        "The $role_name role was not removed"
     1045                );
     1046        }
     1047
     1048        /**
     1049         * Data provider.
     1050         *
     1051         * @return array
     1052         */
     1053        public function data_update_role_unhappy_paths() {
     1054                return array(
     1055                        'true as the role'                       => array(
     1056                                'role'         => true,
     1057                                'display_name' => 'Janitor',
     1058                                'capabilities' => array(
     1059                                        'level_1' => true,
     1060                                ),
     1061                        ),
     1062                        'false as the role'                      => array(
     1063                                'role'         => false,
     1064                                'display_name' => 'Janitor',
     1065                                'capabilities' => array(
     1066                                        'level_1' => true,
     1067                                ),
     1068                        ),
     1069                        'null as the role'                       => array(
     1070                                'role'         => null,
     1071                                'display_name' => 'Janitor',
     1072                                'capabilities' => array(
     1073                                        'level_1' => true,
     1074                                ),
     1075                        ),
     1076                        '(int) 1 as the role'                    => array(
     1077                                'role'         => 1,
     1078                                'display_name' => 'Janitor',
     1079                                'capabilities' => array(
     1080                                        'level_1' => true,
     1081                                ),
     1082                        ),
     1083                        '(float) 1.0 as the role'                => array(
     1084                                'role'         => 1.0,
     1085                                'display_name' => 'Janitor',
     1086                                'capabilities' => array(
     1087                                        'level_1' => true,
     1088                                ),
     1089                        ),
     1090                        '(int) 0 as the role'                    => array(
     1091                                'role'         => 0,
     1092                                'display_name' => 'Janitor',
     1093                                'capabilities' => array(
     1094                                        'level_1' => true,
     1095                                ),
     1096                        ),
     1097                        '(float) 0.0 as the role'                => array(
     1098                                'role'         => 0.0,
     1099                                'display_name' => 'Janitor',
     1100                                'capabilities' => array(
     1101                                        'level_1' => true,
     1102                                ),
     1103                        ),
     1104                        'an empty string as the role'            => array(
     1105                                'role'         => '',
     1106                                'display_name' => 'Janitor',
     1107                                'capabilities' => array(
     1108                                        'level_1' => true,
     1109                                ),
     1110                        ),
     1111                        'a string with only a space as the role' => array(
     1112                                'role'         => ' ',
     1113                                'display_name' => 'Janitor',
     1114                                'capabilities' => array(
     1115                                        'level_1' => true,
     1116                                ),
     1117                        ),
     1118                        'an empty array as the role'             => array(
     1119                                'role'         => array(),
     1120                                'display_name' => 'Janitor',
     1121                                'capabilities' => array(
     1122                                        'level_1' => true,
     1123                                ),
     1124                        ),
     1125                        'a non-empty array as the role'          => array(
     1126                                'role'         => array( 'janitor' ),
     1127                                'display_name' => 'Janitor',
     1128                                'capabilities' => array(
     1129                                        'level_1' => true,
     1130                                ),
     1131                        ),
     1132                        'an object as the role'                  => array(
     1133                                'role'         => (object) array( 'janitor' ),
     1134                                'display_name' => 'Janitor',
     1135                                'capabilities' => array(
     1136                                        'level_1' => true,
     1137                                ),
     1138                        ),
     1139                        'true as the display name'               => array(
     1140                                'role'         => 'janitor',
     1141                                'display_name' => true,
     1142                                'capabilities' => array(
     1143                                        'level_1' => true,
     1144                                ),
     1145                        ),
     1146                        'false as the display name'              => array(
     1147                                'role'         => 'janitor',
     1148                                'display_name' => false,
     1149                                'capabilities' => array(
     1150                                        'level_1' => true,
     1151                                ),
     1152                        ),
     1153                        '(int) 1 as the display name'            => array(
     1154                                'role'         => 'janitor',
     1155                                'display_name' => 1,
     1156                                'capabilities' => array(
     1157                                        'level_1' => true,
     1158                                ),
     1159                        ),
     1160                        '(float) 1.0 as the display name'        => array(
     1161                                'role'         => 'janitor',
     1162                                'display_name' => 1.0,
     1163                                'capabilities' => array(
     1164                                        'level_1' => true,
     1165                                ),
     1166                        ),
     1167                        '(int) 0 as the display name'            => array(
     1168                                'role'         => 'janitor',
     1169                                'display_name' => 0,
     1170                                'capabilities' => array(
     1171                                        'level_1' => true,
     1172                                ),
     1173                        ),
     1174                        '(float) 0.0 as the display name'        => array(
     1175                                'role'         => 'janitor',
     1176                                'display_name' => 0.0,
     1177                                'capabilities' => array(
     1178                                        'level_1' => true,
     1179                                ),
     1180                        ),
     1181                        'an empty string as the display name'    => array(
     1182                                'role'         => 'janitor',
     1183                                'display_name' => '',
     1184                                'capabilities' => array(
     1185                                        'level_1' => true,
     1186                                ),
     1187                        ),
     1188                        'a string with only a space as the display name' => array(
     1189                                'role'         => 'janitor',
     1190                                'display_name' => ' ',
     1191                                'capabilities' => array(
     1192                                        'level_1' => true,
     1193                                ),
     1194                        ),
     1195                        'an empty array as the display name'     => array(
     1196                                'role'         => 'janitor',
     1197                                'display_name' => array(),
     1198                                'capabilities' => array(
     1199                                        'level_1' => true,
     1200                                ),
     1201                        ),
     1202                        'a non-empty array as the display name'  => array(
     1203                                'role'         => 'janitor',
     1204                                'display_name' => array( 'Janitor' ),
     1205                                'capabilities' => array(
     1206                                        'level_1' => true,
     1207                                ),
     1208                        ),
     1209                        'an object as the display name'          => array(
     1210                                'role'         => 'janitor',
     1211                                'display_name' => (object) array( 'Janitor' ),
     1212                                'capabilities' => array(
     1213                                        'level_1' => true,
     1214                                ),
     1215                        ),
     1216                        'true as the capabilities'               => array(
     1217                                'role'         => 'janitor',
     1218                                'display_name' => 'Janitor',
     1219                                'capabilities' => true,
     1220                        ),
     1221                        'false as the capabilities'              => array(
     1222                                'role'         => (object) array( 'janitor' ),
     1223                                'display_name' => 'Janitor',
     1224                                'capabilities' => false,
     1225                        ),
     1226                        '(int) 1 as the capabilities'            => array(
     1227                                'role'         => 'janitor',
     1228                                'display_name' => 'Janitor',
     1229                                'capabilities' => 1,
     1230                        ),
     1231                        '(float) 1.0 as the capabilities'        => array(
     1232                                'role'         => 'janitor',
     1233                                'display_name' => 'Janitor',
     1234                                'capabilities' => 1.0,
     1235                        ),
     1236                        '(int) 0 as the capabilities'            => array(
     1237                                'role'         => 'janitor',
     1238                                'display_name' => 'Janitor',
     1239                                'capabilities' => 0,
     1240                        ),
     1241                        '(float) 0.0 as the capabilities'        => array(
     1242                                'role'         => 'janitor',
     1243                                'display_name' => 'Janitor',
     1244                                'capabilities' => 0.0,
     1245                        ),
     1246                        'an empty string as the capabilities'    => array(
     1247                                'role'         => 'janitor',
     1248                                'display_name' => 'Janitor',
     1249                                'capabilities' => '',
     1250                        ),
     1251                        'a string with only a space as the capabilities' => array(
     1252                                'role'         => 'janitor',
     1253                                'display_name' => 'Janitor',
     1254                                'capabilities' => ' ',
     1255                        ),
     1256                        'an object as the capabilities'          => array(
     1257                                'role'         => 'janitor',
     1258                                'display_name' => 'Janitor',
     1259                                'capabilities' => (object) array( 'level_1' => true ),
     1260                        ),
     1261                );
     1262        }
     1263
     1264        /**
     1265         * @ticket 54572
     1266         *
     1267         * @covers WP_Roles::update_role
     1268         * @covers ::update_role
     1269         */
     1270        public function test_update_role_should_create_a_role_if_it_does_not_exist() {
     1271                global $wp_roles;
     1272
     1273                // Create role if it does not exist.
     1274                $role_name     = 'janitor';
     1275                $expected_caps = array(
     1276                        'edit_posts' => true,
     1277                        'edit_pages' => true,
     1278                        'level_0'    => true,
     1279                        'level_1'    => true,
     1280                        'level_2'    => true,
     1281                );
     1282                update_role( $role_name, 'Janitor', $expected_caps );
     1283                $this->flush_roles();
     1284                $this->assertTrue( $wp_roles->is_role( $role_name ) );
     1285        }
     1286
     1287        /**
     1288         * @ticket 54572
     1289         *
     1290         * @covers WP_Roles::update_role
     1291         * @covers ::update_role
     1292         */
     1293        public function test_update_role_should_change_the_display_name_of_a_role() {
     1294                global $wp_roles;
     1295                $role_name = 'janitor';
     1296
     1297                add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
     1298                $this->flush_roles();
     1299
     1300                $expected_display_name = 'Janitor Executive';
     1301                update_role( $role_name, $expected_display_name );
     1302                $this->flush_roles();
     1303
     1304                $this->assertSame(
     1305                        $expected_display_name,
     1306                        $wp_roles->roles[ $role_name ]['name'],
     1307                        'The expected display name was not correct'
     1308                );
     1309                $this->assertSame(
     1310                        array( 'level_1' => true ),
     1311                        $wp_roles->roles[ $role_name ]['capabilities'],
     1312                        'The expected capabilities were not correct'
     1313                );
     1314
     1315                // Clean up.
     1316                remove_role( $role_name );
     1317                $this->flush_roles();
     1318                $this->assertFalse(
     1319                        $wp_roles->is_role( $role_name ),
     1320                        "The $role_name role was not removed"
     1321                );
     1322        }
     1323
     1324        /**
     1325         * @dataProvider data_update_role_should_change_the_capabilities_of_a_role
     1326         *
     1327         * @ticket 54572
     1328         *
     1329         * @covers WP_Roles::update_role
     1330         * @covers ::update_role
     1331         *
     1332         * @param array $capabilities An array of capabilities.
     1333         */
     1334        public function test_update_role_should_change_the_capabilities_of_a_role( $capabilities ) {
     1335                global $wp_roles;
     1336                $role_name = 'janitor';
     1337
     1338                add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
     1339                $this->flush_roles();
     1340
     1341                update_role( $role_name, null, $capabilities );
     1342                $this->flush_roles();
     1343
     1344                $this->assertSame(
     1345                        'Janitor',
     1346                        $wp_roles->roles[ $role_name ]['name'],
     1347                        'The display name was changed'
     1348                );
     1349                $this->assertSame(
     1350                        $capabilities,
     1351                        $wp_roles->roles[ $role_name ]['capabilities'],
     1352                        'The expected capabilities were not correct'
     1353                );
     1354
     1355                // Clean up.
     1356                remove_role( $role_name );
     1357                $this->flush_roles();
     1358                $this->assertFalse(
     1359                        $wp_roles->is_role( $role_name ),
     1360                        "The $role_name role was not removed"
     1361                );
     1362        }
     1363
     1364        /**
     1365         * Data provider.
     1366         *
     1367         * @return array
     1368         */
     1369        public function data_update_role_should_change_the_capabilities_of_a_role() {
     1370                return array(
     1371                        'an empty array'           => array( 'capabilities' => array() ),
     1372                        'an array of capabilities' => array( 'capabilities' => array( 'level_2' => true ) ),
     1373                );
     1374        }
     1375
     1376        /**
     1377         * @ticket 54572
     1378         *
     1379         * @covers WP_Roles::update_role
     1380         * @covers ::update_role
     1381         */
     1382        public function test_update_role_should_change_display_name_and_capabilities_of_role() {
     1383                global $wp_roles;
     1384                $role_name = 'janitor';
     1385
     1386                add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
     1387                $this->flush_roles();
     1388
     1389                $expected_display_name = 'Janitor Manager';
     1390                $new_expected_caps     = array(
     1391                        'level_3' => true,
     1392                        'level_4' => true,
     1393                );
     1394                update_role( $role_name, $expected_display_name, $new_expected_caps );
     1395                $this->flush_roles();
     1396
     1397                $this->assertSame(
     1398                        $expected_display_name,
     1399                        $wp_roles->roles[ $role_name ]['name'],
     1400                        'The expected display name was not correct'
     1401                );
     1402
     1403                $this->assertSame(
     1404                        $new_expected_caps,
     1405                        $wp_roles->roles[ $role_name ]['capabilities'],
     1406                        'The expected capabilities were not correct'
     1407                );
     1408
     1409                // Clean up.
     1410                remove_role( $role_name );
     1411                $this->flush_roles();
     1412                $this->assertFalse(
     1413                        $wp_roles->is_role( $role_name ),
     1414                        "The $role_name role was not removed"
     1415                );
     1416        }
     1417
     1418        /**
     1419         * @ticket 54572
     1420         *
     1421         * @covers WP_Roles::update_role
     1422         * @covers ::update_role
     1423         */
     1424        public function test_update_role_should_change_capabilities_of_a_user() {
     1425                global $wp_roles;
     1426                $role_name = 'janitor';
     1427
     1428                add_role( $role_name, 'Janitor', array( 'level_1' => true ) );
     1429                $this->flush_roles();
     1430
     1431                // Assign a user to the role.
     1432                $id = self::factory()->user->create( array( 'role' => $role_name ) );
     1433
     1434                // Update empty capabilities.
     1435                update_role( $role_name, null, array( 'level_2' => true ) );
     1436                $this->flush_roles();
     1437
     1438                $this->assertTrue(
     1439                        user_can( $id, 'level_2' ),
     1440                        'The user does not have level_2 capabilities'
     1441                );
     1442                $this->assertFalse(
     1443                        user_can( $id, 'level_1' ),
     1444                        'The user has level_1 capabilities'
     1445                );
     1446
     1447                // Clean up.
     1448                remove_role( $role_name );
     1449                $this->flush_roles();
     1450                $this->assertFalse(
     1451                        $wp_roles->is_role( $role_name ),
     1452                        "The $role_name role was not removed"
     1453                );
     1454        }
     1455
     1456        /**
    10021457         * Change the capabilites associated with a role and make sure the change
    10031458         * is reflected in has_cap().
Note: See TracChangeset for help on using the changeset viewer.