Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SendForceRenameNotification
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 4
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
90
 getQueuedCount
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getLocalizedText
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3use MediaWiki\Extension\CentralAuth\CentralAuthServices;
4use MediaWiki\Extension\CentralAuth\UsersToRename\UsersToRenameDatabaseUpdates;
5use MediaWiki\MassMessage\Job\MassMessageServerSideJob;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\Title\Title;
8use MediaWiki\WikiMap\WikiMap;
9
10$IP = getenv( 'MW_INSTALL_PATH' );
11if ( $IP === false ) {
12    $IP = __DIR__ . '/../../..';
13}
14require_once "$IP/maintenance/Maintenance.php";
15
16/**
17 * Script to notify users listed in the users_to_rename table
18 * that they will be renamed. Requires the MassMessage extension
19 *
20 * Setup:
21 *   - A directory with localized message files, named in the format of "$langCode.txt".
22 *     The string "{{WIKI}}" will be expanded to the current database name
23 *   - A directory with localized subject files, named in the format of "$langCode.txt".
24 *     A file for "en" must exist as it is the base fallback.
25 */
26class SendForceRenameNotification extends Maintenance {
27    public function __construct() {
28        parent::__construct();
29
30        $this->requireExtension( 'CentralAuth' );
31        $this->requireExtension( 'MassMessage' );
32
33        $this->addOption( 'message', 'Location of directory with messages', true, true );
34        $this->addOption( 'subject', 'Location of directory with subjects', true, true );
35        $this->addOption( 'sleep', 'How long to sleep for', false, true );
36        $this->setBatchSize( 100 );
37    }
38
39    public function execute() {
40        $message = $this->getLocalizedText( $this->getOption( 'message' ) );
41        $message = str_replace( '{{WIKI}}', WikiMap::getCurrentWikiId(), $message );
42        $message .= " ~~~~~\n<!-- SUL finalisation notification -->";
43        $databaseManager = CentralAuthServices::getDatabaseManager();
44        $dbw = $databaseManager->getCentralPrimaryDB();
45        $updates = new UsersToRenameDatabaseUpdates( $dbw );
46        $commonParams = [
47            'subject' => $this->getLocalizedText( $this->getOption( 'subject' ) ),
48        ];
49
50        $services = MediaWikiServices::getInstance();
51        $lbFactory = $services->getDBLoadBalancerFactory();
52        $namespaceInfo = $services->getNamespaceInfo();
53        $jobQueueGroup = $services->getJobQueueGroup();
54        $linkBatchFactory = $services->getLinkBatchFactory();
55
56        while ( true ) {
57            $jobs = [];
58            $markNotified = [];
59            $rows = $updates->findUsers( WikiMap::getCurrentWikiId(), 0, $this->mBatchSize );
60            if ( $rows->numRows() === 0 ) {
61                break;
62            }
63            $lb = $linkBatchFactory->newLinkBatch();
64            foreach ( $rows as $row ) {
65                $title = Title::makeTitleSafe( NS_USER_TALK, $row->utr_name );
66                if ( !$title ) {
67                    $this->output( "ERROR: Invalid username for {$row->utr_name}\n" );
68                    continue;
69                }
70                $lb->addObj( $title );
71            }
72            $lb->execute();
73            foreach ( $rows as $row ) {
74                $title = $namespaceInfo->getCanonicalName( NS_USER_TALK ) . ':' . $row->utr_name;
75                $titleObj = Title::newFromText( $title );
76                if ( $titleObj->isRedirect() ) {
77                    // @fixme find a way to notify users with a redirected user-talk
78                    $this->output( "Skipping {$title} because it is a redirect\n" );
79                    $updates->markRedirectSkipped( $row->utr_name, $row->utr_wiki );
80                    continue;
81                }
82                $jobs[] = new MassMessageServerSideJob(
83                    Title::newFromText( $title ),
84                    [
85                        'title' => $title,
86                        'message' => str_replace( '{{subst:PAGENAME}}', $row->utr_name, $message )
87                    ] + $commonParams
88                );
89                $this->output( "Will notify {$row->utr_name}\n" );
90                $markNotified[] = $row;
91            }
92
93            $count = count( $jobs );
94            $this->output( "Queued job for $count users.\n" );
95            $jobQueueGroup->push( $jobs );
96            foreach ( $markNotified as $row ) {
97                $updates->markNotified( $row->utr_name, $row->utr_wiki );
98            }
99            $this->output( "Waiting for replicas..." );
100            // users_to_rename
101            $databaseManager->waitForReplication();
102            // And on the local wiki!
103            $lbFactory->waitForReplication();
104
105            $this->output( " done.\n" );
106            $queued = $this->getQueuedCount();
107            while ( $queued > 100000 ) {
108                $this->output( "Currently $queued jobs, sleeping for 5 seconds...\n" );
109                sleep( 5 );
110                $queued = $this->getQueuedCount();
111            }
112        }
113    }
114
115    /**
116     * @return int
117     */
118    protected function getQueuedCount() {
119        $group = MediaWikiServices::getInstance()->getJobQueueGroup();
120        $queue = $group->get( 'MassMessageServerSideJob' );
121        $pending = $queue->getSize();
122        $claimed = $queue->getAcquiredCount();
123        $abandoned = $queue->getAbandonedCount();
124        $active = max( $claimed - $abandoned, 0 );
125
126        return $active + $pending;
127    }
128
129    /**
130     * @param string $dir
131     *
132     * @return string
133     */
134    protected function getLocalizedText( $dir ) {
135        $langCode = $this->getConfig()->get( 'LanguageCode' );
136        $fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()->getAll( $langCode );
137        array_unshift( $fallbacks, $langCode );
138        foreach ( $fallbacks as $code ) {
139            if ( file_exists( "$dir/$code.txt" ) ) {
140                return trim( file_get_contents( "$dir/$code.txt" ) );
141            }
142        }
143
144        $this->fatalError( "Could not find a valid localized file for $langCode." );
145    }
146}
147
148$maintClass = SendForceRenameNotification::class;
149require_once RUN_MAINTENANCE_IF_MAIN;