Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RollbackEdits
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 3
182
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
110
 getRollbackTitles
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Rollback all edits by a given user or IP provided they're the most
4 * recent edit (just like real rollback)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 * @ingroup Maintenance
23 */
24
25use MediaWiki\Title\Title;
26use MediaWiki\User\User;
27
28require_once __DIR__ . '/Maintenance.php';
29
30/**
31 * Maintenance script to rollback all edits by a given user or IP provided
32 * they're the most recent edit.
33 *
34 * @ingroup Maintenance
35 */
36class RollbackEdits extends Maintenance {
37    public function __construct() {
38        parent::__construct();
39        $this->addDescription(
40            "Rollback all edits by a given user or IP provided they're the most recent edit" );
41        $this->addOption(
42            'titles',
43            'A list of titles, none means all titles where the given user is the most recent',
44            false,
45            true
46        );
47        $this->addOption( 'user', 'A user or IP to rollback all edits for', true, true );
48        $this->addOption( 'summary', 'Edit summary to use', false, true );
49        $this->addOption( 'bot', 'Mark the edits as bot' );
50    }
51
52    public function execute() {
53        $user = $this->getOption( 'user' );
54        $services = $this->getServiceContainer();
55        $userNameUtils = $services->getUserNameUtils();
56        $username = $userNameUtils->isIP( $user ) ? $user : $userNameUtils->getCanonical( $user );
57        if ( !$username ) {
58            $this->fatalError( 'Invalid username' );
59        }
60
61        $bot = $this->hasOption( 'bot' );
62        $summary = $this->getOption( 'summary', $this->mSelf . ' mass rollback' );
63        $titles = [];
64        if ( $this->hasOption( 'titles' ) ) {
65            foreach ( explode( '|', $this->getOption( 'titles' ) ) as $title ) {
66                $t = Title::newFromText( $title );
67                if ( !$t ) {
68                    $this->error( 'Invalid title, ' . $title );
69                } else {
70                    $titles[] = $t;
71                }
72            }
73        } else {
74            $titles = $this->getRollbackTitles( $user );
75        }
76
77        if ( !$titles ) {
78            $this->output( 'No suitable titles to be rolled back.' );
79
80            return;
81        }
82
83        $doer = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
84        $byUser = $services->getUserIdentityLookup()->getUserIdentityByName( $username );
85
86        if ( !$byUser ) {
87            $this->fatalError( 'Unknown user.' );
88        }
89
90        $wikiPageFactory = $services->getWikiPageFactory();
91        $rollbackPageFactory = $services->getRollbackPageFactory();
92        foreach ( $titles as $t ) {
93            $page = $wikiPageFactory->newFromTitle( $t );
94            $this->output( 'Processing ' . $t->getPrefixedText() . '...' );
95            $rollbackResult = $rollbackPageFactory
96                ->newRollbackPage( $page, $doer, $byUser )
97                ->markAsBot( $bot )
98                ->setSummary( $summary )
99                ->rollback();
100            if ( $rollbackResult->isGood() ) {
101                $this->output( "Done!\n" );
102            } else {
103                $this->output( "Failed!\n" );
104            }
105        }
106    }
107
108    /**
109     * Get all pages that should be rolled back for a given user
110     * @param string $user A name to check against
111     * @return array
112     */
113    private function getRollbackTitles( $user ) {
114        $dbr = $this->getReplicaDB();
115        $titles = [];
116
117        $results = $dbr->newSelectQueryBuilder()
118            ->select( [ 'page_namespace', 'page_title' ] )
119            ->from( 'page' )
120            ->join( 'revision', null, 'page_latest = rev_id' )
121            ->join( 'actor', null, 'rev_actor = actor_id' )
122            ->where( [ 'actor_name' => $user ] )
123            ->caller( __METHOD__ )->fetchResultSet();
124        foreach ( $results as $row ) {
125            $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
126        }
127
128        return $titles;
129    }
130}
131
132$maintClass = RollbackEdits::class;
133require_once RUN_MAINTENANCE_IF_MAIN;