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