Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportExtensionMessages
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 8
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getMessagesDirs
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 processDir
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 processFile
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 getCoreData
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 setCoreData
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3use MediaWiki\Json\FormatJson;
4use MediaWiki\MainConfigNames;
5
6// @codeCoverageIgnoreStart
7require_once __DIR__ . '/../Maintenance.php';
8// @codeCoverageIgnoreEnd
9
10class ImportExtensionMessages extends Maintenance {
11    /** @var string */
12    private $extensionDir;
13    /** @var string */
14    private $extName;
15    /** @var string[] */
16    private $excludedMsgs;
17    /** @var string */
18    private $outDir;
19    /** @var string[] */
20    private $coreDataCache;
21
22    public function __construct() {
23        parent::__construct();
24        $this->addArg( 'extension', 'The extension name' );
25        $this->addOption( 'outdir',
26            'The output directory, default $IP/languages/i18n', false, true );
27    }
28
29    public function execute() {
30        $this->init();
31
32        $this->outDir = $this->getOption( 'outdir', MW_INSTALL_PATH . '/languages/i18n' );
33        if ( !file_exists( $this->outDir ) ) {
34            mkdir( $this->outDir, 0777, true );
35        }
36
37        $this->extName = $this->getArg();
38        $extJsonPath = $this->extensionDir . "/{$this->extName}/extension.json";
39        $extJson = file_get_contents( $extJsonPath );
40        if ( $extJson === false ) {
41            $this->fatalError( "Unable to open \"$extJsonPath\"" );
42        }
43        $extData = json_decode( $extJson, JSON_THROW_ON_ERROR );
44
45        $this->excludedMsgs = [];
46        foreach ( [ 'namemsg', 'descriptionmsg' ] as $key ) {
47            if ( isset( $extData[$key] ) ) {
48                $this->excludedMsgs[] = $extData[$key];
49            }
50        }
51
52        foreach ( $this->getMessagesDirs( $extData ) as $dir ) {
53            $this->processDir( $dir );
54        }
55    }
56
57    private function init() {
58        $services = $this->getServiceContainer();
59        $config = $services->getMainConfig();
60        $this->extensionDir = $config->get( MainConfigNames::ExtensionDirectory );
61    }
62
63    private function getMessagesDirs( $extData ) {
64        if ( isset( $extData['MessagesDirs'] ) ) {
65            $messagesDirs = [];
66            foreach ( $extData['MessagesDirs'] as $dirs ) {
67                if ( is_array( $dirs ) ) {
68                    foreach ( $dirs as $dir ) {
69                        $messagesDirs[] = $dir;
70                    }
71                } else {
72                    $messagesDirs[] = $dirs;
73                }
74            }
75        } else {
76            $messagesDirs = [ 'i18n' ];
77        }
78        return $messagesDirs;
79    }
80
81    private function processDir( $dir ) {
82        $path = $this->extensionDir . "/{$this->extName}/$dir";
83
84        foreach ( new DirectoryIterator( $path ) as $file ) {
85            if ( !$file->isDot() && str_ends_with( $file->getFilename(), '.json' ) ) {
86                $this->processFile(
87                    substr( $file->getFilename(), 0, -5 ),
88                    $file->getPathname()
89                );
90            }
91        }
92    }
93
94    private function processFile( $lang, $extI18nPath ) {
95        $extJson = file_get_contents( $extI18nPath );
96        if ( $extJson === false ) {
97            $this->error( "Unable to read i18n file \"$extI18nPath\"" );
98            return;
99        }
100        $extData = json_decode( $extJson, JSON_THROW_ON_ERROR );
101        $coreData = $this->getCoreData( $lang );
102
103        if ( isset( $extData['@metadata']['authors'] ) ) {
104            $authors = array_unique( array_merge(
105                $coreData['@metadata']['authors'] ?? [],
106                $extData['@metadata']['authors']
107            ) );
108            // Fix numeric authors
109            foreach ( $authors as &$author ) {
110                $author = (string)$author;
111            }
112            sort( $authors );
113            $coreData['@metadata']['authors'] = $authors;
114        }
115
116        foreach ( $extData as $name => $value ) {
117            if ( str_starts_with( $name, '@' ) ) {
118                continue;
119            }
120            if ( in_array( $name, $this->excludedMsgs ) ) {
121                continue;
122            }
123            $coreData[$name] = $value;
124        }
125
126        $this->setCoreData( $lang, $coreData );
127    }
128
129    private function getCoreData( $lang ) {
130        if ( !isset( $this->coreDataCache[$lang] ) ) {
131            $corePath = MW_INSTALL_PATH . "/languages/i18n/$lang.json";
132            // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
133            $coreJson = @file_get_contents( $corePath );
134            if ( $coreJson === false ) {
135                $this->error( "Warning: discarding extension localisation " .
136                    "for language \"$lang\" not present in core" );
137                // Do not write to coreDataCache -- suppress creation of the core file.
138                return [];
139            }
140            $this->coreDataCache[$lang] = json_decode( $coreJson, JSON_THROW_ON_ERROR );
141        }
142        return $this->coreDataCache[$lang];
143    }
144
145    private function setCoreData( $lang, $data ) {
146        if ( !isset( $this->coreDataCache[$lang] ) ) {
147            // Non-existent file, do not create
148            return;
149        }
150
151        $this->coreDataCache[$lang] = $data;
152        $outPath = "{$this->outDir}/$lang.json";
153        if ( !file_put_contents(
154            $outPath,
155            FormatJson::encode( $data, "\t", FormatJson::ALL_OK ) . "\n"
156        ) ) {
157            $this->error( "Unable to write core i18n file \"$outPath\"" );
158        }
159    }
160}
161
162// @codeCoverageIgnoreStart
163$maintClass = ImportExtensionMessages::class;
164require_once RUN_MAINTENANCE_IF_MAIN;
165// @codeCoverageIgnoreEnd