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
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
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
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
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
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;
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
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 ?
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
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}
Class for creating new log entries and inserting them into the database.
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:56
Base class for language-specific code.
Definition Language.php:81
A service that provides utilities to do with language names and codes.
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:155
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
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...
Service interface for looking up Interwiki records.
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)
$header