Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 79 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ImportExtensionMessages | |
0.00% |
0 / 79 |
|
0.00% |
0 / 8 |
930 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
init | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getMessagesDirs | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
processDir | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
processFile | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
56 | |||
getCoreData | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
setCoreData | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | use MediaWiki\Json\FormatJson; |
4 | use MediaWiki\MainConfigNames; |
5 | |
6 | // @codeCoverageIgnoreStart |
7 | require_once __DIR__ . '/../Maintenance.php'; |
8 | // @codeCoverageIgnoreEnd |
9 | |
10 | class 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; |
164 | require_once RUN_MAINTENANCE_IF_MAIN; |
165 | // @codeCoverageIgnoreEnd |