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