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