MessageChecks.php

Go to the documentation of this file.
00001 <?php
00047 class MessageChecker {
00048     protected $checks = array();
00049     protected $group = null;
00050     private static $globalBlacklist = null;
00051 
00056     public function __construct( MessageGroup $group ) {
00057         global $wgTranslateCheckBlacklist;
00058 
00059         if ( $wgTranslateCheckBlacklist === false ) {
00060             self::$globalBlacklist = array();
00061         } elseif ( self::$globalBlacklist === null ) {
00062             $file = $wgTranslateCheckBlacklist;
00063             $list = PHPVariableLoader::loadVariableFromPHPFile( $file, 'checkBlacklist' );
00064             $keys = array( 'group', 'check', 'subcheck', 'code', 'message' );
00065 
00066             foreach ( $list as $key => $pattern ) {
00067                 foreach ( $keys as $checkKey ) {
00068                     if ( !isset( $pattern[$checkKey] ) ) {
00069                         $list[$key][$checkKey] = '#';
00070                     } elseif ( is_array( $pattern[$checkKey] ) ) {
00071                         $list[$key][$checkKey] =
00072                             array_map( array( $this, 'foldValue' ), $pattern[$checkKey] );
00073                     } else {
00074                         $list[$key][$checkKey] = $this->foldValue( $pattern[$checkKey] );
00075                     }
00076                 }
00077             }
00078 
00079             self::$globalBlacklist = $list;
00080         }
00081 
00082         $this->group = $group;
00083     }
00084 
00090     protected function foldValue( $value ) {
00091         return str_replace( ' ', '_', strtolower( $value ) );
00092     }
00093 
00098     public function setChecks( $checks ) {
00099         foreach ( $checks as $k => $c ) {
00100             if ( !is_callable( $c ) ) {
00101                 unset( $checks[$k] );
00102                 wfWarn( "Check function for check $k is not callable" );
00103             }
00104         }
00105         $this->checks = $checks;
00106     }
00107 
00113     public function addCheck( $check ) {
00114         if ( is_callable( $check ) ) {
00115             $this->checks[] = $check;
00116         }
00117     }
00118 
00127     public function checkMessage( TMessage $message, $code ) {
00128         $warningsArray = array();
00129         $messages = array( $message );
00130 
00131         foreach ( $this->checks as $check ) {
00132             call_user_func_array( $check, array( $messages, $code, &$warningsArray ) );
00133         }
00134 
00135         $warningsArray = $this->filterWarnings( $warningsArray );
00136         if ( !count( $warningsArray ) ) {
00137             return array();
00138         }
00139 
00140         $warnings = $warningsArray[$message->key()];
00141         $warnings = $this->fixMessageParams( $warnings );
00142 
00143         return $warnings;
00144     }
00145 
00152     public function checkMessageFast( TMessage $message, $code ) {
00153         $warningsArray = array();
00154         $messages = array( $message );
00155 
00156         foreach ( $this->checks as $check ) {
00157             call_user_func_array( $check, array( $messages, $code, &$warningsArray ) );
00158             if ( count( $warningsArray ) ) {
00159                 return true;
00160             }
00161         }
00162 
00163         return false;
00164     }
00165 
00171     protected function filterWarnings( $warningsArray ) {
00172         $groupId = $this->group->getId();
00173 
00174         // There is an array of messages...
00175         foreach ( $warningsArray as $mkey => $warnings ) {
00176             // ... each which has an array of warnings.
00177             foreach ( $warnings as $wkey => $warning ) {
00178                 $check = array_shift( $warning );
00179                 // Check if the key is blacklisted...
00180                 foreach ( self::$globalBlacklist as $pattern ) {
00181                     if ( !$this->match( $pattern['group'], $groupId ) ) {
00182                         continue;
00183                     }
00184                     if ( !$this->match( $pattern['check'], $check[0] ) ) {
00185                         continue;
00186                     }
00187                     if ( !$this->match( $pattern['subcheck'], $check[1] ) ) {
00188                         continue;
00189                     }
00190                     if ( !$this->match( $pattern['message'], $check[2] ) ) {
00191                         continue;
00192                     }
00193                     if ( !$this->match( $pattern['code'], $check[3] ) ) {
00194                         continue;
00195                     }
00196 
00197                     // If all of the aboce match, filter the check
00198                     unset( $warningsArray[$mkey][$wkey] );
00199                 }
00200             }
00201         }
00202 
00203         return $warningsArray;
00204     }
00205 
00212     protected function match( $pattern, $value ) {
00213         if ( $pattern === '#' ) {
00214             return true;
00215         } elseif ( is_array( $pattern ) ) {
00216             return in_array( strtolower( $value ), $pattern, true );
00217         } else {
00218             return strtolower( $value ) === $pattern;
00219         }
00220     }
00221 
00229     protected function fixMessageParams( $warnings ) {
00230         $lang = RequestContext::getMain()->getLanguage();
00231 
00232         foreach ( $warnings as $wkey => $warning ) {
00233             array_shift( $warning );
00234             $message = array( array_shift( $warning ) );
00235 
00236             foreach ( $warning as $param ) {
00237                 if ( !is_array( $param ) ) {
00238                     $message[] = $param;
00239                 } else {
00240                     list( $type, $value ) = $param;
00241                     if ( $type === 'COUNT' ) {
00242                         $message[] = $lang->formatNum( $value );
00243                     } elseif ( $type === 'PARAMS' ) {
00244                         $message[] = $lang->commaList( $value );
00245                     } else {
00246                         throw new MWException( "Unknown type $type" );
00247                     }
00248                 }
00249             }
00250             $warnings[$wkey] = $message;
00251         }
00252 
00253         return $warnings;
00254     }
00255 
00262     protected static function compareArrays( $defs, $trans ) {
00263         $missing = array();
00264 
00265         foreach ( $defs as $defVar ) {
00266             if ( !in_array( $defVar, $trans ) ) {
00267                 $missing[] = $defVar;
00268             }
00269         }
00270 
00271         return $missing;
00272     }
00273 
00281     protected function printfCheck( $messages, $code, array &$warnings ) {
00282         $this->parameterCheck( $messages, $code, $warnings, '/%(\d+\$)?[sduf]/U' );
00283     }
00284 
00292     protected function rubyVariableCheck( $messages, $code, array &$warnings ) {
00293         $this->parameterCheck( $messages, $code, $warnings, '/%{[a-zA-Z_]+}/' );
00294     }
00295 
00303     protected function pythonInterpolationCheck( $messages, $code, array &$warnings ) {
00304         $pattern = '/\%\([a-zA-Z0-9]*?\)[diouxXeEfFgGcrs]/U';
00305         $this->parameterCheck( $messages, $code, $warnings, $pattern );
00306     }
00307 
00315     protected function braceBalanceCheck( $messages, $code, array &$warnings ) {
00316         foreach ( $messages as $message ) {
00317             $key = $message->key();
00318             $translation = $message->translation();
00319             $translation = preg_replace( '/[^{}[\]()]/u', '', $translation );
00320 
00321             $subcheck = 'brace';
00322             $counts = array(
00323                 '{' => 0, '}' => 0,
00324                 '[' => 0, ']' => 0,
00325                 '(' => 0, ')' => 0,
00326             );
00327 
00328             $len = strlen( $translation );
00329             for ( $i = 0; $i < $len; $i++ ) {
00330                 $char = $translation[$i];
00331                 $counts[$char]++;
00332             }
00333 
00334             $balance = array();
00335             if ( $counts['['] !== $counts[']'] ) {
00336                 $balance[] = '[]: ' . ( $counts['['] - $counts[']'] );
00337             }
00338 
00339             if ( $counts['{'] !== $counts['}'] ) {
00340                 $balance[] = '{}: ' . ( $counts['{'] - $counts['}'] );
00341             }
00342 
00343             if ( $counts['('] !== $counts[')'] ) {
00344                 $balance[] = '(): ' . ( $counts['('] - $counts[')'] );
00345             }
00346 
00347             if ( count( $balance ) ) {
00348                 $warnings[$key][] = array(
00349                     array( 'balance', $subcheck, $key, $code ),
00350                     'translate-checks-balance',
00351                     array( 'PARAMS', $balance ),
00352                     array( 'COUNT', count( $balance ) ),
00353                 );
00354             }
00355         }
00356     }
00357 
00366     protected function parameterCheck( $messages, $code, array &$warnings, $pattern ) {
00367         foreach ( $messages as $message ) {
00368             $key = $message->key();
00369             $definition = $message->definition();
00370             $translation = $message->translation();
00371 
00372             preg_match_all( $pattern, $definition, $defVars );
00373             preg_match_all( $pattern, $translation, $transVars );
00374 
00375             // Check for missing variables in the translation
00376             $subcheck = 'missing';
00377             $params = self::compareArrays( $defVars[0], $transVars[0] );
00378 
00379             if ( count( $params ) ) {
00380                 $warnings[$key][] = array(
00381                     array( 'variable', $subcheck, $key, $code ),
00382                     'translate-checks-parameters',
00383                     array( 'PARAMS', $params ),
00384                     array( 'COUNT', count( $params ) ),
00385                 );
00386             }
00387 
00388             // Check for unknown variables in the translatio
00389             $subcheck = 'unknown';
00390             $params = self::compareArrays( $transVars[0], $defVars[0] );
00391 
00392             if ( count( $params ) ) {
00393                 $warnings[$key][] = array(
00394                     array( 'variable', $subcheck, $key, $code ),
00395                     'translate-checks-parameters-unknown',
00396                     array( 'PARAMS', $params ),
00397                     array( 'COUNT', count( $params ) ),
00398                 );
00399             }
00400         }
00401     }
00402 
00408     protected function balancedTagsCheck( $messages, $code, array &$warnings ) {
00409         foreach ( $messages as $message ) {
00410             $key = $message->key();
00411             $translation = $message->translation();
00412 
00413             libxml_use_internal_errors( true );
00414             libxml_clear_errors();
00415             $doc = simplexml_load_string( Xml::tags( 'root', null, $translation ) );
00416             if ( $doc ) {
00417                 continue;
00418             }
00419 
00420             $errors = libxml_get_errors();
00421             $params = array();
00422             foreach ( $errors as $error ) {
00423                 if ( $error->code !== 76 && $error->code !== 73 ) {
00424                     continue;
00425                 }
00426                 $params[] = "<br />• [{$error->code}] $error->message";
00427             }
00428 
00429             if ( !count( $params ) ) {
00430                 continue;
00431             }
00432 
00433             $warnings[$key][] = array(
00434                 array( 'tags', 'balance', $key, $code ),
00435                 'translate-checks-format',
00436                 array( 'PARAMS', $params ),
00437                 array( 'COUNT', count( $params ) ),
00438             );
00439         }
00440 
00441         libxml_clear_errors();
00442     }
00443 }
Generated on Tue Oct 29 00:00:24 2013 for MediaWiki Translate Extension by  doxygen 1.6.3