MediaWiki  master
SpecialUndelete.php
Go to the documentation of this file.
1 <?php
28 
36  private $mAction;
37  private $mTarget;
38  private $mTimestamp;
39  private $mRestore;
40  private $mRevdel;
41  private $mInvert;
42  private $mFilename;
44  private $mAllowed;
45  private $mCanView;
46  private $mComment;
47  private $mToken;
49  private $mPreview;
51  private $mDiff;
53  private $mDiffOnly;
55  private $mUnsuppress;
57  private $mFileVersions;
58 
60  private $mTargetObj;
64  private $mSearchPrefix;
65 
66  function __construct() {
67  parent::__construct( 'Undelete', 'deletedhistory' );
68  }
69 
70  public function doesWrites() {
71  return true;
72  }
73 
74  function loadRequest( $par ) {
75  $request = $this->getRequest();
76  $user = $this->getUser();
77 
78  $this->mAction = $request->getVal( 'action' );
79  if ( $par !== null && $par !== '' ) {
80  $this->mTarget = $par;
81  } else {
82  $this->mTarget = $request->getVal( 'target' );
83  }
84 
85  $this->mTargetObj = null;
86 
87  if ( $this->mTarget !== null && $this->mTarget !== '' ) {
88  $this->mTargetObj = Title::newFromText( $this->mTarget );
89  }
90 
91  $this->mSearchPrefix = $request->getText( 'prefix' );
92  $time = $request->getVal( 'timestamp' );
93  $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
94  $this->mFilename = $request->getVal( 'file' );
95 
96  $posted = $request->wasPosted() &&
97  $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
98  $this->mRestore = $request->getCheck( 'restore' ) && $posted;
99  $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
100  $this->mInvert = $request->getCheck( 'invert' ) && $posted;
101  $this->mPreview = $request->getCheck( 'preview' ) && $posted;
102  $this->mDiff = $request->getCheck( 'diff' );
103  $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
104  $this->mComment = $request->getText( 'wpComment' );
105  $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && MediaWikiServices::getInstance()
106  ->getPermissionManager()
107  ->userHasRight( $user, 'suppressrevision' );
108  $this->mToken = $request->getVal( 'token' );
109 
110  $block = $user->getBlock();
111  if ( $this->isAllowed( 'undelete' ) && !( $block && $block->isSitewide() ) ) {
112  $this->mAllowed = true; // user can restore
113  $this->mCanView = true; // user can view content
114  } elseif ( $this->isAllowed( 'deletedtext' ) ) {
115  $this->mAllowed = false; // user cannot restore
116  $this->mCanView = true; // user can view content
117  $this->mRestore = false;
118  } else { // user can only view the list of revisions
119  $this->mAllowed = false;
120  $this->mCanView = false;
121  $this->mTimestamp = '';
122  $this->mRestore = false;
123  }
124 
125  if ( $this->mRestore || $this->mInvert ) {
126  $timestamps = [];
127  $this->mFileVersions = [];
128  foreach ( $request->getValues() as $key => $val ) {
129  $matches = [];
130  if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
131  array_push( $timestamps, $matches[1] );
132  }
133 
134  if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
135  $this->mFileVersions[] = intval( $matches[1] );
136  }
137  }
138  rsort( $timestamps );
139  $this->mTargetTimestamp = $timestamps;
140  }
141  }
142 
151  protected function isAllowed( $permission, User $user = null ) {
152  $user = $user ?: $this->getUser();
153  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
154 
155  if ( $this->mTargetObj !== null ) {
156  return $permissionManager->userCan( $permission, $user, $this->mTargetObj );
157  } else {
158  return $permissionManager->userHasRight( $user, $permission );
159  }
160  }
161 
162  function userCanExecute( User $user ) {
163  return $this->isAllowed( $this->mRestriction, $user );
164  }
165 
166  function execute( $par ) {
167  $this->useTransactionalTimeLimit();
168 
169  $user = $this->getUser();
170 
171  $this->setHeaders();
172  $this->outputHeader();
173  $this->addHelpLink( 'Help:Deletion_and_undeletion' );
174 
175  $this->loadRequest( $par );
176  $this->checkPermissions(); // Needs to be after mTargetObj is set
177 
178  $out = $this->getOutput();
179 
180  if ( is_null( $this->mTargetObj ) ) {
181  $out->addWikiMsg( 'undelete-header' );
182 
183  # Not all users can just browse every deleted page from the list
184  if ( MediaWikiServices::getInstance()
186  ->userHasRight( $user, 'browsearchive' )
187  ) {
188  $this->showSearchForm();
189  }
190 
191  return;
192  }
193 
194  $this->addHelpLink( 'Help:Undelete' );
195  if ( $this->mAllowed ) {
196  $out->setPageTitle( $this->msg( 'undeletepage' ) );
197  } else {
198  $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
199  }
200 
201  $this->getSkin()->setRelevantTitle( $this->mTargetObj );
202 
203  if ( $this->mTimestamp !== '' ) {
204  $this->showRevision( $this->mTimestamp );
205  } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
206  $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
207  // Check if user is allowed to see this file
208  if ( !$file->exists() ) {
209  $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
210  } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
211  if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
212  throw new PermissionsError( 'suppressrevision' );
213  } else {
214  throw new PermissionsError( 'deletedtext' );
215  }
216  } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
217  $this->showFileConfirmationForm( $this->mFilename );
218  } else {
219  $this->showFile( $this->mFilename );
220  }
221  } elseif ( $this->mAction === 'submit' ) {
222  if ( $this->mRestore ) {
223  $this->undelete();
224  } elseif ( $this->mRevdel ) {
225  $this->redirectToRevDel();
226  }
227 
228  } else {
229  $this->showHistory();
230  }
231  }
232 
237  private function redirectToRevDel() {
238  $archive = new PageArchive( $this->mTargetObj );
239 
240  $revisions = [];
241 
242  foreach ( $this->getRequest()->getValues() as $key => $val ) {
243  $matches = [];
244  if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
245  $revisions[$archive->getRevision( $matches[1] )->getId()] = 1;
246  }
247  }
248 
249  $query = [
250  'type' => 'revision',
251  'ids' => $revisions,
252  'target' => $this->mTargetObj->getPrefixedText()
253  ];
254  $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
255  $this->getOutput()->redirect( $url );
256  }
257 
258  function showSearchForm() {
259  $out = $this->getOutput();
260  $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
261  $fuzzySearch = $this->getRequest()->getVal( 'fuzzy', true );
262 
263  $out->enableOOUI();
264 
265  $fields = [];
266  $fields[] = new OOUI\ActionFieldLayout(
267  new OOUI\TextInputWidget( [
268  'name' => 'prefix',
269  'inputId' => 'prefix',
270  'infusable' => true,
271  'value' => $this->mSearchPrefix,
272  'autofocus' => true,
273  ] ),
274  new OOUI\ButtonInputWidget( [
275  'label' => $this->msg( 'undelete-search-submit' )->text(),
276  'flags' => [ 'primary', 'progressive' ],
277  'inputId' => 'searchUndelete',
278  'type' => 'submit',
279  ] ),
280  [
281  'label' => new OOUI\HtmlSnippet(
282  $this->msg(
283  $fuzzySearch ? 'undelete-search-full' : 'undelete-search-prefix'
284  )->parse()
285  ),
286  'align' => 'left',
287  ]
288  );
289 
290  $fieldset = new OOUI\FieldsetLayout( [
291  'label' => $this->msg( 'undelete-search-box' )->text(),
292  'items' => $fields,
293  ] );
294 
295  $form = new OOUI\FormLayout( [
296  'method' => 'get',
297  'action' => wfScript(),
298  ] );
299 
300  $form->appendContent(
301  $fieldset,
302  new OOUI\HtmlSnippet(
303  Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
304  Html::hidden( 'fuzzy', $fuzzySearch )
305  )
306  );
307 
308  $out->addHTML(
309  new OOUI\PanelLayout( [
310  'expanded' => false,
311  'padded' => true,
312  'framed' => true,
313  'content' => $form,
314  ] )
315  );
316 
317  # List undeletable articles
318  if ( $this->mSearchPrefix ) {
319  // For now, we enable search engine match only when specifically asked to
320  // by using fuzzy=1 parameter.
321  if ( $fuzzySearch ) {
322  $result = PageArchive::listPagesBySearch( $this->mSearchPrefix );
323  } else {
324  $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
325  }
326  $this->showList( $result );
327  }
328  }
329 
336  private function showList( $result ) {
337  $out = $this->getOutput();
338 
339  if ( $result->numRows() == 0 ) {
340  $out->addWikiMsg( 'undelete-no-results' );
341 
342  return false;
343  }
344 
345  $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
346 
347  $linkRenderer = $this->getLinkRenderer();
348  $undelete = $this->getPageTitle();
349  $out->addHTML( "<ul id='undeleteResultsList'>\n" );
350  foreach ( $result as $row ) {
351  $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
352  if ( $title !== null ) {
353  $item = $linkRenderer->makeKnownLink(
354  $undelete,
355  $title->getPrefixedText(),
356  [],
357  [ 'target' => $title->getPrefixedText() ]
358  );
359  } else {
360  // The title is no longer valid, show as text
361  $item = Html::element(
362  'span',
363  [ 'class' => 'mw-invalidtitle' ],
365  $this->getContext(),
366  $row->ar_namespace,
367  $row->ar_title
368  )
369  );
370  }
371  $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
372  $out->addHTML(
374  'li',
375  [ 'class' => 'undeleteResult' ],
376  "{$item} ({$revs})"
377  )
378  );
379  }
380  $result->free();
381  $out->addHTML( "</ul>\n" );
382 
383  return true;
384  }
385 
386  private function showRevision( $timestamp ) {
387  if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
388  return;
389  }
390 
391  $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
392  if ( !Hooks::run( 'UndeleteForm::showRevision', [ &$archive, $this->mTargetObj ] ) ) {
393  return;
394  }
395  $rev = $archive->getRevision( $timestamp );
396 
397  $out = $this->getOutput();
398  $user = $this->getUser();
399 
400  if ( !$rev ) {
401  $out->addWikiMsg( 'undeleterevision-missing' );
402 
403  return;
404  }
405 
406  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
407  if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
408  $out->wrapWikiMsg(
409  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
410  $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
411  'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
412  );
413 
414  return;
415  }
416 
417  $out->wrapWikiMsg(
418  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
419  $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
420  'rev-suppressed-text-view' : 'rev-deleted-text-view'
421  );
422  $out->addHTML( '<br />' );
423  // and we are allowed to see...
424  }
425 
426  if ( $this->mDiff ) {
427  $previousRev = $archive->getPreviousRevision( $timestamp );
428  if ( $previousRev ) {
429  $this->showDiff( $previousRev, $rev );
430  if ( $this->mDiffOnly ) {
431  return;
432  }
433 
434  $out->addHTML( '<hr />' );
435  } else {
436  $out->addWikiMsg( 'undelete-nodiff' );
437  }
438  }
439 
440  $link = $this->getLinkRenderer()->makeKnownLink(
441  $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
442  $this->mTargetObj->getPrefixedText()
443  );
444 
445  $lang = $this->getLanguage();
446 
447  // date and time are separate parameters to facilitate localisation.
448  // $time is kept for backward compat reasons.
449  $time = $lang->userTimeAndDate( $timestamp, $user );
450  $d = $lang->userDate( $timestamp, $user );
451  $t = $lang->userTime( $timestamp, $user );
452  $userLink = Linker::revUserTools( $rev );
453 
454  $content = $rev->getContent( RevisionRecord::FOR_THIS_USER, $user );
455 
456  // TODO: MCR: this will have to become something like $hasTextSlots and $hasNonTextSlots
457  $isText = ( $content instanceof TextContent );
458 
459  if ( $this->mPreview || $isText ) {
460  $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
461  } else {
462  $openDiv = '<div id="mw-undelete-revision">';
463  }
464  $out->addHTML( $openDiv );
465 
466  // Revision delete links
467  if ( !$this->mDiff ) {
468  $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
469  if ( $revdel ) {
470  $out->addHTML( "$revdel " );
471  }
472  }
473 
474  $out->addWikiMsg(
475  'undelete-revision',
476  Message::rawParam( $link ), $time,
477  Message::rawParam( $userLink ), $d, $t
478  );
479  $out->addHTML( '</div>' );
480 
481  if ( !Hooks::run( 'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) {
482  return;
483  }
484 
485  if ( $this->mPreview || !$isText ) {
486  // NOTE: non-text content has no source view, so always use rendered preview
487 
488  $popts = $out->parserOptions();
489  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
490 
491  $rendered = $renderer->getRenderedRevision(
492  $rev->getRevisionRecord(),
493  $popts,
494  $user,
495  [ 'audience' => RevisionRecord::FOR_THIS_USER ]
496  );
497 
498  // Fail hard if the audience check fails, since we already checked
499  // at the beginning of this method.
500  $pout = $rendered->getRevisionParserOutput();
501 
502  $out->addParserOutput( $pout, [
503  'enableSectionEditLinks' => false,
504  ] );
505  }
506 
507  $out->enableOOUI();
508  $buttonFields = [];
509 
510  if ( $isText ) {
511  '@phan-var TextContent $content';
512  // TODO: MCR: make this work for multiple slots
513  // source view for textual content
514  $sourceView = Xml::element( 'textarea', [
515  'readonly' => 'readonly',
516  'cols' => 80,
517  'rows' => 25
518  ], $content->getText() . "\n" );
519 
520  $buttonFields[] = new OOUI\ButtonInputWidget( [
521  'type' => 'submit',
522  'name' => 'preview',
523  'label' => $this->msg( 'showpreview' )->text()
524  ] );
525  } else {
526  $sourceView = '';
527  }
528 
529  $buttonFields[] = new OOUI\ButtonInputWidget( [
530  'name' => 'diff',
531  'type' => 'submit',
532  'label' => $this->msg( 'showdiff' )->text()
533  ] );
534 
535  $out->addHTML(
536  $sourceView .
537  Xml::openElement( 'div', [
538  'style' => 'clear: both' ] ) .
539  Xml::openElement( 'form', [
540  'method' => 'post',
541  'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
542  Xml::element( 'input', [
543  'type' => 'hidden',
544  'name' => 'target',
545  'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
546  Xml::element( 'input', [
547  'type' => 'hidden',
548  'name' => 'timestamp',
549  'value' => $timestamp ] ) .
550  Xml::element( 'input', [
551  'type' => 'hidden',
552  'name' => 'wpEditToken',
553  'value' => $user->getEditToken() ] ) .
554  new OOUI\FieldLayout(
555  new OOUI\Widget( [
556  'content' => new OOUI\HorizontalLayout( [
557  'items' => $buttonFields
558  ] )
559  ] )
560  ) .
561  Xml::closeElement( 'form' ) .
562  Xml::closeElement( 'div' )
563  );
564  }
565 
573  function showDiff( $previousRev, $currentRev ) {
574  $diffContext = clone $this->getContext();
575  $diffContext->setTitle( $currentRev->getTitle() );
576  $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
577 
578  $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
579  $diffEngine->setRevisions( $previousRev->getRevisionRecord(), $currentRev->getRevisionRecord() );
580  $diffEngine->showDiffStyle();
581  $formattedDiff = $diffEngine->getDiff(
582  $this->diffHeader( $previousRev, 'o' ),
583  $this->diffHeader( $currentRev, 'n' )
584  );
585 
586  $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
587  }
588 
594  private function diffHeader( $rev, $prefix ) {
595  $isDeleted = !( $rev->getId() && $rev->getTitle() );
596  if ( $isDeleted ) {
598  $targetPage = $this->getPageTitle();
599  $targetQuery = [
600  'target' => $this->mTargetObj->getPrefixedText(),
601  'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
602  ];
603  } else {
605  $targetPage = $rev->getTitle();
606  $targetQuery = [ 'oldid' => $rev->getId() ];
607  }
608 
609  // Add show/hide deletion links if available
610  $user = $this->getUser();
611  $lang = $this->getLanguage();
612  $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
613 
614  if ( $rdel ) {
615  $rdel = " $rdel";
616  }
617 
618  $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
619 
620  $tagIds = wfGetDB( DB_REPLICA )->selectFieldValues(
621  'change_tag',
622  'ct_tag_id',
623  [ 'ct_rev_id' => $rev->getId() ],
624  __METHOD__
625  );
626  $tags = [];
627  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
628  foreach ( $tagIds as $tagId ) {
629  try {
630  $tags[] = $changeTagDefStore->getName( (int)$tagId );
631  } catch ( NameTableAccessException $exception ) {
632  continue;
633  }
634  }
635  $tags = implode( ',', $tags );
636  $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
637 
638  // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
639  // and partially #showDiffPage, but worse
640  return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
641  $this->getLinkRenderer()->makeLink(
642  $targetPage,
643  $this->msg(
644  'revisionasof',
645  $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
646  $lang->userDate( $rev->getTimestamp(), $user ),
647  $lang->userTime( $rev->getTimestamp(), $user )
648  )->text(),
649  [],
650  $targetQuery
651  ) .
652  '</strong></div>' .
653  '<div id="mw-diff-' . $prefix . 'title2">' .
654  Linker::revUserTools( $rev ) . '<br />' .
655  '</div>' .
656  '<div id="mw-diff-' . $prefix . 'title3">' .
657  $minor . Linker::revComment( $rev ) . $rdel . '<br />' .
658  '</div>' .
659  '<div id="mw-diff-' . $prefix . 'title5">' .
660  $tagSummary[0] . '<br />' .
661  '</div>';
662  }
663 
668  private function showFileConfirmationForm( $key ) {
669  $out = $this->getOutput();
670  $lang = $this->getLanguage();
671  $user = $this->getUser();
672  $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
673  $out->addWikiMsg( 'undelete-show-file-confirm',
674  $this->mTargetObj->getText(),
675  $lang->userDate( $file->getTimestamp(), $user ),
676  $lang->userTime( $file->getTimestamp(), $user ) );
677  $out->addHTML(
678  Xml::openElement( 'form', [
679  'method' => 'POST',
680  'action' => $this->getPageTitle()->getLocalURL( [
681  'target' => $this->mTarget,
682  'file' => $key,
683  'token' => $user->getEditToken( $key ),
684  ] ),
685  ]
686  ) .
687  Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
688  '</form>'
689  );
690  }
691 
696  private function showFile( $key ) {
697  $this->getOutput()->disable();
698 
699  # We mustn't allow the output to be CDN cached, otherwise
700  # if an admin previews a deleted image, and it's cached, then
701  # a user without appropriate permissions can toddle off and
702  # nab the image, and CDN will serve it
703  $response = $this->getRequest()->response();
704  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
705  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
706  $response->header( 'Pragma: no-cache' );
707 
708  $repo = RepoGroup::singleton()->getLocalRepo();
709  $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
710  $repo->streamFileWithStatus( $path );
711  }
712 
713  protected function showHistory() {
714  $this->checkReadOnly();
715 
716  $out = $this->getOutput();
717  if ( $this->mAllowed ) {
718  $out->addModules( 'mediawiki.special.undelete' );
719  }
720  $out->wrapWikiMsg(
721  "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
722  [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
723  );
724 
725  $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
726  Hooks::run( 'UndeleteForm::showHistory', [ &$archive, $this->mTargetObj ] );
727 
728  $out->addHTML( '<div class="mw-undelete-history">' );
729  if ( $this->mAllowed ) {
730  $out->addWikiMsg( 'undeletehistory' );
731  $out->addWikiMsg( 'undeleterevdel' );
732  } else {
733  $out->addWikiMsg( 'undeletehistorynoadmin' );
734  }
735  $out->addHTML( '</div>' );
736 
737  # List all stored revisions
738  $revisions = $archive->listRevisions();
739  $files = $archive->listFiles();
740 
741  $haveRevisions = $revisions && $revisions->numRows() > 0;
742  $haveFiles = $files && $files->numRows() > 0;
743 
744  # Batch existence check on user and talk pages
745  if ( $haveRevisions ) {
746  $batch = new LinkBatch();
747  foreach ( $revisions as $row ) {
748  $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
749  $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
750  }
751  $batch->execute();
752  $revisions->seek( 0 );
753  }
754  if ( $haveFiles ) {
755  $batch = new LinkBatch();
756  foreach ( $files as $row ) {
757  $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
758  $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
759  }
760  $batch->execute();
761  $files->seek( 0 );
762  }
763 
764  if ( $this->mAllowed ) {
765  $out->enableOOUI();
766 
767  $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
768  # Start the form here
769  $form = new OOUI\FormLayout( [
770  'method' => 'post',
771  'action' => $action,
772  'id' => 'undelete',
773  ] );
774  }
775 
776  # Show relevant lines from the deletion log:
777  $deleteLogPage = new LogPage( 'delete' );
778  $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
779  LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
780  # Show relevant lines from the suppression log:
781  $suppressLogPage = new LogPage( 'suppress' );
782  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
783  if ( $permissionManager->userHasRight( $this->getUser(), 'suppressionlog' ) ) {
784  $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
785  LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
786  }
787 
788  if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
789  $fields = [];
790  $fields[] = new OOUI\Layout( [
791  'content' => new OOUI\HtmlSnippet( $this->msg( 'undeleteextrahelp' )->parseAsBlock() )
792  ] );
793 
794  $fields[] = new OOUI\FieldLayout(
795  new OOUI\TextInputWidget( [
796  'name' => 'wpComment',
797  'inputId' => 'wpComment',
798  'infusable' => true,
799  'value' => $this->mComment,
800  'autofocus' => true,
801  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
802  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
803  // Unicode codepoints.
805  ] ),
806  [
807  'label' => $this->msg( 'undeletecomment' )->text(),
808  'align' => 'top',
809  ]
810  );
811 
812  $fields[] = new OOUI\FieldLayout(
813  new OOUI\Widget( [
814  'content' => new OOUI\HorizontalLayout( [
815  'items' => [
816  new OOUI\ButtonInputWidget( [
817  'name' => 'restore',
818  'inputId' => 'mw-undelete-submit',
819  'value' => '1',
820  'label' => $this->msg( 'undeletebtn' )->text(),
821  'flags' => [ 'primary', 'progressive' ],
822  'type' => 'submit',
823  ] ),
824  new OOUI\ButtonInputWidget( [
825  'name' => 'invert',
826  'inputId' => 'mw-undelete-invert',
827  'value' => '1',
828  'label' => $this->msg( 'undeleteinvert' )->text()
829  ] ),
830  ]
831  ] )
832  ] )
833  );
834 
835  if ( $permissionManager->userHasRight( $this->getUser(), 'suppressrevision' ) ) {
836  $fields[] = new OOUI\FieldLayout(
837  new OOUI\CheckboxInputWidget( [
838  'name' => 'wpUnsuppress',
839  'inputId' => 'mw-undelete-unsuppress',
840  'value' => '1',
841  ] ),
842  [
843  'label' => $this->msg( 'revdelete-unsuppress' )->text(),
844  'align' => 'inline',
845  ]
846  );
847  }
848 
849  $fieldset = new OOUI\FieldsetLayout( [
850  'label' => $this->msg( 'undelete-fieldset-title' )->text(),
851  'id' => 'mw-undelete-table',
852  'items' => $fields,
853  ] );
854 
855  $form->appendContent(
856  new OOUI\PanelLayout( [
857  'expanded' => false,
858  'padded' => true,
859  'framed' => true,
860  'content' => $fieldset,
861  ] ),
862  new OOUI\HtmlSnippet(
863  Html::hidden( 'target', $this->mTarget ) .
864  Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
865  )
866  );
867  }
868 
869  $history = '';
870  $history .= Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n";
871 
872  if ( $haveRevisions ) {
873  # Show the page's stored (deleted) history
874 
875  if ( $permissionManager->userHasRight( $this->getUser(), 'deleterevision' ) ) {
876  $history .= Html::element(
877  'button',
878  [
879  'name' => 'revdel',
880  'type' => 'submit',
881  'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
882  ],
883  $this->msg( 'showhideselectedversions' )->text()
884  ) . "\n";
885  }
886 
887  $history .= '<ul class="mw-undelete-revlist">';
888  $remaining = $revisions->numRows();
889  $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
890 
891  foreach ( $revisions as $row ) {
892  $remaining--;
893  $history .= $this->formatRevisionRow( $row, $earliestLiveTime, $remaining );
894  }
895  $revisions->free();
896  $history .= '</ul>';
897  } else {
898  $out->addWikiMsg( 'nohistory' );
899  }
900 
901  if ( $haveFiles ) {
902  $history .= Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n";
903  $history .= '<ul class="mw-undelete-revlist">';
904  foreach ( $files as $row ) {
905  $history .= $this->formatFileRow( $row );
906  }
907  $files->free();
908  $history .= '</ul>';
909  }
910 
911  if ( $this->mAllowed ) {
912  # Slip in the hidden controls here
913  $misc = Html::hidden( 'target', $this->mTarget );
914  $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
915  $history .= $misc;
916 
917  $form->appendContent( new OOUI\HtmlSnippet( $history ) );
918  $out->addHTML( $form );
919  } else {
920  $out->addHTML( $history );
921  }
922 
923  return true;
924  }
925 
926  protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
927  $rev = Revision::newFromArchiveRow( $row,
928  [
929  'title' => $this->mTargetObj
930  ] );
931 
932  $revTextSize = '';
933  $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
934  // Build checkboxen...
935  if ( $this->mAllowed ) {
936  if ( $this->mInvert ) {
937  if ( in_array( $ts, $this->mTargetTimestamp ) ) {
938  $checkBox = Xml::check( "ts$ts" );
939  } else {
940  $checkBox = Xml::check( "ts$ts", true );
941  }
942  } else {
943  $checkBox = Xml::check( "ts$ts" );
944  }
945  } else {
946  $checkBox = '';
947  }
948 
949  // Build page & diff links...
950  $user = $this->getUser();
951  if ( $this->mCanView ) {
952  $titleObj = $this->getPageTitle();
953  # Last link
954  if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getUser() ) ) {
955  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
956  $last = $this->msg( 'diff' )->escaped();
957  } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
958  $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
959  $last = $this->getLinkRenderer()->makeKnownLink(
960  $titleObj,
961  $this->msg( 'diff' )->text(),
962  [],
963  [
964  'target' => $this->mTargetObj->getPrefixedText(),
965  'timestamp' => $ts,
966  'diff' => 'prev'
967  ]
968  );
969  } else {
970  $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
971  $last = $this->msg( 'diff' )->escaped();
972  }
973  } else {
974  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
975  $last = $this->msg( 'diff' )->escaped();
976  }
977 
978  // User links
979  $userLink = Linker::revUserTools( $rev );
980 
981  // Minor edit
982  $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
983 
984  // Revision text size
985  $size = $row->ar_len;
986  if ( !is_null( $size ) ) {
987  $revTextSize = Linker::formatRevisionSize( $size );
988  }
989 
990  // Edit summary
991  $comment = Linker::revComment( $rev );
992 
993  // Tags
994  $attribs = [];
995  list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
996  $row->ts_tags,
997  'deletedhistory',
998  $this->getContext()
999  );
1000  if ( $classes ) {
1001  $attribs['class'] = implode( ' ', $classes );
1002  }
1003 
1004  $revisionRow = $this->msg( 'undelete-revision-row2' )
1005  ->rawParams(
1006  $checkBox,
1007  $last,
1008  $pageLink,
1009  $userLink,
1010  $minor,
1011  $revTextSize,
1012  $comment,
1013  $tagSummary
1014  )
1015  ->escaped();
1016 
1017  return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1018  }
1019 
1020  private function formatFileRow( $row ) {
1021  $file = ArchivedFile::newFromRow( $row );
1022  $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1023  $user = $this->getUser();
1024 
1025  $checkBox = '';
1026  if ( $this->mCanView && $row->fa_storage_key ) {
1027  if ( $this->mAllowed ) {
1028  $checkBox = Xml::check( 'fileid' . $row->fa_id );
1029  }
1030  $key = urlencode( $row->fa_storage_key );
1031  $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1032  } else {
1033  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1034  }
1035  $userLink = $this->getFileUser( $file );
1036  $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1037  $bytes = $this->msg( 'parentheses' )
1038  ->plaintextParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1039  ->plain();
1040  $data = htmlspecialchars( $data . ' ' . $bytes );
1041  $comment = $this->getFileComment( $file );
1042 
1043  // Add show/hide deletion links if available
1044  $canHide = $this->isAllowed( 'deleterevision' );
1045  if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1046  if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1047  // Revision was hidden from sysops
1048  $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1049  } else {
1050  $query = [
1051  'type' => 'filearchive',
1052  'target' => $this->mTargetObj->getPrefixedDBkey(),
1053  'ids' => $row->fa_id
1054  ];
1055  $revdlink = Linker::revDeleteLink( $query,
1056  $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1057  }
1058  } else {
1059  $revdlink = '';
1060  }
1061 
1062  return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1063  }
1064 
1073  function getPageLink( $rev, $titleObj, $ts ) {
1074  $user = $this->getUser();
1075  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1076 
1077  if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
1078  return '<span class="history-deleted">' . $time . '</span>';
1079  }
1080 
1081  $link = $this->getLinkRenderer()->makeKnownLink(
1082  $titleObj,
1083  $time,
1084  [],
1085  [
1086  'target' => $this->mTargetObj->getPrefixedText(),
1087  'timestamp' => $ts
1088  ]
1089  );
1090 
1091  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1092  $link = '<span class="history-deleted">' . $link . '</span>';
1093  }
1094 
1095  return $link;
1096  }
1097 
1108  function getFileLink( $file, $titleObj, $ts, $key ) {
1109  $user = $this->getUser();
1110  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1111 
1112  if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1113  return '<span class="history-deleted">' . htmlspecialchars( $time ) . '</span>';
1114  }
1115 
1116  $link = $this->getLinkRenderer()->makeKnownLink(
1117  $titleObj,
1118  $time,
1119  [],
1120  [
1121  'target' => $this->mTargetObj->getPrefixedText(),
1122  'file' => $key,
1123  'token' => $user->getEditToken( $key )
1124  ]
1125  );
1126 
1127  if ( $file->isDeleted( File::DELETED_FILE ) ) {
1128  $link = '<span class="history-deleted">' . $link . '</span>';
1129  }
1130 
1131  return $link;
1132  }
1133 
1140  function getFileUser( $file ) {
1141  if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
1142  return '<span class="history-deleted">' .
1143  $this->msg( 'rev-deleted-user' )->escaped() .
1144  '</span>';
1145  }
1146 
1147  $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
1148  Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1149 
1150  if ( $file->isDeleted( File::DELETED_USER ) ) {
1151  $link = '<span class="history-deleted">' . $link . '</span>';
1152  }
1153 
1154  return $link;
1155  }
1156 
1163  function getFileComment( $file ) {
1164  if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
1165  return '<span class="history-deleted"><span class="comment">' .
1166  $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
1167  }
1168 
1169  $link = Linker::commentBlock( $file->getRawDescription() );
1170 
1171  if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1172  $link = '<span class="history-deleted">' . $link . '</span>';
1173  }
1174 
1175  return $link;
1176  }
1177 
1178  function undelete() {
1179  if ( $this->getConfig()->get( 'UploadMaintenance' )
1180  && $this->mTargetObj->getNamespace() == NS_FILE
1181  ) {
1182  throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1183  }
1184 
1185  $this->checkReadOnly();
1186 
1187  $out = $this->getOutput();
1188  $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1189  Hooks::run( 'UndeleteForm::undelete', [ &$archive, $this->mTargetObj ] );
1190  $ok = $archive->undelete(
1191  $this->mTargetTimestamp,
1192  $this->mComment,
1193  $this->mFileVersions,
1194  $this->mUnsuppress,
1195  $this->getUser()
1196  );
1197 
1198  if ( is_array( $ok ) ) {
1199  if ( $ok[1] ) { // Undeleted file count
1200  Hooks::run( 'FileUndeleteComplete', [
1201  $this->mTargetObj, $this->mFileVersions,
1202  $this->getUser(), $this->mComment ] );
1203  }
1204 
1205  $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
1206  $out->addWikiMsg( 'undeletedpage', Message::rawParam( $link ) );
1207  } else {
1208  $out->setPageTitle( $this->msg( 'undelete-error' ) );
1209  }
1210 
1211  // Show revision undeletion warnings and errors
1212  $status = $archive->getRevisionStatus();
1213  if ( $status && !$status->isGood() ) {
1214  $out->wrapWikiTextAsInterface(
1215  'error',
1216  '<div id="mw-error-cannotundelete">' .
1217  $status->getWikiText(
1218  'cannotundelete',
1219  'cannotundelete',
1220  $this->getLanguage()
1221  ) . '</div>'
1222  );
1223  }
1224 
1225  // Show file undeletion warnings and errors
1226  $status = $archive->getFileStatus();
1227  if ( $status && !$status->isGood() ) {
1228  $out->wrapWikiTextAsInterface(
1229  'error',
1230  $status->getWikiText(
1231  'undelete-error-short',
1232  'undelete-error-long',
1233  $this->getLanguage()
1234  )
1235  );
1236  }
1237  }
1238 
1247  public function prefixSearchSubpages( $search, $limit, $offset ) {
1248  return $this->prefixSearchString( $search, $limit, $offset );
1249  }
1250 
1251  protected function getGroupName() {
1252  return 'pagetools';
1253  }
1254 }
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
$response
static rawParam( $raw)
Definition: Message.php:1027
showList( $result)
Generic list of deleted pages.
static newFromRow( $row)
Loads a file object from the filearchive table.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:172
const DELETED_COMMENT
Definition: File.php:64
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
getFileUser( $file)
Fetch file&#39;s user id if it&#39;s available to this user.
getContext()
Gets the context this SpecialPage is executed in.
static formatRevisionSize( $size)
Definition: Linker.php:1597
showRevision( $timestamp)
Exception representing a failure to look up a row from a name table.
if(!isset( $args[0])) $lang
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
bool null $mUnsuppress
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
showFileConfirmationForm( $key)
Show a form confirming whether a tokenless user really wants to see a file.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
static listPagesBySearch( $term)
List deleted pages recorded in the archive matching the given term, using search engine archive...
Definition: PageArchive.php:79
getOutput()
Get the OutputPage being used for this instance.
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
showDiff( $previousRev, $currentRev)
Build a diff display between this and the previous either deleted or non-deleted edit.
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix. ...
const DELETED_FILE
Definition: File.php:63
Class to simplify the use of log pages.
Definition: LogPage.php:33
formatRevisionRow( $row, $earliestLiveTime, $remaining)
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
$last
getPermissionManager()
redirectToRevDel()
Convert submitted form data to format expected by RevisionDelete and redirect the request...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Class representing a row of the &#39;filearchive&#39; table.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
An error page which can definitely be safely rendered using the OutputPage.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition: Linker.php:943
static singleton()
Definition: RepoGroup.php:60
int [] null $mFileVersions
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:459
getSkin()
Shortcut to get the skin being used for this instance.
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:130
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
string $mSearchPrefix
Search prefix.
isAllowed( $permission, User $user=null)
Checks whether a user is allowed the permission for the specific title if one is set.
const NS_FILE
Definition: Defines.php:66
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
getPageLink( $rev, $titleObj, $ts)
Fetch revision text link if it&#39;s available to all users.
bool null $mDiffOnly
const DELETED_USER
Definition: File.php:65
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:898
const DELETED_RESTRICTED
Definition: File.php:66
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:612
getFileLink( $file, $titleObj, $ts, $key)
Fetch image view link if it&#39;s available to all users.
static getRevDeleteLink(User $user, Revision $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2105
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
Special page allowing users with the appropriate permissions to view and restore deleted content...
userCanExecute(User $user)
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getUser()
Shortcut to get the User executing this instance.
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:802
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2149
getConfig()
Shortcut to get main config object.
Show an error when a user tries to do something they do not have the necessary permissions for...
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:31
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
getLanguage()
Shortcut to get user&#39;s language.
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:94
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision&#39;s comment block, if the current user is allowed to view it...
Definition: Linker.php:1572
diffHeader( $rev, $prefix)
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition: Linker.php:187
const DB_REPLICA
Definition: defines.php:25
$content
Definition: router.php:78
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
showFile( $key)
Show a deleted file version requested by the visitor.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getFileComment( $file)
Fetch file upload comment if it&#39;s available to this user.
const NS_USER_TALK
Definition: Defines.php:63
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getPageTitle( $subpage=false)
Get a self-referential title object.
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:323
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it&#39;s non-empty, otherwise return empty strin...
Definition: Linker.php:1542
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2171
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1124
$matches
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319