35 private array $virtualDomainsMapping;
36 private bool $interwikiMagic;
45 parent::__construct(
'Interwiki' );
47 $this->contLang = $contLang;
48 $this->interwikiLookup = $interwikiLookup;
49 $this->languageNameUtils = $languageNameUtils;
50 $this->urlUtils = $urlUtils;
51 $this->dbProvider = $dbProvider;
67 return $this->
msg( $this->canModify() ?
'interwiki' :
'interwiki-title-norights' );
87 $out->addModuleStyles(
'mediawiki.special.interwiki' );
89 $action = $par ?? $request->getRawVal(
'action' );
91 if ( in_array( $action, [
'add',
'edit',
'delete' ] ) && $this->canModify( $out ) ) {
105 private function canModify( $out =
false ) {
106 if ( !$this->
getUser()->isAllowed(
'interwiki' ) ) {
116 $out->addWikiMsg(
'interwiki-cached' );
133 $formDescriptor = [];
138 $status = Status::newGood();
140 $prefix = $request->getText(
'prefix' );
148 'label-message' =>
'interwiki-prefix-label',
155 'id' =>
'mw-interwiki-local',
156 'label-message' =>
'interwiki-local-label',
162 'id' =>
'mw-interwiki-trans',
163 'label-message' =>
'interwiki-trans-label',
169 'id' =>
'mw-interwiki-url',
170 'label-message' =>
'interwiki-url-label',
178 'id' =>
'mw-interwiki-api',
179 'label-message' =>
'interwiki-api-label',
187 'id' =>
"mw-interwiki-{$action}reason",
188 'label-message' =>
'interwiki_reasonfield',
201 'default' => $prefix,
207 'label-message' =>
'interwiki_reasonfield',
213 throw new LogicException(
"Unexpected action: {$action}" );
216 if ( $action ===
'edit' || $action ===
'delete' ) {
217 $dbr = $this->dbProvider->getReplicaDatabase();
218 $row = $dbr->newSelectQueryBuilder()
220 ->from(
'interwiki' )
221 ->where( [
'iw_prefix' => $prefix ] )
222 ->caller( __METHOD__ )->fetchRow();
224 if ( $action ===
'edit' ) {
226 $status->fatal(
'interwiki_editerror', $prefix );
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;
238 $status->fatal(
'interwiki_delfailed', $prefix );
243 if ( !$status->isOK() ) {
244 $formDescriptor = [];
247 $htmlForm = HTMLForm::factory(
'ooui', $formDescriptor, $this->
getContext() );
249 ->addHiddenFields( $hiddenFields )
250 ->setSubmitCallback( [ $this,
'onSubmit' ] );
252 if ( $status->isOK() ) {
253 if ( $action ===
'delete' ) {
254 $htmlForm->setSubmitDestructive();
257 $htmlForm->setSubmitTextMsg( $action !==
'add' ? $action :
'interwiki_addbutton' )
258 ->setPreHtml( $this->
msg( $action !==
'delete' ?
"interwiki_{$action}intro" :
259 'interwiki_deleting', $prefix )->escaped() )
262 $htmlForm->suppressDefaultSubmit()
264 ->displayForm( $status );
271 $status = Status::newGood();
274 $prefix = $data[
'prefix'];
275 $do = $request->getRawVal(
'action' );
280 $validPrefixChars = preg_replace(
'/[ :&=]/',
'', Title::legalChars() );
281 if ( $do ===
'add' && preg_match(
"/\s|[^$validPrefixChars]/", $prefix ) ) {
282 $status->fatal(
'interwiki-badprefix', htmlspecialchars( $prefix ) );
288 && $this->isLanguagePrefix( $prefix )
289 && isset( $this->virtualDomainsMapping[
'virtual-interwiki-interlanguage'] )
291 $status->fatal(
'interwiki-cannotaddlocallanguage', htmlspecialchars( $prefix ) );
294 $reason = $data[
'reason'];
296 $dbw = $this->dbProvider->getPrimaryDatabase();
299 $dbw->newDeleteQueryBuilder()
300 ->deleteFrom(
'interwiki' )
301 ->where( [
'iw_prefix' => $prefix ] )
302 ->caller( __METHOD__ )->execute();
304 if ( $dbw->affectedRows() === 0 ) {
305 $status->fatal(
'interwiki_delfailed', $prefix );
307 $this->
getOutput()->addWikiMsg(
'interwiki_deleted', $prefix );
309 $log->setTarget( $selfTitle );
310 $log->setComment( $reason );
311 $log->setParameters( [
312 '4::prefix' => $prefix
314 $log->setPerformer( $this->
getUser() );
316 $this->interwikiLookup->invalidateCache( $prefix );
321 $prefix = $this->contLang->lc( $prefix );
326 $theurl = trim( $data[
'url'] );
327 $api = trim( $data[
'api'] ??
'' );
328 $local = $data[
'local'] ? 1 : 0;
329 $trans = $data[
'trans'] ? 1 : 0;
331 'iw_prefix' => $prefix,
335 'iw_local' => $local,
339 if ( $prefix ===
'' || $theurl ===
'' ) {
340 $status->fatal(
'interwiki-submit-empty' );
347 if ( !$this->urlUtils->parse( $theurl ) ) {
348 $status->fatal(
'interwiki-submit-invalidurl' );
352 if ( $do ===
'add' ) {
353 $dbw->newInsertQueryBuilder()
354 ->insertInto(
'interwiki' )
357 ->caller( __METHOD__ )->execute();
359 $dbw->newUpdateQueryBuilder()
360 ->update(
'interwiki' )
362 ->where( [
'iw_prefix' => $prefix ] )
364 ->caller( __METHOD__ )->execute();
368 if ( $dbw->affectedRows() === 0 ) {
369 $status->fatal(
"interwiki_{$do}failed", $prefix );
371 $this->
getOutput()->addWikiMsg(
"interwiki_{$do}ed", $prefix );
373 $log->setTarget( $selfTitle );
374 $log->setComment( $reason );
375 $log->setParameters( [
376 '4::prefix' => $prefix,
378 '6::trans' => $trans,
381 $log->setPerformer( $this->
getUser() );
383 $this->interwikiLookup->invalidateCache( $prefix );
387 throw new LogicException(
"Unexpected action: {$do}" );
399 private function isLanguagePrefix( $prefix ) {
400 return $this->interwikiMagic
401 && $this->languageNameUtils->getLanguageName( $prefix );
405 $canModify = $this->canModify();
408 $iwPrefixes = $this->interwikiLookup->getAllPrefixes(
null );
409 $iwGlobalPrefixes = [];
410 $iwGlobalLanguagePrefixes = [];
411 if ( isset( $this->virtualDomainsMapping[
'virtual-interwiki'] ) ) {
413 $dbrCentralDB = $this->dbProvider->getReplicaDatabase(
'virtual-interwiki' );
414 $res = $dbrCentralDB->newSelectQueryBuilder()
416 ->from(
'interwiki' )
417 ->caller( __METHOD__ )->fetchResultSet();
419 foreach ( $res as $row ) {
421 if ( !$this->isLanguagePrefix( $row[
'iw_prefix'] ) ) {
425 $iwGlobalPrefixes = $retval;
430 $usingGlobalLanguages = isset( $this->virtualDomainsMapping[
'virtual-interwiki-interlanguage'] );
431 if ( $usingGlobalLanguages ) {
433 $dbrCentralLangDB = $this->dbProvider->getReplicaDatabase(
'virtual-interwiki-interlanguage' );
434 $res = $dbrCentralLangDB->newSelectQueryBuilder()
436 ->from(
'interwiki' )
437 ->caller( __METHOD__ )->fetchResultSet();
439 foreach ( $res as $row ) {
443 if ( $this->isLanguagePrefix( $row[
'iw_prefix'] ) ) {
447 $iwGlobalLanguagePrefixes = $retval2;
451 $iwLocalPrefixes = [];
452 $iwLanguagePrefixes = [];
453 foreach ( $iwPrefixes as $iwPrefix ) {
454 if ( $this->isLanguagePrefix( $iwPrefix[
'iw_prefix'] ) ) {
455 $iwLanguagePrefixes[] = $iwPrefix;
457 $iwLocalPrefixes[] = $iwPrefix;
463 if ( $usingGlobalLanguages ) {
464 unset( $iwLanguagePrefixes );
465 $iwLanguagePrefixes = $iwGlobalLanguagePrefixes;
469 $this->
getOutput()->addWikiMsg(
'interwiki_intro' );
474 $this->
msg(
'interwiki-logtext' )->text()
477 Html::rawElement(
'p', [
'class' =>
'mw-interwiki-log' ], $logLink )
482 if ( count( $iwGlobalPrefixes ) !== 0 ) {
483 if ( $usingGlobalLanguages ) {
484 $addtext =
'interwiki-addtext-local-nolang';
486 $addtext =
'interwiki-addtext-local';
489 if ( $usingGlobalLanguages ) {
490 $addtext =
'interwiki-addtext-nolang';
492 $addtext =
'interwiki_addtext';
495 $addtext = $this->
msg( $addtext )->text();
499 Html::rawElement(
'p', [
'class' =>
'mw-interwiki-addlink' ], $addlink )
503 $this->
getOutput()->addWikiMsg(
'interwiki-legend' );
505 if ( $iwPrefixes === [] && $iwGlobalPrefixes === [] ) {
507 $this->
error(
'interwiki_error' );
512 if ( count( $iwGlobalPrefixes ) !== 0 ) {
516 [
'class' =>
'interwikitable-global' ],
517 $this->
msg(
'interwiki-global-links' )->parse()
520 $this->
getOutput()->addWikiMsg(
'interwiki-global-description' );
523 $this->
makeTable(
false, $iwGlobalPrefixes );
527 if ( count( $iwLocalPrefixes ) !== 0 ) {
528 if ( count( $iwGlobalPrefixes ) !== 0 ) {
532 [
'class' =>
'interwikitable-local' ],
533 $this->
msg(
'interwiki-local-links' )->parse()
536 $this->
getOutput()->addWikiMsg(
'interwiki-local-description' );
541 [
'class' =>
'interwikitable-local' ],
542 $this->
msg(
'interwiki-links' )->parse()
545 $this->
getOutput()->addWikiMsg(
'interwiki-description' );
547 $this->
makeTable( $canModify, $iwLocalPrefixes );
551 if ( count( $iwLanguagePrefixes ) !== 0 ) {
552 if ( $usingGlobalLanguages ) {
553 $header =
'interwiki-global-language-links';
554 $description =
'interwiki-global-language-description';
556 $header =
'interwiki-language-links';
557 $description =
'interwiki-language-description';
563 [
'class' =>
'interwikitable-language' ],
564 $this->
msg( $header )->parse()
567 $this->
getOutput()->addWikiMsg( $description );
571 $canModify = ( $usingGlobalLanguages ? false : $canModify );
572 $this->
makeTable( $canModify, $iwLanguagePrefixes );
576 protected function makeTable( $canModify, $iwPrefixes ) {
578 $out = Html::openElement(
580 [
'class' =>
'mw-interwikitable wikitable sortable' ]
582 $out .= Html::openElement(
'thead' ) .
583 Html::openElement(
'tr', [
'class' =>
'interwikitable-header' ] ) .
591 [
'class' =>
'unsortable' ],
592 $this->
msg(
'interwiki_edit' )->text()
596 $out .= Html::closeElement(
'tr' ) .
597 Html::closeElement(
'thead' ) .
"\n" .
598 Html::openElement(
'tbody' );
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'] );
609 [
'class' =>
'mw-interwikitable-url' ],
612 $attribs = [
'class' =>
'mw-interwikitable-local' ];
614 if ( isset( $iwPrefix[
'iw_local'] ) && $iwPrefix[
'iw_local'] ) {
615 $attribs[
'class'] .=
' mw-interwikitable-local-yes';
618 $contents = isset( $iwPrefix[
'iw_local'] ) ?
619 $this->
msg(
'interwiki_' . $iwPrefix[
'iw_local'] )->text() :
622 $attribs = [
'class' =>
'mw-interwikitable-trans' ];
624 if ( isset( $iwPrefix[
'iw_trans'] ) && $iwPrefix[
'iw_trans'] ) {
625 $attribs[
'class'] .=
' mw-interwikitable-trans-yes';
628 $contents = isset( $iwPrefix[
'iw_trans'] ) ?
629 $this->
msg(
'interwiki_' . $iwPrefix[
'iw_trans'] )->text() :
635 $out .= Html::rawElement(
'td', [
'class' =>
'mw-interwikitable-modify' ],
638 $this->
msg(
'edit' )->text(),
640 [
'action' =>
'edit',
'prefix' => $iwPrefix[
'iw_prefix'] ]
642 $this->
msg(
'comma-separator' )->escaped() .
645 $this->
msg(
'delete' )->text(),
647 [
'action' =>
'delete',
'prefix' => $iwPrefix[
'iw_prefix'] ]
651 $out .= Html::closeElement(
'tr' ) .
"\n";
653 $out .= Html::closeElement(
'tbody' ) .
654 Html::closeElement(
'table' );
657 $this->
getOutput()->addModuleStyles(
'jquery.tablesorter.styles' );
658 $this->
getOutput()->addModules(
'jquery.tablesorter' );
664 protected function error( ...$args ) {
665 $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...