Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
EmptyUserGroup
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
7
 createLogEntry
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Removes all users from a given user group.
5 *
6 * @license GPL-2.0-or-later
7 * @file
8 * @ingroup Maintenance
9 */
10
11use MediaWiki\Logging\ManualLogEntry;
12use MediaWiki\Maintenance\Maintenance;
13use MediaWiki\Title\Title;
14use MediaWiki\User\User;
15use MediaWiki\User\UserIdentity;
16
17// @codeCoverageIgnoreStart
18require_once __DIR__ . '/Maintenance.php';
19// @codeCoverageIgnoreEnd
20
21class EmptyUserGroup extends Maintenance {
22    public function __construct() {
23        parent::__construct();
24        $this->addDescription( 'Remove all users from a given user group' );
25        $this->addArg( 'group', 'Group to be removed', true );
26        $this->addOption(
27            'create-log',
28            'If specified, then log entries are created for each user in the group when emptying the user group.',
29        );
30        $this->addOption(
31            'log-reason',
32            'If create-log is specified, then this is used as the reason for the log entries created for ' .
33            'emptying the user group. If not provided, then the log will have no reason.',
34            false,
35            true
36        );
37        $this->setBatchSize( 100 );
38    }
39
40    public function execute() {
41        $group = $this->getArg( 0 );
42        $userGroupManager = $this->getServiceContainer()->getUserGroupManager();
43
44        $totalCount = 0;
45        $this->output( "Removing users from $group...\n" );
46        while ( true ) {
47            $users = User::findUsersByGroup( $group, $this->getBatchSize() );
48            if ( iterator_count( $users ) === 0 ) {
49                break;
50            }
51
52            foreach ( $users as $user ) {
53                $oldGroups = $userGroupManager->getUserGroups( $user );
54                $groupRemoved = $userGroupManager->removeUserFromGroup( $user, $group );
55
56                if ( $groupRemoved ) {
57                    $totalCount += 1;
58                    if ( $this->hasOption( 'create-log' ) ) {
59                        $this->createLogEntry( $user, $group, $oldGroups );
60                    }
61                }
62            }
63
64            $this->waitForReplication();
65        }
66        if ( $totalCount ) {
67            $this->output( "  ...done! Removed $totalCount users in total.\n" );
68        } else {
69            $this->output( "  ...nothing to do, group was empty.\n" );
70        }
71    }
72
73    /**
74     * Creates a log entry for a user having their groups changed.
75     *
76     * This does not send the log entry to recentchanges to avoid spamming the list of recent changes.
77     *
78     * @param UserIdentity $target
79     * @param string $removedGroup
80     * @param array $oldGroups
81     * @return void
82     */
83    private function createLogEntry( UserIdentity $target, string $removedGroup, array $oldGroups ) {
84        $newGroups = $oldGroups;
85        $newGroups = array_diff( $newGroups, [ $removedGroup ] );
86
87        $logEntry = new ManualLogEntry( 'rights', 'rights' );
88        $logEntry->setPerformer( User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] ) );
89        $logEntry->setTarget( Title::makeTitle( NS_USER, $target->getName() ) );
90        $logEntry->setComment( $this->getOption( 'log-reason', '' ) );
91        $logEntry->setParameters( [
92            '4::oldgroups' => $oldGroups,
93            '5::newgroups' => $newGroups,
94        ] );
95        $logEntry->insert();
96    }
97}
98
99// @codeCoverageIgnoreStart
100$maintClass = EmptyUserGroup::class;
101require_once RUN_MAINTENANCE_IF_MAIN;
102// @codeCoverageIgnoreEnd