processMessageChanges.php

Go to the documentation of this file.
00001 <?php
00012 // Standard boilerplate to define $IP
00013 if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
00014     $IP = getenv( 'MW_INSTALL_PATH' );
00015 } else {
00016     $dir = __DIR__;
00017     $IP = "$dir/../../..";
00018 }
00019 require_once "$IP/maintenance/Maintenance.php";
00020 
00030 class ProcessMessageChanges extends Maintenance {
00031     protected $changes = array();
00032 
00036     protected $counter;
00037 
00038     public function __construct() {
00039         parent::__construct();
00040         $this->mDescription = 'Script for processing message changes in file based message groups';
00041         $this->addOption(
00042             'group',
00043             '(optional) Comma separated list of group IDs to process (can use * as wildcard). ' .
00044                 'Default: "*"',
00045             false, /*required*/
00046             true /*has arg*/
00047         );
00048         $this->addOption(
00049             'skipgroup',
00050             '(optional) Comma separated list of group IDs to not process (can use * ' .
00051                 'as wildcard). Overrides --group parameter.',
00052             false, /*required*/
00053             true /*has arg*/
00054         );
00055     }
00056 
00057     public function execute() {
00058         $groups = $this->getGroups();
00059 
00060         $this->counter = 0;
00062         foreach ( $groups as $id => $group ) {
00063             $this->output( "Processing $id\n" );
00064             $this->processMessageGroup( $group );
00065             if ( $this->counter > 25000 ) {
00066                 $this->output( "Too many changes. Rerun this script after processing current changes\n" );
00067                 break;
00068             }
00069         }
00070         if ( count( $this->changes ) ) {
00071             $this->writeChanges();
00072             $this->output( "Process changes with Special:ManageMessageGroups\n" );
00073         } else {
00074             $this->output( "No changes found\n" );
00075         }
00076     }
00077 
00082     protected function getGroups() {
00084         $groups = MessageGroups::getGroupsByType( 'FileBasedMessageGroup' );
00085 
00086         // Include all if option not given
00087         $include = $this->getOption( 'group', '*' );
00088         $include = explode( ',', $include );
00089         $include = array_map( 'trim', $include );
00090         $include = MessageGroups::expandWildcards( $include );
00091 
00092         // Exclude nothing if option not given
00093         $exclude = $this->getOption( 'skipgroup', '' );
00094         $exclude = explode( ',', $exclude );
00095         $exclude = array_map( 'trim', $exclude );
00096         $exclude = MessageGroups::expandWildcards( $exclude );
00097 
00098         // Flip to allow isset
00099         $include = array_flip( $include );
00100         $exclude = array_flip( $exclude );
00101 
00102         $groups = array_filter( $groups,
00103             function ( MessageGroup $group ) use ( $include, $exclude ) {
00104                 $id = $group->getId();
00105 
00106                 return isset( $include[$id] ) && !isset( $exclude[$id] );
00107             }
00108         );
00109 
00110         return $groups;
00111     }
00112 
00113     protected function writeChanges() {
00114         // This method is almost identical with MessageIndex::store
00115         wfProfileIn( __METHOD__ );
00116         $array = $this->changes;
00117         /* This will overwrite the previous cache file if any. Once the cache
00118          * file is processed with Special:ManageMessageGroups, it is
00119          * renamed so that it wont be processed again. */
00120         $file = TranslateUtils::cacheFile( SpecialManageGroups::CHANGEFILE );
00121         $cache = CdbWriter::open( $file );
00122         $keys = array_keys( $array );
00123         $cache->set( '#keys', serialize( $keys ) );
00124 
00125         foreach ( $array as $key => $value ) {
00126             $value = serialize( $value );
00127             $cache->set( $key, $value );
00128         }
00129         $cache->close();
00130         wfProfileOut( __METHOD__ );
00131     }
00132 
00133     protected function processMessageGroup( FileBasedMessageGroup $group ) {
00134         $languages = Language::getLanguageNames( false );
00135 
00136         // Process the source language before others
00137         $sourceLanguage = $group->getSourceLanguage();
00138         unset( $languages[$sourceLanguage] );
00139         $languages = array_keys( $languages );
00140         $this->processLanguage( $group, $sourceLanguage );
00141 
00142         foreach ( $languages as $code ) {
00143             $this->processLanguage( $group, $code );
00144         }
00145     }
00146 
00147     protected function processLanguage( FileBasedMessageGroup $group, $code ) {
00148         wfProfileIn( __METHOD__ );
00149         $cache = new MessageGroupCache( $group, $code );
00150         $reason = 0;
00151         if ( !$cache->isValid( $reason ) ) {
00152             $this->addMessageUpdateChanges( $group, $code, $reason, $cache );
00153 
00154             if ( !isset( $this->changes[$group->getId()][$code] ) ) {
00155                 /* Update the cache immediately if file and wiki state match.
00156                  * Otherwise the cache will get outdated compared to file state
00157                  * and will give false positive conflicts later. */
00158                 $cache->create();
00159             }
00160         }
00161         wfProfileOut( __METHOD__ );
00162     }
00163 
00180     protected function addMessageUpdateChanges( FileBasedMessageGroup $group, $code,
00181         $reason, $cache
00182     ) {
00183         wfProfileIn( __METHOD__ );
00184         /* This throws a warning if message definitions are not yet
00185          * cached and will read the file for definitions. */
00186         wfSuppressWarnings();
00187         $wiki = $group->initCollection( $code );
00188         wfRestoreWarnings();
00189         $wiki->filter( 'hastranslation', false );
00190         $wiki->loadTranslations();
00191         $wikiKeys = $wiki->getMessageKeys();
00192 
00193         // By-pass cached message definitions
00194         $ffs = $group->getFFS();
00195         if ( $code === $group->getSourceLanguage() && !$ffs->exists( $code ) ) {
00196             $path = $group->getSourceFilePath( $code );
00197             $this->error( "Source message file for {$group->getId()} does not exist. Looking for $path", 1 );
00198         }
00199         $file = $ffs->read( $code );
00200         if ( !isset( $file['MESSAGES'] ) ) {
00201             error_log( "{$group->getId()} has an FFS - the FFS didn't return cake for $code" );
00202         }
00203         $fileKeys = array_keys( $file['MESSAGES'] );
00204 
00205         $common = array_intersect( $fileKeys, $wikiKeys );
00206 
00207         $supportsFuzzy = $ffs->supportsFuzzy();
00208 
00209         foreach ( $common as $key ) {
00210             $sourceContent = $file['MESSAGES'][$key];
00211             $wikiContent = $wiki[$key]->translation();
00212 
00213             // If FFS doesn't support it, ignore fuzziness as difference
00214             $wikiContent = str_replace( TRANSLATE_FUZZY, '', $wikiContent );
00215             // But if it does, ensure we have exactly one fuzzy marker prefixed
00216             if ( $supportsFuzzy === 'yes' && $wiki[$key]->hasTag( 'fuzzy' ) ) {
00217                 $wikiContent = TRANSLATE_FUZZY . $wikiContent;
00218             }
00219 
00220             if ( self::compareContent( $sourceContent, $wikiContent ) ) {
00221                 // File and wiki stage agree, nothing to do
00222                 continue;
00223             }
00224 
00225             // Check against interim cache to see whether we have changes
00226             // in the wiki, in the file or both.
00227 
00228             if ( $reason !== MessageGroupCache::NO_CACHE ) {
00229                 $cacheContent = $cache->get( $key );
00230 
00231                 /* We want to ignore the common situation that the string
00232                  * in the wiki has been changed since the last export.
00233                  * Hence we check that source === cache && cache !== wiki
00234                  * and if so we skip this string. */
00235                 if (
00236                     !self::compareContent( $wikiContent, $cacheContent ) &&
00237                     self::compareContent( $sourceContent, $cacheContent )
00238                 ) {
00239                     continue;
00240                 }
00241             }
00242 
00243             $this->addChange( 'change', $group, $code, $key, $sourceContent );
00244         }
00245 
00246         $added = array_diff( $fileKeys, $wikiKeys );
00247         foreach ( $added as $key ) {
00248             $sourceContent = $file['MESSAGES'][$key];
00249             if ( trim( $sourceContent ) === '' ) {
00250                 continue;
00251             }
00252             $this->addChange( 'addition', $group, $code, $key, $sourceContent );
00253         }
00254 
00255         /* Should the cache not exist, don't consider the messages
00256          * missing from the file as deleted - they probably aren't
00257          * yet exported. For example new language translations are
00258          * exported the first time. */
00259         if ( $reason !== MessageGroupCache::NO_CACHE ) {
00260             $deleted = array_diff( $wikiKeys, $fileKeys );
00261             foreach ( $deleted as $key ) {
00262                 if ( $cache->get( $key ) === false ) {
00263                     /* This message has never existed in the cache, so it
00264                      * must be a newly made in the wiki. */
00265                     continue;
00266                 }
00267                 $this->addChange( 'deletion', $group, $code, $key, null );
00268             }
00269         }
00270 
00271         wfProfileOut( __METHOD__ );
00272     }
00273 
00274     protected function addChange( $type, $group, $language, $key, $content ) {
00275         $this->counter++;
00276         $this->changes[$group->getId()][$language][$type][] = array(
00277             'key' => $key,
00278             'content' => $content,
00279         );
00280     }
00281 
00289     protected static function compareContent( $a, $b ) {
00290         return $a === $b;
00291     }
00292 }
00293 
00294 $maintClass = 'ProcessMessageChanges';
00295 require_once RUN_MAINTENANCE_IF_MAIN;
Generated on Tue Oct 29 00:00:24 2013 for MediaWiki Translate Extension by  doxygen 1.6.3