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->getVal( '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, $this->getConfig() );
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  if ( $this->mPreview || $isText ) {
578  $openDiv = '<div id="mw-undelete-revision" class="warningbox">';
579  } else {
580  $openDiv = '<div id="mw-undelete-revision">';
581  }
582  $out->addHTML( $openDiv );
583 
584  // Revision delete links
585  if ( !$this->mDiff ) {
586  $revdel = Linker::getRevDeleteLink(
587  $user,
588  $revRecord,
589  $this->mTargetObj
590  );
591  if ( $revdel ) {
592  $out->addHTML( "$revdel " );
593  }
594  }
595 
596  $out->addWikiMsg(
597  'undelete-revision',
598  Message::rawParam( $link ), $time,
599  Message::rawParam( $userLink ), $d, $t
600  );
601  $out->addHTML( '</div>' );
602 
603  // Hook hard deprecated since 1.35
604  if ( $this->getHookContainer()->isRegistered( 'UndeleteShowRevision' ) ) {
605  // Only create the Revision object if needed
606  $rev = new Revision( $revRecord );
607  if ( !$this->getHookRunner()->onUndeleteShowRevision(
608  $this->mTargetObj,
609  $rev
610  ) ) {
611  return;
612  }
613  }
614 
615  if ( $this->mPreview || !$isText ) {
616  // NOTE: non-text content has no source view, so always use rendered preview
617 
618  $popts = $out->parserOptions();
619 
620  $rendered = $this->revisionRenderer->getRenderedRevision(
621  $revRecord,
622  $popts,
623  $user,
624  [ 'audience' => RevisionRecord::FOR_THIS_USER ]
625  );
626 
627  // Fail hard if the audience check fails, since we already checked
628  // at the beginning of this method.
629  $pout = $rendered->getRevisionParserOutput();
630 
631  $out->addParserOutput( $pout, [
632  'enableSectionEditLinks' => false,
633  ] );
634  }
635 
636  $out->enableOOUI();
637  $buttonFields = [];
638 
639  if ( $isText ) {
640  '@phan-var TextContent $content';
641  // TODO: MCR: make this work for multiple slots
642  // source view for textual content
643  $sourceView = Xml::element( 'textarea', [
644  'readonly' => 'readonly',
645  'cols' => 80,
646  'rows' => 25
647  ], $content->getText() . "\n" );
648 
649  $buttonFields[] = new OOUI\ButtonInputWidget( [
650  'type' => 'submit',
651  'name' => 'preview',
652  'label' => $this->msg( 'showpreview' )->text()
653  ] );
654  } else {
655  $sourceView = '';
656  }
657 
658  $buttonFields[] = new OOUI\ButtonInputWidget( [
659  'name' => 'diff',
660  'type' => 'submit',
661  'label' => $this->msg( 'showdiff' )->text()
662  ] );
663 
664  $out->addHTML(
665  $sourceView .
666  Xml::openElement( 'div', [
667  'style' => 'clear: both' ] ) .
668  Xml::openElement( 'form', [
669  'method' => 'post',
670  'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
671  Xml::element( 'input', [
672  'type' => 'hidden',
673  'name' => 'target',
674  'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
675  Xml::element( 'input', [
676  'type' => 'hidden',
677  'name' => 'timestamp',
678  'value' => $timestamp ] ) .
679  Xml::element( 'input', [
680  'type' => 'hidden',
681  'name' => 'wpEditToken',
682  'value' => $user->getEditToken() ] ) .
683  new OOUI\FieldLayout(
684  new OOUI\Widget( [
685  'content' => new OOUI\HorizontalLayout( [
686  'items' => $buttonFields
687  ] )
688  ] )
689  ) .
690  Xml::closeElement( 'form' ) .
691  Xml::closeElement( 'div' )
692  );
693  }
694 
702  private function showDiff(
703  RevisionRecord $previousRevRecord,
704  RevisionRecord $currentRevRecord
705  ) {
706  $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
707 
708  $diffContext = clone $this->getContext();
709  $diffContext->setTitle( $currentTitle );
710  $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
711 
712  $contentModel = $currentRevRecord->getSlot(
713  SlotRecord::MAIN,
714  RevisionRecord::RAW
715  )->getModel();
716 
717  $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
718  ->createDifferenceEngine( $diffContext );
719 
720  $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
721  $diffEngine->showDiffStyle();
722  $formattedDiff = $diffEngine->getDiff(
723  $this->diffHeader( $previousRevRecord, 'o' ),
724  $this->diffHeader( $currentRevRecord, 'n' )
725  );
726 
727  $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
728  }
729 
735  private function diffHeader( RevisionRecord $revRecord, $prefix ) {
736  $isDeleted = !( $revRecord->getId() && $revRecord->getPageAsLinkTarget() );
737  if ( $isDeleted ) {
738  // @todo FIXME: $rev->getTitle() is null for deleted revs...?
739  $targetPage = $this->getPageTitle();
740  $targetQuery = [
741  'target' => $this->mTargetObj->getPrefixedText(),
742  'timestamp' => wfTimestamp( TS_MW, $revRecord->getTimestamp() )
743  ];
744  } else {
745  // @todo FIXME: getId() may return non-zero for deleted revs...
746  $targetPage = $revRecord->getPageAsLinkTarget();
747  $targetQuery = [ 'oldid' => $revRecord->getId() ];
748  }
749 
750  // Add show/hide deletion links if available
751  $user = $this->getUser();
752  $lang = $this->getLanguage();
753  $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
754 
755  if ( $rdel ) {
756  $rdel = " $rdel";
757  }
758 
759  $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
760 
761  $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
762  $tagIds = $dbr->selectFieldValues(
763  'change_tag',
764  'ct_tag_id',
765  [ 'ct_rev_id' => $revRecord->getId() ],
766  __METHOD__
767  );
768  $tags = [];
769  foreach ( $tagIds as $tagId ) {
770  try {
771  $tags[] = $this->changeTagDefStore->getName( (int)$tagId );
772  } catch ( NameTableAccessException $exception ) {
773  continue;
774  }
775  }
776  $tags = implode( ',', $tags );
777  $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
778 
779  // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
780  // and partially #showDiffPage, but worse
781  return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
782  $this->getLinkRenderer()->makeLink(
783  $targetPage,
784  $this->msg(
785  'revisionasof',
786  $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
787  $lang->userDate( $revRecord->getTimestamp(), $user ),
788  $lang->userTime( $revRecord->getTimestamp(), $user )
789  )->text(),
790  [],
791  $targetQuery
792  ) .
793  '</strong></div>' .
794  '<div id="mw-diff-' . $prefix . 'title2">' .
795  Linker::revUserTools( $revRecord ) . '<br />' .
796  '</div>' .
797  '<div id="mw-diff-' . $prefix . 'title3">' .
798  $minor . Linker::revComment( $revRecord ) . $rdel . '<br />' .
799  '</div>' .
800  '<div id="mw-diff-' . $prefix . 'title5">' .
801  $tagSummary[0] . '<br />' .
802  '</div>';
803  }
804 
809  private function showFileConfirmationForm( $key ) {
810  $out = $this->getOutput();
811  $lang = $this->getLanguage();
812  $user = $this->getUser();
813  $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
814  $out->addWikiMsg( 'undelete-show-file-confirm',
815  $this->mTargetObj->getText(),
816  $lang->userDate( $file->getTimestamp(), $user ),
817  $lang->userTime( $file->getTimestamp(), $user ) );
818  $out->addHTML(
819  Xml::openElement( 'form', [
820  'method' => 'POST',
821  'action' => $this->getPageTitle()->getLocalURL( [
822  'target' => $this->mTarget,
823  'file' => $key,
824  'token' => $user->getEditToken( $key ),
825  ] ),
826  ]
827  ) .
828  Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
829  '</form>'
830  );
831  }
832 
837  private function showFile( $key ) {
838  $this->getOutput()->disable();
839 
840  # We mustn't allow the output to be CDN cached, otherwise
841  # if an admin previews a deleted image, and it's cached, then
842  # a user without appropriate permissions can toddle off and
843  # nab the image, and CDN will serve it
844  $response = $this->getRequest()->response();
845  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
846  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
847  $response->header( 'Pragma: no-cache' );
848 
849  $path = $this->localRepo->getZonePath( 'deleted' ) . '/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
850  $this->localRepo->streamFileWithStatus( $path );
851  }
852 
853  protected function showHistory() {
854  $this->checkReadOnly();
855 
856  $out = $this->getOutput();
857  if ( $this->mAllowed ) {
858  $out->addModules( 'mediawiki.misc-authed-ooui' );
859  }
860  $out->wrapWikiMsg(
861  "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
862  [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
863  );
864 
865  $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
866  $this->getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
867 
868  $out->addHTML( '<div class="mw-undelete-history">' );
869  if ( $this->mAllowed ) {
870  $out->addWikiMsg( 'undeletehistory' );
871  $out->addWikiMsg( 'undeleterevdel' );
872  } else {
873  $out->addWikiMsg( 'undeletehistorynoadmin' );
874  }
875  $out->addHTML( '</div>' );
876 
877  # List all stored revisions
878  $revisions = $archive->listRevisions();
879  $files = $archive->listFiles();
880 
881  $haveRevisions = $revisions && $revisions->numRows() > 0;
882  $haveFiles = $files && $files->numRows() > 0;
883 
884  # Batch existence check on user and talk pages
885  if ( $haveRevisions || $haveFiles ) {
886  $batch = $this->linkBatchFactory->newLinkBatch();
887  if ( $haveRevisions ) {
888  foreach ( $revisions as $row ) {
889  $batch->add( NS_USER, $row->ar_user_text );
890  $batch->add( NS_USER_TALK, $row->ar_user_text );
891  }
892  $revisions->seek( 0 );
893  }
894  if ( $haveFiles ) {
895  foreach ( $files as $row ) {
896  $batch->add( NS_USER, $row->fa_user_text );
897  $batch->add( NS_USER_TALK, $row->fa_user_text );
898  }
899  $files->seek( 0 );
900  }
901  $batch->execute();
902  }
903 
904  if ( $this->mAllowed ) {
905  $out->enableOOUI();
906 
907  $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
908  # Start the form here
909  $form = new OOUI\FormLayout( [
910  'method' => 'post',
911  'action' => $action,
912  'id' => 'undelete',
913  ] );
914  }
915 
916  # Show relevant lines from the deletion log:
917  $deleteLogPage = new LogPage( 'delete' );
918  $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
919  LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
920  # Show relevant lines from the suppression log:
921  $suppressLogPage = new LogPage( 'suppress' );
922  if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressionlog' ) ) {
923  $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
924  LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
925  }
926 
927  if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
928  $fields = [];
929  $fields[] = new OOUI\Layout( [
930  'content' => new OOUI\HtmlSnippet( $this->msg( 'undeleteextrahelp' )->parseAsBlock() )
931  ] );
932 
933  $fields[] = new OOUI\FieldLayout(
934  new OOUI\TextInputWidget( [
935  'name' => 'wpComment',
936  'inputId' => 'wpComment',
937  'infusable' => true,
938  'value' => $this->mComment,
939  'autofocus' => true,
940  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
941  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
942  // Unicode codepoints.
944  ] ),
945  [
946  'label' => $this->msg( 'undeletecomment' )->text(),
947  'align' => 'top',
948  ]
949  );
950 
951  $fields[] = new OOUI\FieldLayout(
952  new OOUI\Widget( [
953  'content' => new OOUI\HorizontalLayout( [
954  'items' => [
955  new OOUI\ButtonInputWidget( [
956  'name' => 'restore',
957  'inputId' => 'mw-undelete-submit',
958  'value' => '1',
959  'label' => $this->msg( 'undeletebtn' )->text(),
960  'flags' => [ 'primary', 'progressive' ],
961  'type' => 'submit',
962  ] ),
963  new OOUI\ButtonInputWidget( [
964  'name' => 'invert',
965  'inputId' => 'mw-undelete-invert',
966  'value' => '1',
967  'label' => $this->msg( 'undeleteinvert' )->text()
968  ] ),
969  ]
970  ] )
971  ] )
972  );
973 
974  if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressrevision' ) ) {
975  $fields[] = new OOUI\FieldLayout(
976  new OOUI\CheckboxInputWidget( [
977  'name' => 'wpUnsuppress',
978  'inputId' => 'mw-undelete-unsuppress',
979  'value' => '1',
980  ] ),
981  [
982  'label' => $this->msg( 'revdelete-unsuppress' )->text(),
983  'align' => 'inline',
984  ]
985  );
986  }
987 
988  $fieldset = new OOUI\FieldsetLayout( [
989  'label' => $this->msg( 'undelete-fieldset-title' )->text(),
990  'id' => 'mw-undelete-table',
991  'items' => $fields,
992  ] );
993 
994  $form->appendContent(
995  new OOUI\PanelLayout( [
996  'expanded' => false,
997  'padded' => true,
998  'framed' => true,
999  'content' => $fieldset,
1000  ] ),
1001  new OOUI\HtmlSnippet(
1002  Html::hidden( 'target', $this->mTarget ) .
1003  Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
1004  )
1005  );
1006  }
1007 
1008  $history = '';
1009  $history .= Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n";
1010 
1011  if ( $haveRevisions ) {
1012  # Show the page's stored (deleted) history
1013 
1014  if ( $this->permissionManager->userHasRight( $this->getUser(), 'deleterevision' ) ) {
1015  $history .= Html::element(
1016  'button',
1017  [
1018  'name' => 'revdel',
1019  'type' => 'submit',
1020  'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
1021  ],
1022  $this->msg( 'showhideselectedversions' )->text()
1023  ) . "\n";
1024  }
1025 
1026  $history .= '<ul class="mw-undelete-revlist">';
1027  $remaining = $revisions->numRows();
1028  $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1029  $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() : null;
1030 
1031  foreach ( $revisions as $row ) {
1032  $remaining--;
1033  $history .= $this->formatRevisionRow( $row, $earliestLiveTime, $remaining );
1034  }
1035  $revisions->free();
1036  $history .= '</ul>';
1037  } else {
1038  $out->addWikiMsg( 'nohistory' );
1039  }
1040 
1041  if ( $haveFiles ) {
1042  $history .= Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n";
1043  $history .= '<ul class="mw-undelete-revlist">';
1044  foreach ( $files as $row ) {
1045  $history .= $this->formatFileRow( $row );
1046  }
1047  $files->free();
1048  $history .= '</ul>';
1049  }
1050 
1051  if ( $this->mAllowed ) {
1052  # Slip in the hidden controls here
1053  $misc = Html::hidden( 'target', $this->mTarget );
1054  $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
1055  $history .= $misc;
1056 
1057  $form->appendContent( new OOUI\HtmlSnippet( $history ) );
1058  $out->addHTML( $form );
1059  } else {
1060  $out->addHTML( $history );
1061  }
1062 
1063  return true;
1064  }
1065 
1066  protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
1067  $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1068  $row,
1069  RevisionStore::READ_NORMAL,
1070  $this->mTargetObj
1071  );
1072 
1073  $revTextSize = '';
1074  $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1075  // Build checkboxen...
1076  if ( $this->mAllowed ) {
1077  if ( $this->mInvert ) {
1078  if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1079  $checkBox = Xml::check( "ts$ts" );
1080  } else {
1081  $checkBox = Xml::check( "ts$ts", true );
1082  }
1083  } else {
1084  $checkBox = Xml::check( "ts$ts" );
1085  }
1086  } else {
1087  $checkBox = '';
1088  }
1089 
1090  // Build page & diff links...
1091  $user = $this->getUser();
1092  if ( $this->mCanView ) {
1093  $titleObj = $this->getPageTitle();
1094  # Last link
1095  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1096  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1097  $last = $this->msg( 'diff' )->escaped();
1098  } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1099  $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1100  $last = $this->getLinkRenderer()->makeKnownLink(
1101  $titleObj,
1102  $this->msg( 'diff' )->text(),
1103  [],
1104  [
1105  'target' => $this->mTargetObj->getPrefixedText(),
1106  'timestamp' => $ts,
1107  'diff' => 'prev'
1108  ]
1109  );
1110  } else {
1111  $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1112  $last = $this->msg( 'diff' )->escaped();
1113  }
1114  } else {
1115  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1116  $last = $this->msg( 'diff' )->escaped();
1117  }
1118 
1119  // User links
1120  $userLink = Linker::revUserTools( $revRecord );
1121 
1122  // Minor edit
1123  $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
1124 
1125  // Revision text size
1126  $size = $row->ar_len;
1127  if ( $size !== null ) {
1128  $revTextSize = Linker::formatRevisionSize( $size );
1129  }
1130 
1131  // Edit summary
1132  $comment = Linker::revComment( $revRecord );
1133 
1134  // Tags
1135  $attribs = [];
1136  list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
1137  $row->ts_tags,
1138  'deletedhistory',
1139  $this->getContext()
1140  );
1141  if ( $classes ) {
1142  $attribs['class'] = implode( ' ', $classes );
1143  }
1144 
1145  $revisionRow = $this->msg( 'undelete-revision-row2' )
1146  ->rawParams(
1147  $checkBox,
1148  $last,
1149  $pageLink,
1150  $userLink,
1151  $minor,
1152  $revTextSize,
1153  $comment,
1154  $tagSummary
1155  )
1156  ->escaped();
1157 
1158  return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1159  }
1160 
1161  private function formatFileRow( $row ) {
1162  $file = ArchivedFile::newFromRow( $row );
1163  $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1164  $user = $this->getUser();
1165 
1166  $checkBox = '';
1167  if ( $this->mCanView && $row->fa_storage_key ) {
1168  if ( $this->mAllowed ) {
1169  $checkBox = Xml::check( 'fileid' . $row->fa_id );
1170  }
1171  $key = urlencode( $row->fa_storage_key );
1172  $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1173  } else {
1174  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1175  }
1176  $userLink = $this->getFileUser( $file );
1177  $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1178  $bytes = $this->msg( 'parentheses' )
1179  ->plaintextParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1180  ->plain();
1181  $data = htmlspecialchars( $data . ' ' . $bytes );
1182  $comment = $this->getFileComment( $file );
1183 
1184  // Add show/hide deletion links if available
1185  $canHide = $this->isAllowed( 'deleterevision' );
1186  if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1187  if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1188  // Revision was hidden from sysops
1189  $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1190  } else {
1191  $query = [
1192  'type' => 'filearchive',
1193  'target' => $this->mTargetObj->getPrefixedDBkey(),
1194  'ids' => $row->fa_id
1195  ];
1196  $revdlink = Linker::revDeleteLink( $query,
1197  $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1198  }
1199  } else {
1200  $revdlink = '';
1201  }
1202 
1203  return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1204  }
1205 
1214  private function getPageLink( RevisionRecord $revRecord, $titleObj, $ts ) {
1215  $user = $this->getUser();
1216  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1217 
1218  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1219  // TODO The condition cannot be true when the function is called
1220  return '<span class="history-deleted">' . htmlspecialchars( $time ) . '</span>';
1221  }
1222 
1223  $link = $this->getLinkRenderer()->makeKnownLink(
1224  $titleObj,
1225  $time,
1226  [],
1227  [
1228  'target' => $this->mTargetObj->getPrefixedText(),
1229  'timestamp' => $ts
1230  ]
1231  );
1232 
1233  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1234  $link = '<span class="history-deleted">' . $link . '</span>';
1235  }
1236 
1237  return $link;
1238  }
1239 
1250  private function getFileLink( $file, $titleObj, $ts, $key ) {
1251  $user = $this->getUser();
1252  $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1253 
1254  if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1255  return '<span class="history-deleted">' . htmlspecialchars( $time ) . '</span>';
1256  }
1257 
1258  $link = $this->getLinkRenderer()->makeKnownLink(
1259  $titleObj,
1260  $time,
1261  [],
1262  [
1263  'target' => $this->mTargetObj->getPrefixedText(),
1264  'file' => $key,
1265  'token' => $user->getEditToken( $key )
1266  ]
1267  );
1268 
1269  if ( $file->isDeleted( File::DELETED_FILE ) ) {
1270  $link = '<span class="history-deleted">' . $link . '</span>';
1271  }
1272 
1273  return $link;
1274  }
1275 
1282  private function getFileUser( $file ) {
1283  if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
1284  return '<span class="history-deleted">' .
1285  $this->msg( 'rev-deleted-user' )->escaped() .
1286  '</span>';
1287  }
1288 
1289  $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
1290  Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1291 
1292  if ( $file->isDeleted( File::DELETED_USER ) ) {
1293  $link = '<span class="history-deleted">' . $link . '</span>';
1294  }
1295 
1296  return $link;
1297  }
1298 
1305  private function getFileComment( $file ) {
1306  if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
1307  return '<span class="history-deleted"><span class="comment">' .
1308  $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
1309  }
1310 
1311  $link = Linker::commentBlock( $file->getRawDescription() );
1312 
1313  if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1314  $link = '<span class="history-deleted">' . $link . '</span>';
1315  }
1316 
1317  return $link;
1318  }
1319 
1320  private function undelete() {
1321  if ( $this->getConfig()->get( 'UploadMaintenance' )
1322  && $this->mTargetObj->getNamespace() === NS_FILE
1323  ) {
1324  throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1325  }
1326 
1327  $this->checkReadOnly();
1328 
1329  $out = $this->getOutput();
1330  $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1331  $this->getHookRunner()->onUndeleteForm__undelete( $archive, $this->mTargetObj );
1332  $ok = $archive->undeleteAsUser(
1333  $this->mTargetTimestamp,
1334  $this->getUser(),
1335  $this->mComment,
1336  $this->mFileVersions,
1337  $this->mUnsuppress
1338  );
1339 
1340  if ( is_array( $ok ) ) {
1341  if ( $ok[1] ) { // Undeleted file count
1342  $this->getHookRunner()->onFileUndeleteComplete(
1343  $this->mTargetObj, $this->mFileVersions, $this->getUser(), $this->mComment );
1344  }
1345 
1346  $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
1347  $out->addWikiMsg( 'undeletedpage', Message::rawParam( $link ) );
1348  } else {
1349  $out->setPageTitle( $this->msg( 'undelete-error' ) );
1350  }
1351 
1352  // Show revision undeletion warnings and errors
1353  $status = $archive->getRevisionStatus();
1354  if ( $status && !$status->isGood() ) {
1355  $out->wrapWikiTextAsInterface(
1356  'error',
1357  '<div id="mw-error-cannotundelete">' .
1358  $status->getWikiText(
1359  'cannotundelete',
1360  'cannotundelete',
1361  $this->getLanguage()
1362  ) . '</div>'
1363  );
1364  }
1365 
1366  // Show file undeletion warnings and errors
1367  $status = $archive->getFileStatus();
1368  if ( $status && !$status->isGood() ) {
1369  $out->wrapWikiTextAsInterface(
1370  'error',
1371  $status->getWikiText(
1372  'undelete-error-short',
1373  'undelete-error-long',
1374  $this->getLanguage()
1375  )
1376  );
1377  }
1378  }
1379 
1388  public function prefixSearchSubpages( $search, $limit, $offset ) {
1389  return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
1390  }
1391 
1392  protected function getGroupName() {
1393  return 'pagetools';
1394  }
1395 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:743
SpecialUndelete\formatRevisionRow
formatRevisionRow( $row, $earliestLiveTime, $remaining)
Definition: SpecialUndelete.php:1066
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:911
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:363
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:69
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:32
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:32
Linker\userLink
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:897
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:789
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:525
File\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: File.php:70
$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:1392
Revision\RevisionRecord\isDeleted
isDeleted( $field)
MCR migration note: this replaces Revision::isDeleted.
Definition: RevisionRecord.php:453
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: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:1832
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:942
SpecialUndelete\$mAction
$mAction
Definition: SpecialUndelete.php:45
SpecialUndelete\showRevision
showRevision( $timestamp)
Definition: SpecialUndelete.php:491
Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
Definition: RevisionRecord.php:475
SpecialUndelete\showHistory
showHistory()
Definition: SpecialUndelete.php:853
SpecialUndelete\$mDiff
bool null $mDiff
Definition: SpecialUndelete.php:60
SpecialUndelete\showList
showList( $result)
Generic list of deleted pages.
Definition: SpecialUndelete.php:441
SpecialUndelete\showSearchForm
showSearchForm()
Definition: SpecialUndelete.php:363
Linker\revComment
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1597
PageArchive\listPagesByPrefix
static listPagesByPrefix( $prefix)
List deleted pages recorded in the archive table matching the given title prefix.
Definition: PageArchive.php:129
SpecialPage\displayRestrictionError
displayRestrictionError()
Output an error message telling the user what access level they have to have.
Definition: SpecialPage.php:345
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:13
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\getHookContainer
getHookContainer()
Definition: SpecialPage.php:1081
SpecialPage\getSkin
getSkin()
Shortcut to get the skin being used for this instance.
Definition: SpecialPage.php:819
SpecialPage\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: SpecialPage.php:1017
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:829
Linker\getInvalidTitleDescription
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition: Linker.php:189
Html\warningBox
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:729
SpecialUndelete\getFileUser
getFileUser( $file)
Fetch file's user id if it's available to this user.
Definition: SpecialUndelete.php:1282
PageArchive\listPagesBySearch
static listPagesBySearch( $term)
List deleted pages recorded in the archive matching the given term, using search engine archive.
Definition: PageArchive.php:79
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:576
Revision
Definition: Revision.php:40
SpecialUndelete\$mTargetObj
Title $mTargetObj
Definition: SpecialUndelete.php:69
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:947
SpecialUndelete\$mTimestamp
$mTimestamp
Definition: SpecialUndelete.php:47
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1094
SpecialUndelete\$mCanView
$mCanView
Definition: SpecialUndelete.php:54
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:877
File\DELETED_COMMENT
const DELETED_COMMENT
Definition: File.php:68
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:2534
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
SpecialUndelete\userCanExecute
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
Definition: SpecialUndelete.php:241
SpecialUndelete\$localRepo
LocalRepo $localRepo
Definition: SpecialUndelete.php:94
$matches
$matches
Definition: NoLocalSettings.php:24
MediaWiki\Cache\LinkBatchFactory
Definition: LinkBatchFactory.php:38
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:702
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:37
SpecialUndelete\$mUnsuppress
bool null $mUnsuppress
Definition: SpecialUndelete.php:64
Revision\RevisionRecord\isMinor
isMinor()
MCR migration note: this replaces Revision::isMinor.
Definition: RevisionRecord.php:442
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:270
Linker\revDeleteLinkDisabled
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2265
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:617
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:799
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:602
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SpecialUndelete\$mRevdel
$mRevdel
Definition: SpecialUndelete.php:49
Linker\getRevDeleteLink
static getRevDeleteLink(Authority $performer, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2190
Linker\revUserTools
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1138
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:763
SpecialUndelete\diffHeader
diffHeader(RevisionRecord $revRecord, $prefix)
Definition: SpecialUndelete.php:735
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:805
SpecialUndelete\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialUndelete.php:1388
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:652
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:51
SpecialUndelete\formatFileRow
formatFileRow( $row)
Definition: SpecialUndelete.php:1161
$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:357
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:1566
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ArchivedFile
Class representing a row of the 'filearchive' table.
Definition: ArchivedFile.php:33
Message\rawParam
static rawParam( $raw)
Definition: Message.php:1027
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
Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:295
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:779
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1638
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1505
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:1305
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:291
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:1214
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:1027
SpecialUndelete\getFileLink
getFileLink( $file, $titleObj, $ts, $key)
Fetch image view link if it's available to all users.
Definition: SpecialUndelete.php:1250
SpecialUndelete\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: SpecialUndelete.php:85
Revision\RevisionRecord\getPageAsLinkTarget
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
Definition: RevisionRecord.php:371
Title
Represents a title within MediaWiki.
Definition: Title.php:46
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:119
SpecialUndelete\showFile
showFile( $key)
Show a deleted file version requested by the visitor.
Definition: SpecialUndelete.php:837
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
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:212
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:2243
RepoGroup
Prioritized list of file repositories.
Definition: RepoGroup.php:31
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
File\DELETED_FILE
const DELETED_FILE
Definition: File.php:67
SpecialPage\checkReadOnly
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
Definition: SpecialPage.php:370
$t
$t
Definition: testCompression.php:74
SpecialUndelete\$mTarget
$mTarget
Definition: SpecialUndelete.php:46
SpecialUndelete\undelete
undelete()
Definition: SpecialUndelete.php:1320
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:234
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:809
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:66
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:708
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:176
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:37
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
ArchivedFile\newFromRow
static newFromRow( $row)
Loads a file object from the filearchive table.
Definition: ArchivedFile.php:217
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
Revision\RevisionRecord\getSlot
getSlot( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns meta-data for the given slot.
Definition: RevisionRecord.php:196
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