├── .actrc ├── .distignore ├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE ├── PULL_REQUEST_TEMPLATE ├── dependabot.yml └── workflows │ ├── code-quality.yml │ ├── regenerate-readme.yml │ └── testing.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── behat.yml ├── composer.json ├── features └── super-admin.feature ├── phpcs.xml.dist ├── src └── Super_Admin_Command.php ├── super-admin-command.php └── wp-cli.yml /.actrc: -------------------------------------------------------------------------------- 1 | # Configuration file for nektos/act. 2 | # See https://github.com/nektos/act#configuration 3 | -P ubuntu-latest=shivammathur/node:latest 4 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | .gitignore 4 | .gitlab-ci.yml 5 | .editorconfig 6 | .travis.yml 7 | behat.yml 8 | circle.yml 9 | phpcs.xml.dist 10 | phpunit.xml.dist 11 | bin/ 12 | features/ 13 | utils/ 14 | *.zip 15 | *.tar.gz 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | # From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | indent_style = tab 17 | 18 | [{*.yml,*.feature,.jshintrc,*.json}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [{*.txt,wp-config-sample.php}] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wp-cli/committers 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - scope:distribution 10 | - package-ecosystem: github-actions 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 10 15 | labels: 16 | - scope:distribution 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | jobs: 11 | code-quality: 12 | uses: wp-cli/.github/.github/workflows/reusable-code-quality.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/regenerate-readme.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate README file 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths-ignore: 10 | - "features/**" 11 | - "README.md" 12 | 13 | jobs: 14 | regenerate-readme: 15 | uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - master 10 | schedule: 11 | - cron: '17 1 * * *' # Run every day on a seemly random time. 12 | 13 | jobs: 14 | test: 15 | uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .phpcs.xml 3 | phpunit.xml 4 | phpcs.xml 5 | wp-cli.local.yml 6 | node_modules/ 7 | vendor/ 8 | *.zip 9 | *.tar.gz 10 | composer.lock 11 | *.log 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We appreciate you taking the initiative to contribute to this project. 5 | 6 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 7 | 8 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2011-2018 WP-CLI Development Group (https://github.com/wp-cli/super-admin-command/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wp-cli/super-admin-command 2 | ========================== 3 | 4 | Lists, adds, or removes super admin users on a multisite installation. 5 | 6 | [![Testing](https://github.com/wp-cli/super-admin-command/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/super-admin-command/actions/workflows/testing.yml) 7 | 8 | Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) | [Support](#support) 9 | 10 | ## Using 11 | 12 | This package implements the following commands: 13 | 14 | ### wp super-admin 15 | 16 | Lists, adds, or removes super admin users on a multisite installation. 17 | 18 | ~~~ 19 | wp super-admin 20 | ~~~ 21 | 22 | **EXAMPLES** 23 | 24 | # List user with super-admin capabilities. 25 | $ wp super-admin list 26 | supervisor 27 | administrator 28 | 29 | # Grant super-admin privileges to the user. 30 | $ wp super-admin add superadmin2 31 | Success: Granted super-admin capabilities to 1 user. 32 | 33 | # Revoke super-admin privileges from the user. 34 | $ wp super-admin remove superadmin2 35 | Success: Revoked super-admin capabilities from 1 user. 36 | 37 | 38 | 39 | ### wp super-admin add 40 | 41 | Grants super admin privileges to one or more users. 42 | 43 | ~~~ 44 | wp super-admin add ... 45 | ~~~ 46 | 47 | **OPTIONS** 48 | 49 | ... 50 | One or more user IDs, user emails, or user logins. 51 | 52 | **EXAMPLES** 53 | 54 | # Grant super-admin privileges to the user. 55 | $ wp super-admin add superadmin2 56 | Success: Granted super-admin capabilities to 1 user. 57 | 58 | 59 | 60 | ### wp super-admin list 61 | 62 | Lists users with super admin capabilities. 63 | 64 | ~~~ 65 | wp super-admin list [--format=] 66 | ~~~ 67 | 68 | **OPTIONS** 69 | 70 | [--format=] 71 | Render output in a particular format. 72 | --- 73 | default: list 74 | options: 75 | - list 76 | - table 77 | - csv 78 | - json 79 | - count 80 | - ids 81 | - yaml 82 | --- 83 | 84 | **EXAMPLES** 85 | 86 | # List user with super-admin capabilities. 87 | $ wp super-admin list 88 | supervisor 89 | administrator 90 | 91 | 92 | 93 | ### wp super-admin remove 94 | 95 | Removes super admin privileges from one or more users. 96 | 97 | ~~~ 98 | wp super-admin remove ... 99 | ~~~ 100 | 101 | **OPTIONS** 102 | 103 | ... 104 | One or more user IDs, user emails, or user logins. 105 | 106 | **EXAMPLES** 107 | 108 | # Revoke super-admin privileges from the user. 109 | $ wp super-admin remove superadmin2 110 | Success: Revoked super-admin capabilities from 1 user. 111 | 112 | ## Installing 113 | 114 | This package is included with WP-CLI itself, no additional installation necessary. 115 | 116 | To install the latest version of this package over what's included in WP-CLI, run: 117 | 118 | wp package install git@github.com:wp-cli/super-admin-command.git 119 | 120 | ## Contributing 121 | 122 | We appreciate you taking the initiative to contribute to this project. 123 | 124 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 125 | 126 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 127 | 128 | ### Reporting a bug 129 | 130 | Think you’ve found a bug? We’d love for you to help us get it fixed. 131 | 132 | Before you create a new issue, you should [search existing issues](https://github.com/wp-cli/super-admin-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. 133 | 134 | Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/super-admin-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). 135 | 136 | ### Creating a pull request 137 | 138 | Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/super-admin-command/issues/new) to discuss whether the feature is a good fit for the project. 139 | 140 | Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. 141 | 142 | ## Support 143 | 144 | GitHub issues aren't for general support questions, but there are other venues you can try: https://wp-cli.org/#support 145 | 146 | 147 | *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* 148 | -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - WP_CLI\Tests\Context\FeatureContext 6 | paths: 7 | - features 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cli/super-admin-command", 3 | "type": "wp-cli-package", 4 | "description": "Lists, adds, or removes super admin users on a multisite installation.", 5 | "homepage": "https://github.com/wp-cli/super-admin-command", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Daniel Bachhuber", 10 | "email": "daniel@runcommand.io", 11 | "homepage": "https://runcommand.io" 12 | } 13 | ], 14 | "require": { 15 | "wp-cli/wp-cli": "^2.12" 16 | }, 17 | "require-dev": { 18 | "wp-cli/entity-command": "^1.3 || ^2", 19 | "wp-cli/wp-cli-tests": "^4" 20 | }, 21 | "config": { 22 | "process-timeout": 7200, 23 | "sort-packages": true, 24 | "allow-plugins": { 25 | "dealerdirect/phpcodesniffer-composer-installer": true, 26 | "johnpbloch/wordpress-core-installer": true 27 | }, 28 | "lock": false 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-main": "2.x-dev" 33 | }, 34 | "bundled": true, 35 | "commands": [ 36 | "super-admin", 37 | "super-admin add", 38 | "super-admin list", 39 | "super-admin remove" 40 | ] 41 | }, 42 | "autoload": { 43 | "classmap": [ 44 | "src/" 45 | ], 46 | "files": [ 47 | "super-admin-command.php" 48 | ] 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true, 52 | "scripts": { 53 | "behat": "run-behat-tests", 54 | "behat-rerun": "rerun-behat-tests", 55 | "lint": "run-linter-tests", 56 | "phpcs": "run-phpcs-tests", 57 | "phpcbf": "run-phpcbf-cleanup", 58 | "phpunit": "run-php-unit-tests", 59 | "prepare-tests": "install-package-tests", 60 | "test": [ 61 | "@lint", 62 | "@phpcs", 63 | "@phpunit", 64 | "@behat" 65 | ] 66 | }, 67 | "support": { 68 | "issues": "https://github.com/wp-cli/super-admin-command/issues" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /features/super-admin.feature: -------------------------------------------------------------------------------- 1 | Feature: Manage super admins associated with a multisite instance 2 | 3 | Scenario: Add, list, and remove super admins. 4 | Given a WP multisite installation 5 | 6 | When I run `wp user create superadmin superadmin@example.com` 7 | And I run `wp super-admin list` 8 | Then STDOUT should be: 9 | """ 10 | admin 11 | """ 12 | 13 | When I run `wp super-admin add superadmin` 14 | Then STDOUT should be: 15 | """ 16 | Success: Granted super-admin capabilities to 1 user. 17 | """ 18 | And the return code should be 0 19 | 20 | When I run `wp super-admin list` 21 | Then STDOUT should be: 22 | """ 23 | admin 24 | superadmin 25 | """ 26 | 27 | When I try `wp super-admin add superadmin` 28 | Then STDERR should be: 29 | """ 30 | Warning: User 'superadmin' already has super-admin capabilities. 31 | """ 32 | And STDOUT should be: 33 | """ 34 | Success: Super admins remain unchanged. 35 | """ 36 | And the return code should be 0 37 | 38 | When I run `wp super-admin list` 39 | Then STDOUT should be: 40 | """ 41 | admin 42 | superadmin 43 | """ 44 | 45 | When I run `wp super-admin list --format=table` 46 | Then STDOUT should be a table containing rows: 47 | | user_login | 48 | | admin | 49 | | superadmin | 50 | 51 | When I run `wp super-admin remove admin` 52 | And I run `wp super-admin list` 53 | Then STDOUT should be: 54 | """ 55 | superadmin 56 | """ 57 | 58 | When I run `wp super-admin list --format=json` 59 | Then STDOUT should be: 60 | """ 61 | [{"user_login":"superadmin"}] 62 | """ 63 | 64 | When I try `wp super-admin add noadmin` 65 | Then STDERR should be: 66 | """ 67 | Warning: Invalid user ID, email or login: 'noadmin' 68 | Error: Couldn't grant super-admin capabilities to 1 of 1 users. 69 | """ 70 | And the return code should be 1 71 | 72 | When I try `wp super-admin add admin noadmin` 73 | Then STDERR should be: 74 | """ 75 | Warning: Invalid user ID, email or login: 'noadmin' 76 | Error: Only granted super-admin capabilities to 1 of 2 users. 77 | """ 78 | And the return code should be 1 79 | 80 | When I try `wp super-admin remove noadmin` 81 | Then STDERR should be: 82 | """ 83 | Warning: Invalid user ID, email or login: 'noadmin' 84 | Error: The given user is not a super admin. 85 | """ 86 | And the return code should be 1 87 | 88 | When I try `wp super-admin remove admin admin@example.com noadmin superadmin` 89 | Then STDERR should be: 90 | """ 91 | Warning: Invalid user ID, email or login: 'noadmin' 92 | """ 93 | And STDOUT should be: 94 | """ 95 | Success: Revoked super-admin capabilities from 2 of 3 users. There are no remaining super admins. 96 | """ 97 | 98 | When I run `wp super-admin add superadmin` 99 | And I try `wp super-admin remove admin superadmin` 100 | Then STDOUT should be: 101 | """ 102 | Success: Revoked super-admin capabilities from 1 of 2 users. There are no remaining super admins. 103 | """ 104 | And STDERR should be empty 105 | 106 | When I run `wp super-admin list` 107 | Then STDOUT should be empty 108 | 109 | When I try `wp super-admin remove superadmin` 110 | Then STDERR should be: 111 | """ 112 | Error: No super admins to revoke super-admin privileges from. 113 | """ 114 | And STDOUT should be empty 115 | And the return code should be 1 116 | 117 | When I run `wp super-admin add superadmin admin` 118 | And I run `wp super-admin remove superadmin admin` 119 | Then STDOUT should be: 120 | """ 121 | Success: Revoked super-admin capabilities from 2 users. There are no remaining super admins. 122 | """ 123 | And STDERR should be empty 124 | 125 | When I run `wp super-admin list` 126 | Then STDOUT should be empty 127 | 128 | When I run `wp user create admin2 admin2@example.com` 129 | And I run `wp super-admin add superadmin admin admin2` 130 | And I run `wp super-admin list` 131 | Then STDOUT should be: 132 | """ 133 | superadmin 134 | admin 135 | admin2 136 | """ 137 | 138 | When I run `wp eval 'global $wpdb; $wpdb->delete( $wpdb->users, array( "user_login" => "admin2" ) );'` 139 | And I run `wp user list --field=user_login --orderby=user_login` 140 | Then STDOUT should be: 141 | """ 142 | admin 143 | superadmin 144 | """ 145 | 146 | When I run `wp super-admin list` 147 | Then STDOUT should be: 148 | """ 149 | superadmin 150 | admin 151 | admin2 152 | """ 153 | 154 | When I try `wp super-admin remove admin2` 155 | Then STDERR should be: 156 | """ 157 | Warning: Invalid user ID, email or login: 'admin2' 158 | """ 159 | And STDOUT should be: 160 | """ 161 | Success: Revoked super-admin capabilities from 1 user. 162 | """ 163 | 164 | When I try `wp super-admin remove 999999` 165 | Then STDERR should be: 166 | """ 167 | Warning: Invalid user ID, email or login: '999999' 168 | Error: No valid user logins given to revoke super-admin privileges from. 169 | """ 170 | And STDOUT should be empty 171 | And the return code should be 1 172 | 173 | When I run `wp user create notadmin notadmin@example.com` 174 | And I try `wp super-admin remove notadmin notuser` 175 | Then STDERR should be: 176 | """ 177 | Warning: Invalid user ID, email or login: 'notuser' 178 | Error: None of the given users is a super admin. 179 | """ 180 | And STDOUT should be empty 181 | And the return code should be 1 182 | 183 | Scenario: List super admins in ids format. 184 | Given a WP multisite installation 185 | 186 | When I run `wp user get admin --field=ID` 187 | Then save STDOUT as {USER_1} 188 | 189 | When I run `wp user create admin2 admin2@example.com --porcelain` 190 | Then save STDOUT as {USER_2} 191 | 192 | When I run `wp super-admin add admin2` 193 | And I run `wp super-admin list --format=ids` 194 | Then STDOUT should be: 195 | """ 196 | {USER_1} {USER_2} 197 | """ 198 | 199 | Scenario: Manage a super admin user_login 'admin' 200 | Given a WP multisite installation 201 | 202 | When I run `wp user get admin --field=user_login` 203 | Then STDOUT should contain: 204 | """ 205 | admin 206 | """ 207 | 208 | When I try `wp super-admin add admin` 209 | Then STDOUT should be: 210 | """ 211 | Success: Super admins remain unchanged. 212 | """ 213 | And STDERR should be: 214 | """ 215 | Warning: User 'admin' already has super-admin capabilities. 216 | """ 217 | 218 | When I run `wp super-admin remove admin` 219 | Then STDOUT should be: 220 | """ 221 | Success: Revoked super-admin capabilities from 1 user. There are no remaining super admins. 222 | """ 223 | 224 | When I run `wp super-admin list` 225 | Then STDOUT should be empty 226 | 227 | When I run `wp super-admin add admin` 228 | Then STDOUT should be: 229 | """ 230 | Success: Granted super-admin capabilities to 1 user. 231 | """ 232 | And STDERR should be empty 233 | 234 | When I run `wp super-admin list` 235 | Then STDOUT should be: 236 | """ 237 | admin 238 | """ 239 | 240 | Scenario: Handle a site with an empty site_admins option without errors 241 | Given a WP multisite installation 242 | 243 | When I run `wp site option set site_admins ''` 244 | Then STDOUT should be: 245 | """ 246 | Success: Updated 'site_admins' site option. 247 | """ 248 | And STDERR should be empty 249 | 250 | When I run `wp super-admin list` 251 | Then STDERR should be empty 252 | 253 | Scenario: Hooks should be firing as expected 254 | Given a WP multisite installation 255 | And a wp-content/mu-plugins/test-hooks.php file: 256 | """ 257 | 2 | 3 | Custom ruleset for WP-CLI super-admin-command 4 | 5 | 12 | 13 | 14 | . 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | */src/Super_Admin_Command\.php$ 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Super_Admin_Command.php: -------------------------------------------------------------------------------- 1 | fetcher = new UserFetcher(); 35 | } 36 | 37 | /** 38 | * Lists users with super admin capabilities. 39 | * 40 | * ## OPTIONS 41 | * 42 | * [--format=] 43 | * : Render output in a particular format. 44 | * --- 45 | * default: list 46 | * options: 47 | * - list 48 | * - table 49 | * - csv 50 | * - json 51 | * - count 52 | * - ids 53 | * - yaml 54 | * --- 55 | * 56 | * ## EXAMPLES 57 | * 58 | * # List user with super-admin capabilities. 59 | * $ wp super-admin list 60 | * supervisor 61 | * administrator 62 | * 63 | * @subcommand list 64 | */ 65 | public function list_subcommand( $_, $assoc_args ) { 66 | $super_admins = self::get_admins(); 67 | 68 | if ( 'list' === $assoc_args['format'] ) { 69 | foreach ( $super_admins as $user_login ) { 70 | WP_CLI::line( $user_login ); 71 | } 72 | } else { 73 | $output_users = []; 74 | foreach ( $super_admins as $user_login ) { 75 | $output_user = new stdClass(); 76 | 77 | $output_user->user_login = $user_login; 78 | 79 | $output_users[] = $output_user; 80 | } 81 | 82 | if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) { 83 | $formatter = new \WP_CLI\Formatter( $assoc_args ); 84 | 85 | $user_ids = []; 86 | foreach ( $super_admins as $user_login ) { 87 | $user_obj = get_user_by( 'login', $user_login ); 88 | $user_ids[] = $user_obj->ID; 89 | } 90 | $formatter->display_items( $user_ids ); 91 | } else { 92 | $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields ); 93 | $formatter->display_items( $output_users ); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Grants super admin privileges to one or more users. 100 | * 101 | * ## OPTIONS 102 | * 103 | * ... 104 | * : One or more user IDs, user emails, or user logins. 105 | * 106 | * ## EXAMPLES 107 | * 108 | * # Grant super-admin privileges to the user. 109 | * $ wp super-admin add superadmin2 110 | * Success: Granted super-admin capabilities to 1 user. 111 | */ 112 | public function add( $args, $_ ) { 113 | 114 | $successes = 0; 115 | $errors = 0; 116 | $users = $this->fetcher->get_many( $args ); 117 | 118 | if ( count( $users ) !== count( $args ) ) { 119 | $errors = count( $args ) - count( $users ); 120 | } 121 | 122 | $super_admins = self::get_admins(); 123 | $num_super_admins = count( $super_admins ); 124 | 125 | $new_super_admins = []; 126 | foreach ( $users as $user ) { 127 | do_action( 'grant_super_admin', (int) $user->ID ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 128 | 129 | if ( in_array( $user->user_login, $super_admins, true ) ) { 130 | WP_CLI::warning( "User '{$user->user_login}' already has super-admin capabilities." ); 131 | continue; 132 | } 133 | 134 | $new_super_admins[] = $user->ID; 135 | $super_admins[] = $user->user_login; 136 | ++$successes; 137 | } 138 | 139 | if ( count( $super_admins ) === $num_super_admins ) { 140 | if ( $errors ) { 141 | $user_count = count( $args ); 142 | WP_CLI::error( "Couldn't grant super-admin capabilities to {$errors} of {$user_count} users." ); 143 | } else { 144 | WP_CLI::success( 'Super admins remain unchanged.' ); 145 | } 146 | } elseif ( update_site_option( 'site_admins', $super_admins ) ) { 147 | if ( $errors ) { 148 | $user_count = count( $args ); 149 | WP_CLI::error( "Only granted super-admin capabilities to {$successes} of {$user_count} users." ); 150 | } else { 151 | $message = $successes > 1 ? 'users' : 'user'; 152 | WP_CLI::success( "Granted super-admin capabilities to {$successes} {$message}." ); 153 | } 154 | } else { 155 | WP_CLI::error( 'Site options update failed.' ); 156 | } 157 | 158 | foreach ( $new_super_admins as $user_id ) { 159 | do_action( 'granted_super_admin', (int) $user_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 160 | } 161 | } 162 | 163 | /** 164 | * Removes super admin privileges from one or more users. 165 | * 166 | * ## OPTIONS 167 | * 168 | * ... 169 | * : One or more user IDs, user emails, or user logins. 170 | * 171 | * ## EXAMPLES 172 | * 173 | * # Revoke super-admin privileges from the user. 174 | * $ wp super-admin remove superadmin2 175 | * Success: Revoked super-admin capabilities from 1 user. 176 | */ 177 | public function remove( $args, $_ ) { 178 | $users = $this->fetcher->get_many( $args ); 179 | $user_logins = $users ? array_values( array_unique( wp_list_pluck( $users, 'user_login' ) ) ) : []; 180 | $user_logins_count = count( $user_logins ); 181 | 182 | $user_ids = []; 183 | foreach ( $users as $user ) { 184 | $user_ids[ $user->user_login ] = $user->ID; 185 | 186 | do_action( 'revoke_super_admin', (int) $user->ID ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 187 | } 188 | 189 | $super_admins = self::get_admins(); 190 | if ( ! $super_admins ) { 191 | WP_CLI::error( 'No super admins to revoke super-admin privileges from.' ); 192 | } 193 | 194 | if ( $user_logins_count < count( $args ) ) { 195 | $flipped_user_logins = array_flip( $user_logins ); 196 | // Fetcher has already warned so don't bother here, but continue with any args that are possible login names to cater for invalid users in the site options meta. 197 | 198 | $user_logins = array_merge( 199 | $user_logins, 200 | array_unique( 201 | array_filter( 202 | $args, 203 | static function ( $v ) use ( $flipped_user_logins ) { 204 | // Exclude numeric and email-like logins (login names can be email-like but ignore this given the circumstances). 205 | return ! isset( $flipped_user_logins[ $v ] ) && ! is_numeric( $v ) && ! is_email( $v ); 206 | } 207 | ) 208 | ) 209 | ); 210 | $user_logins_count = count( $user_logins ); 211 | } 212 | if ( ! $user_logins ) { 213 | WP_CLI::error( 'No valid user logins given to revoke super-admin privileges from.' ); 214 | } 215 | 216 | $update_super_admins = array_diff( $super_admins, $user_logins ); 217 | if ( $update_super_admins === $super_admins ) { 218 | WP_CLI::error( $user_logins_count > 1 ? 'None of the given users is a super admin.' : 'The given user is not a super admin.' ); 219 | } 220 | 221 | update_site_option( 'site_admins', $update_super_admins ); 222 | 223 | $successes = count( $super_admins ) - count( $update_super_admins ); 224 | if ( $successes === $user_logins_count ) { 225 | $message = $user_logins_count > 1 ? 'users' : 'user'; 226 | $msg = "Revoked super-admin capabilities from {$user_logins_count} {$message}."; 227 | } else { 228 | $msg = "Revoked super-admin capabilities from {$successes} of {$user_logins_count} users."; 229 | } 230 | if ( ! $update_super_admins ) { 231 | $msg .= ' There are no remaining super admins.'; 232 | } 233 | WP_CLI::success( $msg ); 234 | 235 | $removed_logins = array_intersect( $user_logins, $super_admins ); 236 | 237 | foreach ( $removed_logins as $user_login ) { 238 | $user_id = null; 239 | 240 | if ( array_key_exists( $user_login, $user_ids ) ) { 241 | $user_id = $user_ids[ $user_login ]; 242 | } else { 243 | $user = get_user_by( 'login', $user_login ); 244 | if ( $user instanceof WP_User ) { 245 | $user_id = $user->ID; 246 | } 247 | } 248 | 249 | if ( null !== $user_id ) { 250 | do_action( 'revoked_super_admin', (int) $user_ids[ $user_login ] ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 251 | } 252 | } 253 | } 254 | 255 | private static function get_admins() { 256 | // We don't use get_super_admins() because we don't want to mess with the global 257 | return (array) get_site_option( 'site_admins', [ 'admin' ] ); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /super-admin-command.php: -------------------------------------------------------------------------------- 1 | function () { 17 | if ( ! is_multisite() ) { 18 | WP_CLI::error( 'This is not a multisite installation.' ); 19 | } 20 | }, 21 | ) 22 | ); 23 | -------------------------------------------------------------------------------- /wp-cli.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - super-admin-command.php 3 | --------------------------------------------------------------------------------