Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 115
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteEqualMessages
0.00% covered (danger)
0.00%
0 / 112
0.00% covered (danger)
0.00%
0 / 3
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 fetchMessageInfo
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
90
 execute
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
272
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup Maintenance
20 */
21
22use MediaWiki\Languages\LanguageNameUtils;
23use MediaWiki\Pager\AllMessagesTablePager;
24use MediaWiki\StubObject\StubGlobalUser;
25use MediaWiki\Title\Title;
26use MediaWiki\User\User;
27
28require_once __DIR__ . '/Maintenance.php';
29
30/**
31 * Maintenance script that deletes all pages in the MediaWiki namespace
32 * of which the content is equal to the system default.
33 *
34 * @ingroup Maintenance
35 */
36class DeleteEqualMessages extends Maintenance {
37    public function __construct() {
38        parent::__construct();
39        $this->addDescription( 'Deletes all pages in the MediaWiki namespace that are equal to '
40            . 'the default message' );
41        $this->addOption( 'delete', 'Actually delete the pages (default: dry run)' );
42        $this->addOption( 'delete-talk', 'Don\'t leave orphaned talk pages behind during deletion' );
43        $this->addOption( 'lang-code', 'Check for subpages of this language code (default: root '
44            . 'page against content language). Use value "*" to run for all mwfile language code '
45            . 'subpages (including the base pages that override content language).', false, true );
46    }
47
48    /**
49     * @param string|false $langCode See --lang-code option.
50     * @param array &$messageInfo
51     */
52    protected function fetchMessageInfo( $langCode, array &$messageInfo ) {
53        $services = $this->getServiceContainer();
54        $contLang = $services->getContentLanguage();
55        if ( $langCode ) {
56            $this->output( "\n... fetching message info for language: $langCode" );
57            $nonContentLanguage = true;
58        } else {
59            $this->output( "\n... fetching message info for content language" );
60            $langCode = $contLang->getCode();
61            $nonContentLanguage = false;
62        }
63
64        /* Based on SpecialAllmessages::reallyDoQuery #filter=modified */
65
66        $l10nCache = $services->getLocalisationCache();
67        $messageNames = $l10nCache->getSubitemList( 'en', 'messages' );
68        // Normalise message names for NS_MEDIAWIKI page_title
69        $messageNames = array_map( [ $contLang, 'ucfirst' ], $messageNames );
70
71        $statuses = AllMessagesTablePager::getCustomisedStatuses(
72            $messageNames,
73            $langCode,
74            $nonContentLanguage,
75            $this->getReplicaDB()
76        );
77        // getCustomisedStatuses is stripping the sub page from the page titles, add it back
78        $titleSuffix = $nonContentLanguage ? "/$langCode" : '';
79
80        foreach ( $messageNames as $key ) {
81            $customised = isset( $statuses['pages'][$key] );
82            if ( $customised ) {
83                $actual = wfMessage( $key )->inLanguage( $langCode )->plain();
84                $default = wfMessage( $key )->inLanguage( $langCode )->useDatabase( false )->plain();
85
86                $messageInfo['relevantPages']++;
87
88                if (
89                    // Exclude messages that are empty by default, such as sitenotice, specialpage
90                    // summaries and accesskeys.
91                    $default !== '' && $default !== '-' &&
92                        $actual === $default
93                ) {
94                    $hasTalk = isset( $statuses['talks'][$key] );
95                    $messageInfo['results'][] = [
96                        'title' => $key . $titleSuffix,
97                        'hasTalk' => $hasTalk,
98                    ];
99                    $messageInfo['equalPages']++;
100                    if ( $hasTalk ) {
101                        $messageInfo['equalPagesTalks']++;
102                    }
103                }
104            }
105        }
106    }
107
108    public function execute() {
109        $doDelete = $this->hasOption( 'delete' );
110        $doDeleteTalk = $this->hasOption( 'delete-talk' );
111        $langCode = $this->getOption( 'lang-code' );
112
113        $messageInfo = [
114            'relevantPages' => 0,
115            'equalPages' => 0,
116            'equalPagesTalks' => 0,
117            'results' => [],
118        ];
119
120        $this->output( 'Checking for pages with default message...' );
121
122        $services = $this->getServiceContainer();
123        // Load message information
124        if ( $langCode ) {
125            $langCodes = $services->getLanguageNameUtils()
126                ->getLanguageNames( LanguageNameUtils::AUTONYMS, LanguageNameUtils::SUPPORTED );
127            if ( $langCode === '*' ) {
128                // All valid lang-code subpages in NS_MEDIAWIKI that
129                // override the messages in that language
130                foreach ( $langCodes as $key => $value ) {
131                    $this->fetchMessageInfo( $key, $messageInfo );
132                }
133                // Lastly, the base pages in NS_MEDIAWIKI that override
134                // messages in content language
135                $this->fetchMessageInfo( false, $messageInfo );
136            } else {
137                if ( !isset( $langCodes[$langCode] ) ) {
138                    $this->fatalError( 'Invalid language code: ' . $langCode );
139                }
140                $this->fetchMessageInfo( $langCode, $messageInfo );
141            }
142        } else {
143            $this->fetchMessageInfo( false, $messageInfo );
144        }
145
146        if ( $messageInfo['equalPages'] === 0 ) {
147            // No more equal messages left
148            $this->output( "\ndone.\n" );
149
150            return;
151        }
152
153        $this->output( "\n{$messageInfo['relevantPages']} pages in the MediaWiki namespace "
154            . "override messages." );
155        $this->output( "\n{$messageInfo['equalPages']} pages are equal to the default message "
156            . "(+ {$messageInfo['equalPagesTalks']} talk pages).\n" );
157
158        if ( !$doDelete ) {
159            $list = '';
160            foreach ( $messageInfo['results'] as $result ) {
161                $title = Title::makeTitle( NS_MEDIAWIKI, $result['title'] );
162                $list .= "* [[$title]]\n";
163                if ( $result['hasTalk'] ) {
164                    $title = Title::makeTitle( NS_MEDIAWIKI_TALK, $result['title'] );
165                    $list .= "* [[$title]]\n";
166                }
167            }
168            $this->output( "\nList:\n$list\nRun the script again with --delete to delete these pages" );
169            if ( $messageInfo['equalPagesTalks'] !== 0 ) {
170                $this->output( " (include --delete-talk to also delete the talk pages)" );
171            }
172            $this->output( "\n" );
173
174            return;
175        }
176
177        $user = User::newSystemUser( 'MediaWiki default', [ 'steal' => true ] );
178        if ( !$user ) {
179            $this->fatalError( "Invalid username" );
180        }
181        StubGlobalUser::setUser( $user );
182
183        // Hide deletions from RecentChanges
184        $userGroupManager = $services->getUserGroupManager();
185        $userGroupManager->addUserToGroup( $user, 'bot' );
186
187        // Handle deletion
188        $this->output( "\n...deleting equal messages (this may take a long time!)..." );
189        $dbw = $this->getPrimaryDB();
190        $wikiPageFactory = $services->getWikiPageFactory();
191        $delPageFactory = $services->getDeletePageFactory();
192        foreach ( $messageInfo['results'] as $result ) {
193            $this->waitForReplication();
194            $dbw->ping();
195            $title = Title::makeTitle( NS_MEDIAWIKI, $result['title'] );
196            $this->output( "\n* [[$title]]" );
197            $page = $wikiPageFactory->newFromTitle( $title );
198            $status = $delPageFactory->newDeletePage( $page, $user )->deleteUnsafe( 'No longer required' );
199            if ( !$status->isOK() ) {
200                $this->output( " (Failed!)" );
201            }
202            if ( $result['hasTalk'] && $doDeleteTalk ) {
203                $title = Title::makeTitle( NS_MEDIAWIKI_TALK, $result['title'] );
204                $this->output( "\n* [[$title]]" );
205                $page = $wikiPageFactory->newFromTitle( $title );
206                $status = $delPageFactory->newDeletePage( $page, $user )
207                    ->deleteUnsafe( 'Orphaned talk page of no longer required message' );
208                if ( !$status->isOK() ) {
209                    $this->output( " (Failed!)" );
210                }
211            }
212        }
213        $this->output( "\n\ndone!\n" );
214    }
215}
216
217$maintClass = DeleteEqualMessages::class;
218require_once RUN_MAINTENANCE_IF_MAIN;