wiki.techinc.nl/maintenance/cleanupBlocks.php

154 lines
4.1 KiB
PHP
Raw Normal View History

<?php
/**
* Cleans up user blocks with user names not matching the 'user' table
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Maintenance
*/
require_once __DIR__ . '/Maintenance.php';
use MediaWiki\Block\DatabaseBlock;
/**
* Maintenance script to clean up user blocks with user names not matching the
* 'user' table.
*
* @ingroup Maintenance
*/
class CleanupBlocks extends Maintenance {
public function __construct() {
parent::__construct();
$this->addDescription( "Cleanup user blocks with user names not matching the 'user' table" );
$this->setBatchSize( 1000 );
}
public function execute() {
$db = $this->getDB( DB_PRIMARY );
$blockQuery = DatabaseBlock::getQueryInfo();
$max = $db->selectField( 'ipblocks', 'MAX(ipb_user)', [], __METHOD__ );
// Step 1: Clean up any duplicate user blocks
$batchSize = $this->getBatchSize();
for ( $from = 1; $from <= $max; $from += $batchSize ) {
$to = min( $max, $from + $batchSize - 1 );
$this->output( "Cleaning up duplicate ipb_user ($from-$to of $max)\n" );
$delete = [];
$res = $db->select(
'ipblocks',
[ 'ipb_user' ],
[
"ipb_user >= " . $from,
"ipb_user <= " . (int)$to,
],
__METHOD__,
[
'GROUP BY' => 'ipb_user',
'HAVING' => 'COUNT(*) > 1',
]
);
foreach ( $res as $row ) {
$bestBlock = null;
$res2 = $db->select(
$blockQuery['tables'],
$blockQuery['fields'],
[
'ipb_user' => $row->ipb_user,
],
__METHOD__,
[],
$blockQuery['joins']
);
foreach ( $res2 as $row2 ) {
$block = DatabaseBlock::newFromRow( $row2 );
if ( !$bestBlock ) {
$bestBlock = $block;
continue;
}
// Find the most-restrictive block.
$keep = null;
if ( $keep === null && $block->getExpiry() !== $bestBlock->getExpiry() ) {
// This works for infinite blocks because 'infinity' > '20141024234513'
$keep = $block->getExpiry() > $bestBlock->getExpiry();
}
if ( $keep === null ) {
Separate out different functionalities of Block::prevents Block::prevents plays several different roles: * acts as get/setter for Boolean properties that correspond to ipb_create_account, ipb_block_email and ipb_allow_usertalk * calculates whether a block blocks a given right, based on Block properties, global configs, white/blacklists and anonymous user rights * decides whether a block prevents editing of the target's own user talk page (listed separately because 'editownusertalk' is not a right) This patch: * renames mDisableUsertalk to allowEditUsertalk (and reverses the value), to match the field ipb_allow_usertalk and make this logic easier to follow * renames mCreateAccount to blockCreateAccount, to make it clear that the flag blocks account creation when true, and make this logic easier to follow * decouples the block that is stored in the database (which now reflects the form that the admin submitted) and the behaviour of the block on enforcement (since the properties set by the admin can be overridden by global configs) - so if the global configs change, the block behaviour could too * creates get/setters for blockCreateAccount, mBlockEmail and allowEditUsertalk properties * creates appliesToRight, exclusively for checking whether the block blocks a given right, taking into account the block properties, global configs and anonymous user rights * creates appliesToUsertalk, for checking whether the block blocks a user from editing their own talk page. The block is unaware of the user trying to make the edit, and this user is not always the same as the block target, e.g. if the block target is an IP range. Therefore the user's talk page is passed in to this method. appliesToUsertalk can be called from anywhere where the user is known * uses the get/setters wherever Block::prevents was being used as such * uses appliesToRight whenever Block::prevents was being used to determine if the block blocks a given right * uses appliesToUsertalk in User::isBlockedFrom Bug: T211578 Bug: T214508 Change-Id: I0e131696419211319082cb454f4f05297e55d22e
2019-02-09 12:17:54 +00:00
if ( $block->isCreateAccountBlocked() xor $bestBlock->isCreateAccountBlocked() ) {
$keep = $block->isCreateAccountBlocked();
} elseif ( $block->isEmailBlocked() xor $bestBlock->isEmailBlocked() ) {
$keep = $block->isEmailBlocked();
} elseif ( $block->isUsertalkEditAllowed() xor $bestBlock->isUsertalkEditAllowed() ) {
$keep = $block->isUsertalkEditAllowed();
}
}
if ( $keep ) {
$delete[] = $bestBlock->getId();
$bestBlock = $block;
} else {
$delete[] = $block->getId();
}
}
}
if ( $delete ) {
$db->delete(
'ipblocks',
[ 'ipb_id' => $delete ],
__METHOD__
);
}
}
// Step 2: Update the user name in any blocks where it doesn't match
for ( $from = 1; $from <= $max; $from += $batchSize ) {
$to = min( $max, $from + $batchSize - 1 );
$this->output( "Cleaning up mismatched user name ($from-$to of $max)\n" );
$res = $db->select(
[ 'ipblocks', 'user' ],
[ 'ipb_id', 'user_name' ],
[
'ipb_user = user_id',
"ipb_user >= " . $from,
"ipb_user <= " . (int)$to,
'ipb_address != user_name',
],
__METHOD__
);
foreach ( $res as $row ) {
$db->update(
'ipblocks',
[ 'ipb_address' => $row->user_name ],
[ 'ipb_id' => $row->ipb_id ],
__METHOD__
);
}
}
$this->output( "Done!\n" );
}
}
$maintClass = CleanupBlocks::class;
require_once RUN_MAINTENANCE_IF_MAIN;