00001 <?php
00012
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
00021 class CommandlineExport extends Maintenance {
00022 public function __construct() {
00023 parent::__construct();
00024 $this->mDescription = 'Message exporter.';
00025 $this->addOption(
00026 'group',
00027 'Comma separated list of group IDs (can use * as wildcard)',
00028 true,
00029 true
00030 );
00031 $this->addOption(
00032 'lang',
00033 'Comma separated list of language codes or *',
00034 true,
00035 true
00036 );
00037 $this->addOption(
00038 'target',
00039 'Target directory for exported files',
00040 true,
00041 true
00042 );
00043 $this->addOption(
00044 'skip',
00045 '(optional) Languages to skip, comma separated list',
00046 false,
00047 true
00048 );
00049 $this->addOption(
00050 'skipgroup',
00051 '(optional) Comma separated list of group IDs that should not be exported',
00052 false,
00053 true
00054 );
00055 $this->addOption(
00056 'threshold',
00057 '(optional) Do not export under this percentage translated',
00058 false,
00059 true
00060 );
00061 $this->addOption(
00062 'hours',
00063 '(optional) Only export languages with changes in the last given number of hours',
00064 false,
00065 true
00066 );
00067 $this->addOption(
00068 'ppgettext',
00069 '(optional) Group root path for checkout of product. "msgmerge" will post ' .
00070 'process on the export result based on the current source file ' .
00071 'in that location (from sourcePattern or definitionFile)',
00072 false,
00073 true
00074 );
00075 $this->addOption(
00076 'no-location',
00077 '(optional) Only used combined with "ppgettext". This option will rebuild ' .
00078 'the gettext file without location information',
00079 false,
00080 false
00081 );
00082 $this->addOption(
00083 'no-fuzzy',
00084 '(optional) Do not include any messages marked as fuzzy/outdated',
00085 false,
00086 false
00087 );
00088 $this->addOption(
00089 'codemaponly',
00090 '(optional) Only export languages that have a codeMap entry',
00091 false,
00092 false
00093 );
00094 $this->addOption(
00095 'core-meta',
00096 '(optional) Allow export of specific MediaWiki core meta groups ' .
00097 '(translatewiki.net specific)',
00098 false,
00099 false
00100 );
00101 }
00102
00103 public function execute() {
00104 $target = $this->getOption( 'target' );
00105 if ( !is_writable( $target ) ) {
00106 $this->error( "Target directory is not writable ($target).", 1 );
00107 }
00108
00109 $threshold = $this->getOption( 'threshold' );
00110 $noFuzzy = $this->hasOption( 'no-fuzzy' );
00111
00112 $noLocation = '';
00113 if ( $this->hasOption( 'no-location' ) ) {
00114 $noLocation = '--no-location ';
00115 };
00116
00117 $skip = array();
00118 if ( $this->hasOption( 'skip' ) ) {
00119 $skip = array_map( 'trim', explode( ',', $this->getOption( 'skip' ) ) );
00120 }
00121
00122 $reqLangs = TranslateUtils::parseLanguageCodes( $this->getOption( 'lang' ) );
00123 $reqLangs = array_flip( $reqLangs );
00124 foreach ( $skip as $skipLang ) {
00125 unset( $reqLangs[$skipLang] );
00126 }
00127 $reqLangs = array_flip( $reqLangs );
00128
00129 $codemapOnly = $this->hasOption( 'codemaponly' );
00130
00131 $groupIds = explode( ',', trim( $this->getOption( 'group' ) ) );
00132 $groupIds = MessageGroups::expandWildcards( $groupIds );
00133 $groups = MessageGroups::getGroupsById( $groupIds );
00134
00135 $coreMeta = $this->hasOption( 'core-meta' );
00136 foreach ( $groups as $groupId => $group ) {
00137 if ( !$group instanceof MessageGroup ) {
00138 $this->error( "EE2: Unknown message group $groupId.", 1 );
00139 }
00140
00141 if ( $group->isMeta() ) {
00142 if ( !$coreMeta ) {
00143 $this->output( "Skipping meta message group $groupId.\n" );
00144 unset( $groups[$groupId] );
00145 continue;
00146 } elseif ( strstr( $group->getId(), 'core-1', true ) !== '' ) {
00147
00148 $this->output( "Skipping meta message group $groupId.\n" );
00149 unset( $groups[$groupId] );
00150 continue;
00151 }
00152 }
00153 }
00154
00155 if ( !count( $groups ) ) {
00156 $this->error( "EE1: No valid message groups identified.", 1 );
00157 }
00158
00159 $changeFilter = false;
00160 $hours = $this->getOption( 'hours' );
00161 if ( $hours ) {
00162 $namespaces = array();
00163
00165 foreach ( $groups as $group ) {
00166 $namespaces[$group->getNamespace()] = true;
00167 }
00168 $namespaces = array_keys( $namespaces );
00169 $bots = true;
00170
00171 $changeFilter = array();
00172 $rows = TranslateUtils::translationChanges( $hours, $bots, $namespaces );
00173 foreach ( $rows as $row ) {
00174 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
00175 $handle = new MessageHandle( $title );
00176 $code = $handle->getCode();
00177 if ( !$code ) {
00178 continue;
00179 }
00180 $groupIds = $handle->getGroupIds();
00181 foreach ( $groupIds as $groupId ) {
00182 $changeFilter[$groupId][$code] = true;
00183 }
00184 }
00185 }
00186
00187 $skipGroups = array();
00188 if ( $this->hasOption( 'skipgroup' ) ) {
00189 $skipGroups = array_map( 'trim', explode( ',', $this->getOption( 'skipgroup' ) ) );
00190 }
00191
00192 foreach ( $groups as $groupId => $group ) {
00193 if ( in_array( $groupId, $skipGroups ) ) {
00194 $this->output( "Group $groupId is in skipgroup.\n" );
00195 continue;
00196 }
00197
00198
00199 if ( is_array( $changeFilter ) && !isset( $changeFilter[$groupId] ) ) {
00200 $this->output( "No recent changes to $groupId.\n" );
00201 continue;
00202 }
00203
00204 $langs = $reqLangs;
00205
00206 if ( $codemapOnly ) {
00207 foreach ( $langs as $index => $code ) {
00208 if ( $group->mapCode( $code ) === $code ) {
00209 unset( $langs[$index] );
00210 }
00211 }
00212 }
00213
00214 if ( $threshold ) {
00215 $stats = MessageGroupStats::forGroup( $groupId );
00216 foreach ( $langs as $index => $code ) {
00217 if ( !isset( $stats[$code] ) ) {
00218 unset( $langs[$index] );
00219 }
00220
00221 $total = $stats[$code][MessageGroupStats::TOTAL];
00222 $translated = $stats[$code][MessageGroupStats::TRANSLATED];
00223 if ( $translated / $total * 100 < $threshold ) {
00224 unset( $langs[$index] );
00225 }
00226 }
00227 }
00228
00229
00230 if ( is_array( $changeFilter ) ) {
00231 $langs = array_intersect( $langs, array_keys( $changeFilter[$groupId] ) );
00232 }
00233
00234 if ( !count( $langs ) ) {
00235 continue;
00236 }
00237
00238 $this->output( "Exporting $groupId...\n" );
00239
00240 if ( $group instanceof FileBasedMessageGroup ) {
00241 $ffs = $group->getFFS();
00242 $ffs->setWritePath( $target );
00243 $sourceLanguage = $group->getSourceLanguage();
00244 $collection = $group->initCollection( $sourceLanguage );
00245
00246 $definitionFile = false;
00247
00248 if ( $this->hasOption( 'ppgettext' ) && $ffs instanceof GettextFFS ) {
00249 global $wgMaxShellMemory, $wgTranslateGroupRoot;
00250
00251
00252 $wgMaxShellMemory = 402400;
00253
00254 $path = $group->getSourceFilePath( $sourceLanguage );
00255 $definitionFile = str_replace(
00256 $wgTranslateGroupRoot,
00257 $this->getOption( 'ppgettext' ),
00258 $path
00259 );
00260 }
00261
00262 $whitelist = $group->getTranslatableLanguages();
00263
00264 foreach ( $langs as $lang ) {
00265
00266
00267
00268 if ( is_array( $whitelist ) && !isset( $whitelist[$lang] ) ) {
00269 continue;
00270 }
00271
00272 $collection->resetForNewLanguage( $lang );
00273 $collection->loadTranslations();
00274
00275
00276 global $wgTranslateDocumentationLanguageCode;
00277 if ( $lang !== $wgTranslateDocumentationLanguageCode
00278 && $lang !== $sourceLanguage
00279 ) {
00280 $collection->filter( 'ignored' );
00281 }
00282
00283 if ( $noFuzzy ) {
00284 $collection->filter( 'fuzzy' );
00285 }
00286
00287 $ffs->write( $collection );
00288
00289
00290 if ( $definitionFile ) {
00291 if ( is_file( $definitionFile ) ) {
00292 $targetFileName = $ffs->getWritePath() .
00293 "/" . $group->getTargetFilename( $collection->code );
00294 $cmd = "msgmerge --quiet " . $noLocation . "--output-file=" .
00295 $targetFileName . ' ' . $targetFileName . ' ' . $definitionFile;
00296 wfShellExec( $cmd, $ret );
00297
00298
00299 if ( $ret ) {
00300 $this->error( "ERROR: $ret" );
00301 }
00302 } else {
00303 $this->error( "$definitionFile does not exist.", 1 );
00304 }
00305 }
00306 }
00307 } else {
00308 if ( $noFuzzy ) {
00309 $this->error( "--no-fuzzy is not supported for this message group." );
00310 }
00311
00312 $writer = $group->getWriter();
00313 $writer->fileExport( $langs, $target );
00314 }
00315 }
00316 }
00317 }
00318
00319 $maintClass = 'CommandlineExport';
00320 require_once RUN_MAINTENANCE_IF_MAIN;