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