MediaWiki  master
SpecialUndelete.php
Go to the documentation of this file.
1 <?php
40 
48  private $mAction;
49  private $mTarget;
50  private $mTimestamp;
51  private $mRestore;
52  private $mRevdel;
53  private $mInvert;
54  private $mFilename;
56  private $mTargetTimestamp = [];
57  private $mAllowed;
58  private $mCanView;
60  private $mComment = '';
61  private $mToken;
63  private $mPreview;
65  private $mDiff;
67  private $mDiffOnly;
69  private $mUnsuppress;
71  private $mFileVersions = [];
72 
74  private $mTargetObj;
78  private $mSearchPrefix;
79 
82 
84  private $revisionStore;
85 
88 
91 
94 
97 
99  private $localRepo;
100 
102  private $loadBalancer;
103 
106 
109 
112 
115 
118 
134  public function __construct(
141  RepoGroup $repoGroup,
148  ) {
149  parent::__construct( 'Undelete', 'deletedhistory' );
150  $this->permissionManager = $permissionManager;
151  $this->revisionStore = $revisionStore;
152  $this->revisionRenderer = $revisionRenderer;
153  $this->contentHandlerFactory = $contentHandlerFactory;
154  $this->changeTagDefStore = $changeTagDefStore;
155  $this->linkBatchFactory = $linkBatchFactory;
156  $this->localRepo = $repoGroup->getLocalRepo();
157  $this->loadBalancer = $loadBalancer;
158  $this->userOptionsLookup = $userOptionsLookup;
159  $this->wikiPageFactory = $wikiPageFactory;
160  $this->searchEngineFactory = $searchEngineFactory;
161  $this->undeletePageFactory = $undeletePageFactory;
162  $this->archivedRevisionLookup = $archivedRevisionLookup;
163  }
164 
165  public function doesWrites() {
166  return true;
167  }
168 
169  private function loadRequest( $par ) {
170  $request = $this->getRequest();
171  $user = $this->getUser();
172 
173  $this->mAction = $request->getRawVal( 'action' );
174  if ( $par !== null && $par !== '' ) {
175  $this->mTarget = $par;
176  } else {
177  $this->mTarget = $request->getVal( 'target' );
178  }
179 
180  $this->mTargetObj = null;
181 
182  if ( $this->mTarget !== null && $this->mTarget !== '' ) {
183  $this->mTargetObj = Title::newFromText( $this->mTarget );
184  }
185 
186  $this->mSearchPrefix = $request->getText( 'prefix' );
187  $time = $request->getVal( 'timestamp' );
188  $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
189  $this->mFilename = $request->getVal( 'file' );
190 
191  $posted = $request->wasPosted() &&
192  $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
193  $this->mRestore = $request->getCheck( 'restore' ) && $posted;
194  $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
195  $this->mInvert = $request->getCheck( 'invert' ) && $posted;
196  $this->mPreview = $request->getCheck( 'preview' ) && $posted;
197  $this->mDiff = $request->getCheck( 'diff' );
198  $this->mDiffOnly = $request->getBool( 'diffonly',
199  $this->userOptionsLookup->getOption( $this->getUser(), 'diffonly' ) );
200  $this->mComment = $request->getText( 'wpComment' );
201  $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) &&
202  $this->permissionManager->userHasRight( $user, 'suppressrevision' );
203  $this->mToken = $request->getVal( 'token' );
204 
205  if ( $this->isAllowed( 'undelete' ) ) {
206  $this->mAllowed = true; // user can restore
207  $this->mCanView = true; // user can view content
208  } elseif ( $this->isAllowed( 'deletedtext' ) ) {
209  $this->mAllowed = false; // user cannot restore
210  $this->mCanView = true; // user can view content
211  $this->mRestore = false;
212  } else { // user can only view the list of revisions
213  $this->mAllowed = false;
214  $this->mCanView = false;
215  $this->mTimestamp = '';
216  $this->mRestore = false;
217  }
218 
219  if ( $this->mRestore || $this->mInvert ) {
220  $timestamps = [];
221  $this->mFileVersions = [];
222  foreach ( $request->getValues() as $key => $val ) {
223  $matches = [];
224  if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
225  array_push( $timestamps, $matches[1] );
226  }
227 
228  if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
229  $this->mFileVersions[] = intval( $matches[1] );
230  }
231  }
232  rsort( $timestamps );
233  $this->mTargetTimestamp = $timestamps;
234  }
235  }
236 
245  protected function isAllowed( $permission, User $user = null ) {
246  $user = $user ?: $this->getUser();
247  $block = $user->getBlock();
248 
249  if ( $this->mTargetObj !== null ) {
250  return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
251  } else {
252  $hasRight = $this->permissionManager->userHasRight( $user, $permission );
253  $sitewideBlock = $block && $block->isSitewide();
254  return $permission === 'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
255  }
256  }
257 
258  public function userCanExecute( User $user ) {
259  return $this->isAllowed( $this->mRestriction, $user );
260  }
261 
265  public function checkPermissions() {
266  $user = $this->getUser();
267 
268  // First check if user has the right to use this page. If not,
269  // show a permissions error whether they are blocked or not.
270  if ( !parent::userCanExecute( $user ) ) {
271  $this->displayRestrictionError();
272  }
273 
274  // If a user has the right to use this page, but is blocked from
275  // the target, show a block error.
276  if (
277  $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
278  throw new UserBlockedError( $user->getBlock() );
279  }
280 
281  // Finally, do the comprehensive permission check via isAllowed.
282  if ( !$this->userCanExecute( $user ) ) {
283  $this->displayRestrictionError();
284  }
285  }
286 
287  public function execute( $par ) {
288  $this->useTransactionalTimeLimit();
289 
290  $user = $this->getUser();
291 
292  $this->setHeaders();
293  $this->outputHeader();
294  $this->addHelpLink( 'Help:Deletion_and_undeletion' );
295 
296  $this->loadRequest( $par );
297  $this->checkPermissions(); // Needs to be after mTargetObj is set
298 
299  $out = $this->getOutput();
300 
301  if ( $this->mTargetObj === null ) {
302  $out->addWikiMsg( 'undelete-header' );
303 
304  # Not all users can just browse every deleted page from the list
305  if ( $this->permissionManager->userHasRight( $user, 'browsearchive' ) ) {
306  $this->showSearchForm();
307  }
308 
309  return;
310  }
311 
312  $this->addHelpLink( 'Help:Undelete' );
313  if ( $this->mAllowed ) {
314  $out->setPageTitle( $this->msg( 'undeletepage' ) );
315  } else {
316  $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
317  }
318 
319  $this->getSkin()->setRelevantTitle( $this->mTargetObj );
320 
321  if ( $this->mTimestamp !== '' ) {
322  $this->showRevision( $this->mTimestamp );
323  } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
324  $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
325  // Check if user is allowed to see this file
326  if ( !$file->exists() ) {
327  $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
328  } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
329  if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
330  throw new PermissionsError( 'suppressrevision' );
331  } else {
332  throw new PermissionsError( 'deletedtext' );
333  }
334  } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
335  $this->showFileConfirmationForm( $this->mFilename );
336  } else {
337  $this->showFile( $this->mFilename );
338  }
339  } elseif ( $this->mAction === 'submit' ) {
340  if ( $this->mRestore ) {
341  $this->undelete();
342  } elseif ( $this->mRevdel ) {
343  $this->redirectToRevDel();
344  }
345 
346  } else {
347  $this->showHistory();
348  }
349  }
350 
355  private function redirectToRevDel() {
356  $revisions = [];
357 
358  foreach ( $this->getRequest()->getValues() as $key => $val ) {
359  $matches = [];
360  if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
361  $revisionRecord = $this->archivedRevisionLookup
362  ->getRevisionRecordByTimestamp( $this->mTargetObj, $matches[1] );
363  if ( $revisionRecord ) {
364  // Can return null
365  $revisions[ $revisionRecord->getId() ] = 1;
366  }
367  }
368  }
369 
370  $query = [
371  'type' => 'revision',
372  'ids' => $revisions,
373  'target' => $this->mTargetObj->getPrefixedText()
374  ];
375  $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
376  $this->getOutput()->redirect( $url );
377  }
378 
379  private function showSearchForm() {
380  $out = $this->getOutput();
381  $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
382  $fuzzySearch = $this->getRequest()->getVal( 'fuzzy', true );
383 
384  $out->enableOOUI();
385 
386  $fields = [];
387  $fields[] = new OOUI\ActionFieldLayout(
388  new OOUI\TextInputWidget( [
389  'name' => 'prefix',
390  'inputId' => 'prefix',
391  'infusable' => true,
392  'value' => $this->mSearchPrefix,
393  'autofocus' => true,
394  ] ),
395  new OOUI\ButtonInputWidget( [
396  'label' => $this->msg( 'undelete-search-submit' )->text(),
397  'flags' => [ 'primary', 'progressive' ],
398  'inputId' => 'searchUndelete',
399  'type' => 'submit',
400  ] ),
401  [
402  'label' => new OOUI\HtmlSnippet(
403  $this->msg(
404  $fuzzySearch ? 'undelete-search-full' : 'undelete-search-prefix'
405  )->parse()
406  ),
407  'align' => 'left',
408  ]
409  );
410 
411  $fieldset = new OOUI\FieldsetLayout( [
412  'label' => $this->msg( 'undelete-search-box' )->text(),
413  'items' => $fields,
414  ] );
415 
416  $form = new OOUI\FormLayout( [
417  'method' => 'get',
418  'action' => wfScript(),
419  ] );
420 
421  $form->appendContent(
422  $fieldset,
423  new OOUI\HtmlSnippet(
424  Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
425  Html::hidden( 'fuzzy', $fuzzySearch )
426  )
427  );
428 
429  $out->addHTML(
430  new OOUI\PanelLayout( [
431  'expanded' => false,
432  'padded' => true,
433  'framed' => true,
434  'content' => $form,
435  ] )
436  );
437 
438  # List undeletable articles
439  if ( $this->mSearchPrefix ) {
440  // For now, we enable search engine match only when specifically asked to
441  // by using fuzzy=1 parameter.
442  if ( $fuzzySearch ) {
443  $result = PageArchive::listPagesBySearch( $this->mSearchPrefix );
444  } else {
445  $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
446  }
447  $this->showList( $result );
448  }
449  }
450 
457  private function showList( $result ) {
458  $out = $this->getOutput();
459 
460  if ( $result->numRows() == 0 ) {
461  $out->addWikiMsg( 'undelete-no-results' );
462 
463  return false;
464  }
465 
466  $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
467 
468  $linkRenderer = $this->getLinkRenderer();
469  $undelete = $this->getPageTitle();
470  $out->addHTML( "<ul id='undeleteResultsList'>\n" );
471  foreach ( $result as $row ) {
472  $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
473  if ( $title !== null ) {
474  $item = $linkRenderer->makeKnownLink(
475  $undelete,
476  $title->getPrefixedText(),
477  [],
478  [ 'target' => $title->getPrefixedText() ]
479  );
480  } else {
481  // The title is no longer valid, show as text
482  $item = Html::element(
483  'span',
484  [ 'class' => 'mw-invalidtitle' ],
486  $this->getContext(),
487  $row->ar_namespace,
488  $row->ar_title
489  )
490  );
491  }
492  $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
493  $out->addHTML(
495  'li',
496  [ 'class' => 'undeleteResult' ],
497  "{$item} ({$revs})"
498  )
499  );
500  }
501  $result->free();
502  $out->addHTML( "</ul>\n" );
503 
504  return true;
505  }
506 
507  private function showRevision( $timestamp ) {
508  if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
509  return;
510  }
511 
512  $archive = new PageArchive( $this->mTargetObj );
513  // FIXME: This hook must be deprecated, passing PageArchive by ref is awful.
514  if ( !$this->getHookRunner()->onUndeleteForm__showRevision(
515  $archive, $this->mTargetObj )
516  ) {
517  return;
518  }
519  $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
520 
521  $out = $this->getOutput();
522  $user = $this->getUser();
523 
524  if ( !$revRecord ) {
525  $out->addWikiMsg( 'undeleterevision-missing' );
526  return;
527  }
528 
529  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
530  // Used in wikilinks, should not contain whitespaces
531  $titleText = $this->mTargetObj->getPrefixedDBkey();
532  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
533  $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
534  ? [ 'rev-suppressed-text-permission', $titleText ]
535  : [ 'rev-deleted-text-permission', $titleText ];
536  $out->addHtml(
538  $this->msg( $msg[0], $msg[1] )->parse(),
539  'plainlinks'
540  )
541  );
542  return;
543  }
544 
545  $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
546  ? [ 'rev-suppressed-text-view', $titleText ]
547  : [ 'rev-deleted-text-view', $titleText ];
548  $out->addHtml(
550  $this->msg( $msg[0], $msg[1] )->parse(),
551  'plainlinks'
552  )
553  );
554  // and we are allowed to see...
555  }
556 
557  if ( $this->mDiff ) {
558  $previousRevRecord = $this->archivedRevisionLookup
559  ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
560  if ( $previousRevRecord ) {
561  $this->showDiff( $previousRevRecord, $revRecord );
562  if ( $this->mDiffOnly ) {
563  return;
564  }
565 
566  $out->addHTML( '<hr />' );
567  } else {
568  $out->addWikiMsg( 'undelete-nodiff' );
569  }
570  }
571 
572  $link = $this->getLinkRenderer()->makeKnownLink(
573  $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
574  $this->mTargetObj->getPrefixedText()
575  );
576 
577  $lang = $this->getLanguage();
578 
579  // date and time are separate parameters to facilitate localisation.
580  // $time is kept for backward compat reasons.
581  $time = $lang->userTimeAndDate( $timestamp, $user );
582  $d = $lang->userDate( $timestamp, $user );
583  $t = $lang->userTime( $timestamp, $user );
584  $userLink = Linker::revUserTools( $revRecord );
585 
586  $content = $revRecord->getContent(
587  SlotRecord::MAIN,
588  RevisionRecord::FOR_THIS_USER,
589  $user
590  );
591 
592  // TODO: MCR: this will have to become something like $hasTextSlots and $hasNonTextSlots
593  $isText = ( $content instanceof TextContent );
594 
595  $out->addHTML(
597  'div',
598  [
599  'id' => 'mw-undelete-revision',
600  'class' => $this->mPreview || $isText ? 'warningbox' : '',
601  ]
602  )
603  );
604 
605  // Revision delete links
606  if ( !$this->mDiff ) {
607  $revdel = Linker::getRevDeleteLink(
608  $user,
609  $revRecord,
610  $this->mTargetObj
611  );
612  if ( $revdel ) {
613  $out->addHTML( "$revdel " );
614  }
615  }
616 
617  $out->addWikiMsg(
618  'undelete-revision',
619  Message::rawParam( $link ), $time,
620  Message::rawParam( $userLink ), $d, $t
621  );
622  $out->addHTML( Html::closeElement( 'div' ) );
623 
624  if ( $this->mPreview || !$isText ) {
625  // NOTE: non-text content has no source view, so always use rendered preview
626 
627  $popts = $out->parserOptions();
628 
629  $rendered = $this->revisionRenderer->getRenderedRevision(
630  $revRecord,
631  $popts,
632  $user,
633  [ 'audience' => RevisionRecord::FOR_THIS_USER ]
634  );
635 
636  // Fail hard if the audience check fails, since we already checked
637  // at the beginning of this method.
638  $pout = $rendered->getRevisionParserOutput();
639 
640  $out->addParserOutput( $pout, [
641  'enableSectionEditLinks' => false,
642  ] );
643  }
644 
645  $out->enableOOUI();
646  $buttonFields = [];
647 
648  if ( $isText ) {
649  '@phan-var TextContent $content';
650  // TODO: MCR: make this work for multiple slots
651  // source view for textual content
652  $sourceView = Xml::element( 'textarea', [
653  'readonly' => 'readonly',
654  'cols' => 80,
655  'rows' => 25
656  ], $content->getText() . "\n" );
657 
658  $buttonFields[] = new OOUI\ButtonInputWidget( [
659  'type' => 'submit',
660  'name' => 'preview',
661  'label' => $this->msg( 'showpreview' )->text()
662  ] );
663  } else {
664  $sourceView = '';
665  }
666 
667  $buttonFields[] = new OOUI\ButtonInputWidget( [
668  'name' => 'diff',
669  'type' => 'submit',
670  'label' => $this->msg( 'showdiff' )->text()
671  ] );
672 
673  $out->addHTML(
674  $sourceView .
675  Xml::openElement( 'div', [
676  'style' => 'clear: both' ] ) .
677  Xml::openElement( 'form', [
678  'method' => 'post',
679  'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
680  Xml::element( 'input', [
681  'type' => 'hidden',
682  'name' => 'target',
683  'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
684  Xml::element( 'input', [
685  'type' => 'hidden',
686  'name' => 'timestamp',
687  'value' => $timestamp ] ) .
688  Xml::element( 'input', [
689  'type' => 'hidden',
690  'name' => 'wpEditToken',
691  'value' => $user->getEditToken() ] ) .
692  new OOUI\FieldLayout(
693  new OOUI\Widget( [
694  'content' => new OOUI\HorizontalLayout( [
695  'items' => $buttonFields
696  ] )
697  ] )
698  ) .
699  Xml::closeElement( 'form' ) .
700  Xml::closeElement( 'div' )
701  );
702  }
703 
711  private function showDiff(
712  RevisionRecord $previousRevRecord,
713  RevisionRecord $currentRevRecord
714  ) {
715  $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
716 
717  $diffContext = clone $this->getContext();
718  $diffContext->setTitle( $currentTitle );
719  $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
720 
721  $contentModel = $currentRevRecord->getSlot(
722  SlotRecord::MAIN,
723  RevisionRecord::RAW
724  )->getModel();
725 
726  $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
727  ->createDifferenceEngine( $diffContext );
728 
729  $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
730  $diffEngine->showDiffStyle();
731  $formattedDiff = $diffEngine->getDiff(
732  $this->diffHeader( $previousRevRecord, 'o' ),
733  $this->diffHeader( $currentRevRecord, 'n' )
734  );
735 
736  $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
737  }
738 
744  private function diffHeader( RevisionRecord $revRecord, $prefix ) {
745  $isDeleted = !( $revRecord->getId() && $revRecord->getPageAsLinkTarget() );
746  if ( $isDeleted ) {
747  // @todo FIXME: $rev->getTitle() is null for deleted revs...?
748  $targetPage = $this->getPageTitle();
749  $targetQuery = [
750  'target' => $this->mTargetObj->getPrefixedText(),
751  'timestamp' => wfTimestamp( TS_MW, $revRecord->getTimestamp() )
752  ];
753  } else {
754  // @todo FIXME: getId() may return non-zero for deleted revs...
755  $targetPage = $revRecord->getPageAsLinkTarget();
756  $targetQuery = [ 'oldid' => $revRecord->getId() ];
757  }
758 
759  // Add show/hide deletion links if available
760  $user = $this->getUser();
761  $lang = $this->getLanguage();
762  $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
763 
764  if ( $rdel ) {
765  $rdel = " $rdel";
766  }
767 
768  $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
769 
770  $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
771  $tagIds = $dbr->selectFieldValues(
772  'change_tag',
773  'ct_tag_id',
774  [ 'ct_rev_id' => $revRecord->getId() ],
775  __METHOD__
776  );
777  $tags = [];
778  foreach ( $tagIds as $tagId ) {
779  try {
780  $tags[] = $this->changeTagDefStore->getName( (int)$tagId );
781  } catch ( NameTableAccessException $exception ) {
782  continue;
783  }
784  }
785  $tags = implode( ',', $tags );
786  $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
787 
788  // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
789  // and partially #showDiffPage, but worse
790  return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
791  $this->getLinkRenderer()->makeLink(
792  $targetPage,
793  $this->msg(
794  'revisionasof',
795  $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
796  $lang->userDate( $revRecord->getTimestamp(), $user ),
797  $lang->userTime( $revRecord->getTimestamp(), $user )
798  )->text(),
799  [],
800  $targetQuery
801  ) .
802  '</strong></div>' .
803  '<div id="mw-diff-' . $prefix . 'title2">' .
804  Linker::revUserTools( $revRecord ) . '<br />' .
805  '</div>' .
806  '<div id="mw-diff-' . $prefix . 'title3">' .
807  $minor . Linker::revComment( $revRecord ) . $rdel . '<br />' .
808  '</div>' .
809  '<div id="mw-diff-' . $prefix . 'title5">' .
810  $tagSummary[0] . '<br />' .
811  '</div>';
812  }
813 
818  private function showFileConfirmationForm( $key ) {
819  $out = $this->getOutput();
820  $lang = $this->getLanguage();
821  $user = $this->getUser();
822  $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
823  $out->addWikiMsg( 'undelete-show-file-confirm',
824  $this->mTargetObj->getText(),
825  $lang->userDate( $file->getTimestamp(), $user ),
826  $lang->userTime( $file->getTimestamp(), $user ) );
827  $out->addHTML(
828  Xml::openElement( 'form', [
829  'method' => 'POST',
830  'action' => $this->getPageTitle()->getLocalURL( [
831  'target' => $this->mTarget,
832  'file' => $key,
833  'token' => $user->getEditToken( $key ),
834  ] ),
835  ]
836  ) .
837  Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
838  '</form>'
839  );
840  }
841 
846  private function showFile( $key ) {
847  $this->getOutput()->disable();
848 
849  # We mustn't allow the output to be CDN cached, otherwise
850  # if an admin previews a deleted image, and it's cached, then
851  # a user without appropriate permissions can toddle off and
852  # nab the image, and CDN will serve it
853  $response = $this->getRequest()->response();
854  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
855  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
856  $response->header( 'Pragma: no-cache' );
857 
858  $path = $this->localRepo->getZonePath( 'deleted' ) . '/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
859  $this->localRepo->streamFileWithStatus( $path );
860  }
861 
862  protected function showHistory() {
863  $this->checkReadOnly();
864 
865  $out = $this->getOutput();
866  if ( $this->mAllowed ) {
867  $out->addModules( 'mediawiki.misc-authed-ooui' );
868  }
869  $out->wrapWikiMsg(
870  "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
871  [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
872  );
873 
874  $archive = new PageArchive( $this->mTargetObj );
875  // FIXME: This hook must be deprecated, passing PageArchive by ref is awful.
876  $this->getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
877 
878  $out->addHTML( Html::openElement( 'div', [ 'class' => 'mw-undelete-history' ] ) );
879  if ( $this->mAllowed ) {
880  $out->addWikiMsg( 'undeletehistory' );
881  $out->addWikiMsg( 'undeleterevdel' );
882  } else {
883  $out->addWikiMsg( 'undeletehistorynoadmin' );
884  }
885  $out->addHTML( Html::closeElement( 'div' ) );
886 
887  # List all stored revisions
888  $revisions = $this->archivedRevisionLookup->listRevisions( $this->mTargetObj );
889  $files = $archive->listFiles();
890 
891  $haveRevisions = $revisions && $revisions->numRows() > 0;
892  $haveFiles = $files && $files->numRows() > 0;
893 
894  # Batch existence check on user and talk pages
895  if ( $haveRevisions || $haveFiles ) {
896  $batch = $this->linkBatchFactory->newLinkBatch();
897  if ( $haveRevisions ) {
898  foreach ( $revisions as $row ) {
899  $batch->add( NS_USER, $row->ar_user_text );
900  $batch->add( NS_USER_TALK, $row->ar_user_text );
901  }
902  $revisions->seek( 0 );
903  }
904  if ( $haveFiles ) {
905  foreach ( $files as $row ) {
906  $batch->add( NS_USER, $row->fa_user_text );
907  $batch->add( NS_USER_TALK, $row->fa_user_text );
908  }
909  $files->seek( 0 );
910  }
911  $batch->execute();
912  }
913 
914  if ( $this->mAllowed ) {
915  $out->enableOOUI();
916 
917  $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
918  # Start the form here
919  $form = new OOUI\FormLayout( [
920  'method' => 'post',
921  'action' => $action,
922  'id' => 'undelete',
923  ] );
924  }
925 
926  # Show relevant lines from the deletion log:
927  $deleteLogPage = new LogPage( 'delete' );
928  $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
929  LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
930  # Show relevant lines from the suppression log:
931  $suppressLogPage = new LogPage( 'suppress' );
932  if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressionlog' ) ) {
933  $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
934  LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
935  }
936 
937  if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
938  $fields = [];
939  $fields[] = new OOUI\Layout( [
940  'content' => new OOUI\HtmlSnippet( $this->msg( 'undeleteextrahelp' )->parseAsBlock() )
941  ] );
942 
943  $fields[] = new OOUI\FieldLayout(
944  new OOUI\TextInputWidget( [
945  'name' => 'wpComment',
946  'inputId' => 'wpComment',
947  'infusable' => true,
948  'value' => $this->mComment,
949  'autofocus' => true,
950  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
951  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
952  // Unicode codepoints.
954  ] ),
955  [
956  'label' => $this->msg( 'undeletecomment' )->text(),
957  'align' => 'top',
958  ]
959  );
960 
961  $fields[] = new OOUI\FieldLayout(
962  new OOUI\Widget( [
963  'content' => new OOUI\HorizontalLayout( [
964  'items' => [
965  new OOUI\ButtonInputWidget( [
966  'name' => 'restore',
967  'inputId' => 'mw-undelete-submit',
968  'value' => '1',
969  'label' => $this->msg( 'undeletebtn' )->text(),
970  'flags' => [ 'primary', 'progressive' ],
971  'type' => 'submit',
972  ] ),
973  new OOUI\ButtonInputWidget( [
974  'name' => 'invert',
975  'inputId' => 'mw-undelete-invert',
976  'value' => '1',
977  'label' => $this->msg( 'undeleteinvert' )->text()
978  ] ),
979  ]
980  ] )
981  ] )
982  );
983 
984  if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressrevision' ) ) {
985  $fields[] = new OOUI\FieldLayout(
986  new OOUI\CheckboxInputWidget( [
987  'name' => 'wpUnsuppress',
988  'inputId' => 'mw-undelete-unsuppress',
989  'value' => '1',
990  ] ),
991  [
992  'label' => $this->msg( 'revdelete-unsuppress' )->text(),
993  'align' => 'inline',
994  ]
995  );
996  }
997 
998  $fieldset = new OOUI\FieldsetLayout( [
999  'label' => $this->msg( 'undelete-fieldset-title' )->text(),
1000  'id' => 'mw-undelete-table',
1001  'items' => $fields,
1002  ] );
1003 
1004  $form->appendContent(
1005  new OOUI\PanelLayout( [
1006  'expanded' => false,
1007  'padded' => true,
1008  'framed' => true,
1009  'content' => $fieldset,
1010  ] ),
1011  new OOUI\HtmlSnippet(
1012  Html::hidden( 'target', $this->mTarget ) .
1013  Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
1014  )
1015  );
1016  }
1017 
1018  $history = '';
1019  $history .= Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n";
1020 
1021  if ( $haveRevisions ) {
1022  # Show the page's stored (deleted) history
1023 
1024  if ( $this->permissionManager->userHasRight( $this->getUser(), 'deleterevision' ) ) {
1025  $history .= Html::element(
1026  'button',
1027  [
1028  'name' => 'revdel',
1029  'type' => 'submit',
1030  'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
1031  ],
1032  $this->msg( 'showhideselectedversions' )->text()
1033  ) . "\n";
1034  }
1035 
1036  $history .= Html::openElement( 'ul', [ 'class' => 'mw-undelete-revlist' ] );
1037  $remaining = $revisions->numRows();
1038  $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1039  $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() : null;
1040 
1041  foreach ( $revisions as $row ) {
1042  $remaining--;
1043  $history .= $this->formatRevisionRow( $row, $earliestLiveTime, $remaining );
1044  }
1045  $revisions->free();
1046  $history .= Html::closeElement( 'ul' );
1047  } else {
1048  $out->addWikiMsg( 'nohistory' );
1049  }
1050 
1051  if ( $haveFiles ) {
1052  $history .= Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n";
1053  $history .= Html::openElement( 'ul', [ 'class' => 'mw-undelete-revlist' ] );
1054  foreach ( $files as $row ) {
1055  $history .= $this->formatFileRow( $row );
1056  }
1057  $files->free();
1058  $history .= Html::closeElement( 'ul' );
1059  }
1060 
1061  if ( $this->mAllowed ) {
1062  # Slip in the hidden controls here
1063  $misc = Html::hidden( 'target', $this->mTarget );
1064  $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
1065  $history .= $misc;
1066 
1067  $form->appendContent( new OOUI\HtmlSnippet( $history ) );
1068  $out->addHTML( $form );
1069  } else {
1070  $out->addHTML( $history );
1071  }
1072 
1073  return true;
1074  }
1075 
1076  protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
1077  $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1078  $row,
1079  RevisionStore::READ_NORMAL,
1080  $this->mTargetObj
1081  );
1082 
1083  $revTextSize = '';
1084  $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1085  // Build checkboxen...
1086  if ( $this->mAllowed ) {
1087  if ( $this->mInvert ) {
1088  if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1089  $checkBox = Xml::check( "ts$ts" );
1090  } else {
1091  $checkBox = Xml::check( "ts$ts", true );
1092  }
1093  } else {
1094  $checkBox = Xml::check( "ts$ts" );
1095  }
1096  } else {
1097  $checkBox = '';
1098  }
1099 
1100  // Build page & diff links...
1101  $user = $this->getUser();
1102  if ( $this->mCanView ) {
1103  $titleObj = $this->getPageTitle();
1104  # Last link
1105  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1106  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1107  $last = $this->msg( 'diff' )->escaped();
1108  } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1109  $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1110  $last = $this->getLinkRenderer()->makeKnownLink(
1111  $titleObj,
1112  $this->msg( 'diff' )->text(),
1113  [],
1114  [
1115  'target' => $this->mTargetObj->getPrefixedText(),
1116  'timestamp' => $ts,
1117  'diff' => 'prev'
1118  ]
1119  );
1120  } else {
1121  $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1122  $last = $this->msg( 'diff' )->escaped();
1123  }
1124  } else {
1125  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1126  $last = $this->msg( 'diff' )->escaped();
1127  }
1128 
1129  // User links
1130  $userLink = Linker::revUserTools( $revRecord );
1131 
1132  // Minor edit
1133  $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
1134 
1135  // Revision text size
1136  $size = $row->ar_len;
1137  if ( $size !== null ) {
1138  $revTextSize = Linker::formatRevisionSize( $size );
1139  }
1140 
1141  // Edit summary
1142  $comment = Linker::revComment( $revRecord );
1143 
1144  // Tags
1145  $attribs = [];
1146  list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
1147  $row->ts_tags,
1148  'deletedhistory',
1149  $this->getContext()
1150  );
1151  if ( $classes ) {
1152  $attribs['class'] = implode( ' ', $classes );
1153  }
1154 
1155  $revisionRow = $this->msg( 'undelete-revision-row2' )
1156  ->rawParams(
1157  $checkBox,
1158  $last,
1159  $pageLink,
1160  $userLink,
1161  $minor,
1162  $revTextSize,
1163  $comment,
1164  $tagSummary
1165  )
1166  ->escaped();
1167 
1168  return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1169  }
1170 
1171  private function formatFileRow( $row ) {
1172  $file = ArchivedFile::newFromRow( $row );
1173  $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1174  $user = $this->getUser();
1175 
1176  $checkBox = '';
1177  if ( $this->mCanView && $row->fa_storage_key ) {
1178  if ( $this->mAllowed ) {
1179  $checkBox = Xml::check( 'fileid' . $row->fa_id );
1180  }
1181  $key = urlencode( $row->fa_storage_key );
1182  $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1183  } else {
1184  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1185  }
1186  $userLink = $this->getFileUser( $file );
1187  $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1188  $bytes = $this->msg( 'parentheses' )
1189  ->plaintextParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1190  ->plain();
1191  $data = htmlspecialchars( $data . ' ' . $bytes );
1192  $comment = $this->getFileComment( $file );
1193 
1194  // Add show/hide deletion links if available
1195  $canHide = $this->isAllowed( 'deleterevision' );
1196  if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1197  if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1198  // Revision was hidden from sysops
1199  $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1200  } else {
1201  $query = [
1202  'type' => 'filearchive',
1203  'target' => $this->mTargetObj->getPrefixedDBkey(),
1204  'ids' => $row->fa_id
1205  ];
1206  $revdlink = Linker::revDeleteLink( $query,
1207  $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1208  }
1209  } else {
1210  $revdlink = '';
1211  }
1212 
1213  return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1214  }
1215 
1224  private function getPageLink( RevisionRecord $revRecord, $titleObj, $ts ) {
1225  $user = $this->getUser();
1226  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1227 
1228  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1229  // TODO The condition cannot be true when the function is called
1230  // TODO use Html::element and let it handle escaping
1231  return Html::rawElement(
1232  'span',
1233  [ 'class' => 'history-deleted' ],
1234  htmlspecialchars( $time )
1235  );
1236  }
1237 
1238  $link = $this->getLinkRenderer()->makeKnownLink(
1239  $titleObj,
1240  $time,
1241  [],
1242  [
1243  'target' => $this->mTargetObj->getPrefixedText(),
1244  'timestamp' => $ts
1245  ]
1246  );
1247 
1248  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1249  $class = Linker::getRevisionDeletedClass( $revRecord );
1250  $link = '<span class="' . $class . '">' . $link . '</span>';
1251  }
1252 
1253  return $link;
1254  }
1255 
1266  private function getFileLink( $file, $titleObj, $ts, $key ) {
1267  $user = $this->getUser();
1268  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1269 
1270  if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1271  // TODO use Html::element and let it handle escaping
1272  return Html::rawElement(
1273  'span',
1274  [ 'class' => 'history-deleted' ],
1275  htmlspecialchars( $time )
1276  );
1277  }
1278 
1279  $link = $this->getLinkRenderer()->makeKnownLink(
1280  $titleObj,
1281  $time,
1282  [],
1283  [
1284  'target' => $this->mTargetObj->getPrefixedText(),
1285  'file' => $key,
1286  'token' => $user->getEditToken( $key )
1287  ]
1288  );
1289 
1290  if ( $file->isDeleted( File::DELETED_FILE ) ) {
1291  $link = '<span class="history-deleted">' . $link . '</span>';
1292  }
1293 
1294  return $link;
1295  }
1296 
1303  private function getFileUser( $file ) {
1304  $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() );
1305  if ( !$uploader ) {
1306  return Html::rawElement(
1307  'span',
1308  [ 'class' => 'history-deleted' ],
1309  $this->msg( 'rev-deleted-user' )->escaped()
1310  );
1311  }
1312 
1313  $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1314  Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1315 
1316  if ( $file->isDeleted( File::DELETED_USER ) ) {
1317  $link = Html::rawElement(
1318  'span',
1319  [ 'class' => 'history-deleted' ],
1320  $link
1321  );
1322  }
1323 
1324  return $link;
1325  }
1326 
1333  private function getFileComment( $file ) {
1334  $comment = $file->getDescription( File::FOR_THIS_USER, $this->getAuthority() );
1335  if ( ( $comment ?? '' ) === '' ) {
1336  return Html::rawElement(
1337  'span',
1338  [ 'class' => 'history-deleted' ],
1340  'span',
1341  [ 'class' => 'comment' ],
1342  $this->msg( 'rev-deleted-comment' )->escaped()
1343  )
1344  );
1345  }
1346 
1347  $link = Linker::commentBlock( $comment );
1348 
1349  if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1350  $link = Html::rawElement(
1351  'span',
1352  [ 'class' => 'history-deleted' ],
1353  $link
1354  );
1355  }
1356 
1357  return $link;
1358  }
1359 
1360  private function undelete() {
1361  if ( $this->getConfig()->get( 'UploadMaintenance' )
1362  && $this->mTargetObj->getNamespace() === NS_FILE
1363  ) {
1364  throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1365  }
1366 
1367  $this->checkReadOnly();
1368 
1369  $out = $this->getOutput();
1370  $undeletePage = $this->undeletePageFactory->newUndeletePage(
1371  $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1372  $this->getAuthority()
1373  );
1374  $status = $undeletePage
1375  ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1376  ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1377  ->setUnsuppress( $this->mUnsuppress )
1378  // TODO Should use undeleteIfAllowed instead.
1379  ->undeleteUnsafe( $this->mComment );
1380 
1381  if ( !$status->isGood() ) {
1382  $out->setPageTitle( $this->msg( 'undelete-error' ) );
1383  $out->wrapWikiTextAsInterface(
1384  'error',
1385  Status::wrap( $status )->getWikiText(
1386  'cannotundelete',
1387  'cannotundelete',
1388  $this->getLanguage()
1389  )
1390  );
1391  return;
1392  }
1393 
1394  $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
1395  $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
1396 
1397  if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1398  // TODO Should use a different message here
1399  $out->setPageTitle( $this->msg( 'undelete-error' ) );
1400  } else {
1401  if ( $status->getValue()[UndeletePage::FILES_RESTORED] !== 0 ) {
1402  $this->getHookRunner()->onFileUndeleteComplete(
1403  $this->mTargetObj, $this->mFileVersions, $this->getUser(), $this->mComment );
1404  }
1405 
1406  $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
1407  $out->addWikiMsg( 'undeletedpage', Message::rawParam( $link ) );
1408  }
1409  }
1410 
1419  public function prefixSearchSubpages( $search, $limit, $offset ) {
1420  return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
1421  }
1422 
1423  protected function getGroupName() {
1424  return 'pagetools';
1425  }
1426 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:768
SpecialUndelete\formatRevisionRow
formatRevisionRow( $row, $earliestLiveTime, $remaining)
Definition: SpecialUndelete.php:1076
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:936
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:377
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
SpecialUndelete\$mPreview
bool null $mPreview
Definition: SpecialUndelete.php:63
SpecialUndelete\$mAllowed
$mAllowed
Definition: SpecialUndelete.php:57
File\DELETED_USER
const DELETED_USER
Definition: File.php:73
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:32
Linker\revUserTools
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1333
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:31
Linker\userLink
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:1078
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:814
File\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: File.php:74
$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:1423
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:89
SpecialUndelete
Special page allowing users with the appropriate permissions to view and restore deleted content.
Definition: SpecialUndelete.php:47
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:245
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1649
SpecialUndelete\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialUndelete.php:287
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:1123
SpecialUndelete\$mAction
$mAction
Definition: SpecialUndelete.php:48
SpecialUndelete\showRevision
showRevision( $timestamp)
Definition: SpecialUndelete.php:507
SpecialUndelete\showHistory
showHistory()
Definition: SpecialUndelete.php:862
SpecialUndelete\$mDiff
bool null $mDiff
Definition: SpecialUndelete.php:65
SpecialUndelete\showList
showList( $result)
Generic list of deleted pages.
Definition: SpecialUndelete.php:457
MediaWiki\Revision\RevisionRecord\isMinor
isMinor()
MCR migration note: this replaced Revision::isMinor.
Definition: RevisionRecord.php:426
SpecialUndelete\showSearchForm
showSearchForm()
Definition: SpecialUndelete.php:379
PageArchive\listPagesByPrefix
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix.
Definition: PageArchive.php:107
SpecialPage\displayRestrictionError
displayRestrictionError()
Output an error message telling the user what access level they have to have.
Definition: SpecialPage.php:370
SpecialUndelete\checkPermissions
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.Stability: stableto override 1....
Definition: SpecialUndelete.php:265
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
SearchEngineFactory
Factory class for SearchEngine.
Definition: SearchEngineFactory.php:12
SpecialUndelete\$revisionRenderer
RevisionRenderer $revisionRenderer
Definition: SpecialUndelete.php:87
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:131
SpecialPage\getSkin
getSkin()
Shortcut to get the skin being used for this instance.
Definition: SpecialPage.php:844
SpecialPage\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: SpecialPage.php:1042
SpecialPage\getAuthority
getAuthority()
Shortcut to get the Authority executing this instance.
Definition: SpecialPage.php:834
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:32
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:854
Linker\getInvalidTitleDescription
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition: Linker.php:186
Html\warningBox
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:758
SpecialUndelete\getFileUser
getFileUser( $file)
Fetch file's user id if it's available to this user.
Definition: SpecialUndelete.php:1303
PageArchive\listPagesBySearch
static listPagesBySearch( $term)
List deleted pages recorded in the archive matching the given term, using search engine archive.
Definition: PageArchive.php:57
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:111
SpecialUndelete\$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: SpecialUndelete.php:105
SpecialUndelete\$mFilename
$mFilename
Definition: SpecialUndelete.php:54
SpecialUndelete\$mRestore
$mRestore
Definition: SpecialUndelete.php:51
$dbr
$dbr
Definition: testCompression.php:54
Page\UndeletePage
Definition: UndeletePage.php:52
SpecialPage\prefixSearchString
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
Definition: SpecialPage.php:601
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:319
SpecialUndelete\$changeTagDefStore
NameTableStore $changeTagDefStore
Definition: SpecialUndelete.php:93
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:972
SpecialUndelete\$mTimestamp
$mTimestamp
Definition: SpecialUndelete.php:50
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1119
SpecialUndelete\$mCanView
$mCanView
Definition: SpecialUndelete.php:58
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:902
File\DELETED_COMMENT
const DELETED_COMMENT
Definition: File.php:72
SpecialUndelete\redirectToRevDel
redirectToRevDel()
Convert submitted form data to format expected by RevisionDelete and redirect the request.
Definition: SpecialUndelete.php:355
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2268
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:26
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:258
SpecialUndelete\__construct
__construct(PermissionManager $permissionManager, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, IContentHandlerFactory $contentHandlerFactory, NameTableStore $changeTagDefStore, LinkBatchFactory $linkBatchFactory, RepoGroup $repoGroup, ILoadBalancer $loadBalancer, UserOptionsLookup $userOptionsLookup, WikiPageFactory $wikiPageFactory, SearchEngineFactory $searchEngineFactory, UndeletePageFactory $undeletePageFactory, ArchivedRevisionLookup $archivedRevisionLookup)
Definition: SpecialUndelete.php:134
Linker\getRevisionDeletedClass
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition: Linker.php:1313
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62
SpecialUndelete\$localRepo
LocalRepo $localRepo
Definition: SpecialUndelete.php:99
$matches
$matches
Definition: NoLocalSettings.php:24
MediaWiki\Cache\LinkBatchFactory
Definition: LinkBatchFactory.php:39
Xml\check
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:330
SpecialUndelete\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.
Definition: SpecialUndelete.php:165
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:711
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:38
SpecialUndelete\$mUnsuppress
bool null $mUnsuppress
Definition: SpecialUndelete.php:69
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:42
Page\WikiPageFactory
Definition: WikiPageFactory.php:19
ChangesList\flag
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
Definition: ChangesList.php:273
Linker\revDeleteLinkDisabled
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2201
SpecialUndelete\$mToken
$mToken
Definition: SpecialUndelete.php:61
$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:642
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:824
File\FOR_THIS_USER
const FOR_THIS_USER
Definition: File.php:88
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:597
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SpecialUndelete\$mRevdel
$mRevdel
Definition: SpecialUndelete.php:52
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:788
SpecialUndelete\diffHeader
diffHeader(RevisionRecord $revRecord, $prefix)
Definition: SpecialUndelete.php:744
Page\UndeletePageFactory
Definition: UndeletePageFactory.php:10
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:834
SpecialUndelete\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialUndelete.php:1419
MediaWiki\Revision\RevisionRenderer
The RevisionRenderer service provides access to rendered output for revisions.
Definition: RevisionRenderer.php:45
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:674
Linker\revComment
static revComment(RevisionRecord $revRecord, $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:1569
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:52
SpecialUndelete\formatFileRow
formatFileRow( $row)
Definition: SpecialUndelete.php:1171
$content
$content
Definition: router.php:76
SpecialUndelete\$mSearchPrefix
string $mSearchPrefix
Search prefix.
Definition: SpecialUndelete.php:78
SpecialUndelete\$linkBatchFactory
LinkBatchFactory $linkBatchFactory
Definition: SpecialUndelete.php:96
RepoGroup\getLocalRepo
getLocalRepo()
Get the local repository, i.e.
Definition: RepoGroup.php:341
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:1547
SpecialUndelete\$archivedRevisionLookup
ArchivedRevisionLookup $archivedRevisionLookup
Definition: SpecialUndelete.php:117
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ArchivedFile
Class representing a row of the 'filearchive' table.
Definition: ArchivedFile.php:35
MediaWiki\Revision\RevisionRecord\getPageAsLinkTarget
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
Definition: RevisionRecord.php:355
Message\rawParam
static rawParam( $raw)
Definition: Message.php:1116
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
SpecialUndelete\$mDiffOnly
bool null $mDiffOnly
Definition: SpecialUndelete.php:67
SpecialUndelete\$mTargetObj
Title null $mTargetObj
Definition: SpecialUndelete.php:74
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:133
SpecialUndelete\$mInvert
$mInvert
Definition: SpecialUndelete.php:53
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:804
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1585
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1440
MediaWiki\Storage\NameTableStore
Definition: NameTableStore.php:36
NS_USER
const NS_USER
Definition: Defines.php:66
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
SpecialUndelete\getFileComment
getFileComment( $file)
Fetch file upload comment if it's available to this user.
Definition: SpecialUndelete.php:1333
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:289
TextContent
Content object implementation for representing flat text.
Definition: TextContent.php:39
SpecialUndelete\$permissionManager
PermissionManager $permissionManager
Definition: SpecialUndelete.php:81
SpecialUndelete\$mComment
string $mComment
Definition: SpecialUndelete.php:60
SpecialUndelete\getPageLink
getPageLink(RevisionRecord $revRecord, $titleObj, $ts)
Fetch revision text link if it's available to all users.
Definition: SpecialUndelete.php:1224
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:1052
SpecialUndelete\getFileLink
getFileLink( $file, $titleObj, $ts, $key)
Fetch image view link if it's available to all users.
Definition: SpecialUndelete.php:1266
SpecialUndelete\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: SpecialUndelete.php:90
Title
Represents a title within MediaWiki.
Definition: Title.php:47
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:120
MediaWiki\Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:279
MediaWiki\Revision\RevisionRecord\isDeleted
isDeleted( $field)
MCR migration note: this replaced Revision::isDeleted.
Definition: RevisionRecord.php:437
SpecialUndelete\showFile
showFile( $key)
Show a deleted file version requested by the visitor.
Definition: SpecialUndelete.php:846
SpecialUndelete\$searchEngineFactory
SearchEngineFactory $searchEngineFactory
Definition: SpecialUndelete.php:111
$path
$path
Definition: NoLocalSettings.php:25
MediaWiki\Revision\RevisionRecord\userCan
userCan( $field, Authority $performer)
Determine if the give authority is allowed to view a particular field of this revision,...
Definition: RevisionRecord.php:509
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:255
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:213
MediaWiki\Storage\NameTableAccessException
Exception representing a failure to look up a row from a name table.
Definition: NameTableAccessException.php:33
SpecialUndelete\$mTargetTimestamp
string[] $mTargetTimestamp
Definition: SpecialUndelete.php:56
Linker\revDeleteLink
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2177
RepoGroup
Prioritized list of file repositories.
Definition: RepoGroup.php:32
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
MediaWiki\Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
Definition: RevisionRecord.php:459
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:71
SpecialPage\checkReadOnly
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
Definition: SpecialPage.php:395
$t
$t
Definition: testCompression.php:74
SpecialUndelete\$mTarget
$mTarget
Definition: SpecialUndelete.php:49
SpecialUndelete\undelete
undelete()
Definition: SpecialUndelete.php:1360
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:235
SpecialUndelete\$revisionStore
RevisionStore $revisionStore
Definition: SpecialUndelete.php:84
NS_FILE
const NS_FILE
Definition: Defines.php:70
SpecialUndelete\$mFileVersions
int[] $mFileVersions
Definition: SpecialUndelete.php:71
SpecialUndelete\showFileConfirmationForm
showFileConfirmationForm( $key)
Show a form confirming whether a tokenless user really wants to see a file.
Definition: SpecialUndelete.php:818
SpecialUndelete\$undeletePageFactory
UndeletePageFactory $undeletePageFactory
Definition: SpecialUndelete.php:114
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:67
Linker\getRevDeleteLink
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2125
MediaWiki\Revision\ArchivedRevisionLookup
Definition: ArchivedRevisionLookup.php:31
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:733
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:194
SpecialUndelete\$loadBalancer
ILoadBalancer $loadBalancer
Definition: SpecialUndelete.php:102
LocalRepo
A repository that stores files in the local filesystem and registers them in the wiki's own database.
Definition: LocalRepo.php:41
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Revision\RevisionRecord\getSlot
getSlot( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns meta-data for the given slot.
Definition: RevisionRecord.php:180
ArchivedFile\newFromRow
static newFromRow( $row)
Loads a file object from the filearchive table.
Definition: ArchivedFile.php:225
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
SpecialUndelete\loadRequest
loadRequest( $par)
Definition: SpecialUndelete.php:169
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:466
SpecialUndelete\$wikiPageFactory
WikiPageFactory $wikiPageFactory
Definition: SpecialUndelete.php:108