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