MediaWiki master
demoteIneligibleUsers.php
Go to the documentation of this file.
1<?php
17
18// @codeCoverageIgnoreStart
19require_once __DIR__ . '/Maintenance.php';
20// @codeCoverageIgnoreEnd
21
26 public function __construct() {
27 parent::__construct();
28 $this->addDescription( 'Demote users who no longer meet conditions for group membership' );
29 $this->addOption( 'dry-run', 'Perform a dry run' );
30 $this->addOption(
31 'relay-log',
32 'Groups for which the rights changes should be logged on designated remote wikis. ' .
33 'Use syntax group=wiki. This setting can be specified many times.',
34 false,
35 true,
36 false,
37 true
38 );
39 }
40
41 public function execute() {
42 $dryRun = $this->hasOption( 'dry-run' );
43
44 // Keyed by group name, value is array of wikis to relay the logs to
45 $relayLog = $this->readRelayLog();
46
47 $services = $this->getServiceContainer();
48 $restrictedGroups = $services->getRestrictedUserGroupConfigReader()->getConfig();
49
50 $demotableGroups = array_filter(
51 $restrictedGroups,
52 static fn ( $restriction ) => $restriction->allowsAutomaticDemotion()
53 );
54
55 if ( !$demotableGroups ) {
56 $this->output( "No groups are configured for automatic demotion, exiting.\n" );
57 return;
58 }
59
60 $dbr = $this->getReplicaDB();
61 $groupMembers = $dbr->newSelectQueryBuilder()
62 ->select( [ 'user_id', 'user_name', 'ug_group' ] )
63 ->from( 'user' )
64 ->join( 'user_groups', null, 'ug_user = user_id' )
65 ->where( [ 'ug_group' => array_keys( $demotableGroups ) ] )
66 ->caller( __METHOD__ )
67 ->fetchResultSet();
68
69 // Remove all groups later at once, not to produce multiple logs for a single user; both arrays keyed by user ID
70 $userIdentities = [];
71 $groupsToRemove = [];
72
73 $userRequirementsChecker = $services->getUserRequirementsConditionChecker();
74 $userFactory = $services->getUserFactory();
75 foreach ( $groupMembers as $member ) {
76 $userIdentity = UserIdentityValue::newRegistered( $member->user_id, $member->user_name );
77 $groupConditions = $demotableGroups[$member->ug_group]->getMemberConditions();
78
79 if (
80 $userRequirementsChecker->recursivelyCheckCondition( $groupConditions, $userIdentity )
81 || $userFactory->newFromUserIdentity( $userIdentity )->isSystemUser()
82 ) {
83 continue;
84 }
85
86 $userIdentities[$userIdentity->getId()] = $userIdentity;
87 $groupsToRemove[$userIdentity->getId()][] = $member->ug_group;
88 }
89
90 if ( !$userIdentities ) {
91 $this->output( "No ineligible users found, exiting.\n" );
92 return;
93 }
94
95 $numUsers = count( $userIdentities );
96 if ( $dryRun ) {
97 $this->output( "DRY RUN: $numUsers users would be affected normally\n" );
98 } else {
99 $this->output( "Removing groups from $numUsers users...\n" );
100 }
101
102 $performingUser = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
103 $performingAuthority = new UltimateAuthority( $performingUser );
104
105 $groupAssignmentService = $services->getUserGroupAssignmentService();
106 $removedText = $dryRun ? 'Would remove' : 'Removed';
107 foreach ( $userIdentities as $userIdentity ) {
108 $userName = $userIdentity->getName();
109 $removeUserGroups = $groupsToRemove[$userIdentity->getId()];
110 if ( !$dryRun ) {
111 $logReason = wfMessage( 'restrictedgroups-autodemotion-log-reason' )
112 ->params( $userName )
113 ->numParams( count( $removeUserGroups ) )
114 ->text();
115 $additionalWikis = [];
116 foreach ( $removeUserGroups as $group ) {
117 if ( isset( $relayLog[$group] ) ) {
118 $additionalWikis = array_merge( $additionalWikis, $relayLog[$group] );
119 }
120 }
121 $groupAssignmentService->saveChangesToUserGroups(
122 $performingAuthority, $userIdentity, [], $removeUserGroups, [], $logReason, [], $additionalWikis );
123 }
124 $groupsList = implode( ', ', $removeUserGroups );
125 $this->output( "$removedText groups from $userName: $groupsList\n" );
126 }
127 $this->output( "Finished processing. $removedText groups from $numUsers users\n" );
128 }
129
135 private function readRelayLog(): array {
136 if ( !$this->hasOption( 'relay-log' ) ) {
137 return [];
138 }
139 $additionalWikis = [];
140 foreach ( $this->getOption( 'relay-log' ) as $relayOption ) {
141 $parts = explode( '=', $relayOption );
142 if ( count( $parts ) !== 2 ) {
143 $this->output( "Invalid relay-log option: $relayOption, skipping.\n" );
144 continue;
145 }
146 [ $group, $wiki ] = $parts;
147 $additionalWikis[$group][] = $wiki;
148 }
149 return $additionalWikis;
150 }
151}
152
153// @codeCoverageIgnoreStart
154$maintClass = DemoteIneligibleUsers::class;
155require_once RUN_MAINTENANCE_IF_MAIN;
156// @codeCoverageIgnoreEnd
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
execute()
Do the actual work.
__construct()
Default constructor.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
output( $out, $channel=null)
Throw some output to the user.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
hasOption( $name)
Checks to see if a particular option was set.
getOption( $name, $default=null)
Get an option, or return the default.
getReplicaDB(string|false $virtualDomain=false)
getServiceContainer()
Returns the main service container.
addDescription( $text)
Set the description text.
Represents an authority that has all permissions.
Value object representing a user's identity.
User class for the MediaWiki software.
Definition User.php:130