MediaWiki REL1_31
SpecialInterwiki.php
Go to the documentation of this file.
1<?php
10 public function __construct() {
11 parent::__construct( 'Interwiki' );
12 }
13
14 public function doesWrites() {
15 return true;
16 }
17
23 function getDescription() {
24 return $this->msg( $this->canModify() ?
25 'interwiki' : 'interwiki-title-norights' )->plain();
26 }
27
28 public function getSubpagesForPrefixSearch() {
29 // delete, edit both require the prefix parameter.
30 return [ 'add' ];
31 }
32
38 public function execute( $par ) {
39 $this->setHeaders();
40 $this->outputHeader();
41
42 $out = $this->getOutput();
43 $request = $this->getRequest();
44
45 $out->addModuleStyles( 'ext.interwiki.specialpage' );
46
47 $action = $par ?: $request->getVal( 'action', $par );
48 $return = $this->getPageTitle();
49
50 switch ( $action ) {
51 case 'delete':
52 case 'edit':
53 case 'add':
54 if ( $this->canModify( $out ) ) {
55 $this->showForm( $action );
56 }
57 $out->returnToMain( false, $return );
58 break;
59 case 'submit':
60 if ( !$this->canModify( $out ) ) {
61 // Error msg added by canModify()
62 } elseif ( !$request->wasPosted() ||
63 !$this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) )
64 ) {
65 // Prevent cross-site request forgeries
66 $out->addWikiMsg( 'sessionfailure' );
67 } else {
68 $this->doSubmit();
69 }
70 $out->returnToMain( false, $return );
71 break;
72 default:
73 $this->showList();
74 break;
75 }
76 }
77
84 public function canModify( $out = false ) {
85 global $wgInterwikiCache;
86 if ( !$this->getUser()->isAllowed( 'interwiki' ) ) {
87 // Check permissions
88 if ( $out ) {
89 throw new PermissionsError( 'interwiki' );
90 }
91
92 return false;
93 } elseif ( $wgInterwikiCache ) {
94 // Editing the interwiki cache is not supported
95 if ( $out ) {
96 $out->addWikiMsg( 'interwiki-cached' );
97 }
98
99 return false;
100 } elseif ( wfReadOnly() ) {
101 throw new ReadOnlyError;
102 }
103
104 return true;
105 }
106
110 protected function showForm( $action ) {
111 $request = $this->getRequest();
112
113 $prefix = $request->getVal( 'prefix' );
114 $wpPrefix = '';
115 $label = [ 'class' => 'mw-label' ];
116 $input = [ 'class' => 'mw-input' ];
117
118 if ( $action === 'delete' ) {
119 $topmessage = $this->msg( 'interwiki_delquestion', $prefix )->text();
120 $intromessage = $this->msg( 'interwiki_deleting', $prefix )->escaped();
121 $wpPrefix = Html::hidden( 'wpInterwikiPrefix', $prefix );
122 $button = 'delete';
123 $formContent = '';
124 } elseif ( $action === 'edit' ) {
126 $row = $dbr->selectRow( 'interwiki', '*', [ 'iw_prefix' => $prefix ], __METHOD__ );
127
128 if ( !$row ) {
129 $this->error( 'interwiki_editerror', $prefix );
130 return;
131 }
132
133 $prefix = $prefixElement = $row->iw_prefix;
134 $defaulturl = $row->iw_url;
135 $trans = $row->iw_trans;
136 $local = $row->iw_local;
137 $wpPrefix = Html::hidden( 'wpInterwikiPrefix', $row->iw_prefix );
138 $topmessage = $this->msg( 'interwiki_edittext' )->text();
139 $intromessage = $this->msg( 'interwiki_editintro' )->escaped();
140 $button = 'edit';
141 } elseif ( $action === 'add' ) {
142 $prefix = $request->getVal( 'wpInterwikiPrefix', $request->getVal( 'prefix' ) );
143 $prefixElement = Xml::input( 'wpInterwikiPrefix', 20, $prefix,
144 [ 'tabindex' => 1, 'id' => 'mw-interwiki-prefix', 'maxlength' => 20 ] );
145 $local = $request->getCheck( 'wpInterwikiLocal' );
146 $trans = $request->getCheck( 'wpInterwikiTrans' );
147 $defaulturl = $request->getVal( 'wpInterwikiURL', $this->msg( 'interwiki-defaulturl' )->text() );
148 $topmessage = $this->msg( 'interwiki_addtext' )->text();
149 $intromessage = $this->msg( 'interwiki_addintro' )->escaped();
150 $button = 'interwiki_addbutton';
151 }
152
153 if ( $action === 'add' || $action === 'edit' ) {
154 $formContent = Html::rawElement( 'tr', null,
155 Html::element( 'td', $label, $this->msg( 'interwiki-prefix-label' )->text() ) .
156 Html::rawElement( 'td', null, '<code>' . $prefixElement . '</code>' )
157 ) . Html::rawElement(
158 'tr',
159 null,
160 Html::rawElement(
161 'td',
162 $label,
163 Xml::label( $this->msg( 'interwiki-local-label' )->text(), 'mw-interwiki-local' )
164 ) .
165 Html::rawElement(
166 'td',
167 $input,
168 Xml::check( 'wpInterwikiLocal', $local, [ 'id' => 'mw-interwiki-local' ] )
169 )
170 ) . Html::rawElement( 'tr', null,
171 Html::rawElement(
172 'td',
173 $label,
174 Xml::label( $this->msg( 'interwiki-trans-label' )->text(), 'mw-interwiki-trans' )
175 ) .
176 Html::rawElement(
177 'td',
178 $input, Xml::check( 'wpInterwikiTrans', $trans, [ 'id' => 'mw-interwiki-trans' ] ) )
179 ) . Html::rawElement( 'tr', null,
180 Html::rawElement(
181 'td',
182 $label,
183 Xml::label( $this->msg( 'interwiki-url-label' )->text(), 'mw-interwiki-url' )
184 ) .
185 Html::rawElement( 'td', $input, Xml::input( 'wpInterwikiURL', 60, $defaulturl,
186 [ 'tabindex' => 1, 'maxlength' => 200, 'id' => 'mw-interwiki-url' ] ) )
187 );
188 }
189
190 $form = Xml::fieldset( $topmessage, Html::rawElement(
191 'form',
192 [
193 'id' => "mw-interwiki-{$action}form",
194 'method' => 'post',
195 'action' => $this->getPageTitle()->getLocalURL( [
196 'action' => 'submit',
197 'prefix' => $prefix
198 ] )
199 ],
200 Html::rawElement( 'p', null, $intromessage ) .
201 Html::rawElement( 'table', [ 'id' => "mw-interwiki-{$action}" ],
202 $formContent . Html::rawElement( 'tr', null,
203 Html::rawElement( 'td', $label, Xml::label( $this->msg( 'interwiki_reasonfield' )->text(),
204 "mw-interwiki-{$action}reason" ) ) .
205 Html::rawElement( 'td', $input, Xml::input( 'wpInterwikiReason', 60, '',
206 [ 'tabindex' => 1, 'id' => "mw-interwiki-{$action}reason", 'maxlength' => 200 ] ) )
207 ) . Html::rawElement( 'tr', null,
208 Html::rawElement( 'td', null, '' ) .
209 Html::rawElement( 'td', [ 'class' => 'mw-submit' ],
210 Xml::submitButton( $this->msg( $button )->text(), [ 'id' => 'mw-interwiki-submit' ] ) )
211 ) . $wpPrefix .
212 Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
213 Html::hidden( 'wpInterwikiAction', $action )
214 )
215 ) );
216 $this->getOutput()->addHTML( $form );
217 }
218
219 protected function doSubmit() {
220 global $wgContLang;
221
222 $request = $this->getRequest();
223 $prefix = $request->getVal( 'wpInterwikiPrefix' );
224 $do = $request->getVal( 'wpInterwikiAction' );
225 // Show an error if the prefix is invalid (only when adding one).
226 // Invalid characters for a title should also be invalid for a prefix.
227 // Whitespace, ':', '&' and '=' are invalid, too.
228 // (Bug 30599).
229 global $wgLegalTitleChars;
230 $validPrefixChars = preg_replace( '/[ :&=]/', '', $wgLegalTitleChars );
231 if ( $do === 'add' && preg_match( "/\s|[^$validPrefixChars]/", $prefix ) ) {
232 $this->error( 'interwiki-badprefix', htmlspecialchars( $prefix ) );
233 $this->showForm( $do );
234 return;
235 }
236 $reason = $request->getText( 'wpInterwikiReason' );
237 $selfTitle = $this->getPageTitle();
238 $dbw = wfGetDB( DB_MASTER );
239 switch ( $do ) {
240 case 'delete':
241 $dbw->delete( 'interwiki', [ 'iw_prefix' => $prefix ], __METHOD__ );
242
243 if ( $dbw->affectedRows() === 0 ) {
244 $this->error( 'interwiki_delfailed', $prefix );
245 $this->showForm( $do );
246 } else {
247 $this->getOutput()->addWikiMsg( 'interwiki_deleted', $prefix );
248 $log = new LogPage( 'interwiki' );
249 $log->addEntry( 'iw_delete', $selfTitle, $reason, [ $prefix ] );
251 }
252 break;
254 case 'add':
255 $prefix = $wgContLang->lc( $prefix );
256 case 'edit':
257 $theurl = $request->getVal( 'wpInterwikiURL' );
258 $local = $request->getCheck( 'wpInterwikiLocal' ) ? 1 : 0;
259 $trans = $request->getCheck( 'wpInterwikiTrans' ) ? 1 : 0;
260 $data = [
261 'iw_prefix' => $prefix,
262 'iw_url' => $theurl,
263 'iw_local' => $local,
264 'iw_trans' => $trans
265 ];
266
267 if ( $prefix === '' || $theurl === '' ) {
268 $this->error( 'interwiki-submit-empty' );
269 $this->showForm( $do );
270 return;
271 }
272
273 // Simple URL validation: check that the protocol is one of
274 // the supported protocols for this wiki.
275 // (bug 30600)
276 if ( !wfParseUrl( $theurl ) ) {
277 $this->error( 'interwiki-submit-invalidurl' );
278 $this->showForm( $do );
279 return;
280 }
281
282 if ( $do === 'add' ) {
283 $dbw->insert( 'interwiki', $data, __METHOD__, 'IGNORE' );
284 } else { // $do === 'edit'
285 $dbw->update( 'interwiki', $data, [ 'iw_prefix' => $prefix ], __METHOD__, 'IGNORE' );
286 }
287
288 // used here: interwiki_addfailed, interwiki_added, interwiki_edited
289 if ( $dbw->affectedRows() === 0 ) {
290 $this->error( "interwiki_{$do}failed", $prefix );
291 $this->showForm( $do );
292 } else {
293 $this->getOutput()->addWikiMsg( "interwiki_{$do}ed", $prefix );
294 $log = new LogPage( 'interwiki' );
295 $log->addEntry( 'iw_' . $do, $selfTitle, $reason, [ $prefix, $theurl, $trans, $local ] );
297 }
298 break;
299 }
300 }
301
302 protected function showList() {
303 global $wgInterwikiCentralDB, $wgInterwikiViewOnly;
304 $canModify = $this->canModify();
305
306 // Build lists
307 if ( !method_exists( 'Interwiki', 'getAllPrefixes' ) ) {
308 // version 2.0 is not backwards compatible (but will still display a nice error)
309 $this->error( 'interwiki_error' );
310 return;
311 }
312 $iwPrefixes = Interwiki::getAllPrefixes( null );
313 $iwGlobalPrefixes = [];
314 if ( $wgInterwikiCentralDB !== null && $wgInterwikiCentralDB !== wfWikiID() ) {
315 // Fetch list from global table
316 $dbrCentralDB = wfGetDB( DB_REPLICA, [], $wgInterwikiCentralDB );
317 $res = $dbrCentralDB->select( 'interwiki', '*', false, __METHOD__ );
318 $retval = [];
319 foreach ( $res as $row ) {
320 $row = (array)$row;
321 if ( !Language::fetchLanguageName( $row['iw_prefix'] ) ) {
322 $retval[] = $row;
323 }
324 }
325 $iwGlobalPrefixes = $retval;
326 }
327
328 // Split out language links
329 $iwLocalPrefixes = [];
330 $iwLanguagePrefixes = [];
331 foreach ( $iwPrefixes as $iwPrefix ) {
332 if ( Language::fetchLanguageName( $iwPrefix['iw_prefix'] ) ) {
333 $iwLanguagePrefixes[] = $iwPrefix;
334 } else {
335 $iwLocalPrefixes[] = $iwPrefix;
336 }
337 }
338
339 // Page intro content
340 $this->getOutput()->addWikiMsg( 'interwiki_intro' );
341
342 // Add 'view log' link when possible
343 if ( $wgInterwikiViewOnly === false ) {
344 $logLink = Linker::link(
345 SpecialPage::getTitleFor( 'Log', 'interwiki' ),
346 $this->msg( 'interwiki-logtext' )->escaped()
347 );
348 $this->getOutput()->addHTML( '<p class="mw-interwiki-log">' . $logLink . '</p>' );
349 }
350
351 // Add 'add' link
352 if ( $canModify ) {
353 if ( count( $iwGlobalPrefixes ) !== 0 ) {
354 $addtext = $this->msg( 'interwiki-addtext-local' )->escaped();
355 } else {
356 $addtext = $this->msg( 'interwiki_addtext' )->escaped();
357 }
358 $addlink = Linker::linkKnown( $this->getPageTitle( 'add' ), $addtext );
359 $this->getOutput()->addHTML( '<p class="mw-interwiki-addlink">' . $addlink . '</p>' );
360 }
361
362 $this->getOutput()->addWikiMsg( 'interwiki-legend' );
363
364 if ( ( !is_array( $iwPrefixes ) || count( $iwPrefixes ) === 0 ) &&
365 ( !is_array( $iwGlobalPrefixes ) || count( $iwGlobalPrefixes ) === 0 )
366 ) {
367 // If the interwiki table(s) are empty, display an error message
368 $this->error( 'interwiki_error' );
369 return;
370 }
371
372 // Add the global table
373 if ( count( $iwGlobalPrefixes ) !== 0 ) {
374 $this->getOutput()->addHTML(
375 '<h2 id="interwikitable-global">' .
376 $this->msg( 'interwiki-global-links' )->parse() .
377 '</h2>'
378 );
379 $this->getOutput()->addWikiMsg( 'interwiki-global-description' );
380
381 // $canModify is false here because this is just a display of remote data
382 $this->makeTable( false, $iwGlobalPrefixes );
383 }
384
385 // Add the local table
386 if ( count( $iwLocalPrefixes ) !== 0 ) {
387 if ( count( $iwGlobalPrefixes ) !== 0 ) {
388 $this->getOutput()->addHTML(
389 '<h2 id="interwikitable-local">' .
390 $this->msg( 'interwiki-local-links' )->parse() .
391 '</h2>'
392 );
393 $this->getOutput()->addWikiMsg( 'interwiki-local-description' );
394 } else {
395 $this->getOutput()->addHTML(
396 '<h2 id="interwikitable-local">' .
397 $this->msg( 'interwiki-links' )->parse() .
398 '</h2>'
399 );
400 $this->getOutput()->addWikiMsg( 'interwiki-description' );
401 }
402 $this->makeTable( $canModify, $iwLocalPrefixes );
403 }
404
405 // Add the language table
406 if ( count( $iwLanguagePrefixes ) !== 0 ) {
407 $this->getOutput()->addHTML(
408 '<h2 id="interwikitable-language">' .
409 $this->msg( 'interwiki-language-links' )->parse() .
410 '</h2>'
411 );
412 $this->getOutput()->addWikiMsg( 'interwiki-language-description' );
413
414 $this->makeTable( $canModify, $iwLanguagePrefixes );
415 }
416 }
417
418 protected function makeTable( $canModify, $iwPrefixes ) {
419 // Output the existing Interwiki prefixes table header
420 $out = '';
421 $out .= Html::openElement(
422 'table',
423 [ 'class' => 'mw-interwikitable wikitable sortable body' ]
424 ) . "\n";
425 $out .= Html::openElement( 'tr', [ 'class' => 'interwikitable-header' ] ) .
426 Html::element( 'th', null, $this->msg( 'interwiki_prefix' )->text() ) .
427 Html::element( 'th', null, $this->msg( 'interwiki_url' )->text() ) .
428 Html::element( 'th', null, $this->msg( 'interwiki_local' )->text() ) .
429 Html::element( 'th', null, $this->msg( 'interwiki_trans' )->text() ) .
430 ( $canModify ?
431 Html::element(
432 'th',
433 [ 'class' => 'unsortable' ],
434 $this->msg( 'interwiki_edit' )->text()
435 ) :
436 ''
437 );
438 $out .= Html::closeElement( 'tr' ) . "\n";
439
440 $selfTitle = $this->getPageTitle();
441
442 // Output the existing Interwiki prefixes table rows
443 foreach ( $iwPrefixes as $iwPrefix ) {
444 $out .= Html::openElement( 'tr', [ 'class' => 'mw-interwikitable-row' ] );
445 $out .= Html::element( 'td', [ 'class' => 'mw-interwikitable-prefix' ],
446 $iwPrefix['iw_prefix'] );
447 $out .= Html::element(
448 'td',
449 [ 'class' => 'mw-interwikitable-url' ],
450 $iwPrefix['iw_url']
451 );
452 $attribs = [ 'class' => 'mw-interwikitable-local' ];
453 // Green background for cells with "yes".
454 if ( isset( $iwPrefix['iw_local'] ) && $iwPrefix['iw_local'] ) {
455 $attribs['class'] .= ' mw-interwikitable-local-yes';
456 }
457 // The messages interwiki_0 and interwiki_1 are used here.
458 $contents = isset( $iwPrefix['iw_local'] ) ?
459 $this->msg( 'interwiki_' . $iwPrefix['iw_local'] )->text() :
460 '-';
461 $out .= Html::element( 'td', $attribs, $contents );
462 $attribs = [ 'class' => 'mw-interwikitable-trans' ];
463 // Green background for cells with "yes".
464 if ( isset( $iwPrefix['iw_trans'] ) && $iwPrefix['iw_trans'] ) {
465 $attribs['class'] .= ' mw-interwikitable-trans-yes';
466 }
467 // The messages interwiki_0 and interwiki_1 are used here.
468 $contents = isset( $iwPrefix['iw_trans'] ) ?
469 $this->msg( 'interwiki_' . $iwPrefix['iw_trans'] )->text() :
470 '-';
471 $out .= Html::element( 'td', $attribs, $contents );
472
473 // Additional column when the interwiki table can be modified.
474 if ( $canModify ) {
475 $out .= Html::rawElement( 'td', [ 'class' => 'mw-interwikitable-modify' ],
476 Linker::linkKnown( $selfTitle, $this->msg( 'edit' )->escaped(), [],
477 [ 'action' => 'edit', 'prefix' => $iwPrefix['iw_prefix'] ] ) .
478 $this->msg( 'comma-separator' ) .
479 Linker::linkKnown( $selfTitle, $this->msg( 'delete' )->escaped(), [],
480 [ 'action' => 'delete', 'prefix' => $iwPrefix['iw_prefix'] ] )
481 );
482 }
483 $out .= Html::closeElement( 'tr' ) . "\n";
484 }
485 $out .= Html::closeElement( 'table' );
486
487 $this->getOutput()->addHTML( $out );
488 }
489
490 protected function error() {
491 $args = func_get_args();
492 $this->getOutput()->wrapWikiMsg( "<p class='error'>$1</p>", $args );
493 }
494
495 protected function getGroupName() {
496 return 'wiki';
497 }
498}
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing.
bool array string $wgInterwikiCache
Interwiki cache, either as an associative array or a path to a constant database (....
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
if( $line===false) $args
Definition cdb.php:64
static getAllPrefixes( $local=null)
Returns all interwiki prefix definitions.
static invalidateCache( $prefix)
Purge the cache (local and persistent) for an interwiki prefix.
Definition Interwiki.php:90
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:107
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:164
Class to simplify the use of log pages.
Definition LogPage.php:31
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...
Implements Special:Interwiki.
canModify( $out=false)
Returns boolean whether the user can modify the data.
getDescription()
Different description will be shown on Special:SpecialPage depending on whether the user can modify t...
__construct()
Constructor - sets up the new special page.
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept for prefix searches.
doesWrites()
Indicates whether this special page may perform database writes.
execute( $par)
Show the special page.
makeTable( $canModify, $iwPrefixes)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Parent class for all special pages.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
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,...
msg( $key)
Wrapper around wfMessage that sets the current context.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
$res
Definition database.txt:21
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
the array() calling protocol came about after MediaWiki 1.4rc1.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2806
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:266
either a plain
Definition hooks.txt:2056
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:864
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2014
if(is_array($mode)) switch( $mode) $input
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29