This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Helper class for checkLanguage.php script. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup MaintenanceLanguage |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * @ingroup MaintenanceLanguage |
||
26 | */ |
||
27 | class CheckLanguageCLI { |
||
28 | protected $code = null; |
||
29 | protected $level = 2; |
||
30 | protected $doLinks = false; |
||
31 | protected $linksPrefix = ''; |
||
32 | protected $wikiCode = 'en'; |
||
33 | protected $checkAll = false; |
||
34 | protected $output = 'plain'; |
||
35 | protected $checks = []; |
||
36 | protected $L = null; |
||
37 | |||
38 | protected $results = []; |
||
39 | |||
40 | private $includeExif = false; |
||
41 | |||
42 | /** |
||
43 | * Constructor. |
||
44 | * @param array $options Options for script. |
||
45 | */ |
||
46 | public function __construct( array $options ) { |
||
47 | if ( isset( $options['help'] ) ) { |
||
48 | echo $this->help(); |
||
49 | exit( 1 ); |
||
50 | } |
||
51 | |||
52 | View Code Duplication | if ( isset( $options['lang'] ) ) { |
|
53 | $this->code = $options['lang']; |
||
54 | } else { |
||
55 | global $wgLanguageCode; |
||
56 | $this->code = $wgLanguageCode; |
||
57 | } |
||
58 | |||
59 | if ( isset( $options['level'] ) ) { |
||
60 | $this->level = $options['level']; |
||
61 | } |
||
62 | |||
63 | $this->doLinks = isset( $options['links'] ); |
||
64 | $this->includeExif = !isset( $options['noexif'] ); |
||
65 | $this->checkAll = isset( $options['all'] ); |
||
66 | |||
67 | if ( isset( $options['prefix'] ) ) { |
||
68 | $this->linksPrefix = $options['prefix']; |
||
69 | } |
||
70 | |||
71 | if ( isset( $options['wikilang'] ) ) { |
||
72 | $this->wikiCode = $options['wikilang']; |
||
73 | } |
||
74 | |||
75 | View Code Duplication | if ( isset( $options['whitelist'] ) ) { |
|
76 | $this->checks = explode( ',', $options['whitelist'] ); |
||
77 | } elseif ( isset( $options['blacklist'] ) ) { |
||
78 | $this->checks = array_diff( |
||
79 | isset( $options['easy'] ) ? $this->easyChecks() : $this->defaultChecks(), |
||
80 | explode( ',', $options['blacklist'] ) |
||
81 | ); |
||
82 | } elseif ( isset( $options['easy'] ) ) { |
||
83 | $this->checks = $this->easyChecks(); |
||
84 | } else { |
||
85 | $this->checks = $this->defaultChecks(); |
||
86 | } |
||
87 | |||
88 | if ( isset( $options['output'] ) ) { |
||
89 | $this->output = $options['output']; |
||
90 | } |
||
91 | |||
92 | $this->L = new Languages( $this->includeExif ); |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Get the default checks. |
||
97 | * @return array A list of the default checks. |
||
98 | */ |
||
99 | protected function defaultChecks() { |
||
100 | return [ |
||
101 | 'untranslated', 'duplicate', 'obsolete', 'variables', 'empty', 'plural', |
||
102 | 'whitespace', 'xhtml', 'chars', 'links', 'unbalanced', 'namespace', |
||
103 | 'projecttalk', 'magic', 'magic-old', 'magic-over', 'magic-case', |
||
104 | 'special', 'special-old', |
||
105 | ]; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Get the checks which check other things than messages. |
||
110 | * @return array A list of the non-message checks. |
||
111 | */ |
||
112 | protected function nonMessageChecks() { |
||
113 | return [ |
||
114 | 'namespace', 'projecttalk', 'magic', 'magic-old', 'magic-over', |
||
115 | 'magic-case', 'special', 'special-old', |
||
116 | ]; |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Get the checks that can easily be treated by non-speakers of the language. |
||
121 | * @return array A list of the easy checks. |
||
122 | */ |
||
123 | protected function easyChecks() { |
||
124 | return [ |
||
125 | 'duplicate', 'obsolete', 'empty', 'whitespace', 'xhtml', 'chars', 'magic-old', |
||
126 | 'magic-over', 'magic-case', 'special-old', |
||
127 | ]; |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Get all checks. |
||
132 | * @return array An array of all check names mapped to their function names. |
||
133 | */ |
||
134 | View Code Duplication | protected function getChecks() { |
|
135 | return [ |
||
136 | 'untranslated' => 'getUntranslatedMessages', |
||
137 | 'duplicate' => 'getDuplicateMessages', |
||
138 | 'obsolete' => 'getObsoleteMessages', |
||
139 | 'variables' => 'getMessagesWithMismatchVariables', |
||
140 | 'plural' => 'getMessagesWithoutPlural', |
||
141 | 'empty' => 'getEmptyMessages', |
||
142 | 'whitespace' => 'getMessagesWithWhitespace', |
||
143 | 'xhtml' => 'getNonXHTMLMessages', |
||
144 | 'chars' => 'getMessagesWithWrongChars', |
||
145 | 'links' => 'getMessagesWithDubiousLinks', |
||
146 | 'unbalanced' => 'getMessagesWithUnbalanced', |
||
147 | 'namespace' => 'getUntranslatedNamespaces', |
||
148 | 'projecttalk' => 'getProblematicProjectTalks', |
||
149 | 'magic' => 'getUntranslatedMagicWords', |
||
150 | 'magic-old' => 'getObsoleteMagicWords', |
||
151 | 'magic-over' => 'getOverridingMagicWords', |
||
152 | 'magic-case' => 'getCaseMismatchMagicWords', |
||
153 | 'special' => 'getUntraslatedSpecialPages', |
||
154 | 'special-old' => 'getObsoleteSpecialPages', |
||
155 | ]; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Get total count for each check non-messages check. |
||
160 | * @return array An array of all check names mapped to a two-element array: |
||
161 | * function name to get the total count and language code or null |
||
162 | * for checked code. |
||
163 | */ |
||
164 | protected function getTotalCount() { |
||
165 | return [ |
||
166 | 'namespace' => [ 'getNamespaceNames', 'en' ], |
||
167 | 'projecttalk' => null, |
||
168 | 'magic' => [ 'getMagicWords', 'en' ], |
||
169 | 'magic-old' => [ 'getMagicWords', null ], |
||
170 | 'magic-over' => [ 'getMagicWords', null ], |
||
171 | 'magic-case' => [ 'getMagicWords', null ], |
||
172 | 'special' => [ 'getSpecialPageAliases', 'en' ], |
||
173 | 'special-old' => [ 'getSpecialPageAliases', null ], |
||
174 | ]; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Get all check descriptions. |
||
179 | * @return array An array of all check names mapped to their descriptions. |
||
180 | */ |
||
181 | View Code Duplication | protected function getDescriptions() { |
|
182 | return [ |
||
183 | 'untranslated' => '$1 message(s) of $2 are not translated to $3, but exist in en:', |
||
184 | 'duplicate' => '$1 message(s) of $2 are translated the same in en and $3:', |
||
185 | 'obsolete' => |
||
186 | '$1 message(s) of $2 do not exist in en or are in the ignore list, but exist in $3:', |
||
187 | 'variables' => '$1 message(s) of $2 in $3 don\'t match the variables used in en:', |
||
188 | 'plural' => '$1 message(s) of $2 in $3 don\'t use {{plural}} while en uses:', |
||
189 | 'empty' => '$1 message(s) of $2 in $3 are empty or -:', |
||
190 | 'whitespace' => '$1 message(s) of $2 in $3 have trailing whitespace:', |
||
191 | 'xhtml' => '$1 message(s) of $2 in $3 contain illegal XHTML:', |
||
192 | 'chars' => |
||
193 | '$1 message(s) of $2 in $3 include hidden chars which should not be used in the messages:', |
||
194 | 'links' => '$1 message(s) of $2 in $3 have problematic link(s):', |
||
195 | 'unbalanced' => '$1 message(s) of $2 in $3 have unbalanced {[]}:', |
||
196 | 'namespace' => '$1 namespace name(s) of $2 are not translated to $3, but exist in en:', |
||
197 | 'projecttalk' => |
||
198 | '$1 namespace name(s) and alias(es) in $3 are project talk namespaces without the parameter:', |
||
199 | 'magic' => '$1 magic word(s) of $2 are not translated to $3, but exist in en:', |
||
200 | 'magic-old' => '$1 magic word(s) of $2 do not exist in en, but exist in $3:', |
||
201 | 'magic-over' => '$1 magic word(s) of $2 in $3 do not contain the original en word(s):', |
||
202 | 'magic-case' => |
||
203 | '$1 magic word(s) of $2 in $3 change the case-sensitivity of the original en word:', |
||
204 | 'special' => '$1 special page alias(es) of $2 are not translated to $3, but exist in en:', |
||
205 | 'special-old' => '$1 special page alias(es) of $2 do not exist in en, but exist in $3:', |
||
206 | ]; |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * Get help. |
||
211 | * @return string The help string. |
||
212 | */ |
||
213 | protected function help() { |
||
214 | return <<<ENDS |
||
215 | Run this script to check a specific language file, or all of them. |
||
216 | Command line settings are in form --parameter[=value]. |
||
217 | Parameters: |
||
218 | --help: Show this help. |
||
219 | --lang: Language code (default: the installation default language). |
||
220 | --all: Check all customized languages. |
||
221 | --level: Show the following display level (default: 2): |
||
222 | * 0: Skip the checks (useful for checking syntax). |
||
223 | * 1: Show only the stub headers and number of wrong messages, without |
||
224 | list of messages. |
||
225 | * 2: Show only the headers and the message keys, without the message |
||
226 | values. |
||
227 | * 3: Show both the headers and the complete messages, with both keys and |
||
228 | values. |
||
229 | --links: Link the message values (default off). |
||
230 | --prefix: prefix to add to links. |
||
231 | --wikilang: For the links, what is the content language of the wiki to |
||
232 | display the output in (default en). |
||
233 | --noexif: Do not check for Exif messages (a bit hard and boring to |
||
234 | translate), if you know what they are currently not translated and want |
||
235 | to focus on other problems (default off). |
||
236 | --whitelist: Do only the following checks (form: code,code). |
||
237 | --blacklist: Do not do the following checks (form: code,code). |
||
238 | --easy: Do only the easy checks, which can be treated by non-speakers of |
||
239 | the language. |
||
240 | |||
241 | Check codes (ideally, all of them should result 0; all the checks are executed |
||
242 | by default (except language-specific check blacklists in checkLanguage.inc): |
||
243 | * untranslated: Messages which are required to translate, but are not |
||
244 | translated. |
||
245 | * duplicate: Messages which translation equal to fallback. |
||
246 | * obsolete: Messages which are untranslatable or do not exist, but are |
||
247 | translated. |
||
248 | * variables: Messages without variables which should be used, or with |
||
249 | variables which should not be used. |
||
250 | * empty: Empty messages and messages that contain only -. |
||
251 | * whitespace: Messages which have trailing whitespace. |
||
252 | * xhtml: Messages which are not well-formed XHTML (checks only few common |
||
253 | errors). |
||
254 | * chars: Messages with hidden characters. |
||
255 | * links: Messages which contains broken links to pages (does not find all). |
||
256 | * unbalanced: Messages which contains unequal numbers of opening {[ and |
||
257 | closing ]}. |
||
258 | * namespace: Namespace names that were not translated. |
||
259 | * projecttalk: Namespace names and aliases where the project talk does not |
||
260 | contain $1. |
||
261 | * magic: Magic words that were not translated. |
||
262 | * magic-old: Magic words which do not exist. |
||
263 | * magic-over: Magic words that override the original English word. |
||
264 | * magic-case: Magic words whose translation changes the case-sensitivity of |
||
265 | the original English word. |
||
266 | * special: Special page names that were not translated. |
||
267 | * special-old: Special page names which do not exist. |
||
268 | |||
269 | ENDS; |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * Execute the script. |
||
274 | */ |
||
275 | public function execute() { |
||
276 | $this->doChecks(); |
||
277 | View Code Duplication | if ( $this->level > 0 ) { |
|
278 | switch ( $this->output ) { |
||
279 | case 'plain': |
||
280 | $this->outputText(); |
||
281 | break; |
||
282 | case 'wiki': |
||
283 | $this->outputWiki(); |
||
284 | break; |
||
285 | default: |
||
286 | throw new MWException( "Invalid output type $this->output" ); |
||
287 | } |
||
288 | } |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Execute the checks. |
||
293 | */ |
||
294 | protected function doChecks() { |
||
295 | $ignoredCodes = [ 'en', 'enRTL' ]; |
||
296 | |||
297 | $this->results = []; |
||
298 | # Check the language |
||
299 | if ( $this->checkAll ) { |
||
300 | foreach ( $this->L->getLanguages() as $language ) { |
||
301 | if ( !in_array( $language, $ignoredCodes ) ) { |
||
302 | $this->results[$language] = $this->checkLanguage( $language ); |
||
303 | } |
||
304 | } |
||
305 | } else { |
||
306 | if ( in_array( $this->code, $ignoredCodes ) ) { |
||
307 | throw new MWException( "Cannot check code $this->code." ); |
||
308 | } else { |
||
309 | $this->results[$this->code] = $this->checkLanguage( $this->code ); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | $results = $this->results; |
||
314 | foreach ( $results as $code => $checks ) { |
||
315 | foreach ( $checks as $check => $messages ) { |
||
316 | foreach ( $messages as $key => $details ) { |
||
317 | if ( $this->isCheckBlacklisted( $check, $code, $key ) ) { |
||
318 | unset( $this->results[$code][$check][$key] ); |
||
319 | } |
||
320 | } |
||
321 | } |
||
322 | } |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Get the check blacklist. |
||
327 | * @return array The list of checks which should not be executed. |
||
328 | */ |
||
329 | protected function getCheckBlacklist() { |
||
330 | static $blacklist = null; |
||
331 | |||
332 | if ( $blacklist !== null ) { |
||
333 | return $blacklist; |
||
334 | } |
||
335 | |||
336 | // @codingStandardsIgnoreStart Ignore that globals should have a "wg" prefix. |
||
337 | global $checkBlacklist; |
||
338 | // @codingStandardsIgnoreEnd |
||
339 | |||
340 | $blacklist = $checkBlacklist; |
||
341 | |||
342 | Hooks::run( 'LocalisationChecksBlacklist', [ &$blacklist ] ); |
||
343 | |||
344 | return $blacklist; |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Verify whether a check is blacklisted. |
||
349 | * |
||
350 | * @param string $check Check name |
||
351 | * @param string $code Language code |
||
352 | * @param string|bool $message Message name, or False for a whole language |
||
353 | * @return bool Whether the check is blacklisted |
||
354 | */ |
||
355 | protected function isCheckBlacklisted( $check, $code, $message ) { |
||
356 | $blacklist = $this->getCheckBlacklist(); |
||
357 | |||
358 | foreach ( $blacklist as $item ) { |
||
359 | if ( isset( $item['check'] ) && $check !== $item['check'] ) { |
||
360 | continue; |
||
361 | } |
||
362 | |||
363 | if ( isset( $item['code'] ) && !in_array( $code, $item['code'] ) ) { |
||
364 | continue; |
||
365 | } |
||
366 | |||
367 | if ( isset( $item['message'] ) && |
||
368 | ( $message === false || !in_array( $message, $item['message'] ) ) |
||
369 | ) { |
||
370 | continue; |
||
371 | } |
||
372 | |||
373 | return true; |
||
374 | } |
||
375 | |||
376 | return false; |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * Check a language. |
||
381 | * @param string $code The language code. |
||
382 | * @throws MWException |
||
383 | * @return array The results. |
||
384 | */ |
||
385 | protected function checkLanguage( $code ) { |
||
386 | # Syntax check only |
||
387 | $results = []; |
||
388 | if ( $this->level === 0 ) { |
||
389 | $this->L->getMessages( $code ); |
||
390 | |||
391 | return $results; |
||
392 | } |
||
393 | |||
394 | $checkFunctions = $this->getChecks(); |
||
395 | foreach ( $this->checks as $check ) { |
||
396 | if ( $this->isCheckBlacklisted( $check, $code, false ) ) { |
||
397 | $results[$check] = []; |
||
398 | continue; |
||
399 | } |
||
400 | |||
401 | $callback = [ $this->L, $checkFunctions[$check] ]; |
||
402 | if ( !is_callable( $callback ) ) { |
||
403 | throw new MWException( "Unkown check $check." ); |
||
404 | } |
||
405 | $results[$check] = call_user_func( $callback, $code ); |
||
406 | } |
||
407 | |||
408 | return $results; |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * Format a message key. |
||
413 | * @param string $key The message key. |
||
414 | * @param string $code The language code. |
||
415 | * @return string The formatted message key. |
||
416 | */ |
||
417 | protected function formatKey( $key, $code ) { |
||
418 | if ( $this->doLinks ) { |
||
419 | $displayKey = ucfirst( $key ); |
||
420 | if ( $code == $this->wikiCode ) { |
||
421 | return "[[{$this->linksPrefix}MediaWiki:$displayKey|$key]]"; |
||
422 | } else { |
||
423 | return "[[{$this->linksPrefix}MediaWiki:$displayKey/$code|$key]]"; |
||
424 | } |
||
425 | } else { |
||
426 | return $key; |
||
427 | } |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Output the checks results as plain text. |
||
432 | */ |
||
433 | protected function outputText() { |
||
434 | foreach ( $this->results as $code => $results ) { |
||
435 | $translated = $this->L->getMessages( $code ); |
||
436 | $translated = count( $translated['translated'] ); |
||
437 | foreach ( $results as $check => $messages ) { |
||
438 | $count = count( $messages ); |
||
439 | if ( $count ) { |
||
440 | if ( $check == 'untranslated' ) { |
||
441 | $translatable = $this->L->getGeneralMessages(); |
||
442 | $total = count( $translatable['translatable'] ); |
||
443 | } elseif ( in_array( $check, $this->nonMessageChecks() ) ) { |
||
444 | $totalCount = $this->getTotalCount(); |
||
445 | $totalCount = $totalCount[$check]; |
||
446 | $callback = [ $this->L, $totalCount[0] ]; |
||
447 | $callCode = $totalCount[1] ? $totalCount[1] : $code; |
||
448 | $total = count( call_user_func( $callback, $callCode ) ); |
||
449 | } else { |
||
450 | $total = $translated; |
||
451 | } |
||
452 | $search = [ '$1', '$2', '$3' ]; |
||
453 | $replace = [ $count, $total, $code ]; |
||
454 | $descriptions = $this->getDescriptions(); |
||
455 | echo "\n" . str_replace( $search, $replace, $descriptions[$check] ) . "\n"; |
||
456 | if ( $this->level == 1 ) { |
||
457 | echo "[messages are hidden]\n"; |
||
458 | } else { |
||
459 | foreach ( $messages as $key => $value ) { |
||
460 | if ( !in_array( $check, $this->nonMessageChecks() ) ) { |
||
461 | $key = $this->formatKey( $key, $code ); |
||
462 | } |
||
463 | if ( $this->level == 2 || empty( $value ) ) { |
||
464 | echo "* $key\n"; |
||
465 | } else { |
||
466 | echo "* $key: '$value'\n"; |
||
467 | } |
||
468 | } |
||
469 | } |
||
470 | } |
||
471 | } |
||
472 | } |
||
473 | } |
||
474 | |||
475 | /** |
||
476 | * Output the checks results as wiki text. |
||
477 | */ |
||
478 | function outputWiki() { |
||
479 | $detailText = ''; |
||
480 | $rows[] = '! Language !! Code !! Total !! ' . |
||
0 ignored issues
–
show
|
|||
481 | implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) ); |
||
482 | foreach ( $this->results as $code => $results ) { |
||
483 | $detailTextForLang = "==$code==\n"; |
||
484 | $numbers = []; |
||
485 | $problems = 0; |
||
486 | $detailTextForLangChecks = []; |
||
487 | foreach ( $results as $check => $messages ) { |
||
488 | if ( in_array( $check, $this->nonMessageChecks() ) ) { |
||
489 | continue; |
||
490 | } |
||
491 | $count = count( $messages ); |
||
492 | if ( $count ) { |
||
493 | $problems += $count; |
||
494 | $messageDetails = []; |
||
495 | foreach ( $messages as $key => $details ) { |
||
496 | $displayKey = $this->formatKey( $key, $code ); |
||
497 | $messageDetails[] = $displayKey; |
||
498 | } |
||
499 | $detailTextForLangChecks[] = "=== $code-$check ===\n* " . implode( ', ', $messageDetails ); |
||
500 | $numbers[] = "'''[[#$code-$check|$count]]'''"; |
||
501 | } else { |
||
502 | $numbers[] = $count; |
||
503 | } |
||
504 | } |
||
505 | |||
506 | if ( count( $detailTextForLangChecks ) ) { |
||
507 | $detailText .= $detailTextForLang . implode( "\n", $detailTextForLangChecks ) . "\n"; |
||
508 | } |
||
509 | |||
510 | if ( !$problems ) { |
||
511 | # Don't list languages without problems |
||
512 | continue; |
||
513 | } |
||
514 | $language = Language::fetchLanguageName( $code ); |
||
515 | $rows[] = "| $language || $code || $problems || " . implode( ' || ', $numbers ); |
||
516 | } |
||
517 | |||
518 | $tableRows = implode( "\n|-\n", $rows ); |
||
519 | |||
520 | $version = SpecialVersion::getVersion( 'nodb' ); |
||
521 | // @codingStandardsIgnoreStart Long line. |
||
522 | echo <<<EOL |
||
523 | '''Check results are for:''' <code>$version</code> |
||
524 | |||
525 | |||
526 | {| class="sortable wikitable" border="2" cellpadding="4" cellspacing="0" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear: both;" |
||
527 | $tableRows |
||
528 | |} |
||
529 | |||
530 | $detailText |
||
531 | |||
532 | EOL; |
||
533 | // @codingStandardsIgnoreEnd |
||
534 | } |
||
535 | |||
536 | /** |
||
537 | * Check if there are any results for the checks, in any language. |
||
538 | * @return bool True if there are any results, false if not. |
||
539 | */ |
||
540 | protected function isEmpty() { |
||
541 | foreach ( $this->results as $results ) { |
||
542 | foreach ( $results as $messages ) { |
||
543 | if ( !empty( $messages ) ) { |
||
544 | return false; |
||
545 | } |
||
546 | } |
||
547 | } |
||
548 | |||
549 | return true; |
||
550 | } |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * @ingroup MaintenanceLanguage |
||
555 | */ |
||
556 | class CheckExtensionsCLI extends CheckLanguageCLI { |
||
557 | private $extensions; |
||
558 | |||
559 | /** |
||
560 | * Constructor. |
||
561 | * @param array $options Options for script. |
||
562 | * @param string $extension The extension name (or names). |
||
563 | */ |
||
564 | public function __construct( array $options, $extension ) { |
||
565 | if ( isset( $options['help'] ) ) { |
||
566 | echo $this->help(); |
||
567 | exit( 1 ); |
||
568 | } |
||
569 | |||
570 | View Code Duplication | if ( isset( $options['lang'] ) ) { |
|
571 | $this->code = $options['lang']; |
||
572 | } else { |
||
573 | global $wgLanguageCode; |
||
574 | $this->code = $wgLanguageCode; |
||
575 | } |
||
576 | |||
577 | if ( isset( $options['level'] ) ) { |
||
578 | $this->level = $options['level']; |
||
579 | } |
||
580 | |||
581 | $this->doLinks = isset( $options['links'] ); |
||
582 | |||
583 | if ( isset( $options['wikilang'] ) ) { |
||
584 | $this->wikiCode = $options['wikilang']; |
||
585 | } |
||
586 | |||
587 | View Code Duplication | if ( isset( $options['whitelist'] ) ) { |
|
588 | $this->checks = explode( ',', $options['whitelist'] ); |
||
589 | } elseif ( isset( $options['blacklist'] ) ) { |
||
590 | $this->checks = array_diff( |
||
591 | isset( $options['easy'] ) ? $this->easyChecks() : $this->defaultChecks(), |
||
592 | explode( ',', $options['blacklist'] ) |
||
593 | ); |
||
594 | } elseif ( isset( $options['easy'] ) ) { |
||
595 | $this->checks = $this->easyChecks(); |
||
596 | } else { |
||
597 | $this->checks = $this->defaultChecks(); |
||
598 | } |
||
599 | |||
600 | if ( isset( $options['output'] ) ) { |
||
601 | $this->output = $options['output']; |
||
602 | } |
||
603 | |||
604 | # Some additional checks not enabled by default |
||
605 | if ( isset( $options['duplicate'] ) ) { |
||
606 | $this->checks[] = 'duplicate'; |
||
607 | } |
||
608 | |||
609 | $this->extensions = []; |
||
610 | $extensions = new PremadeMediawikiExtensionGroups(); |
||
611 | $extensions->addAll(); |
||
612 | if ( $extension == 'all' ) { |
||
613 | View Code Duplication | foreach ( MessageGroups::singleton()->getGroups() as $group ) { |
|
614 | if ( strpos( $group->getId(), 'ext-' ) === 0 && !$group->isMeta() ) { |
||
615 | $this->extensions[] = new ExtensionLanguages( $group ); |
||
616 | } |
||
617 | } |
||
618 | } elseif ( $extension == 'wikimedia' ) { |
||
619 | $wikimedia = MessageGroups::getGroup( 'ext-0-wikimedia' ); |
||
620 | foreach ( $wikimedia->wmfextensions() as $extension ) { |
||
621 | $group = MessageGroups::getGroup( $extension ); |
||
622 | $this->extensions[] = new ExtensionLanguages( $group ); |
||
623 | } |
||
624 | } elseif ( $extension == 'flaggedrevs' ) { |
||
625 | View Code Duplication | foreach ( MessageGroups::singleton()->getGroups() as $group ) { |
|
626 | if ( strpos( $group->getId(), 'ext-flaggedrevs-' ) === 0 && !$group->isMeta() ) { |
||
627 | $this->extensions[] = new ExtensionLanguages( $group ); |
||
628 | } |
||
629 | } |
||
630 | } else { |
||
631 | $extensions = explode( ',', $extension ); |
||
632 | foreach ( $extensions as $extension ) { |
||
633 | $group = MessageGroups::getGroup( 'ext-' . $extension ); |
||
634 | if ( $group ) { |
||
635 | $extension = new ExtensionLanguages( $group ); |
||
636 | $this->extensions[] = $extension; |
||
637 | } else { |
||
638 | print "No such extension $extension.\n"; |
||
639 | } |
||
640 | } |
||
641 | } |
||
642 | } |
||
643 | |||
644 | /** |
||
645 | * Get the default checks. |
||
646 | * @return array A list of the default checks. |
||
647 | */ |
||
648 | protected function defaultChecks() { |
||
649 | return [ |
||
650 | 'untranslated', 'duplicate', 'obsolete', 'variables', 'empty', 'plural', |
||
651 | 'whitespace', 'xhtml', 'chars', 'links', 'unbalanced', |
||
652 | ]; |
||
653 | } |
||
654 | |||
655 | /** |
||
656 | * Get the checks which check other things than messages. |
||
657 | * @return array A list of the non-message checks. |
||
658 | */ |
||
659 | protected function nonMessageChecks() { |
||
660 | return []; |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Get the checks that can easily be treated by non-speakers of the language. |
||
665 | * @return array A list of the easy checks. |
||
666 | */ |
||
667 | protected function easyChecks() { |
||
668 | return [ |
||
669 | 'duplicate', 'obsolete', 'empty', 'whitespace', 'xhtml', 'chars', |
||
670 | ]; |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * Get help. |
||
675 | * @return string The help string. |
||
676 | */ |
||
677 | protected function help() { |
||
678 | return <<<ENDS |
||
679 | Run this script to check the status of a specific language in extensions, or |
||
680 | all of them. Command line settings are in form --parameter[=value], except for |
||
681 | the first one. |
||
682 | Parameters: |
||
683 | * First parameter (mandatory): Extension name, multiple extension names |
||
684 | (separated by commas), "all" for all the extensions, "wikimedia" for |
||
685 | extensions used by Wikimedia or "flaggedrevs" for all FLaggedRevs |
||
686 | extension messages. |
||
687 | * lang: Language code (default: the installation default language). |
||
688 | * help: Show this help. |
||
689 | * level: Show the following display level (default: 2). |
||
690 | * links: Link the message values (default off). |
||
691 | * wikilang: For the links, what is the content language of the wiki to |
||
692 | display the output in (default en). |
||
693 | * whitelist: Do only the following checks (form: code,code). |
||
694 | * blacklist: Do not perform the following checks (form: code,code). |
||
695 | * easy: Do only the easy checks, which can be treated by non-speakers of |
||
696 | the language. |
||
697 | |||
698 | Check codes (ideally, all of them should result 0; all the checks are executed |
||
699 | by default (except language-specific check blacklists in checkLanguage.inc): |
||
700 | * untranslated: Messages which are required to translate, but are not |
||
701 | translated. |
||
702 | * duplicate: Messages which translation equal to fallback. |
||
703 | * obsolete: Messages which are untranslatable, but translated. |
||
704 | * variables: Messages without variables which should be used, or with |
||
705 | variables which should not be used. |
||
706 | * empty: Empty messages. |
||
707 | * whitespace: Messages which have trailing whitespace. |
||
708 | * xhtml: Messages which are not well-formed XHTML (checks only few common |
||
709 | errors). |
||
710 | * chars: Messages with hidden characters. |
||
711 | * links: Messages which contains broken links to pages (does not find all). |
||
712 | * unbalanced: Messages which contains unequal numbers of opening {[ and |
||
713 | closing ]}. |
||
714 | |||
715 | Display levels (default: 2): |
||
716 | * 0: Skip the checks (useful for checking syntax). |
||
717 | * 1: Show only the stub headers and number of wrong messages, without list |
||
718 | of messages. |
||
719 | * 2: Show only the headers and the message keys, without the message |
||
720 | values. |
||
721 | * 3: Show both the headers and the complete messages, with both keys and |
||
722 | values. |
||
723 | |||
724 | ENDS; |
||
725 | } |
||
726 | |||
727 | /** |
||
728 | * Execute the script. |
||
729 | */ |
||
730 | public function execute() { |
||
731 | $this->doChecks(); |
||
732 | } |
||
733 | |||
734 | /** |
||
735 | * Check a language and show the results. |
||
736 | * @param string $code The language code. |
||
737 | * @throws MWException |
||
738 | */ |
||
739 | protected function checkLanguage( $code ) { |
||
740 | foreach ( $this->extensions as $extension ) { |
||
741 | $this->L = $extension; |
||
742 | $this->results = []; |
||
743 | $this->results[$code] = parent::checkLanguage( $code ); |
||
744 | |||
745 | if ( !$this->isEmpty() ) { |
||
746 | echo $extension->name() . ":\n"; |
||
747 | |||
748 | View Code Duplication | if ( $this->level > 0 ) { |
|
749 | switch ( $this->output ) { |
||
750 | case 'plain': |
||
751 | $this->outputText(); |
||
752 | break; |
||
753 | case 'wiki': |
||
754 | $this->outputWiki(); |
||
755 | break; |
||
756 | default: |
||
757 | throw new MWException( "Invalid output type $this->output" ); |
||
758 | } |
||
759 | } |
||
760 | |||
761 | echo "\n"; |
||
762 | } |
||
763 | } |
||
764 | } |
||
765 | } |
||
766 | |||
767 | // Blacklist some checks for some languages or some messages |
||
768 | // Possible keys of the sub arrays are: 'check', 'code' and 'message'. |
||
769 | $checkBlacklist = [ |
||
770 | [ |
||
771 | 'check' => 'plural', |
||
772 | 'code' => [ 'az', 'bo', 'cdo', 'dz', 'id', 'fa', 'gan', 'gan-hans', |
||
773 | 'gan-hant', 'gn', 'hak', 'hu', 'ja', 'jv', 'ka', 'kk-arab', |
||
774 | 'kk-cyrl', 'kk-latn', 'km', 'kn', 'ko', 'lzh', 'mn', 'ms', |
||
775 | 'my', 'sah', 'sq', 'tet', 'th', 'to', 'tr', 'vi', 'wuu', 'xmf', |
||
776 | 'yo', 'yue', 'zh', 'zh-classical', 'zh-cn', 'zh-hans', |
||
777 | 'zh-hant', 'zh-hk', 'zh-sg', 'zh-tw', 'zh-yue' |
||
778 | ], |
||
779 | ], |
||
780 | [ |
||
781 | 'check' => 'chars', |
||
782 | 'code' => [ 'my' ], |
||
783 | ], |
||
784 | ]; |
||
785 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArray
is initialized the first time when the foreach loop is entered. You can also see that the value of thebar
key is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.