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