Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.52% |
54 / 61 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
FixGlobalBlockWhitelist | |
98.18% |
54 / 55 |
|
66.67% |
2 / 3 |
8 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
97.14% |
34 / 35 |
|
0.00% |
0 / 1 |
3 | |||
handleDeletions | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\GlobalBlocking\Maintenance; |
4 | |
5 | use MediaWiki\Extension\GlobalBlocking\GlobalBlockingServices; |
6 | use MediaWiki\Maintenance\Maintenance; |
7 | |
8 | $IP = getenv( 'MW_INSTALL_PATH' ); |
9 | if ( $IP === false ) { |
10 | $IP = __DIR__ . '/../../..'; |
11 | } |
12 | require_once "$IP/maintenance/Maintenance.php"; |
13 | |
14 | /** |
15 | * This script can be used to purge global_block_whitelist rows which have no |
16 | * corresponding globalblocks table row. |
17 | */ |
18 | class FixGlobalBlockWhitelist extends Maintenance { |
19 | |
20 | protected bool $dryRun = false; |
21 | |
22 | public function __construct() { |
23 | parent::__construct(); |
24 | $this->addOption( 'dry-run', 'Run the script without any modifications' ); |
25 | $this->setBatchSize( 500 ); |
26 | |
27 | // Allow unregistered options so that users of the script which have specified the --delete option |
28 | // do not break. |
29 | $this->setAllowUnregisteredOptions( true ); |
30 | |
31 | $this->requireExtension( 'GlobalBlocking' ); |
32 | } |
33 | |
34 | public function execute() { |
35 | $this->dryRun = $this->getOption( 'dry-run', false ) !== false; |
36 | $localDbr = $this->getReplicaDB(); |
37 | |
38 | // First check if there are any rows in global_block_whitelist. If there are no rows, then exit now as there is |
39 | // nothing for this script to do. |
40 | $rowsExist = $localDbr->newSelectQueryBuilder() |
41 | ->select( 'gbw_id' ) |
42 | ->from( 'global_block_whitelist' ) |
43 | ->caller( __METHOD__ ) |
44 | ->limit( 1 ) |
45 | ->fetchRowCount(); |
46 | |
47 | if ( !$rowsExist ) { |
48 | $this->output( "No whitelist entries.\n" ); |
49 | return; |
50 | } |
51 | |
52 | $lastGlobalBlockId = 0; |
53 | $broken = []; |
54 | do { |
55 | // Select a batch of whitelist entries to check which start from a gbw_id greater than the greatest gbw_id |
56 | // from the last batch. |
57 | $localWhitelistIds = $localDbr->newSelectQueryBuilder() |
58 | ->select( 'gbw_id' ) |
59 | ->from( 'global_block_whitelist' ) |
60 | ->where( $localDbr->expr( 'gbw_id', '>', $lastGlobalBlockId ) ) |
61 | ->orderBy( 'gbw_id' ) |
62 | ->limit( $this->getBatchSize() ?? 500 ) |
63 | ->caller( __METHOD__ ) |
64 | ->fetchFieldValues(); |
65 | |
66 | // If there were no whitelist entries in the batch, then exit now as there is nothing more to do. |
67 | if ( !count( $localWhitelistIds ) ) { |
68 | break; |
69 | } |
70 | |
71 | // Find the associated global block rows for the whitelist entries in this batch. |
72 | $globalBlockingDbr = GlobalBlockingServices::wrap( $this->getServiceContainer() ) |
73 | ->getGlobalBlockingConnectionProvider() |
74 | ->getReplicaGlobalBlockingDatabase(); |
75 | $matchingGlobalBlockIds = $globalBlockingDbr->newSelectQueryBuilder() |
76 | ->select( 'gb_id' ) |
77 | ->from( 'globalblocks' ) |
78 | ->where( [ 'gb_id' => $localWhitelistIds ] ) |
79 | ->caller( __METHOD__ ) |
80 | ->fetchFieldValues(); |
81 | |
82 | $broken = array_merge( $broken, array_diff( $localWhitelistIds, $matchingGlobalBlockIds ) ); |
83 | } while ( count( $localWhitelistIds ) === ( $this->getBatchSize() ?? 500 ) ); |
84 | |
85 | $this->handleDeletions( $broken ); |
86 | } |
87 | |
88 | /** |
89 | * Handles the deletion of whitelist entries which have no corresponding global block. |
90 | * |
91 | * @param array $nonExistent An array of gbw_ids which have no corresponding global block |
92 | * @return void |
93 | */ |
94 | protected function handleDeletions( array $nonExistent ) { |
95 | $nonExistentCount = count( $nonExistent ); |
96 | if ( $nonExistentCount === 0 ) { |
97 | // Return early if there are no whitelist entries to be deleted. |
98 | $this->output( "All whitelist entries have corresponding global blocks.\n" ); |
99 | return; |
100 | } |
101 | $this->output( "Found $nonExistentCount whitelist entries with no corresponding global blocks with IDs:\n" |
102 | . implode( "\n", $nonExistent ) . "\n" |
103 | ); |
104 | if ( !$this->dryRun ) { |
105 | // Delete the whitelist entries which have no corresponding global block in batches of 'batch-size' |
106 | // targets. |
107 | foreach ( array_chunk( $nonExistent, $this->getBatchSize() ?? 500 ) as $chunk ) { |
108 | $this->getPrimaryDB()->newDeleteQueryBuilder() |
109 | ->deleteFrom( 'global_block_whitelist' ) |
110 | ->where( [ 'gbw_id' => $chunk ] ) |
111 | ->caller( __METHOD__ ) |
112 | ->execute(); |
113 | } |
114 | $this->output( "Finished deleting whitelist entries with no corresponding global blocks.\n" ); |
115 | } |
116 | } |
117 | } |
118 | |
119 | $maintClass = FixGlobalBlockWhitelist::class; |
120 | require_once RUN_MAINTENANCE_IF_MAIN; |