Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 100
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
FlagProtectToSemiProtect
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 3
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 flagToSemiProtect
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
272
1<?php
2/**
3 * @ingroup Maintenance
4 */
5
6use MediaWiki\Maintenance\Maintenance;
7use MediaWiki\Title\Title;
8use MediaWiki\User\User;
9
10if ( getenv( 'MW_INSTALL_PATH' ) ) {
11    $IP = getenv( 'MW_INSTALL_PATH' );
12} else {
13    $IP = __DIR__ . '/../../..';
14}
15
16require_once "$IP/maintenance/Maintenance.php";
17
18class FlagProtectToSemiProtect extends Maintenance {
19
20    public function __construct() {
21        parent::__construct();
22        $this->addDescription( 'Convert flag-protected pages to semi-protection.' );
23        $this->addOption( 'user', 'The name of the admin user to use as the "protector"', true, true );
24        $this->addOption( 'reason', 'The reason for the conversion', false, true );
25        $this->setBatchSize( 500 );
26        $this->requireExtension( 'FlaggedRevs' );
27    }
28
29    /**
30     * @inheritDoc
31     */
32    public function execute() {
33        if ( !$this->getConfig()->get( 'FlaggedRevsProtection' ) ) {
34            $this->output( "\$wgFlaggedRevsProtection not enabled.\n" );
35            return;
36        }
37
38        $user = User::newFromName( $this->getOption( 'user' ) );
39        if ( !$user || !$user->isRegistered() ) {
40            $this->fatalError( "Invalid user specified!" );
41        }
42        $reason = $this->getOption( 'reason',
43            "Converting flagged protection settings to edit protection settings." );
44
45        $this->output( "Protecter username: \"" . $user->getName() . "\"\n" );
46        $this->output( "Running in 5 seconds...Press ctrl-c to abort.\n" );
47        sleep( 5 );
48
49        $this->flagToSemiProtect( $user, $reason );
50    }
51
52    /**
53     * @param User $user
54     * @param string $reason
55     */
56    private function flagToSemiProtect( User $user, $reason ) {
57        $this->output( "Semi-protecting all flag-protected pages...\n" );
58        $reviewNamespaces = $this->getConfig()->get( 'FlaggedRevsNamespaces' );
59        if ( !$reviewNamespaces ) {
60            $this->output( "\$wgFlaggedRevsNamespaces is empty.\n" );
61            return;
62        }
63
64        $db = $this->getPrimaryDB();
65        $start = $db->newSelectQueryBuilder()
66            ->select( 'MIN(fpc_page_id)' )
67            ->from( 'flaggedpage_config' )
68            ->caller( __METHOD__ )
69            ->fetchField();
70        $end = $db->newSelectQueryBuilder()
71            ->select( 'MAX(fpc_page_id)' )
72            ->from( 'flaggedpage_config' )
73            ->caller( __METHOD__ )
74            ->fetchField();
75        if ( $start === null || $end === null ) {
76            $this->output( "...flaggedpage_config table seems to be empty.\n" );
77            return;
78        }
79        # Do remaining chunk
80        $end += $this->mBatchSize - 1;
81        $blockStart = (int)$start;
82        $blockEnd = (int)( $start + $this->mBatchSize - 1 );
83        $count = 0;
84
85        $services = $this->getServiceContainer();
86        $restrictionStore = $services->getRestrictionStore();
87        $wikiPageFactory = $services->getWikiPageFactory();
88
89        while ( $blockEnd <= $end ) {
90            $this->output( "...doing fpc_page_id from $blockStart to $blockEnd\n" );
91            $res = $db->newSelectQueryBuilder()
92                ->select( [ 'fpc_page_id', 'fpc_level', 'fpc_expiry' ] )
93                ->from( 'flaggedpage_config' )
94                ->join( 'page', null, 'page_id = fpc_page_id' )
95                ->where( [
96                    $db->expr( 'fpc_page_id', '>=', $blockStart ),
97                    $db->expr( 'fpc_page_id', '<=', $blockEnd ),
98                    'page_namespace' => $reviewNamespaces,
99                    $db->expr( 'fpc_level', '!=', '' ),
100                ] )
101                ->caller( __METHOD__ )
102                ->fetchResultSet();
103            # Go through and protect each page...
104            foreach ( $res as $row ) {
105                $title = Title::newFromID( $row->fpc_page_id );
106                if ( $restrictionStore->isProtected( $title, 'edit' ) ) {
107                    continue; // page already has edit protection - skip it
108                }
109                # Flagged protection settings
110                $frLimit = trim( $row->fpc_level );
111                $frExpiry = ( $row->fpc_expiry === $db->getInfinity() )
112                    ? 'infinity'
113                    : wfTimestamp( TS_MW, $row->fpc_expiry );
114                # Build the new protection settings
115                $cascade = false;
116                $limit = [];
117                $expiry = [];
118                foreach ( $restrictionStore->listApplicableRestrictionTypes( $title ) as $type ) {
119                    # Get existing restrictions for this action
120                    $oldLimit = $restrictionStore->getRestrictions( $title, $type ); // array
121                    $oldExpiry = $restrictionStore->getRestrictionExpiry( $title, $type ); // MW_TS
122                    # Move or Edit rights - take highest of (flag,type) settings
123                    if ( $type == 'edit' || $type == 'move' ) {
124                        # Sysop flag-protect -> full protect
125                        if ( $frLimit == 'sysop' || in_array( 'sysop', $oldLimit ) ) {
126                            $newLimit = 'sysop';
127                        # Reviewer/autoconfirmed flag-protect -> semi-protect
128                        } else {
129                            $newLimit = 'autoconfirmed';
130                        }
131                        # Take highest expiry of (flag,type) settings
132                        $newExpiry = ( !$oldLimit || $frExpiry >= $oldExpiry )
133                            ? $frExpiry // note: 'infinity' > '99999999999999'
134                            : $oldExpiry;
135                    # Otherwise - maintain original limits
136                    } else {
137                        $newLimit = $oldLimit;
138                        $newExpiry = $oldExpiry;
139                    }
140                    $limit[$type] = $newLimit;
141                    $expiry[$type] = $newExpiry;
142                }
143
144                $db->begin( __METHOD__ );
145                $wikiPage = $wikiPageFactory->newFromTitle( $title );
146                $ok = $wikiPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
147                if ( $ok ) {
148                    $count++;
149                } else {
150                    $this->output( "Could not protect: " . $title->getPrefixedText() . "\n" );
151                }
152                $db->commit( __METHOD__ );
153            }
154            $blockStart += $this->mBatchSize - 1;
155            $blockEnd += $this->mBatchSize - 1;
156            $this->waitForReplication();
157        }
158        $this->output( "Protection of all flag-protected pages complete ... {$count} pages\n" );
159    }
160}
161
162$maintClass = FlagProtectToSemiProtect::class;
163require_once RUN_MAINTENANCE_IF_MAIN;