Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportExternalTranslationsMaintenanceScript
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 5
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
72
 getGroups
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 printChangeInfo
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getImportStrategy
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Synchronization;
5
6use Exception;
7use FileBasedMessageGroup;
8use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups;
9use MediaWiki\Extension\Translate\MessageSync\MessageSourceChange;
10use MediaWiki\Extension\Translate\Services;
11use MediaWiki\Extension\Translate\Utilities\BaseMaintenanceScript;
12use MessageGroup;
13use SpecialPage;
14
15/** Script for processing message changes in file based message groups. */
16class ImportExternalTranslationsMaintenanceScript extends BaseMaintenanceScript {
17    public function __construct() {
18        parent::__construct();
19        $this->addDescription( 'Script for processing message changes in file based message groups' );
20        $this->addOption(
21            'group',
22            '(optional) Comma separated list of group IDs to process (can use * as wildcard). ' .
23                'Default: "*"',
24            self::OPTIONAL,
25            self::HAS_ARG
26        );
27        $this->addOption(
28            'skipgroup',
29            '(optional) Comma separated list of group IDs to not process (can use * ' .
30                'as wildcard). Overrides --group parameter.',
31            self::OPTIONAL,
32            self::HAS_ARG
33        );
34        $this->addOption(
35            'name',
36            '(optional) Unique name to avoid conflicts with multiple invocations of this script.',
37            self::OPTIONAL,
38            self::HAS_ARG
39        );
40        $this->addOption(
41            'safe-import',
42            '(optional) Import "safe" changes: message additions when no other kind of changes.'
43        );
44        $this->addOption(
45            'skip-group-sync-check',
46            '(optional) Skip importing group if synchronization is still in progress or if there ' .
47                'was an error during synchronization. See: ' .
48                'https://www.mediawiki.org/wiki/Help:Extension:Translate/Group_management#Strong_synchronization'
49        );
50        $this->addOption(
51            'import-non-renames',
52            '(optional) Import non renames: if a language in a group has only additions and changes to existing ' .
53            ' strings, then the additions are imported'
54        );
55        $this->requireExtension( 'Translate' );
56    }
57
58    public function execute() {
59        $name = $this->getOption( 'name', MessageChangeStorage::DEFAULT_NAME );
60        if ( !MessageChangeStorage::isValidCdbName( $name ) ) {
61            $this->fatalError( 'Invalid name' );
62        }
63
64        $groups = $this->getGroups();
65        $changes = [];
66        $comparator = Services::getInstance()->getExternalMessageSourceStateComparator();
67
68        $importStrategy = $this->getImportStrategy();
69        $skipGroupSyncCache = $this->hasOption( 'skip-group-sync-check' );
70
71        $services = Services::getInstance();
72        $importer = $services->getExternalMessageSourceStateImporter();
73
74        foreach ( $groups as $id => $group ) {
75            $status = $importer->canImportGroup( $group, $skipGroupSyncCache );
76            if ( !$status->isOK() ) {
77                $this->error( $status->getMessage()->plain() );
78                continue;
79            }
80
81            if ( $importStrategy === ExternalMessageSourceStateImporter::IMPORT_NONE ) {
82                $this->output( "Processing $id\n" );
83            }
84
85            try {
86                $changes[$id] = $comparator->processGroup( $group );
87            } catch ( Exception $e ) {
88                $errorMsg = "Exception occurred while processing group: $id.\nException: $e";
89                $this->error( $errorMsg );
90                error_log( $errorMsg );
91            }
92        }
93
94        // Remove all groups without changes
95        $changes = array_filter( $changes, static function ( MessageSourceChange $change ) {
96            return $change->getAllModifications() !== [];
97        } );
98
99        if ( $changes === [] ) {
100            if ( $importStrategy === ExternalMessageSourceStateImporter::IMPORT_NONE ) {
101                $this->output( "No changes found\n" );
102            }
103
104            return;
105        }
106
107        $info = $importer->import( $changes, $name, $importStrategy );
108        $this->printChangeInfo( $info );
109    }
110
111    /**
112     * Gets list of message groups filtered by user input.
113     * @return FileBasedMessageGroup[]
114     */
115    private function getGroups(): array {
116        $groups = MessageGroups::getGroupsByType( FileBasedMessageGroup::class );
117
118        // Include all if option not given
119        $include = $this->getOption( 'group', '*' );
120        $include = explode( ',', $include );
121        $include = array_map( 'trim', $include );
122        $include = MessageGroups::expandWildcards( $include );
123
124        // Exclude nothing if option not given
125        $exclude = $this->getOption( 'skipgroup', '' );
126        $exclude = explode( ',', $exclude );
127        $exclude = array_map( 'trim', $exclude );
128        $exclude = MessageGroups::expandWildcards( $exclude );
129
130        // Flip to allow isset
131        $include = array_flip( $include );
132        $exclude = array_flip( $exclude );
133
134        return array_filter( $groups,
135            static function ( MessageGroup $group ) use ( $include, $exclude ) {
136                $id = $group->getId();
137
138                return isset( $include[$id] ) && !isset( $exclude[$id] );
139            }
140        );
141    }
142
143    private function printChangeInfo( array $info ): void {
144        foreach ( $info['processed'] as $group => $languages ) {
145            $newMessageCount = array_sum( $languages );
146            if ( $newMessageCount ) {
147                $this->output( "Imported $newMessageCount new messages or translations for $group.\n" );
148            }
149        }
150
151        if ( $info['skipped'] !== [] ) {
152            $skipped = implode( ', ', array_keys( $info['skipped'] ) );
153            $this->output( "There are changes to check for groups $skipped.\n" );
154            $url = SpecialPage::getTitleFor( 'ManageMessageGroups', $info['name'] )->getFullURL();
155            $this->output( "You can process them at $url\n" );
156        }
157    }
158
159    private function getImportStrategy(): int {
160        $importStrategy = ExternalMessageSourceStateImporter::IMPORT_NONE;
161        if ( $this->hasOption( 'safe-import' ) ) {
162            $importStrategy = ExternalMessageSourceStateImporter::IMPORT_SAFE;
163        }
164
165        if ( $this->hasOption( 'import-non-renames' ) ) {
166            $importStrategy = ExternalMessageSourceStateImporter::IMPORT_NON_RENAMES;
167        }
168
169        return $importStrategy;
170    }
171}