Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CleanupBlocks
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 2
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
240
1<?php
2/**
3 * Cleans up user blocks with user names not matching the 'user' table
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 */
23
24require_once __DIR__ . '/Maintenance.php';
25
26use MediaWiki\Block\DatabaseBlock;
27
28/**
29 * Maintenance script to clean up user blocks with user names not matching the
30 * 'user' table.
31 *
32 * TODO: delete this script after the block_target schema migration is complete
33 *
34 * @ingroup Maintenance
35 */
36class CleanupBlocks extends Maintenance {
37
38    public function __construct() {
39        parent::__construct();
40        $this->addDescription( "Cleanup user blocks with user names not matching the 'user' table" );
41        $this->setBatchSize( 1000 );
42    }
43
44    public function execute() {
45        $db = $this->getPrimaryDB();
46        $blockQuery = DatabaseBlock::getQueryInfo();
47
48        $max = $db->newSelectQueryBuilder()
49            ->select( 'MAX(ipb_user)' )
50            ->from( 'ipblocks' )
51            ->caller( __METHOD__ )
52            ->fetchField();
53
54        // Step 1: Clean up any duplicate user blocks
55        $batchSize = $this->getBatchSize();
56        for ( $from = 1; $from <= $max; $from += $batchSize ) {
57            $to = min( $max, $from + $batchSize - 1 );
58            $this->output( "Cleaning up duplicate ipb_user ($from-$to of $max)\n" );
59
60            $delete = [];
61
62            $res = $db->newSelectQueryBuilder()
63                ->select( 'ipb_user' )
64                ->from( 'ipblocks' )
65                ->where( [
66                    'ipb_user >= ' . $from,
67                    'ipb_user <= ' . (int)$to,
68                ] )
69                ->groupBy( 'ipb_user' )
70                ->having( 'COUNT(*) > 1' )
71                ->caller( __METHOD__ )
72                ->fetchResultSet();
73            foreach ( $res as $row ) {
74                $bestBlock = null;
75                $res2 = $db->newSelectQueryBuilder()
76                    ->select( $blockQuery['fields'] )
77                    ->tables( $blockQuery['tables'] )
78                    ->where( [
79                        'ipb_user' => $row->ipb_user,
80                    ] )
81                    ->joinConds( $blockQuery['joins'] )
82                    ->caller( __METHOD__ )
83                    ->fetchResultSet();
84                foreach ( $res2 as $row2 ) {
85                    $block = DatabaseBlock::newFromRow( $row2 );
86                    if ( !$bestBlock ) {
87                        $bestBlock = $block;
88                        continue;
89                    }
90
91                    // Find the most-restrictive block.
92                    $keep = null;
93                    if ( $keep === null && $block->getExpiry() !== $bestBlock->getExpiry() ) {
94                        // This works for infinite blocks because 'infinity' > '20141024234513'
95                        $keep = $block->getExpiry() > $bestBlock->getExpiry();
96                    }
97                    if ( $keep === null ) {
98                        if ( $block->isCreateAccountBlocked() xor $bestBlock->isCreateAccountBlocked() ) {
99                            $keep = $block->isCreateAccountBlocked();
100                        } elseif ( $block->isEmailBlocked() xor $bestBlock->isEmailBlocked() ) {
101                            $keep = $block->isEmailBlocked();
102                        } elseif ( $block->isUsertalkEditAllowed() xor $bestBlock->isUsertalkEditAllowed() ) {
103                            $keep = $block->isUsertalkEditAllowed();
104                        }
105                    }
106
107                    if ( $keep ) {
108                        $delete[] = $bestBlock->getId();
109                        $bestBlock = $block;
110                    } else {
111                        $delete[] = $block->getId();
112                    }
113                }
114            }
115
116            if ( $delete ) {
117                $db->newDeleteQueryBuilder()
118                    ->deleteFrom( 'ipblocks' )
119                    ->where( [ 'ipb_id' => $delete ] )
120                    ->caller( __METHOD__ )->execute();
121            }
122        }
123
124        // Step 2: Update the user name in any blocks where it doesn't match
125        for ( $from = 1; $from <= $max; $from += $batchSize ) {
126            $to = min( $max, $from + $batchSize - 1 );
127            $this->output( "Cleaning up mismatched user name ($from-$to of $max)\n" );
128
129            $res = $db->newSelectQueryBuilder()
130                ->select( [ 'ipb_id', 'user_name' ] )
131                ->tables( [ 'ipblocks', 'user' ] )
132                ->where( [
133                    'ipb_user = user_id',
134                    "ipb_user >= " . $from,
135                    "ipb_user <= " . (int)$to,
136                    'ipb_address != user_name',
137                ] )
138                ->caller( __METHOD__ )
139                ->fetchResultSet();
140            foreach ( $res as $row ) {
141                $db->update(
142                    'ipblocks',
143                    [ 'ipb_address' => $row->user_name ],
144                    [ 'ipb_id' => $row->ipb_id ],
145                    __METHOD__
146                );
147            }
148        }
149
150        $this->output( "Done!\n" );
151    }
152}
153
154$maintClass = CleanupBlocks::class;
155require_once RUN_MAINTENANCE_IF_MAIN;