35 private array $virtualDomainsMapping;
36 private bool $interwikiMagic;
52 parent::__construct(
'Interwiki' );
54 $this->contLang = $contLang;
55 $this->interwikiLookup = $interwikiLookup;
56 $this->languageNameUtils = $languageNameUtils;
57 $this->urlUtils = $urlUtils;
58 $this->dbProvider = $dbProvider;
74 return $this->
msg( $this->canModify() ?
'interwiki' :
'interwiki-title-norights' );
94 $out->addModuleStyles(
'mediawiki.special.interwiki' );
96 $action = $par ?: $request->getRawVal(
'action', $par );
98 if ( in_array( $action, [
'add',
'edit',
'delete' ] ) && $this->canModify( $out ) ) {
112 private function canModify( $out =
false ) {
113 if ( !$this->
getUser()->isAllowed(
'interwiki' ) ) {
123 $out->addWikiMsg(
'interwiki-cached' );
140 $formDescriptor = [];
145 $status = Status::newGood();
147 $prefix = $request->getText(
'prefix' );
155 'label-message' =>
'interwiki-prefix-label',
162 'id' =>
'mw-interwiki-local',
163 'label-message' =>
'interwiki-local-label',
169 'id' =>
'mw-interwiki-trans',
170 'label-message' =>
'interwiki-trans-label',
176 'id' =>
'mw-interwiki-url',
177 'label-message' =>
'interwiki-url-label',
185 'id' =>
'mw-interwiki-api',
186 'label-message' =>
'interwiki-api-label',
194 'id' =>
"mw-interwiki-{$action}reason",
195 'label-message' =>
'interwiki_reasonfield',
208 'default' => $prefix,
214 'label-message' =>
'interwiki_reasonfield',
220 throw new LogicException(
"Unexpected action: {$action}" );
223 if ( $action ===
'edit' || $action ===
'delete' ) {
224 $dbr = $this->dbProvider->getReplicaDatabase();
225 $row = $dbr->newSelectQueryBuilder()
227 ->from(
'interwiki' )
228 ->where( [
'iw_prefix' => $prefix ] )
229 ->caller( __METHOD__ )->fetchRow();
231 if ( $action ===
'edit' ) {
233 $status->fatal(
'interwiki_editerror', $prefix );
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;
245 $status->fatal(
'interwiki_delfailed', $prefix );
250 if ( !$status->isOK() ) {
251 $formDescriptor = [];
254 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
256 ->addHiddenFields( $hiddenFields )
257 ->setSubmitCallback( [ $this,
'onSubmit' ] );
259 if ( $status->isOK() ) {
260 if ( $action ===
'delete' ) {
261 $htmlForm->setSubmitDestructive();
264 $htmlForm->setSubmitTextMsg( $action !==
'add' ? $action :
'interwiki_addbutton' )
265 ->setPreHtml( $this->
msg( $action !==
'delete' ?
"interwiki_{$action}intro" :
266 'interwiki_deleting', $prefix )->escaped() )
269 $htmlForm->suppressDefaultSubmit()
271 ->displayForm( $status );
278 $status = Status::newGood();
281 $prefix = $data[
'prefix'];
282 $do = $request->getRawVal(
'action' );
287 $validPrefixChars = preg_replace(
'/[ :&=]/',
'', Title::legalChars() );
288 if ( $do ===
'add' && preg_match(
"/\s|[^$validPrefixChars]/", $prefix ) ) {
289 $status->fatal(
'interwiki-badprefix', htmlspecialchars( $prefix ) );
295 && $this->isLanguagePrefix( $prefix )
296 && isset( $this->virtualDomainsMapping[
'virtual-interwiki-interlanguage'] )
298 $status->fatal(
'interwiki-cannotaddlocallanguage', htmlspecialchars( $prefix ) );
301 $reason = $data[
'reason'];
303 $dbw = $this->dbProvider->getPrimaryDatabase();
306 $dbw->newDeleteQueryBuilder()
307 ->deleteFrom(
'interwiki' )
308 ->where( [
'iw_prefix' => $prefix ] )
309 ->caller( __METHOD__ )->execute();
311 if ( $dbw->affectedRows() === 0 ) {
312 $status->fatal(
'interwiki_delfailed', $prefix );
314 $this->
getOutput()->addWikiMsg(
'interwiki_deleted', $prefix );
316 $log->setTarget( $selfTitle );
317 $log->setComment( $reason );
318 $log->setParameters( [
319 '4::prefix' => $prefix
321 $log->setPerformer( $this->
getUser() );
323 $this->interwikiLookup->invalidateCache( $prefix );
328 $prefix = $this->contLang->lc( $prefix );
333 $theurl = trim( $data[
'url'] );
334 $api = trim( $data[
'api'] ??
'' );
335 $local = $data[
'local'] ? 1 : 0;
336 $trans = $data[
'trans'] ? 1 : 0;
338 'iw_prefix' => $prefix,
342 'iw_local' => $local,
346 if ( $prefix ===
'' || $theurl ===
'' ) {
347 $status->fatal(
'interwiki-submit-empty' );
354 if ( !$this->urlUtils->parse( $theurl ) ) {
355 $status->fatal(
'interwiki-submit-invalidurl' );
359 if ( $do ===
'add' ) {
360 $dbw->newInsertQueryBuilder()
361 ->insertInto(
'interwiki' )
364 ->caller( __METHOD__ )->execute();
366 $dbw->newUpdateQueryBuilder()
367 ->update(
'interwiki' )
369 ->where( [
'iw_prefix' => $prefix ] )
371 ->caller( __METHOD__ )->execute();
375 if ( $dbw->affectedRows() === 0 ) {
376 $status->fatal(
"interwiki_{$do}failed", $prefix );
378 $this->
getOutput()->addWikiMsg(
"interwiki_{$do}ed", $prefix );
380 $log->setTarget( $selfTitle );
381 $log->setComment( $reason );
382 $log->setParameters( [
383 '4::prefix' => $prefix,
385 '6::trans' => $trans,
388 $log->setPerformer( $this->
getUser() );
390 $this->interwikiLookup->invalidateCache( $prefix );
394 throw new LogicException(
"Unexpected action: {$do}" );
406 private function isLanguagePrefix( $prefix ) {
407 return $this->interwikiMagic
408 && $this->languageNameUtils->getLanguageName( $prefix );
412 $canModify = $this->canModify();
415 $iwPrefixes = $this->interwikiLookup->getAllPrefixes(
null );
416 $iwGlobalPrefixes = [];
417 $iwGlobalLanguagePrefixes = [];
418 if ( isset( $this->virtualDomainsMapping[
'virtual-interwiki'] ) ) {
420 $dbrCentralDB = $this->dbProvider->getReplicaDatabase(
'virtual-interwiki' );
421 $res = $dbrCentralDB->newSelectQueryBuilder()
423 ->from(
'interwiki' )
424 ->caller( __METHOD__ )->fetchResultSet();
426 foreach ( $res as $row ) {
428 if ( !$this->isLanguagePrefix( $row[
'iw_prefix'] ) ) {
432 $iwGlobalPrefixes = $retval;
437 $usingGlobalLanguages = isset( $this->virtualDomainsMapping[
'virtual-interwiki-interlanguage'] );
438 if ( $usingGlobalLanguages ) {
440 $dbrCentralLangDB = $this->dbProvider->getReplicaDatabase(
'virtual-interwiki-interlanguage' );
441 $res = $dbrCentralLangDB->newSelectQueryBuilder()
443 ->from(
'interwiki' )
444 ->caller( __METHOD__ )->fetchResultSet();
446 foreach ( $res as $row ) {
450 if ( $this->isLanguagePrefix( $row[
'iw_prefix'] ) ) {
454 $iwGlobalLanguagePrefixes = $retval2;
458 $iwLocalPrefixes = [];
459 $iwLanguagePrefixes = [];
460 foreach ( $iwPrefixes as $iwPrefix ) {
461 if ( $this->isLanguagePrefix( $iwPrefix[
'iw_prefix'] ) ) {
462 $iwLanguagePrefixes[] = $iwPrefix;
464 $iwLocalPrefixes[] = $iwPrefix;
470 if ( $usingGlobalLanguages ) {
471 unset( $iwLanguagePrefixes );
472 $iwLanguagePrefixes = $iwGlobalLanguagePrefixes;
476 $this->
getOutput()->addWikiMsg(
'interwiki_intro' );
481 $this->
msg(
'interwiki-logtext' )->text()
484 Html::rawElement(
'p', [
'class' =>
'mw-interwiki-log' ], $logLink )
489 if ( count( $iwGlobalPrefixes ) !== 0 ) {
490 if ( $usingGlobalLanguages ) {
491 $addtext =
'interwiki-addtext-local-nolang';
493 $addtext =
'interwiki-addtext-local';
496 if ( $usingGlobalLanguages ) {
497 $addtext =
'interwiki-addtext-nolang';
499 $addtext =
'interwiki_addtext';
502 $addtext = $this->
msg( $addtext )->text();
506 Html::rawElement(
'p', [
'class' =>
'mw-interwiki-addlink' ], $addlink )
510 $this->
getOutput()->addWikiMsg(
'interwiki-legend' );
512 if ( $iwPrefixes === [] && $iwGlobalPrefixes === [] ) {
514 $this->
error(
'interwiki_error' );
519 if ( count( $iwGlobalPrefixes ) !== 0 ) {
523 [
'class' =>
'interwikitable-global' ],
524 $this->
msg(
'interwiki-global-links' )->parse()
527 $this->
getOutput()->addWikiMsg(
'interwiki-global-description' );
530 $this->
makeTable(
false, $iwGlobalPrefixes );
534 if ( count( $iwLocalPrefixes ) !== 0 ) {
535 if ( count( $iwGlobalPrefixes ) !== 0 ) {
539 [
'class' =>
'interwikitable-local' ],
540 $this->
msg(
'interwiki-local-links' )->parse()
543 $this->
getOutput()->addWikiMsg(
'interwiki-local-description' );
548 [
'class' =>
'interwikitable-local' ],
549 $this->
msg(
'interwiki-links' )->parse()
552 $this->
getOutput()->addWikiMsg(
'interwiki-description' );
554 $this->
makeTable( $canModify, $iwLocalPrefixes );
558 if ( count( $iwLanguagePrefixes ) !== 0 ) {
559 if ( $usingGlobalLanguages ) {
560 $header =
'interwiki-global-language-links';
561 $description =
'interwiki-global-language-description';
563 $header =
'interwiki-language-links';
564 $description =
'interwiki-language-description';
570 [
'class' =>
'interwikitable-language' ],
574 $this->
getOutput()->addWikiMsg( $description );
578 $canModify = ( $usingGlobalLanguages ? false : $canModify );
579 $this->
makeTable( $canModify, $iwLanguagePrefixes );
583 protected function makeTable( $canModify, $iwPrefixes ) {
585 $out = Html::openElement(
587 [
'class' =>
'mw-interwikitable wikitable sortable' ]
589 $out .= Html::openElement(
'thead' ) .
590 Html::openElement(
'tr', [
'class' =>
'interwikitable-header' ] ) .
598 [
'class' =>
'unsortable' ],
599 $this->
msg(
'interwiki_edit' )->text()
603 $out .= Html::closeElement(
'tr' ) .
604 Html::closeElement(
'thead' ) .
"\n" .
605 Html::openElement(
'tbody' );
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'] );
616 [
'class' =>
'mw-interwikitable-url' ],
619 $attribs = [
'class' =>
'mw-interwikitable-local' ];
621 if ( isset( $iwPrefix[
'iw_local'] ) && $iwPrefix[
'iw_local'] ) {
622 $attribs[
'class'] .=
' mw-interwikitable-local-yes';
625 $contents = isset( $iwPrefix[
'iw_local'] ) ?
626 $this->
msg(
'interwiki_' . $iwPrefix[
'iw_local'] )->text() :
629 $attribs = [
'class' =>
'mw-interwikitable-trans' ];
631 if ( isset( $iwPrefix[
'iw_trans'] ) && $iwPrefix[
'iw_trans'] ) {
632 $attribs[
'class'] .=
' mw-interwikitable-trans-yes';
635 $contents = isset( $iwPrefix[
'iw_trans'] ) ?
636 $this->
msg(
'interwiki_' . $iwPrefix[
'iw_trans'] )->text() :
642 $out .= Html::rawElement(
'td', [
'class' =>
'mw-interwikitable-modify' ],
645 $this->
msg(
'edit' )->text(),
647 [
'action' =>
'edit',
'prefix' => $iwPrefix[
'iw_prefix'] ]
649 $this->
msg(
'comma-separator' )->escaped() .
652 $this->
msg(
'delete' )->text(),
654 [
'action' =>
'delete',
'prefix' => $iwPrefix[
'iw_prefix'] ]
658 $out .= Html::closeElement(
'tr' ) .
"\n";
660 $out .= Html::closeElement(
'tbody' ) .
661 Html::closeElement(
'table' );
664 $this->
getOutput()->addModuleStyles(
'jquery.tablesorter.styles' );
665 $this->
getOutput()->addModules(
'jquery.tablesorter' );
671 protected function error( ...$args ) {
672 $this->
getOutput()->wrapWikiMsg(
"<p class='error'>$1</p>", $args );
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()
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...
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...