]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - extensions/Interwiki/Interwiki_body.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / extensions / Interwiki / Interwiki_body.php
1 <?php
2 /**
3  * Implements Special:Interwiki
4  * @ingroup SpecialPage
5  */
6 class SpecialInterwiki extends SpecialPage {
7         /**
8          * Constructor - sets up the new special page
9          */
10         public function __construct() {
11                 parent::__construct( 'Interwiki' );
12         }
13
14         public function doesWrites() {
15                 return true;
16         }
17
18         /**
19          * Different description will be shown on Special:SpecialPage depending on
20          * whether the user can modify the data.
21          * @return String
22          */
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
33         /**
34          * Show the special page
35          *
36          * @param $par Mixed: parameter passed to the page or null
37          */
38         public function execute( $par ) {
39                 $this->setHeaders();
40                 $this->outputHeader();
41
42                 $out = $this->getOutput();
43                 $request = $this->getRequest();
44
45                 $out->addModules( '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
78         /**
79          * Returns boolean whether the user can modify the data.
80          * @param $out OutputPage|bool If $wgOut object given, it adds the respective error message.
81          * @throws PermissionsError|ReadOnlyError
82          * @return bool
83          */
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
107         /**
108          * @param $action string
109          */
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' ) {
125                         $dbr = wfGetDB( DB_SLAVE );
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 ] );
250                                 Interwiki::invalidateCache( $prefix );
251                         }
252                         break;
253                 /** @noinspection PhpMissingBreakStatementInspection */
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 ] );
296                                 Interwiki::invalidateCache( $prefix );
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_SLAVE, [], $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 }
499
500 /**
501  * Needed to pass the URL as a raw parameter, because it contains $1
502  */
503 class InterwikiLogFormatter extends LogFormatter {
504         /**
505          * @return array
506          */
507         protected function getMessageParameters() {
508                 $params = parent::getMessageParameters();
509                 if ( isset( $params[4] ) ) {
510                         $params[4] = Message::rawParam( htmlspecialchars( $params[4] ) );
511                 }
512                 return $params;
513         }
514 }