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 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
MergeMessageFileList
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 6
1260
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
240
 finalSetup
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDbType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 readFile
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 generateMessageFileList
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Merge $wgExtensionMessagesFiles from various extensions to produce a
4 * single array containing all message files.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 * @ingroup Maintenance
23 */
24
25# Start from scratch
26use MediaWiki\MainConfigNames;
27use MediaWiki\Settings\SettingsBuilder;
28
29define( 'MW_NO_EXTENSION_MESSAGES', 1 );
30
31require_once __DIR__ . '/Maintenance.php';
32
33/**
34 * Maintenance script that merges $wgExtensionMessagesFiles from various
35 * extensions to produce a single array containing all message files.
36 *
37 * @ingroup Maintenance
38 */
39class MergeMessageFileList extends Maintenance {
40    public function __construct() {
41        parent::__construct();
42        $this->addOption(
43            'list-file',
44            'A file containing a list of extension setup files, one per line.',
45            false,
46            true
47        );
48        $this->addOption( 'extensions-dir', 'Path where extensions can be found.', false, true );
49        $this->addOption( 'output', 'Send output to this file (omit for stdout)', false, true );
50        $this->addDescription( 'Merge $wgExtensionMessagesFiles and $wgMessagesDirs from ' .
51            ' various extensions to produce a single file listing all message files and dirs.'
52        );
53    }
54
55    public function execute() {
56        $config = $this->getConfig();
57        $extensionEntryPointListFiles = $config->get( MainConfigNames::ExtensionEntryPointListFiles );
58
59        if ( !count( $extensionEntryPointListFiles )
60            && !$this->hasOption( 'list-file' )
61            && !$this->hasOption( 'extensions-dir' )
62        ) {
63            $this->fatalError( "Either --list-file or --extensions-dir must be provided if " .
64                "\$wgExtensionEntryPointListFiles is not set" );
65        }
66
67        $setupFiles = [];
68
69        # Add setup files contained in file passed to --list-file
70        if ( $this->hasOption( 'list-file' ) ) {
71            $extensionPaths = $this->readFile( $this->getOption( 'list-file' ) );
72            $setupFiles = array_merge( $setupFiles, $extensionPaths );
73        }
74
75        # Now find out files in a directory
76        if ( $this->hasOption( 'extensions-dir' ) ) {
77            $extdir = $this->getOption( 'extensions-dir' );
78            # Allow multiple directories to be passed with ":" as delimiter
79            $extdirs = explode( ':', $extdir );
80            foreach ( $extdirs as $extdir ) {
81                $entries = scandir( $extdir );
82                foreach ( $entries as $extname ) {
83                    if ( $extname == '.' || $extname == '..' || !is_dir( "$extdir/$extname" ) ) {
84                        continue;
85                    }
86                    $possibilities = [
87                        "$extdir/$extname/extension.json",
88                        "$extdir/$extname/skin.json",
89                    ];
90                    $found = false;
91                    foreach ( $possibilities as $extfile ) {
92                        if ( file_exists( $extfile ) ) {
93                            $setupFiles[] = $extfile;
94                            $found = true;
95                            break;
96                        }
97                    }
98
99                    if ( !$found ) {
100                        $this->error( "Extension {$extname} in {$extdir} lacks expected entry point: " .
101                            "extension.json or skin.json " .
102                            "(PHP entry points are no longer supported by this script)." );
103                    }
104                }
105            }
106        }
107
108        # Add setup files defined via configuration
109        foreach ( $extensionEntryPointListFiles as $points ) {
110            $extensionPaths = $this->readFile( $points );
111            $setupFiles = array_merge( $setupFiles, $extensionPaths );
112        }
113
114        $this->generateMessageFileList( $setupFiles );
115    }
116
117    public function finalSetup( SettingsBuilder $settingsBuilder ) {
118        # This script commonly needs to be run before the l10n cache. But if
119        # LanguageCode is not 'en', it won't be able to run because there is
120        # no l10n cache. Break the cycle by forcing the LanguageCode setting to 'en'.
121        $settingsBuilder->putConfigValue( MainConfigNames::LanguageCode, 'en' );
122        parent::finalSetup( $settingsBuilder );
123    }
124
125    /**
126     * Database access is not needed.
127     *
128     * @return int DB constant
129     */
130    public function getDbType() {
131        return Maintenance::DB_NONE;
132    }
133
134    /**
135     * @param string $fileName
136     * @return array List of absolute extension paths
137     */
138    private function readFile( $fileName ) {
139        $IP = MW_INSTALL_PATH;
140
141        $files = [];
142        $fileLines = file( $fileName );
143        if ( $fileLines === false ) {
144            $this->error( "Unable to open list file $fileName." );
145
146            return $files;
147        }
148        # Strip comments, discard empty lines, and trim leading and trailing
149        # whitespace. Comments start with '#' and extend to the end of the line.
150        foreach ( $fileLines as $extension ) {
151            $extension = trim( preg_replace( '/#.*/', '', $extension ) );
152            if ( $extension !== '' ) {
153                # Paths may use the string $IP to be substituted by the actual value
154                $extension = str_replace( '$IP', $IP, $extension );
155                if ( !str_ends_with( $extension, '.json' ) ) {
156                    $this->error( "Extension {$extension} does not end with .json " .
157                        "(PHP entry points are no longer supported by this script)" );
158                } elseif ( file_exists( $extension ) ) {
159                    $files[] = $extension;
160                } else {
161                    $this->error( "Extension {$extension} doesn't exist" );
162                }
163            }
164        }
165
166        return $files;
167    }
168
169    private function generateMessageFileList( array $setupFiles ) {
170        $IP = MW_INSTALL_PATH;
171
172        $outputFile = $this->getOption( 'output' );
173        $quiet = $this->hasOption( 'quiet' );
174
175        $queue = [];
176
177        foreach ( $setupFiles as $fileName ) {
178            if ( strval( $fileName ) === '' ) {
179                continue;
180            }
181            if ( !$quiet ) {
182                fwrite( STDERR, "Loading data from $fileName\n" );
183            }
184            $queue[$fileName] = 1;
185        }
186
187        $config = $this->getConfig();
188        $vars = [
189            'wgExtensionMessagesFiles' => $config->get( MainConfigNames::ExtensionMessagesFiles ),
190            'wgMessagesDirs' => $config->get( MainConfigNames::MessagesDirs ),
191        ];
192
193        if ( $queue ) {
194            $registry = new ExtensionRegistry();
195            $data = $registry->readFromQueue( $queue );
196            foreach ( [ 'wgExtensionMessagesFiles', 'wgMessagesDirs' ] as $var ) {
197                if ( isset( $data['globals'][$var] ) ) {
198                    $vars[$var] = array_merge( $data['globals'][$var], $vars[$var] );
199                }
200            }
201        }
202
203        if ( !$quiet ) {
204            fwrite( STDERR, "\n" );
205        }
206        $s =
207            "<?php\n" .
208            "## This file is generated by mergeMessageFileList.php. Do not edit it directly.\n\n" .
209            "if ( defined( 'MW_NO_EXTENSION_MESSAGES' ) ) return;\n\n" .
210            '$wgExtensionMessagesFiles = ' . var_export( $vars['wgExtensionMessagesFiles'], true ) . ";\n\n" .
211            '$wgMessagesDirs = ' . var_export( $vars['wgMessagesDirs'], true ) . ";\n\n";
212
213        $dirs = [
214            $IP,
215            dirname( __DIR__ ),
216            realpath( $IP )
217        ];
218
219        foreach ( $dirs as $dir ) {
220            $s = preg_replace( "/'" . preg_quote( $dir, '/' ) . "([^']*)'/", '"$IP\1"', $s );
221        }
222
223        if ( $outputFile !== null ) {
224            $res = file_put_contents( $outputFile, $s );
225            if ( $res === false ) {
226                fwrite( STDERR, "Failed to write to $outputFile\n" );
227                exit( 1 );
228            }
229        } else {
230            echo $s;
231        }
232    }
233}
234
235$maintClass = MergeMessageFileList::class;
236require_once RUN_MAINTENANCE_IF_MAIN;