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