MediaWiki master
SpecialInterwiki.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
5use LogicException;
22
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 public function __construct(
39 Language $contLang,
40 InterwikiLookup $interwikiLookup,
41 LanguageNameUtils $languageNameUtils,
42 UrlUtils $urlUtils,
43 IConnectionProvider $dbProvider
44 ) {
45 parent::__construct( 'Interwiki' );
46
47 $this->contLang = $contLang;
48 $this->interwikiLookup = $interwikiLookup;
49 $this->languageNameUtils = $languageNameUtils;
50 $this->urlUtils = $urlUtils;
51 $this->dbProvider = $dbProvider;
52 $this->virtualDomainsMapping = $this->getConfig()->get( MainConfigNames::VirtualDomainsMapping ) ?? [];
53 $this->interwikiMagic = $this->getConfig()->get( MainConfigNames::InterwikiMagic ) ?? true;
54 }
55
56 public function doesWrites() {
57 return true;
58 }
59
66 public function getDescription() {
67 return $this->msg( $this->canModify() ? 'interwiki' : 'interwiki-title-norights' );
68 }
69
70 public function getSubpagesForPrefixSearch() {
71 // delete, edit both require the prefix parameter.
72 return [ 'add' ];
73 }
74
80 public function execute( $par ) {
81 $this->setHeaders();
82 $this->outputHeader();
83
84 $out = $this->getOutput();
85 $request = $this->getRequest();
86
87 $out->addModuleStyles( 'mediawiki.special.interwiki' );
88
89 $action = $par ?? $request->getRawVal( 'action' );
90
91 if ( in_array( $action, [ 'add', 'edit', 'delete' ] ) && $this->canModify( $out ) ) {
92 $this->showForm( $action );
93 } else {
94 $this->showList();
95 }
96 }
97
105 private function canModify( $out = false ) {
106 if ( !$this->getUser()->isAllowed( 'interwiki' ) ) {
107 // Check permissions
108 if ( $out ) {
109 throw new PermissionsError( 'interwiki' );
110 }
111
112 return false;
113 } elseif ( $this->getConfig()->get( MainConfigNames::InterwikiCache ) ) {
114 // Editing the interwiki cache is not supported
115 if ( $out ) {
116 $out->addWikiMsg( 'interwiki-cached' );
117 }
118
119 return false;
120 } else {
121 if ( $out ) {
122 $this->checkReadOnly();
123 }
124 }
125
126 return true;
127 }
128
132 protected function showForm( $action ) {
133 $formDescriptor = [];
134 $hiddenFields = [
135 'action' => $action,
136 ];
137
138 $status = Status::newGood();
139 $request = $this->getRequest();
140 $prefix = $request->getText( 'prefix' );
141
142 switch ( $action ) {
143 case 'add':
144 case 'edit':
145 $formDescriptor = [
146 'prefix' => [
147 'type' => 'text',
148 'label-message' => 'interwiki-prefix-label',
149 'name' => 'prefix',
150 'autofocus' => true,
151 ],
152
153 'local' => [
154 'type' => 'check',
155 'id' => 'mw-interwiki-local',
156 'label-message' => 'interwiki-local-label',
157 'name' => 'local',
158 ],
159
160 'trans' => [
161 'type' => 'check',
162 'id' => 'mw-interwiki-trans',
163 'label-message' => 'interwiki-trans-label',
164 'name' => 'trans',
165 ],
166
167 'url' => [
168 'type' => 'url',
169 'id' => 'mw-interwiki-url',
170 'label-message' => 'interwiki-url-label',
171 'maxlength' => 200,
172 'name' => 'url',
173 'size' => 60,
174 ],
175
176 'api' => [
177 'type' => 'url',
178 'id' => 'mw-interwiki-api',
179 'label-message' => 'interwiki-api-label',
180 'maxlength' => 200,
181 'name' => 'api',
182 'size' => 60,
183 ],
184
185 'reason' => [
186 'type' => 'text',
187 'id' => "mw-interwiki-{$action}reason",
188 'label-message' => 'interwiki_reasonfield',
189 'maxlength' => 200,
190 'name' => 'reason',
191 'size' => 60,
192 ],
193 ];
194
195 break;
196 case 'delete':
197 $formDescriptor = [
198 'prefix' => [
199 'type' => 'hidden',
200 'name' => 'prefix',
201 'default' => $prefix,
202 ],
203
204 'reason' => [
205 'type' => 'text',
206 'name' => 'reason',
207 'label-message' => 'interwiki_reasonfield',
208 ],
209 ];
210
211 break;
212 default:
213 throw new LogicException( "Unexpected action: {$action}" );
214 }
215
216 if ( $action === 'edit' || $action === 'delete' ) {
217 $dbr = $this->dbProvider->getReplicaDatabase();
218 $row = $dbr->newSelectQueryBuilder()
219 ->select( '*' )
220 ->from( 'interwiki' )
221 ->where( [ 'iw_prefix' => $prefix ] )
222 ->caller( __METHOD__ )->fetchRow();
223
224 if ( $action === 'edit' ) {
225 if ( !$row ) {
226 $status->fatal( 'interwiki_editerror', $prefix );
227 } else {
228 $formDescriptor['prefix']['disabled'] = true;
229 $formDescriptor['prefix']['default'] = $prefix;
230 $hiddenFields['prefix'] = $prefix;
231 $formDescriptor['url']['default'] = $row->iw_url;
232 $formDescriptor['api']['default'] = $row->iw_api;
233 $formDescriptor['trans']['default'] = $row->iw_trans;
234 $formDescriptor['local']['default'] = $row->iw_local;
235 }
236 } else { // $action === 'delete'
237 if ( !$row ) {
238 $status->fatal( 'interwiki_delfailed', $prefix );
239 }
240 }
241 }
242
243 if ( !$status->isOK() ) {
244 $formDescriptor = [];
245 }
246
247 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
248 $htmlForm
249 ->addHiddenFields( $hiddenFields )
250 ->setSubmitCallback( [ $this, 'onSubmit' ] );
251
252 if ( $status->isOK() ) {
253 if ( $action === 'delete' ) {
254 $htmlForm->setSubmitDestructive();
255 }
256
257 $htmlForm->setSubmitTextMsg( $action !== 'add' ? $action : 'interwiki_addbutton' )
258 ->setPreHtml( $this->msg( $action !== 'delete' ? "interwiki_{$action}intro" :
259 'interwiki_deleting', $prefix )->escaped() )
260 ->show();
261 } else {
262 $htmlForm->suppressDefaultSubmit()
263 ->prepareForm()
264 ->displayForm( $status );
265 }
266
267 $this->getOutput()->addBacklinkSubtitle( $this->getPageTitle() );
268 }
269
270 public function onSubmit( array $data ) {
271 $status = Status::newGood();
272 $request = $this->getRequest();
273 $config = $this->getConfig();
274 $prefix = $data['prefix'];
275 $do = $request->getRawVal( 'action' );
276 // Show an error if the prefix is invalid (only when adding one).
277 // Invalid characters for a title should also be invalid for a prefix.
278 // Whitespace, ':', '&' and '=' are invalid, too.
279 // (Bug 30599).
280 $validPrefixChars = preg_replace( '/[ :&=]/', '', Title::legalChars() );
281 if ( $do === 'add' && preg_match( "/\s|[^$validPrefixChars]/", $prefix ) ) {
282 $status->fatal( 'interwiki-badprefix', htmlspecialchars( $prefix ) );
283 return $status;
284 }
285 // Disallow adding local interlanguage definitions if using global
286 if (
287 $do === 'add'
288 && $this->isLanguagePrefix( $prefix )
289 && isset( $this->virtualDomainsMapping['virtual-interwiki-interlanguage'] )
290 ) {
291 $status->fatal( 'interwiki-cannotaddlocallanguage', htmlspecialchars( $prefix ) );
292 return $status;
293 }
294 $reason = $data['reason'];
295 $selfTitle = $this->getPageTitle();
296 $dbw = $this->dbProvider->getPrimaryDatabase();
297 switch ( $do ) {
298 case 'delete':
299 $dbw->newDeleteQueryBuilder()
300 ->deleteFrom( 'interwiki' )
301 ->where( [ 'iw_prefix' => $prefix ] )
302 ->caller( __METHOD__ )->execute();
303
304 if ( $dbw->affectedRows() === 0 ) {
305 $status->fatal( 'interwiki_delfailed', $prefix );
306 } else {
307 $this->getOutput()->addWikiMsg( 'interwiki_deleted', $prefix );
308 $log = new ManualLogEntry( 'interwiki', 'iw_delete' );
309 $log->setTarget( $selfTitle );
310 $log->setComment( $reason );
311 $log->setParameters( [
312 '4::prefix' => $prefix
313 ] );
314 $log->setPerformer( $this->getUser() );
315 $log->insert();
316 $this->interwikiLookup->invalidateCache( $prefix );
317 }
318 break;
320 case 'add':
321 $prefix = $this->contLang->lc( $prefix );
322 // Fall through
323 case 'edit':
324 // T374771: Trim the URL and API URLs to reduce confusion when
325 // the URLs are accidentally provided with extra whitespace
326 $theurl = trim( $data['url'] );
327 $api = trim( $data['api'] ?? '' );
328 $local = $data['local'] ? 1 : 0;
329 $trans = $data['trans'] ? 1 : 0;
330 $row = [
331 'iw_prefix' => $prefix,
332 'iw_url' => $theurl,
333 'iw_api' => $api,
334 'iw_wikiid' => '',
335 'iw_local' => $local,
336 'iw_trans' => $trans
337 ];
338
339 if ( $prefix === '' || $theurl === '' ) {
340 $status->fatal( 'interwiki-submit-empty' );
341 break;
342 }
343
344 // Simple URL validation: check that the protocol is one of
345 // the supported protocols for this wiki.
346 // (T32600)
347 if ( !$this->urlUtils->parse( $theurl ) ) {
348 $status->fatal( 'interwiki-submit-invalidurl' );
349 break;
350 }
351
352 if ( $do === 'add' ) {
353 $dbw->newInsertQueryBuilder()
354 ->insertInto( 'interwiki' )
355 ->row( $row )
356 ->ignore()
357 ->caller( __METHOD__ )->execute();
358 } else { // $do === 'edit'
359 $dbw->newUpdateQueryBuilder()
360 ->update( 'interwiki' )
361 ->set( $row )
362 ->where( [ 'iw_prefix' => $prefix ] )
363 ->ignore()
364 ->caller( __METHOD__ )->execute();
365 }
366
367 // used here: interwiki_addfailed, interwiki_added, interwiki_edited
368 if ( $dbw->affectedRows() === 0 ) {
369 $status->fatal( "interwiki_{$do}failed", $prefix );
370 } else {
371 $this->getOutput()->addWikiMsg( "interwiki_{$do}ed", $prefix );
372 $log = new ManualLogEntry( 'interwiki', 'iw_' . $do );
373 $log->setTarget( $selfTitle );
374 $log->setComment( $reason );
375 $log->setParameters( [
376 '4::prefix' => $prefix,
377 '5::url' => $theurl,
378 '6::trans' => $trans,
379 '7::local' => $local
380 ] );
381 $log->setPerformer( $this->getUser() );
382 $log->insert();
383 $this->interwikiLookup->invalidateCache( $prefix );
384 }
385 break;
386 default:
387 throw new LogicException( "Unexpected action: {$do}" );
388 }
389
390 return $status;
391 }
392
399 private function isLanguagePrefix( $prefix ) {
400 return $this->interwikiMagic
401 && $this->languageNameUtils->getLanguageName( $prefix );
402 }
403
404 protected function showList() {
405 $canModify = $this->canModify();
406
407 // Build lists
408 $iwPrefixes = $this->interwikiLookup->getAllPrefixes( null );
409 $iwGlobalPrefixes = [];
410 $iwGlobalLanguagePrefixes = [];
411 if ( isset( $this->virtualDomainsMapping['virtual-interwiki'] ) ) {
412 // Fetch list from global table
413 $dbrCentralDB = $this->dbProvider->getReplicaDatabase( 'virtual-interwiki' );
414 $res = $dbrCentralDB->newSelectQueryBuilder()
415 ->select( '*' )
416 ->from( 'interwiki' )
417 ->caller( __METHOD__ )->fetchResultSet();
418 $retval = [];
419 foreach ( $res as $row ) {
420 $row = (array)$row;
421 if ( !$this->isLanguagePrefix( $row['iw_prefix'] ) ) {
422 $retval[] = $row;
423 }
424 }
425 $iwGlobalPrefixes = $retval;
426 }
427
428 // Almost the same loop as above, but for global inter*language* links, whereas the above is for
429 // global inter*wiki* links
430 $usingGlobalLanguages = isset( $this->virtualDomainsMapping['virtual-interwiki-interlanguage'] );
431 if ( $usingGlobalLanguages ) {
432 // Fetch list from global table
433 $dbrCentralLangDB = $this->dbProvider->getReplicaDatabase( 'virtual-interwiki-interlanguage' );
434 $res = $dbrCentralLangDB->newSelectQueryBuilder()
435 ->select( '*' )
436 ->from( 'interwiki' )
437 ->caller( __METHOD__ )->fetchResultSet();
438 $retval2 = [];
439 foreach ( $res as $row ) {
440 $row = (array)$row;
441 // Note that the above DB query explicitly *excludes* interlang ones
442 // (which makes sense), whereas here we _only_ care about interlang ones!
443 if ( $this->isLanguagePrefix( $row['iw_prefix'] ) ) {
444 $retval2[] = $row;
445 }
446 }
447 $iwGlobalLanguagePrefixes = $retval2;
448 }
449
450 // Split out language links
451 $iwLocalPrefixes = [];
452 $iwLanguagePrefixes = [];
453 foreach ( $iwPrefixes as $iwPrefix ) {
454 if ( $this->isLanguagePrefix( $iwPrefix['iw_prefix'] ) ) {
455 $iwLanguagePrefixes[] = $iwPrefix;
456 } else {
457 $iwLocalPrefixes[] = $iwPrefix;
458 }
459 }
460
461 // If using global interlanguage links, just ditch the data coming from the
462 // local table and overwrite it with the global data
463 if ( $usingGlobalLanguages ) {
464 unset( $iwLanguagePrefixes );
465 $iwLanguagePrefixes = $iwGlobalLanguagePrefixes;
466 }
467
468 // Page intro content
469 $this->getOutput()->addWikiMsg( 'interwiki_intro' );
470
471 // Add 'view log' link
472 $logLink = $this->getLinkRenderer()->makeLink(
473 SpecialPage::getTitleFor( 'Log', 'interwiki' ),
474 $this->msg( 'interwiki-logtext' )->text()
475 );
476 $this->getOutput()->addHTML(
477 Html::rawElement( 'p', [ 'class' => 'mw-interwiki-log' ], $logLink )
478 );
479
480 // Add 'add' link
481 if ( $canModify ) {
482 if ( count( $iwGlobalPrefixes ) !== 0 ) {
483 if ( $usingGlobalLanguages ) {
484 $addtext = 'interwiki-addtext-local-nolang';
485 } else {
486 $addtext = 'interwiki-addtext-local';
487 }
488 } else {
489 if ( $usingGlobalLanguages ) {
490 $addtext = 'interwiki-addtext-nolang';
491 } else {
492 $addtext = 'interwiki_addtext';
493 }
494 }
495 $addtext = $this->msg( $addtext )->text();
496 $addlink = $this->getLinkRenderer()->makeKnownLink(
497 $this->getPageTitle( 'add' ), $addtext );
498 $this->getOutput()->addHTML(
499 Html::rawElement( 'p', [ 'class' => 'mw-interwiki-addlink' ], $addlink )
500 );
501 }
502
503 $this->getOutput()->addWikiMsg( 'interwiki-legend' );
504
505 if ( $iwPrefixes === [] && $iwGlobalPrefixes === [] ) {
506 // If the interwiki table(s) are empty, display an error message
507 $this->error( 'interwiki_error' );
508 return;
509 }
510
511 // Add the global table
512 if ( count( $iwGlobalPrefixes ) !== 0 ) {
513 $this->getOutput()->addHTML(
514 Html::rawElement(
515 'h2',
516 [ 'class' => 'interwikitable-global' ],
517 $this->msg( 'interwiki-global-links' )->parse()
518 )
519 );
520 $this->getOutput()->addWikiMsg( 'interwiki-global-description' );
521
522 // $canModify is false here because this is just a display of remote data
523 $this->makeTable( false, $iwGlobalPrefixes );
524 }
525
526 // Add the local table
527 if ( count( $iwLocalPrefixes ) !== 0 ) {
528 if ( count( $iwGlobalPrefixes ) !== 0 ) {
529 $this->getOutput()->addHTML(
530 Html::rawElement(
531 'h2',
532 [ 'class' => 'interwikitable-local' ],
533 $this->msg( 'interwiki-local-links' )->parse()
534 )
535 );
536 $this->getOutput()->addWikiMsg( 'interwiki-local-description' );
537 } else {
538 $this->getOutput()->addHTML(
539 Html::rawElement(
540 'h2',
541 [ 'class' => 'interwikitable-local' ],
542 $this->msg( 'interwiki-links' )->parse()
543 )
544 );
545 $this->getOutput()->addWikiMsg( 'interwiki-description' );
546 }
547 $this->makeTable( $canModify, $iwLocalPrefixes );
548 }
549
550 // Add the language table
551 if ( count( $iwLanguagePrefixes ) !== 0 ) {
552 if ( $usingGlobalLanguages ) {
553 $header = 'interwiki-global-language-links';
554 $description = 'interwiki-global-language-description';
555 } else {
556 $header = 'interwiki-language-links';
557 $description = 'interwiki-language-description';
558 }
559
560 $this->getOutput()->addHTML(
561 Html::rawElement(
562 'h2',
563 [ 'class' => 'interwikitable-language' ],
564 $this->msg( $header )->parse()
565 )
566 );
567 $this->getOutput()->addWikiMsg( $description );
568
569 // When using global interlanguage links, don't allow them to be modified
570 // except on the source wiki
571 $canModify = ( $usingGlobalLanguages ? false : $canModify );
572 $this->makeTable( $canModify, $iwLanguagePrefixes );
573 }
574 }
575
576 protected function makeTable( $canModify, $iwPrefixes ) {
577 // Output the existing Interwiki prefixes table header
578 $out = Html::openElement(
579 'table',
580 [ 'class' => 'mw-interwikitable wikitable sortable' ]
581 ) . "\n";
582 $out .= Html::openElement( 'thead' ) .
583 Html::openElement( 'tr', [ 'class' => 'interwikitable-header' ] ) .
584 Html::element( 'th', [], $this->msg( 'interwiki_prefix' )->text() ) .
585 Html::element( 'th', [], $this->msg( 'interwiki_url' )->text() ) .
586 Html::element( 'th', [], $this->msg( 'interwiki_local' )->text() ) .
587 Html::element( 'th', [], $this->msg( 'interwiki_trans' )->text() ) .
588 ( $canModify ?
590 'th',
591 [ 'class' => 'unsortable' ],
592 $this->msg( 'interwiki_edit' )->text()
593 ) :
594 ''
595 );
596 $out .= Html::closeElement( 'tr' ) .
597 Html::closeElement( 'thead' ) . "\n" .
598 Html::openElement( 'tbody' );
599
600 $selfTitle = $this->getPageTitle();
601
602 // Output the existing Interwiki prefixes table rows
603 foreach ( $iwPrefixes as $iwPrefix ) {
604 $out .= Html::openElement( 'tr', [ 'class' => 'mw-interwikitable-row' ] );
605 $out .= Html::element( 'td', [ 'class' => 'mw-interwikitable-prefix' ],
606 $iwPrefix['iw_prefix'] );
607 $out .= Html::element(
608 'td',
609 [ 'class' => 'mw-interwikitable-url' ],
610 $iwPrefix['iw_url']
611 );
612 $attribs = [ 'class' => 'mw-interwikitable-local' ];
613 // Green background for cells with "yes".
614 if ( isset( $iwPrefix['iw_local'] ) && $iwPrefix['iw_local'] ) {
615 $attribs['class'] .= ' mw-interwikitable-local-yes';
616 }
617 // The messages interwiki_0 and interwiki_1 are used here.
618 $contents = isset( $iwPrefix['iw_local'] ) ?
619 $this->msg( 'interwiki_' . $iwPrefix['iw_local'] )->text() :
620 '-';
621 $out .= Html::element( 'td', $attribs, $contents );
622 $attribs = [ 'class' => 'mw-interwikitable-trans' ];
623 // Green background for cells with "yes".
624 if ( isset( $iwPrefix['iw_trans'] ) && $iwPrefix['iw_trans'] ) {
625 $attribs['class'] .= ' mw-interwikitable-trans-yes';
626 }
627 // The messages interwiki_0 and interwiki_1 are used here.
628 $contents = isset( $iwPrefix['iw_trans'] ) ?
629 $this->msg( 'interwiki_' . $iwPrefix['iw_trans'] )->text() :
630 '-';
631 $out .= Html::element( 'td', $attribs, $contents );
632
633 // Additional column when the interwiki table can be modified.
634 if ( $canModify ) {
635 $out .= Html::rawElement( 'td', [ 'class' => 'mw-interwikitable-modify' ],
636 $this->getLinkRenderer()->makeKnownLink(
637 $selfTitle,
638 $this->msg( 'edit' )->text(),
639 [],
640 [ 'action' => 'edit', 'prefix' => $iwPrefix['iw_prefix'] ]
641 ) .
642 $this->msg( 'comma-separator' )->escaped() .
643 $this->getLinkRenderer()->makeKnownLink(
644 $selfTitle,
645 $this->msg( 'delete' )->text(),
646 [],
647 [ 'action' => 'delete', 'prefix' => $iwPrefix['iw_prefix'] ]
648 )
649 );
650 }
651 $out .= Html::closeElement( 'tr' ) . "\n";
652 }
653 $out .= Html::closeElement( 'tbody' ) .
654 Html::closeElement( 'table' );
655
656 $this->getOutput()->addHTML( $out );
657 $this->getOutput()->addModuleStyles( 'jquery.tablesorter.styles' );
658 $this->getOutput()->addModules( 'jquery.tablesorter' );
659 }
660
664 protected function error( ...$args ) {
665 $this->getOutput()->wrapWikiMsg( "<p class='error'>$1</p>", $args );
666 }
667
668 protected function getGroupName() {
669 return 'wiki';
670 }
671}
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Base class for language-specific code.
Definition Language.php:81
A service that provides utilities to do with language names and codes.
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
const InterwikiCache
Name constant for the InterwikiCache setting, for use with Config::get()
const InterwikiMagic
Name constant for the InterwikiMagic setting, for use with Config::get()
const VirtualDomainsMapping
Name constant for the VirtualDomainsMapping setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
This is one of the Core classes and should be read at least once by any new developers.
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getUser()
Shortcut to get the User executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
Implements Special:Interwiki.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
makeTable( $canModify, $iwPrefixes)
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept for prefix searches.
getDescription()
Different description will be shown on Special:SpecialPage depending on whether the user can modify t...
execute( $par)
Show the special page.
doesWrites()
Indicates whether POST requests to this special page require write access to the wiki.
__construct(Language $contLang, InterwikiLookup $interwikiLookup, LanguageNameUtils $languageNameUtils, UrlUtils $urlUtils, IConnectionProvider $dbProvider)
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Service interface for looking up Interwiki records.
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)