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