Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 158 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
PremadeMediaWikiExtensionGroups | |
0.00% |
0 / 157 |
|
0.00% |
0 / 9 |
2862 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultNamespace | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNamespace | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setGroupPrefix | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setNamespace | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
register | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
createMessageGroup | |
0.00% |
0 / 55 |
|
0.00% |
0 / 1 |
210 | |||
parseFile | |
0.00% |
0 / 56 |
|
0.00% |
0 / 1 |
650 | |||
processGroups | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageGroupConfiguration; |
5 | |
6 | use FileDependency; |
7 | use MediaWiki\Extension\Translate\FileFormatSupport\JsonFormat; |
8 | use MediaWiki\Extension\Translate\MessageProcessing\StringMatcher; |
9 | use MediaWiki\Extension\Translate\TranslatorInterface\Insertable\MediaWikiInsertablesSuggester; |
10 | use MediaWiki\Extension\Translate\TranslatorInterface\Insertable\UrlInsertablesSuggester; |
11 | use MediaWikiExtensionMessageGroup; |
12 | use MessageGroup; |
13 | use MessageGroupBase; |
14 | use RuntimeException; |
15 | use UnexpectedValueException; |
16 | |
17 | /** |
18 | * Class which handles special definition format for %MediaWiki extensions and skins. |
19 | * @author Niklas Laxström |
20 | * @license GPL-2.0-or-later |
21 | */ |
22 | class PremadeMediaWikiExtensionGroups { |
23 | protected string $idPrefix = 'ext-'; |
24 | protected ?int $namespace = null; |
25 | /** @see __construct */ |
26 | protected string $path; |
27 | /** @see __construct */ |
28 | protected string $definitionFile; |
29 | |
30 | /** |
31 | * @param string $def Absolute path to the definition file. See |
32 | * tests/data/mediawiki-extensions.txt for example. |
33 | * @param string $path General prefix to the file locations without |
34 | * the extension specific part. Should start with %GROUPROOT%/ or |
35 | * otherwise export path will be wrong. The export path is |
36 | * constructed by replacing %GROUPROOT%/ with target directory. |
37 | */ |
38 | public function __construct( string $def, string $path ) { |
39 | $this->definitionFile = $def; |
40 | $this->path = rtrim( $path, '/' ); |
41 | } |
42 | |
43 | /** Get the default namespace. Subclasses can override this. */ |
44 | protected function getDefaultNamespace(): int { |
45 | return NS_MEDIAWIKI; |
46 | } |
47 | |
48 | /** Get the namespace ID */ |
49 | protected function getNamespace(): int { |
50 | if ( $this->namespace === null ) { |
51 | $this->namespace = $this->getDefaultNamespace(); |
52 | } |
53 | return $this->namespace; |
54 | } |
55 | |
56 | /** How to prefix message group ids. */ |
57 | public function setGroupPrefix( string $value ): void { |
58 | $this->idPrefix = $value; |
59 | } |
60 | |
61 | /** Which namespace holds the messages. */ |
62 | public function setNamespace( int $value ): void { |
63 | $this->namespace = $value; |
64 | } |
65 | |
66 | /** Hook: TranslatePostInitGroups */ |
67 | public function register( array &$list, array &$deps ): void { |
68 | $groups = $this->parseFile(); |
69 | $groups = $this->processGroups( $groups ); |
70 | foreach ( $groups as $id => $g ) { |
71 | $list[$id] = $this->createMessageGroup( $id, $g ); |
72 | } |
73 | |
74 | $deps[] = new FileDependency( $this->definitionFile ); |
75 | } |
76 | |
77 | /** |
78 | * Creates MediaWikiExtensionMessageGroup objects from parsed data. |
79 | * @param string $id unique group id already prefixed |
80 | * @param array $info array of group info |
81 | */ |
82 | protected function createMessageGroup( string $id, array $info ): MessageGroup { |
83 | $conf = []; |
84 | $conf['BASIC']['class'] = MediaWikiExtensionMessageGroup::class; |
85 | $conf['BASIC']['id'] = $id; |
86 | $conf['BASIC']['namespace'] = $this->getNamespace(); |
87 | $conf['BASIC']['label'] = $info['name']; |
88 | |
89 | if ( isset( $info['desc'] ) ) { |
90 | $conf['BASIC']['description'] = $info['desc']; |
91 | } else { |
92 | $conf['BASIC']['descriptionmsg'] = $info['descmsg']; |
93 | } |
94 | |
95 | $conf['FILES']['class'] = JsonFormat::class; |
96 | $conf['FILES']['sourcePattern'] = $this->path . '/' . $info['file']; |
97 | |
98 | // @todo Find a better way |
99 | if ( isset( $info['aliasfile'] ) ) { |
100 | $conf['FILES']['aliasFileSource'] = $this->path . '/' . $info['aliasfile']; |
101 | $conf['FILES']['aliasFile'] = $info['aliasfile']; |
102 | } |
103 | if ( isset( $info['magicfile'] ) ) { |
104 | $conf['FILES']['magicFileSource'] = $this->path . '/' . $info['magicfile']; |
105 | $conf['FILES']['magicFile'] = $info['magicfile']; |
106 | } |
107 | |
108 | if ( isset( $info['prefix'] ) ) { |
109 | $conf['MANGLER']['class'] = StringMatcher::class; |
110 | $conf['MANGLER']['prefix'] = $info['prefix']; |
111 | $conf['MANGLER']['patterns'] = $info['mangle']; |
112 | |
113 | $mangler = new StringMatcher( $info['prefix'], $info['mangle'] ); |
114 | if ( isset( $info['ignored'] ) ) { |
115 | $info['ignored'] = $mangler->mangleList( $info['ignored'] ); |
116 | } |
117 | if ( isset( $info['optional'] ) ) { |
118 | $info['optional'] = $mangler->mangleList( $info['optional'] ); |
119 | } |
120 | } |
121 | |
122 | $conf['VALIDATORS'] = [ |
123 | [ 'id' => 'BraceBalance' ], |
124 | [ 'id' => 'MediaWikiLink' ], |
125 | [ 'id' => 'MediaWikiPageName' ], |
126 | [ 'id' => 'MediaWikiParameter' ], |
127 | [ 'id' => 'MediaWikiPlural' ], |
128 | ]; |
129 | |
130 | $conf['INSERTABLES'] = [ |
131 | [ 'class' => MediaWikiInsertablesSuggester::class ], |
132 | [ 'class' => UrlInsertablesSuggester::class ] |
133 | ]; |
134 | |
135 | if ( isset( $info['optional'] ) ) { |
136 | $conf['TAGS']['optional'] = $info['optional']; |
137 | } |
138 | if ( isset( $info['ignored'] ) ) { |
139 | $conf['TAGS']['ignored'] = $info['ignored']; |
140 | } |
141 | |
142 | if ( isset( $info['languages'] ) ) { |
143 | $conf['LANGUAGES'] = [ |
144 | 'include' => [], |
145 | 'exclude' => [], |
146 | ]; |
147 | |
148 | foreach ( $info['languages'] as $tagSpec ) { |
149 | if ( preg_match( '/^([+-])?(.+)$/', $tagSpec, $m ) ) { |
150 | [ , $sign, $tag ] = $m; |
151 | if ( $sign === '+' ) { |
152 | $conf['LANGUAGES']['include'][] = $tag; |
153 | } elseif ( $sign === '-' ) { |
154 | $conf['LANGUAGES']['exclude'][] = $tag; |
155 | } else { |
156 | $conf['LANGUAGES']['exclude'] = '*'; |
157 | $conf['LANGUAGES']['include'][] = $tag; |
158 | } |
159 | } |
160 | } |
161 | } |
162 | |
163 | return MessageGroupBase::factory( $conf ); |
164 | } |
165 | |
166 | protected function parseFile(): array { |
167 | $defines = file_get_contents( $this->definitionFile ); |
168 | $linefeed = '(\r\n|\n)'; |
169 | $sections = array_map( |
170 | 'trim', |
171 | preg_split( "/$linefeed{2,}/", $defines, -1, PREG_SPLIT_NO_EMPTY ) |
172 | ); |
173 | $groups = []; |
174 | |
175 | foreach ( $sections as $section ) { |
176 | $lines = array_map( 'trim', preg_split( "/$linefeed/", $section ) ); |
177 | $newGroup = []; |
178 | |
179 | foreach ( $lines as $line ) { |
180 | if ( $line === '' || $line[0] === '#' ) { |
181 | continue; |
182 | } |
183 | |
184 | if ( !str_contains( $line, '=' ) ) { |
185 | if ( empty( $newGroup['name'] ) ) { |
186 | $newGroup['name'] = $line; |
187 | } else { |
188 | throw new RuntimeException( 'Trying to define name twice: ' . $line ); |
189 | } |
190 | } else { |
191 | [ $key, $value ] = array_map( 'trim', explode( '=', $line, 2 ) ); |
192 | switch ( $key ) { |
193 | case 'aliasfile': |
194 | case 'desc': |
195 | case 'descmsg': |
196 | case 'file': |
197 | case 'id': |
198 | case 'magicfile': |
199 | case 'var': |
200 | $newGroup[$key] = $value; |
201 | break; |
202 | case 'optional': |
203 | case 'ignored': |
204 | case 'languages': |
205 | $values = array_map( 'trim', explode( ',', $value ) ); |
206 | if ( !isset( $newGroup[$key] ) ) { |
207 | $newGroup[$key] = []; |
208 | } |
209 | $newGroup[$key] = array_merge( $newGroup[$key], $values ); |
210 | break; |
211 | case 'prefix': |
212 | [ $prefix, $messages ] = array_map( |
213 | 'trim', |
214 | explode( '|', $value, 2 ) |
215 | ); |
216 | if ( isset( $newGroup['prefix'] ) && $newGroup['prefix'] !== $prefix ) { |
217 | throw new RuntimeException( |
218 | "Only one prefix supported: {$newGroup['prefix']} !== $prefix" |
219 | ); |
220 | } |
221 | $newGroup['prefix'] = $prefix; |
222 | |
223 | if ( !isset( $newGroup['mangle'] ) ) { |
224 | $newGroup['mangle'] = []; |
225 | } |
226 | |
227 | $messages = array_map( 'trim', explode( ',', $messages ) ); |
228 | $newGroup['mangle'] = array_merge( $newGroup['mangle'], $messages ); |
229 | break; |
230 | default: |
231 | throw new UnexpectedValueException( 'Unknown key:' . $key ); |
232 | } |
233 | } |
234 | } |
235 | |
236 | if ( count( $newGroup ) ) { |
237 | if ( empty( $newGroup['name'] ) ) { |
238 | throw new RuntimeException( "Name missing\n" . print_r( $newGroup, true ) ); |
239 | } |
240 | $groups[] = $newGroup; |
241 | } |
242 | } |
243 | |
244 | return $groups; |
245 | } |
246 | |
247 | protected function processGroups( array $groups ): array { |
248 | $fixedGroups = []; |
249 | foreach ( $groups as $g ) { |
250 | $name = $g['name']; |
251 | |
252 | $id = $g['id'] ?? $this->idPrefix . preg_replace( '/\s+/', '', strtolower( $name ) ); |
253 | |
254 | if ( !isset( $g['file'] ) ) { |
255 | $file = preg_replace( '/\s+/', '', "$name/i18n/%CODE%.json" ); |
256 | } else { |
257 | $file = $g['file']; |
258 | } |
259 | |
260 | $descMsg = $g['descmsg'] ?? str_replace( $this->idPrefix, '', $id ) . '-desc'; |
261 | |
262 | $newGroup = [ |
263 | 'name' => $name, |
264 | 'file' => $file, |
265 | 'descmsg' => $descMsg, |
266 | ]; |
267 | |
268 | $copyVars = [ |
269 | 'aliasfile', |
270 | 'desc', |
271 | 'ignored', |
272 | 'languages', |
273 | 'magicfile', |
274 | 'mangle', |
275 | 'optional', |
276 | 'prefix', |
277 | 'var', |
278 | ]; |
279 | |
280 | foreach ( $copyVars as $var ) { |
281 | if ( isset( $g[$var] ) ) { |
282 | $newGroup[$var] = $g[$var]; |
283 | } |
284 | } |
285 | |
286 | // Mark some fixed form optional messages automatically |
287 | if ( !isset( $newGroup['optional' ] ) ) { |
288 | $newGroup['optional'] = []; |
289 | } |
290 | |
291 | // Mark extension name and skin names optional. |
292 | $newGroup['optional'][] = '*-extensionname'; |
293 | $newGroup['optional'][] = 'skinname-*'; |
294 | |
295 | $fixedGroups[$id] = $newGroup; |
296 | } |
297 | |
298 | return $fixedGroups; |
299 | } |
300 | } |
301 | |
302 | class_alias( PremadeMediaWikiExtensionGroups::class, 'PremadeMediaWikiExtensionGroups' ); |