Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
49.00% |
197 / 402 |
|
23.08% |
3 / 13 |
CRAP | |
0.00% |
0 / 1 |
SpecialInterwiki | |
49.00% |
197 / 402 |
|
23.08% |
3 / 13 |
1019.71 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getSubpagesForPrefixSearch | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
canModify | |
45.45% |
5 / 11 |
|
0.00% |
0 / 1 |
11.84 | |||
showForm | |
93.33% |
98 / 105 |
|
0.00% |
0 / 1 |
15.07 | |||
onSubmit | |
87.21% |
75 / 86 |
|
0.00% |
0 / 1 |
18.68 | |||
isLanguagePrefix | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
showList | |
0.00% |
0 / 109 |
|
0.00% |
0 / 1 |
506 | |||
makeTable | |
0.00% |
0 / 67 |
|
0.00% |
0 / 1 |
110 | |||
error | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Specials; |
4 | |
5 | use LogicException; |
6 | use ManualLogEntry; |
7 | use MediaWiki\Html\Html; |
8 | use MediaWiki\HTMLForm\HTMLForm; |
9 | use MediaWiki\Interwiki\InterwikiLookup; |
10 | use MediaWiki\Language\Language; |
11 | use MediaWiki\Languages\LanguageNameUtils; |
12 | use MediaWiki\MainConfigNames; |
13 | use MediaWiki\Message\Message; |
14 | use MediaWiki\Output\OutputPage; |
15 | use MediaWiki\SpecialPage\SpecialPage; |
16 | use MediaWiki\Status\Status; |
17 | use MediaWiki\Title\Title; |
18 | use MediaWiki\Utils\UrlUtils; |
19 | use PermissionsError; |
20 | use ReadOnlyError; |
21 | use Wikimedia\Rdbms\IConnectionProvider; |
22 | |
23 | /** |
24 | * Implements Special:Interwiki |
25 | * |
26 | * @since 1.44 |
27 | * @ingroup SpecialPage |
28 | */ |
29 | class SpecialInterwiki extends SpecialPage { |
30 | private Language $contLang; |
31 | private InterwikiLookup $interwikiLookup; |
32 | private LanguageNameUtils $languageNameUtils; |
33 | private UrlUtils $urlUtils; |
34 | private IConnectionProvider $dbProvider; |
35 | private array $virtualDomainsMapping; |
36 | private bool $interwikiMagic; |
37 | |
38 | /** |
39 | * @param Language $contLang |
40 | * @param InterwikiLookup $interwikiLookup |
41 | * @param LanguageNameUtils $languageNameUtils |
42 | * @param UrlUtils $urlUtils |
43 | * @param IConnectionProvider $dbProvider |
44 | */ |
45 | public function __construct( |
46 | Language $contLang, |
47 | InterwikiLookup $interwikiLookup, |
48 | LanguageNameUtils $languageNameUtils, |
49 | UrlUtils $urlUtils, |
50 | IConnectionProvider $dbProvider |
51 | ) { |
52 | parent::__construct( 'Interwiki' ); |
53 | |
54 | $this->contLang = $contLang; |
55 | $this->interwikiLookup = $interwikiLookup; |
56 | $this->languageNameUtils = $languageNameUtils; |
57 | $this->urlUtils = $urlUtils; |
58 | $this->dbProvider = $dbProvider; |
59 | $this->virtualDomainsMapping = $this->getConfig()->get( MainConfigNames::VirtualDomainsMapping ) ?? []; |
60 | $this->interwikiMagic = $this->getConfig()->get( MainConfigNames::InterwikiMagic ) ?? true; |
61 | } |
62 | |
63 | public function doesWrites() { |
64 | return true; |
65 | } |
66 | |
67 | /** |
68 | * Different description will be shown on Special:SpecialPage depending on |
69 | * whether the user can modify the data. |
70 | * |
71 | * @return Message |
72 | */ |
73 | public function getDescription() { |
74 | return $this->msg( $this->canModify() ? 'interwiki' : 'interwiki-title-norights' ); |
75 | } |
76 | |
77 | public function getSubpagesForPrefixSearch() { |
78 | // delete, edit both require the prefix parameter. |
79 | return [ 'add' ]; |
80 | } |
81 | |
82 | /** |
83 | * Show the special page |
84 | * |
85 | * @param string|null $par parameter passed to the page or null |
86 | */ |
87 | public function execute( $par ) { |
88 | $this->setHeaders(); |
89 | $this->outputHeader(); |
90 | |
91 | $out = $this->getOutput(); |
92 | $request = $this->getRequest(); |
93 | |
94 | $out->addModuleStyles( 'mediawiki.special.interwiki' ); |
95 | |
96 | $action = $par ?: $request->getRawVal( 'action', $par ); |
97 | |
98 | if ( in_array( $action, [ 'add', 'edit', 'delete' ] ) && $this->canModify( $out ) ) { |
99 | $this->showForm( $action ); |
100 | } else { |
101 | $this->showList(); |
102 | } |
103 | } |
104 | |
105 | /** |
106 | * Returns boolean whether the user can modify the data. |
107 | * |
108 | * @param OutputPage|bool $out If an OutputPage object given, it adds the respective error message. |
109 | * @return bool |
110 | * @throws PermissionsError|ReadOnlyError |
111 | */ |
112 | private function canModify( $out = false ) { |
113 | if ( !$this->getUser()->isAllowed( 'interwiki' ) ) { |
114 | // Check permissions |
115 | if ( $out ) { |
116 | throw new PermissionsError( 'interwiki' ); |
117 | } |
118 | |
119 | return false; |
120 | } elseif ( $this->getConfig()->get( MainConfigNames::InterwikiCache ) ) { |
121 | // Editing the interwiki cache is not supported |
122 | if ( $out ) { |
123 | $out->addWikiMsg( 'interwiki-cached' ); |
124 | } |
125 | |
126 | return false; |
127 | } else { |
128 | if ( $out ) { |
129 | $this->checkReadOnly(); |
130 | } |
131 | } |
132 | |
133 | return true; |
134 | } |
135 | |
136 | /** |
137 | * @param string $action The action of the form |
138 | */ |
139 | protected function showForm( $action ) { |
140 | $formDescriptor = []; |
141 | $hiddenFields = [ |
142 | 'action' => $action, |
143 | ]; |
144 | |
145 | $status = Status::newGood(); |
146 | $request = $this->getRequest(); |
147 | $prefix = $request->getText( 'prefix' ); |
148 | |
149 | switch ( $action ) { |
150 | case 'add': |
151 | case 'edit': |
152 | $formDescriptor = [ |
153 | 'prefix' => [ |
154 | 'type' => 'text', |
155 | 'label-message' => 'interwiki-prefix-label', |
156 | 'name' => 'prefix', |
157 | 'autofocus' => true, |
158 | ], |
159 | |
160 | 'local' => [ |
161 | 'type' => 'check', |
162 | 'id' => 'mw-interwiki-local', |
163 | 'label-message' => 'interwiki-local-label', |
164 | 'name' => 'local', |
165 | ], |
166 | |
167 | 'trans' => [ |
168 | 'type' => 'check', |
169 | 'id' => 'mw-interwiki-trans', |
170 | 'label-message' => 'interwiki-trans-label', |
171 | 'name' => 'trans', |
172 | ], |
173 | |
174 | 'url' => [ |
175 | 'type' => 'url', |
176 | 'id' => 'mw-interwiki-url', |
177 | 'label-message' => 'interwiki-url-label', |
178 | 'maxlength' => 200, |
179 | 'name' => 'url', |
180 | 'size' => 60, |
181 | ], |
182 | |
183 | 'api' => [ |
184 | 'type' => 'url', |
185 | 'id' => 'mw-interwiki-api', |
186 | 'label-message' => 'interwiki-api-label', |
187 | 'maxlength' => 200, |
188 | 'name' => 'api', |
189 | 'size' => 60, |
190 | ], |
191 | |
192 | 'reason' => [ |
193 | 'type' => 'text', |
194 | 'id' => "mw-interwiki-{$action}reason", |
195 | 'label-message' => 'interwiki_reasonfield', |
196 | 'maxlength' => 200, |
197 | 'name' => 'reason', |
198 | 'size' => 60, |
199 | ], |
200 | ]; |
201 | |
202 | break; |
203 | case 'delete': |
204 | $formDescriptor = [ |
205 | 'prefix' => [ |
206 | 'type' => 'hidden', |
207 | 'name' => 'prefix', |
208 | 'default' => $prefix, |
209 | ], |
210 | |
211 | 'reason' => [ |
212 | 'type' => 'text', |
213 | 'name' => 'reason', |
214 | 'label-message' => 'interwiki_reasonfield', |
215 | ], |
216 | ]; |
217 | |
218 | break; |
219 | default: |
220 | throw new LogicException( "Unexpected action: {$action}" ); |
221 | } |
222 | |
223 | if ( $action === 'edit' || $action === 'delete' ) { |
224 | $dbr = $this->dbProvider->getReplicaDatabase(); |
225 | $row = $dbr->newSelectQueryBuilder() |
226 | ->select( '*' ) |
227 | ->from( 'interwiki' ) |
228 | ->where( [ 'iw_prefix' => $prefix ] ) |
229 | ->caller( __METHOD__ )->fetchRow(); |
230 | |
231 | if ( $action === 'edit' ) { |
232 | if ( !$row ) { |
233 | $status->fatal( 'interwiki_editerror', $prefix ); |
234 | } else { |
235 | $formDescriptor['prefix']['disabled'] = true; |
236 | $formDescriptor['prefix']['default'] = $prefix; |
237 | $hiddenFields['prefix'] = $prefix; |
238 | $formDescriptor['url']['default'] = $row->iw_url; |
239 | $formDescriptor['api']['default'] = $row->iw_api; |
240 | $formDescriptor['trans']['default'] = $row->iw_trans; |
241 | $formDescriptor['local']['default'] = $row->iw_local; |
242 | } |
243 | } else { // $action === 'delete' |
244 | if ( !$row ) { |
245 | $status->fatal( 'interwiki_delfailed', $prefix ); |
246 | } |
247 | } |
248 | } |
249 | |
250 | if ( !$status->isOK() ) { |
251 | $formDescriptor = []; |
252 | } |
253 | |
254 | $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() ); |
255 | $htmlForm |
256 | ->addHiddenFields( $hiddenFields ) |
257 | ->setSubmitCallback( [ $this, 'onSubmit' ] ); |
258 | |
259 | if ( $status->isOK() ) { |
260 | if ( $action === 'delete' ) { |
261 | $htmlForm->setSubmitDestructive(); |
262 | } |
263 | |
264 | $htmlForm->setSubmitTextMsg( $action !== 'add' ? $action : 'interwiki_addbutton' ) |
265 | ->setPreHtml( $this->msg( $action !== 'delete' ? "interwiki_{$action}intro" : |
266 | 'interwiki_deleting', $prefix )->escaped() ) |
267 | ->show(); |
268 | } else { |
269 | $htmlForm->suppressDefaultSubmit() |
270 | ->prepareForm() |
271 | ->displayForm( $status ); |
272 | } |
273 | |
274 | $this->getOutput()->addBacklinkSubtitle( $this->getPageTitle() ); |
275 | } |
276 | |
277 | public function onSubmit( array $data ) { |
278 | $status = Status::newGood(); |
279 | $request = $this->getRequest(); |
280 | $config = $this->getConfig(); |
281 | $prefix = $data['prefix']; |
282 | $do = $request->getRawVal( 'action' ); |
283 | // Show an error if the prefix is invalid (only when adding one). |
284 | // Invalid characters for a title should also be invalid for a prefix. |
285 | // Whitespace, ':', '&' and '=' are invalid, too. |
286 | // (Bug 30599). |
287 | $validPrefixChars = preg_replace( '/[ :&=]/', '', Title::legalChars() ); |
288 | if ( $do === 'add' && preg_match( "/\s|[^$validPrefixChars]/", $prefix ) ) { |
289 | $status->fatal( 'interwiki-badprefix', htmlspecialchars( $prefix ) ); |
290 | return $status; |
291 | } |
292 | // Disallow adding local interlanguage definitions if using global |
293 | if ( |
294 | $do === 'add' |
295 | && $this->isLanguagePrefix( $prefix ) |
296 | && isset( $this->virtualDomainsMapping['virtual-interwiki-interlanguage'] ) |
297 | ) { |
298 | $status->fatal( 'interwiki-cannotaddlocallanguage', htmlspecialchars( $prefix ) ); |
299 | return $status; |
300 | } |
301 | $reason = $data['reason']; |
302 | $selfTitle = $this->getPageTitle(); |
303 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
304 | switch ( $do ) { |
305 | case 'delete': |
306 | $dbw->newDeleteQueryBuilder() |
307 | ->deleteFrom( 'interwiki' ) |
308 | ->where( [ 'iw_prefix' => $prefix ] ) |
309 | ->caller( __METHOD__ )->execute(); |
310 | |
311 | if ( $dbw->affectedRows() === 0 ) { |
312 | $status->fatal( 'interwiki_delfailed', $prefix ); |
313 | } else { |
314 | $this->getOutput()->addWikiMsg( 'interwiki_deleted', $prefix ); |
315 | $log = new ManualLogEntry( 'interwiki', 'iw_delete' ); |
316 | $log->setTarget( $selfTitle ); |
317 | $log->setComment( $reason ); |
318 | $log->setParameters( [ |
319 | '4::prefix' => $prefix |
320 | ] ); |
321 | $log->setPerformer( $this->getUser() ); |
322 | $log->insert(); |
323 | $this->interwikiLookup->invalidateCache( $prefix ); |
324 | } |
325 | break; |
326 | /** @noinspection PhpMissingBreakStatementInspection */ |
327 | case 'add': |
328 | $prefix = $this->contLang->lc( $prefix ); |
329 | // Fall through |
330 | case 'edit': |
331 | // T374771: Trim the URL and API URLs to reduce confusion when |
332 | // the URLs are accidentally provided with extra whitespace |
333 | $theurl = trim( $data['url'] ); |
334 | $api = trim( $data['api'] ?? '' ); |
335 | $local = $data['local'] ? 1 : 0; |
336 | $trans = $data['trans'] ? 1 : 0; |
337 | $row = [ |
338 | 'iw_prefix' => $prefix, |
339 | 'iw_url' => $theurl, |
340 | 'iw_api' => $api, |
341 | 'iw_wikiid' => '', |
342 | 'iw_local' => $local, |
343 | 'iw_trans' => $trans |
344 | ]; |
345 | |
346 | if ( $prefix === '' || $theurl === '' ) { |
347 | $status->fatal( 'interwiki-submit-empty' ); |
348 | break; |
349 | } |
350 | |
351 | // Simple URL validation: check that the protocol is one of |
352 | // the supported protocols for this wiki. |
353 | // (T32600) |
354 | if ( !$this->urlUtils->parse( $theurl ) ) { |
355 | $status->fatal( 'interwiki-submit-invalidurl' ); |
356 | break; |
357 | } |
358 | |
359 | if ( $do === 'add' ) { |
360 | $dbw->newInsertQueryBuilder() |
361 | ->insertInto( 'interwiki' ) |
362 | ->row( $row ) |
363 | ->ignore() |
364 | ->caller( __METHOD__ )->execute(); |
365 | } else { // $do === 'edit' |
366 | $dbw->newUpdateQueryBuilder() |
367 | ->update( 'interwiki' ) |
368 | ->set( $row ) |
369 | ->where( [ 'iw_prefix' => $prefix ] ) |
370 | ->ignore() |
371 | ->caller( __METHOD__ )->execute(); |
372 | } |
373 | |
374 | // used here: interwiki_addfailed, interwiki_added, interwiki_edited |
375 | if ( $dbw->affectedRows() === 0 ) { |
376 | $status->fatal( "interwiki_{$do}failed", $prefix ); |
377 | } else { |
378 | $this->getOutput()->addWikiMsg( "interwiki_{$do}ed", $prefix ); |
379 | $log = new ManualLogEntry( 'interwiki', 'iw_' . $do ); |
380 | $log->setTarget( $selfTitle ); |
381 | $log->setComment( $reason ); |
382 | $log->setParameters( [ |
383 | '4::prefix' => $prefix, |
384 | '5::url' => $theurl, |
385 | '6::trans' => $trans, |
386 | '7::local' => $local |
387 | ] ); |
388 | $log->setPerformer( $this->getUser() ); |
389 | $log->insert(); |
390 | $this->interwikiLookup->invalidateCache( $prefix ); |
391 | } |
392 | break; |
393 | default: |
394 | throw new LogicException( "Unexpected action: {$do}" ); |
395 | } |
396 | |
397 | return $status; |
398 | } |
399 | |
400 | /** |
401 | * Determine whether a prefix is a language link |
402 | * |
403 | * @param string $prefix |
404 | * @return bool |
405 | */ |
406 | private function isLanguagePrefix( $prefix ) { |
407 | return $this->interwikiMagic |
408 | && $this->languageNameUtils->getLanguageName( $prefix ); |
409 | } |
410 | |
411 | protected function showList() { |
412 | $canModify = $this->canModify(); |
413 | |
414 | // Build lists |
415 | $iwPrefixes = $this->interwikiLookup->getAllPrefixes( null ); |
416 | $iwGlobalPrefixes = []; |
417 | $iwGlobalLanguagePrefixes = []; |
418 | if ( isset( $this->virtualDomainsMapping['virtual-interwiki'] ) ) { |
419 | // Fetch list from global table |
420 | $dbrCentralDB = $this->dbProvider->getReplicaDatabase( 'virtual-interwiki' ); |
421 | $res = $dbrCentralDB->newSelectQueryBuilder() |
422 | ->select( '*' ) |
423 | ->from( 'interwiki' ) |
424 | ->caller( __METHOD__ )->fetchResultSet(); |
425 | $retval = []; |
426 | foreach ( $res as $row ) { |
427 | $row = (array)$row; |
428 | if ( !$this->isLanguagePrefix( $row['iw_prefix'] ) ) { |
429 | $retval[] = $row; |
430 | } |
431 | } |
432 | $iwGlobalPrefixes = $retval; |
433 | } |
434 | |
435 | // Almost the same loop as above, but for global inter*language* links, whereas the above is for |
436 | // global inter*wiki* links |
437 | $usingGlobalLanguages = isset( $this->virtualDomainsMapping['virtual-interwiki-interlanguage'] ); |
438 | if ( $usingGlobalLanguages ) { |
439 | // Fetch list from global table |
440 | $dbrCentralLangDB = $this->dbProvider->getReplicaDatabase( 'virtual-interwiki-interlanguage' ); |
441 | $res = $dbrCentralLangDB->newSelectQueryBuilder() |
442 | ->select( '*' ) |
443 | ->from( 'interwiki' ) |
444 | ->caller( __METHOD__ )->fetchResultSet(); |
445 | $retval2 = []; |
446 | foreach ( $res as $row ) { |
447 | $row = (array)$row; |
448 | // Note that the above DB query explicitly *excludes* interlang ones |
449 | // (which makes sense), whereas here we _only_ care about interlang ones! |
450 | if ( $this->isLanguagePrefix( $row['iw_prefix'] ) ) { |
451 | $retval2[] = $row; |
452 | } |
453 | } |
454 | $iwGlobalLanguagePrefixes = $retval2; |
455 | } |
456 | |
457 | // Split out language links |
458 | $iwLocalPrefixes = []; |
459 | $iwLanguagePrefixes = []; |
460 | foreach ( $iwPrefixes as $iwPrefix ) { |
461 | if ( $this->isLanguagePrefix( $iwPrefix['iw_prefix'] ) ) { |
462 | $iwLanguagePrefixes[] = $iwPrefix; |
463 | } else { |
464 | $iwLocalPrefixes[] = $iwPrefix; |
465 | } |
466 | } |
467 | |
468 | // If using global interlanguage links, just ditch the data coming from the |
469 | // local table and overwrite it with the global data |
470 | if ( $usingGlobalLanguages ) { |
471 | unset( $iwLanguagePrefixes ); |
472 | $iwLanguagePrefixes = $iwGlobalLanguagePrefixes; |
473 | } |
474 | |
475 | // Page intro content |
476 | $this->getOutput()->addWikiMsg( 'interwiki_intro' ); |
477 | |
478 | // Add 'view log' link |
479 | $logLink = $this->getLinkRenderer()->makeLink( |
480 | SpecialPage::getTitleFor( 'Log', 'interwiki' ), |
481 | $this->msg( 'interwiki-logtext' )->text() |
482 | ); |
483 | $this->getOutput()->addHTML( |
484 | Html::rawElement( 'p', [ 'class' => 'mw-interwiki-log' ], $logLink ) |
485 | ); |
486 | |
487 | // Add 'add' link |
488 | if ( $canModify ) { |
489 | if ( count( $iwGlobalPrefixes ) !== 0 ) { |
490 | if ( $usingGlobalLanguages ) { |
491 | $addtext = 'interwiki-addtext-local-nolang'; |
492 | } else { |
493 | $addtext = 'interwiki-addtext-local'; |
494 | } |
495 | } else { |
496 | if ( $usingGlobalLanguages ) { |
497 | $addtext = 'interwiki-addtext-nolang'; |
498 | } else { |
499 | $addtext = 'interwiki_addtext'; |
500 | } |
501 | } |
502 | $addtext = $this->msg( $addtext )->text(); |
503 | $addlink = $this->getLinkRenderer()->makeKnownLink( |
504 | $this->getPageTitle( 'add' ), $addtext ); |
505 | $this->getOutput()->addHTML( |
506 | Html::rawElement( 'p', [ 'class' => 'mw-interwiki-addlink' ], $addlink ) |
507 | ); |
508 | } |
509 | |
510 | $this->getOutput()->addWikiMsg( 'interwiki-legend' ); |
511 | |
512 | if ( $iwPrefixes === [] && $iwGlobalPrefixes === [] ) { |
513 | // If the interwiki table(s) are empty, display an error message |
514 | $this->error( 'interwiki_error' ); |
515 | return; |
516 | } |
517 | |
518 | // Add the global table |
519 | if ( count( $iwGlobalPrefixes ) !== 0 ) { |
520 | $this->getOutput()->addHTML( |
521 | Html::rawElement( |
522 | 'h2', |
523 | [ 'class' => 'interwikitable-global' ], |
524 | $this->msg( 'interwiki-global-links' )->parse() |
525 | ) |
526 | ); |
527 | $this->getOutput()->addWikiMsg( 'interwiki-global-description' ); |
528 | |
529 | // $canModify is false here because this is just a display of remote data |
530 | $this->makeTable( false, $iwGlobalPrefixes ); |
531 | } |
532 | |
533 | // Add the local table |
534 | if ( count( $iwLocalPrefixes ) !== 0 ) { |
535 | if ( count( $iwGlobalPrefixes ) !== 0 ) { |
536 | $this->getOutput()->addHTML( |
537 | Html::rawElement( |
538 | 'h2', |
539 | [ 'class' => 'interwikitable-local' ], |
540 | $this->msg( 'interwiki-local-links' )->parse() |
541 | ) |
542 | ); |
543 | $this->getOutput()->addWikiMsg( 'interwiki-local-description' ); |
544 | } else { |
545 | $this->getOutput()->addHTML( |
546 | Html::rawElement( |
547 | 'h2', |
548 | [ 'class' => 'interwikitable-local' ], |
549 | $this->msg( 'interwiki-links' )->parse() |
550 | ) |
551 | ); |
552 | $this->getOutput()->addWikiMsg( 'interwiki-description' ); |
553 | } |
554 | $this->makeTable( $canModify, $iwLocalPrefixes ); |
555 | } |
556 | |
557 | // Add the language table |
558 | if ( count( $iwLanguagePrefixes ) !== 0 ) { |
559 | if ( $usingGlobalLanguages ) { |
560 | $header = 'interwiki-global-language-links'; |
561 | $description = 'interwiki-global-language-description'; |
562 | } else { |
563 | $header = 'interwiki-language-links'; |
564 | $description = 'interwiki-language-description'; |
565 | } |
566 | |
567 | $this->getOutput()->addHTML( |
568 | Html::rawElement( |
569 | 'h2', |
570 | [ 'class' => 'interwikitable-language' ], |
571 | $this->msg( $header )->parse() |
572 | ) |
573 | ); |
574 | $this->getOutput()->addWikiMsg( $description ); |
575 | |
576 | // When using global interlanguage links, don't allow them to be modified |
577 | // except on the source wiki |
578 | $canModify = ( $usingGlobalLanguages ? false : $canModify ); |
579 | $this->makeTable( $canModify, $iwLanguagePrefixes ); |
580 | } |
581 | } |
582 | |
583 | protected function makeTable( $canModify, $iwPrefixes ) { |
584 | // Output the existing Interwiki prefixes table header |
585 | $out = Html::openElement( |
586 | 'table', |
587 | [ 'class' => 'mw-interwikitable wikitable sortable' ] |
588 | ) . "\n"; |
589 | $out .= Html::openElement( 'thead' ) . |
590 | Html::openElement( 'tr', [ 'class' => 'interwikitable-header' ] ) . |
591 | Html::element( 'th', [], $this->msg( 'interwiki_prefix' )->text() ) . |
592 | Html::element( 'th', [], $this->msg( 'interwiki_url' )->text() ) . |
593 | Html::element( 'th', [], $this->msg( 'interwiki_local' )->text() ) . |
594 | Html::element( 'th', [], $this->msg( 'interwiki_trans' )->text() ) . |
595 | ( $canModify ? |
596 | Html::element( |
597 | 'th', |
598 | [ 'class' => 'unsortable' ], |
599 | $this->msg( 'interwiki_edit' )->text() |
600 | ) : |
601 | '' |
602 | ); |
603 | $out .= Html::closeElement( 'tr' ) . |
604 | Html::closeElement( 'thead' ) . "\n" . |
605 | Html::openElement( 'tbody' ); |
606 | |
607 | $selfTitle = $this->getPageTitle(); |
608 | |
609 | // Output the existing Interwiki prefixes table rows |
610 | foreach ( $iwPrefixes as $iwPrefix ) { |
611 | $out .= Html::openElement( 'tr', [ 'class' => 'mw-interwikitable-row' ] ); |
612 | $out .= Html::element( 'td', [ 'class' => 'mw-interwikitable-prefix' ], |
613 | $iwPrefix['iw_prefix'] ); |
614 | $out .= Html::element( |
615 | 'td', |
616 | [ 'class' => 'mw-interwikitable-url' ], |
617 | $iwPrefix['iw_url'] |
618 | ); |
619 | $attribs = [ 'class' => 'mw-interwikitable-local' ]; |
620 | // Green background for cells with "yes". |
621 | if ( isset( $iwPrefix['iw_local'] ) && $iwPrefix['iw_local'] ) { |
622 | $attribs['class'] .= ' mw-interwikitable-local-yes'; |
623 | } |
624 | // The messages interwiki_0 and interwiki_1 are used here. |
625 | $contents = isset( $iwPrefix['iw_local'] ) ? |
626 | $this->msg( 'interwiki_' . $iwPrefix['iw_local'] )->text() : |
627 | '-'; |
628 | $out .= Html::element( 'td', $attribs, $contents ); |
629 | $attribs = [ 'class' => 'mw-interwikitable-trans' ]; |
630 | // Green background for cells with "yes". |
631 | if ( isset( $iwPrefix['iw_trans'] ) && $iwPrefix['iw_trans'] ) { |
632 | $attribs['class'] .= ' mw-interwikitable-trans-yes'; |
633 | } |
634 | // The messages interwiki_0 and interwiki_1 are used here. |
635 | $contents = isset( $iwPrefix['iw_trans'] ) ? |
636 | $this->msg( 'interwiki_' . $iwPrefix['iw_trans'] )->text() : |
637 | '-'; |
638 | $out .= Html::element( 'td', $attribs, $contents ); |
639 | |
640 | // Additional column when the interwiki table can be modified. |
641 | if ( $canModify ) { |
642 | $out .= Html::rawElement( 'td', [ 'class' => 'mw-interwikitable-modify' ], |
643 | $this->getLinkRenderer()->makeKnownLink( |
644 | $selfTitle, |
645 | $this->msg( 'edit' )->text(), |
646 | [], |
647 | [ 'action' => 'edit', 'prefix' => $iwPrefix['iw_prefix'] ] |
648 | ) . |
649 | $this->msg( 'comma-separator' )->escaped() . |
650 | $this->getLinkRenderer()->makeKnownLink( |
651 | $selfTitle, |
652 | $this->msg( 'delete' )->text(), |
653 | [], |
654 | [ 'action' => 'delete', 'prefix' => $iwPrefix['iw_prefix'] ] |
655 | ) |
656 | ); |
657 | } |
658 | $out .= Html::closeElement( 'tr' ) . "\n"; |
659 | } |
660 | $out .= Html::closeElement( 'tbody' ) . |
661 | Html::closeElement( 'table' ); |
662 | |
663 | $this->getOutput()->addHTML( $out ); |
664 | $this->getOutput()->addModuleStyles( 'jquery.tablesorter.styles' ); |
665 | $this->getOutput()->addModules( 'jquery.tablesorter' ); |
666 | } |
667 | |
668 | /** |
669 | * @param string ...$args |
670 | */ |
671 | protected function error( ...$args ) { |
672 | $this->getOutput()->wrapWikiMsg( "<p class='error'>$1</p>", $args ); |
673 | } |
674 | |
675 | protected function getGroupName() { |
676 | return 'wiki'; |
677 | } |
678 | } |