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// NO_AUTOLOAD -- file-scope define() used to modify behaviour 26 27# Start from scratch 28define( 'MW_NO_EXTENSION_MESSAGES', 1 ); 29 30require_once __DIR__ . '/Maintenance.php'; 31$maintClass = MergeMessageFileList::class; 32$mmfl = false; 33 34/** 35 * Maintenance script that merges $wgExtensionMessagesFiles from various 36 * extensions to produce a single array containing all message files. 37 * 38 * @ingroup Maintenance 39 */ 40class MergeMessageFileList extends Maintenance { 41 public function __construct() { 42 parent::__construct(); 43 $this->addOption( 44 'list-file', 45 'A file containing a list of extension setup files, one per line.', 46 false, 47 true 48 ); 49 $this->addOption( 'extensions-dir', 'Path where extensions can be found.', false, true ); 50 $this->addOption( 'output', 'Send output to this file (omit for stdout)', false, true ); 51 $this->addDescription( 'Merge $wgExtensionMessagesFiles and $wgMessagesDirs from ' . 52 ' various extensions to produce a single file listing all message files and dirs.' 53 ); 54 } 55 56 public function execute() { 57 global $mmfl; 58 global $wgExtensionEntryPointListFiles; 59 60 if ( !count( $wgExtensionEntryPointListFiles ) 61 && !$this->hasOption( 'list-file' ) 62 && !$this->hasOption( 'extensions-dir' ) 63 ) { 64 $this->fatalError( "Either --list-file or --extensions-dir must be provided if " . 65 "\$wgExtensionEntryPointListFiles is not set" ); 66 } 67 68 $mmfl = [ 'setupFiles' => [] ]; 69 70 # Add setup files contained in file passed to --list-file 71 if ( $this->hasOption( 'list-file' ) ) { 72 $extensionPaths = $this->readFile( $this->getOption( 'list-file' ) ); 73 $mmfl['setupFiles'] = array_merge( $mmfl['setupFiles'], $extensionPaths ); 74 } 75 76 # Now find out files in a directory 77 if ( $this->hasOption( 'extensions-dir' ) ) { 78 $extdir = $this->getOption( 'extensions-dir' ); 79 # Allow multiple directories to be passed with ":" as delimiter 80 $extdirs = explode( ':', $extdir ); 81 foreach ( $extdirs as $extdir ) { 82 $entries = scandir( $extdir ); 83 foreach ( $entries as $extname ) { 84 if ( $extname == '.' || $extname == '..' || !is_dir( "$extdir/$extname" ) ) { 85 continue; 86 } 87 $possibilities = [ 88 "$extdir/$extname/extension.json", 89 "$extdir/$extname/skin.json", 90 "$extdir/$extname/$extname.php" 91 ]; 92 $found = false; 93 foreach ( $possibilities as $extfile ) { 94 if ( file_exists( $extfile ) ) { 95 $mmfl['setupFiles'][] = $extfile; 96 $found = true; 97 break; 98 } 99 } 100 101 if ( !$found ) { 102 $this->error( "Extension {$extname} in {$extdir} lacks expected entry point: " . 103 "extension.json, skin.json, or {$extname}.php." ); 104 } 105 } 106 } 107 } 108 109 # Add setup files defined via configuration 110 foreach ( $wgExtensionEntryPointListFiles as $points ) { 111 $extensionPaths = $this->readFile( $points ); 112 $mmfl['setupFiles'] = array_merge( $mmfl['setupFiles'], $extensionPaths ); 113 } 114 115 if ( $this->hasOption( 'output' ) ) { 116 $mmfl['output'] = $this->getOption( 'output' ); 117 } 118 if ( $this->hasOption( 'quiet' ) ) { 119 $mmfl['quiet'] = true; 120 } 121 } 122 123 public function finalSetup() { 124 # This script commonly needs to be run before the l10n cache. But if 125 # $wgLanguageCode is not 'en', it won't be able to run because there is 126 # no l10n cache. Break the cycle by forcing $wgLanguageCode = 'en'. 127 global $wgLanguageCode; 128 $wgLanguageCode = 'en'; 129 parent::finalSetup(); 130 } 131 132 /** 133 * Database access is not needed. 134 * 135 * @return int DB constant 136 */ 137 public function getDbType() { 138 return Maintenance::DB_NONE; 139 } 140 141 /** 142 * @param string $fileName 143 * @return array List of absolute extension paths 144 */ 145 private function readFile( $fileName ) { 146 global $IP; 147 148 $files = []; 149 $fileLines = file( $fileName ); 150 if ( $fileLines === false ) { 151 $this->error( "Unable to open list file $fileName." ); 152 153 return $files; 154 } 155 # Strip comments, discard empty lines, and trim leading and trailing 156 # whitespace. Comments start with '#' and extend to the end of the line. 157 foreach ( $fileLines as $extension ) { 158 $extension = trim( preg_replace( '/#.*/', '', $extension ) ); 159 if ( $extension !== '' ) { 160 # Paths may use the string $IP to be substituted by the actual value 161 $extension = str_replace( '$IP', $IP, $extension ); 162 if ( file_exists( $extension ) ) { 163 $files[] = $extension; 164 } else { 165 $this->error( "Extension {$extension} doesn't exist" ); 166 } 167 } 168 } 169 170 return $files; 171 } 172} 173 174require_once RUN_MAINTENANCE_IF_MAIN; 175 176$queue = []; 177'@phan-var string[][] $mmfl'; 178foreach ( $mmfl['setupFiles'] as $fileName ) { 179 if ( strval( $fileName ) === '' ) { 180 continue; 181 } 182 if ( empty( $mmfl['quiet'] ) ) { 183 fwrite( STDERR, "Loading data from $fileName\n" ); 184 } 185 // Using extension.json or skin.json 186 if ( substr( $fileName, -strlen( '.json' ) ) === '.json' ) { 187 $queue[$fileName] = 1; 188 } else { 189 require_once $fileName; 190 } 191} 192 193if ( $queue ) { 194 $registry = new ExtensionRegistry(); 195 $data = $registry->readFromQueue( $queue ); 196 foreach ( [ 'wgExtensionMessagesFiles', 'wgMessagesDirs' ] as $var ) { 197 if ( isset( $data['globals'][$var] ) ) { 198 $GLOBALS[$var] = array_merge( $data['globals'][$var], $GLOBALS[$var] ); 199 } 200 } 201} 202 203if ( empty( $mmfl['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( $wgExtensionMessagesFiles, true ) . ";\n\n" . 211 '$wgMessagesDirs = ' . var_export( $wgMessagesDirs, true ) . ";\n\n"; 212 213$dirs = [ 214 $IP, 215 dirname( __DIR__ ), 216 realpath( $IP ) 217]; 218 219foreach ( $dirs as $dir ) { 220 $s = preg_replace( "/'" . preg_quote( $dir, '/' ) . "([^']*)'/", '"$IP\1"', $s ); 221} 222 223if ( isset( $mmfl['output'] ) ) { 224 $outputFile = $mmfl['output']; 225 $res = file_put_contents( $outputFile, $s ); 226 if ( $res === false ) { 227 fwrite( STDERR, "Failed to write to $outputFile\n" ); 228 exit( 1 ); 229 } 230} else { 231 echo $s; 232} 233