MediaWiki REL1_37
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;
67
69 private $mTargetObj;
74
77
80
83
86
89
92
94 private $localRepo;
95
98
101
104
107
121 public function __construct(
128 RepoGroup $repoGroup,
133 ) {
134 parent::__construct( 'Undelete', 'deletedhistory' );
135 $this->permissionManager = $permissionManager;
136 $this->revisionStore = $revisionStore;
137 $this->revisionRenderer = $revisionRenderer;
138 $this->contentHandlerFactory = $contentHandlerFactory;
139 $this->changeTagDefStore = $changeTagDefStore;
140 $this->linkBatchFactory = $linkBatchFactory;
141 $this->localRepo = $repoGroup->getLocalRepo();
142 $this->loadBalancer = $loadBalancer;
143 $this->userOptionsLookup = $userOptionsLookup;
144 $this->wikiPageFactory = $wikiPageFactory;
145 $this->searchEngineFactory = $searchEngineFactory;
146 }
147
148 public function doesWrites() {
149 return true;
150 }
151
152 private function loadRequest( $par ) {
153 $request = $this->getRequest();
154 $user = $this->getUser();
155
156 $this->mAction = $request->getRawVal( 'action' );
157 if ( $par !== null && $par !== '' ) {
158 $this->mTarget = $par;
159 } else {
160 $this->mTarget = $request->getVal( 'target' );
161 }
162
163 $this->mTargetObj = null;
164
165 if ( $this->mTarget !== null && $this->mTarget !== '' ) {
166 $this->mTargetObj = Title::newFromText( $this->mTarget );
167 }
168
169 $this->mSearchPrefix = $request->getText( 'prefix' );
170 $time = $request->getVal( 'timestamp' );
171 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
172 $this->mFilename = $request->getVal( 'file' );
173
174 $posted = $request->wasPosted() &&
175 $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
176 $this->mRestore = $request->getCheck( 'restore' ) && $posted;
177 $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
178 $this->mInvert = $request->getCheck( 'invert' ) && $posted;
179 $this->mPreview = $request->getCheck( 'preview' ) && $posted;
180 $this->mDiff = $request->getCheck( 'diff' );
181 $this->mDiffOnly = $request->getBool( 'diffonly',
182 $this->userOptionsLookup->getOption( $this->getUser(), 'diffonly' ) );
183 $this->mComment = $request->getText( 'wpComment' );
184 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) &&
185 $this->permissionManager->userHasRight( $user, 'suppressrevision' );
186 $this->mToken = $request->getVal( 'token' );
187
188 if ( $this->isAllowed( 'undelete' ) ) {
189 $this->mAllowed = true; // user can restore
190 $this->mCanView = true; // user can view content
191 } elseif ( $this->isAllowed( 'deletedtext' ) ) {
192 $this->mAllowed = false; // user cannot restore
193 $this->mCanView = true; // user can view content
194 $this->mRestore = false;
195 } else { // user can only view the list of revisions
196 $this->mAllowed = false;
197 $this->mCanView = false;
198 $this->mTimestamp = '';
199 $this->mRestore = false;
200 }
201
202 if ( $this->mRestore || $this->mInvert ) {
203 $timestamps = [];
204 $this->mFileVersions = [];
205 foreach ( $request->getValues() as $key => $val ) {
206 $matches = [];
207 if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
208 array_push( $timestamps, $matches[1] );
209 }
210
211 if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
212 $this->mFileVersions[] = intval( $matches[1] );
213 }
214 }
215 rsort( $timestamps );
216 $this->mTargetTimestamp = $timestamps;
217 }
218 }
219
228 protected function isAllowed( $permission, User $user = null ) {
229 $user = $user ?: $this->getUser();
230 $block = $user->getBlock();
231
232 if ( $this->mTargetObj !== null ) {
233 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
234 } else {
235 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
236 $sitewideBlock = $block && $block->isSitewide();
237 return $permission === 'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
238 }
239 }
240
241 public function userCanExecute( User $user ) {
242 return $this->isAllowed( $this->mRestriction, $user );
243 }
244
248 public function checkPermissions() {
249 $user = $this->getUser();
250
251 // First check if user has the right to use this page. If not,
252 // show a permissions error whether they are blocked or not.
253 if ( !parent::userCanExecute( $user ) ) {
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 ) ) {
267 }
268 }
269
270 public function execute( $par ) {
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
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 ) {
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(
478 Html::rawElement(
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 // FIXME: This hook must be deprecated, passing PageArchive by ref is awful.
498 if ( !$this->getHookRunner()->onUndeleteForm__showRevision(
499 $archive, $this->mTargetObj )
500 ) {
501 return;
502 }
503 $revRecord = $archive->getRevisionRecordByTimestamp( $timestamp );
504
505 $out = $this->getOutput();
506 $user = $this->getUser();
507
508 if ( !$revRecord ) {
509 $out->addWikiMsg( 'undeleterevision-missing' );
510 return;
511 }
512
513 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
514 // Used in wikilinks, should not contain whitespaces
515 $titleText = $this->mTargetObj->getPrefixedDBkey();
516 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
517 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
518 ? [ 'rev-suppressed-text-permission', $titleText ]
519 : [ 'rev-deleted-text-permission', $titleText ];
520 $out->addHtml(
521 Html::warningBox(
522 $this->msg( $msg[0], $msg[1] )->parse(),
523 'plainlinks'
524 )
525 );
526 return;
527 }
528
529 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
530 ? [ 'rev-suppressed-text-view', $titleText ]
531 : [ 'rev-deleted-text-view', $titleText ];
532 $out->addHtml(
533 Html::warningBox(
534 $this->msg( $msg[0], $msg[1] )->parse(),
535 'plainlinks'
536 )
537 );
538 // and we are allowed to see...
539 }
540
541 if ( $this->mDiff ) {
542 $previousRevRecord = $archive->getPreviousRevisionRecord( $timestamp );
543 if ( $previousRevRecord ) {
544 $this->showDiff( $previousRevRecord, $revRecord );
545 if ( $this->mDiffOnly ) {
546 return;
547 }
548
549 $out->addHTML( '<hr />' );
550 } else {
551 $out->addWikiMsg( 'undelete-nodiff' );
552 }
553 }
554
555 $link = $this->getLinkRenderer()->makeKnownLink(
556 $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
557 $this->mTargetObj->getPrefixedText()
558 );
559
560 $lang = $this->getLanguage();
561
562 // date and time are separate parameters to facilitate localisation.
563 // $time is kept for backward compat reasons.
564 $time = $lang->userTimeAndDate( $timestamp, $user );
565 $d = $lang->userDate( $timestamp, $user );
566 $t = $lang->userTime( $timestamp, $user );
567 $userLink = Linker::revUserTools( $revRecord );
568
569 $content = $revRecord->getContent(
570 SlotRecord::MAIN,
571 RevisionRecord::FOR_THIS_USER,
572 $user
573 );
574
575 // TODO: MCR: this will have to become something like $hasTextSlots and $hasNonTextSlots
576 $isText = ( $content instanceof TextContent );
577
578 $out->addHTML(
579 Html::openElement(
580 'div',
581 [
582 'id' => 'mw-undelete-revision',
583 'class' => $this->mPreview || $isText ? 'warningbox' : '',
584 ]
585 )
586 );
587
588 // Revision delete links
589 if ( !$this->mDiff ) {
590 $revdel = Linker::getRevDeleteLink(
591 $user,
592 $revRecord,
593 $this->mTargetObj
594 );
595 if ( $revdel ) {
596 $out->addHTML( "$revdel " );
597 }
598 }
599
600 $out->addWikiMsg(
601 'undelete-revision',
602 Message::rawParam( $link ), $time,
603 Message::rawParam( $userLink ), $d, $t
604 );
605 $out->addHTML( Html::closeElement( 'div' ) );
606
607 if ( $this->mPreview || !$isText ) {
608 // NOTE: non-text content has no source view, so always use rendered preview
609
610 $popts = $out->parserOptions();
611
612 $rendered = $this->revisionRenderer->getRenderedRevision(
613 $revRecord,
614 $popts,
615 $user,
616 [ 'audience' => RevisionRecord::FOR_THIS_USER ]
617 );
618
619 // Fail hard if the audience check fails, since we already checked
620 // at the beginning of this method.
621 $pout = $rendered->getRevisionParserOutput();
622
623 $out->addParserOutput( $pout, [
624 'enableSectionEditLinks' => false,
625 ] );
626 }
627
628 $out->enableOOUI();
629 $buttonFields = [];
630
631 if ( $isText ) {
632 '@phan-var TextContent $content';
633 // TODO: MCR: make this work for multiple slots
634 // source view for textual content
635 $sourceView = Xml::element( 'textarea', [
636 'readonly' => 'readonly',
637 'cols' => 80,
638 'rows' => 25
639 ], $content->getText() . "\n" );
640
641 $buttonFields[] = new OOUI\ButtonInputWidget( [
642 'type' => 'submit',
643 'name' => 'preview',
644 'label' => $this->msg( 'showpreview' )->text()
645 ] );
646 } else {
647 $sourceView = '';
648 }
649
650 $buttonFields[] = new OOUI\ButtonInputWidget( [
651 'name' => 'diff',
652 'type' => 'submit',
653 'label' => $this->msg( 'showdiff' )->text()
654 ] );
655
656 $out->addHTML(
657 $sourceView .
658 Xml::openElement( 'div', [
659 'style' => 'clear: both' ] ) .
660 Xml::openElement( 'form', [
661 'method' => 'post',
662 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
663 Xml::element( 'input', [
664 'type' => 'hidden',
665 'name' => 'target',
666 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
667 Xml::element( 'input', [
668 'type' => 'hidden',
669 'name' => 'timestamp',
670 'value' => $timestamp ] ) .
671 Xml::element( 'input', [
672 'type' => 'hidden',
673 'name' => 'wpEditToken',
674 'value' => $user->getEditToken() ] ) .
675 new OOUI\FieldLayout(
676 new OOUI\Widget( [
677 'content' => new OOUI\HorizontalLayout( [
678 'items' => $buttonFields
679 ] )
680 ] )
681 ) .
682 Xml::closeElement( 'form' ) .
683 Xml::closeElement( 'div' )
684 );
685 }
686
694 private function showDiff(
695 RevisionRecord $previousRevRecord,
696 RevisionRecord $currentRevRecord
697 ) {
698 $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
699
700 $diffContext = clone $this->getContext();
701 $diffContext->setTitle( $currentTitle );
702 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
703
704 $contentModel = $currentRevRecord->getSlot(
705 SlotRecord::MAIN,
706 RevisionRecord::RAW
707 )->getModel();
708
709 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
710 ->createDifferenceEngine( $diffContext );
711
712 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
713 $diffEngine->showDiffStyle();
714 $formattedDiff = $diffEngine->getDiff(
715 $this->diffHeader( $previousRevRecord, 'o' ),
716 $this->diffHeader( $currentRevRecord, 'n' )
717 );
718
719 $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
720 }
721
727 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
728 $isDeleted = !( $revRecord->getId() && $revRecord->getPageAsLinkTarget() );
729 if ( $isDeleted ) {
730 // @todo FIXME: $rev->getTitle() is null for deleted revs...?
731 $targetPage = $this->getPageTitle();
732 $targetQuery = [
733 'target' => $this->mTargetObj->getPrefixedText(),
734 'timestamp' => wfTimestamp( TS_MW, $revRecord->getTimestamp() )
735 ];
736 } else {
737 // @todo FIXME: getId() may return non-zero for deleted revs...
738 $targetPage = $revRecord->getPageAsLinkTarget();
739 $targetQuery = [ 'oldid' => $revRecord->getId() ];
740 }
741
742 // Add show/hide deletion links if available
743 $user = $this->getUser();
744 $lang = $this->getLanguage();
745 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
746
747 if ( $rdel ) {
748 $rdel = " $rdel";
749 }
750
751 $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
752
753 $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
754 $tagIds = $dbr->selectFieldValues(
755 'change_tag',
756 'ct_tag_id',
757 [ 'ct_rev_id' => $revRecord->getId() ],
758 __METHOD__
759 );
760 $tags = [];
761 foreach ( $tagIds as $tagId ) {
762 try {
763 $tags[] = $this->changeTagDefStore->getName( (int)$tagId );
764 } catch ( NameTableAccessException $exception ) {
765 continue;
766 }
767 }
768 $tags = implode( ',', $tags );
769 $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
770
771 // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
772 // and partially #showDiffPage, but worse
773 return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
774 $this->getLinkRenderer()->makeLink(
775 $targetPage,
776 $this->msg(
777 'revisionasof',
778 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
779 $lang->userDate( $revRecord->getTimestamp(), $user ),
780 $lang->userTime( $revRecord->getTimestamp(), $user )
781 )->text(),
782 [],
783 $targetQuery
784 ) .
785 '</strong></div>' .
786 '<div id="mw-diff-' . $prefix . 'title2">' .
787 Linker::revUserTools( $revRecord ) . '<br />' .
788 '</div>' .
789 '<div id="mw-diff-' . $prefix . 'title3">' .
790 $minor . Linker::revComment( $revRecord ) . $rdel . '<br />' .
791 '</div>' .
792 '<div id="mw-diff-' . $prefix . 'title5">' .
793 $tagSummary[0] . '<br />' .
794 '</div>';
795 }
796
801 private function showFileConfirmationForm( $key ) {
802 $out = $this->getOutput();
803 $lang = $this->getLanguage();
804 $user = $this->getUser();
805 $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
806 $out->addWikiMsg( 'undelete-show-file-confirm',
807 $this->mTargetObj->getText(),
808 $lang->userDate( $file->getTimestamp(), $user ),
809 $lang->userTime( $file->getTimestamp(), $user ) );
810 $out->addHTML(
811 Xml::openElement( 'form', [
812 'method' => 'POST',
813 'action' => $this->getPageTitle()->getLocalURL( [
814 'target' => $this->mTarget,
815 'file' => $key,
816 'token' => $user->getEditToken( $key ),
817 ] ),
818 ]
819 ) .
820 Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
821 '</form>'
822 );
823 }
824
829 private function showFile( $key ) {
830 $this->getOutput()->disable();
831
832 # We mustn't allow the output to be CDN cached, otherwise
833 # if an admin previews a deleted image, and it's cached, then
834 # a user without appropriate permissions can toddle off and
835 # nab the image, and CDN will serve it
836 $response = $this->getRequest()->response();
837 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
838 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
839 $response->header( 'Pragma: no-cache' );
840
841 $path = $this->localRepo->getZonePath( 'deleted' ) . '/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
842 $this->localRepo->streamFileWithStatus( $path );
843 }
844
845 protected function showHistory() {
846 $this->checkReadOnly();
847
848 $out = $this->getOutput();
849 if ( $this->mAllowed ) {
850 $out->addModules( 'mediawiki.misc-authed-ooui' );
851 }
852 $out->wrapWikiMsg(
853 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
854 [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
855 );
856
857 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
858 // FIXME: This hook must be deprecated, passing PageArchive by ref is awful.
859 $this->getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
860
861 $out->addHTML( Html::openElement( 'div', [ 'class' => 'mw-undelete-history' ] ) );
862 if ( $this->mAllowed ) {
863 $out->addWikiMsg( 'undeletehistory' );
864 $out->addWikiMsg( 'undeleterevdel' );
865 } else {
866 $out->addWikiMsg( 'undeletehistorynoadmin' );
867 }
868 $out->addHTML( Html::closeElement( 'div' ) );
869
870 # List all stored revisions
871 $revisions = $archive->listRevisions();
872 $files = $archive->listFiles();
873
874 $haveRevisions = $revisions && $revisions->numRows() > 0;
875 $haveFiles = $files && $files->numRows() > 0;
876
877 # Batch existence check on user and talk pages
878 if ( $haveRevisions || $haveFiles ) {
879 $batch = $this->linkBatchFactory->newLinkBatch();
880 if ( $haveRevisions ) {
881 foreach ( $revisions as $row ) {
882 $batch->add( NS_USER, $row->ar_user_text );
883 $batch->add( NS_USER_TALK, $row->ar_user_text );
884 }
885 $revisions->seek( 0 );
886 }
887 if ( $haveFiles ) {
888 foreach ( $files as $row ) {
889 $batch->add( NS_USER, $row->fa_user_text );
890 $batch->add( NS_USER_TALK, $row->fa_user_text );
891 }
892 $files->seek( 0 );
893 }
894 $batch->execute();
895 }
896
897 if ( $this->mAllowed ) {
898 $out->enableOOUI();
899
900 $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
901 # Start the form here
902 $form = new OOUI\FormLayout( [
903 'method' => 'post',
904 'action' => $action,
905 'id' => 'undelete',
906 ] );
907 }
908
909 # Show relevant lines from the deletion log:
910 $deleteLogPage = new LogPage( 'delete' );
911 $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
912 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
913 # Show relevant lines from the suppression log:
914 $suppressLogPage = new LogPage( 'suppress' );
915 if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressionlog' ) ) {
916 $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
917 LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
918 }
919
920 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
921 $fields = [];
922 $fields[] = new OOUI\Layout( [
923 'content' => new OOUI\HtmlSnippet( $this->msg( 'undeleteextrahelp' )->parseAsBlock() )
924 ] );
925
926 $fields[] = new OOUI\FieldLayout(
927 new OOUI\TextInputWidget( [
928 'name' => 'wpComment',
929 'inputId' => 'wpComment',
930 'infusable' => true,
931 'value' => $this->mComment,
932 'autofocus' => true,
933 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
934 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
935 // Unicode codepoints.
936 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
937 ] ),
938 [
939 'label' => $this->msg( 'undeletecomment' )->text(),
940 'align' => 'top',
941 ]
942 );
943
944 $fields[] = new OOUI\FieldLayout(
945 new OOUI\Widget( [
946 'content' => new OOUI\HorizontalLayout( [
947 'items' => [
948 new OOUI\ButtonInputWidget( [
949 'name' => 'restore',
950 'inputId' => 'mw-undelete-submit',
951 'value' => '1',
952 'label' => $this->msg( 'undeletebtn' )->text(),
953 'flags' => [ 'primary', 'progressive' ],
954 'type' => 'submit',
955 ] ),
956 new OOUI\ButtonInputWidget( [
957 'name' => 'invert',
958 'inputId' => 'mw-undelete-invert',
959 'value' => '1',
960 'label' => $this->msg( 'undeleteinvert' )->text()
961 ] ),
962 ]
963 ] )
964 ] )
965 );
966
967 if ( $this->permissionManager->userHasRight( $this->getUser(), 'suppressrevision' ) ) {
968 $fields[] = new OOUI\FieldLayout(
969 new OOUI\CheckboxInputWidget( [
970 'name' => 'wpUnsuppress',
971 'inputId' => 'mw-undelete-unsuppress',
972 'value' => '1',
973 ] ),
974 [
975 'label' => $this->msg( 'revdelete-unsuppress' )->text(),
976 'align' => 'inline',
977 ]
978 );
979 }
980
981 $fieldset = new OOUI\FieldsetLayout( [
982 'label' => $this->msg( 'undelete-fieldset-title' )->text(),
983 'id' => 'mw-undelete-table',
984 'items' => $fields,
985 ] );
986
987 $form->appendContent(
988 new OOUI\PanelLayout( [
989 'expanded' => false,
990 'padded' => true,
991 'framed' => true,
992 'content' => $fieldset,
993 ] ),
994 new OOUI\HtmlSnippet(
995 Html::hidden( 'target', $this->mTarget ) .
996 Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
997 )
998 );
999 }
1000
1001 $history = '';
1002 $history .= Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n";
1003
1004 if ( $haveRevisions ) {
1005 # Show the page's stored (deleted) history
1006
1007 if ( $this->permissionManager->userHasRight( $this->getUser(), 'deleterevision' ) ) {
1008 $history .= Html::element(
1009 'button',
1010 [
1011 'name' => 'revdel',
1012 'type' => 'submit',
1013 'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
1014 ],
1015 $this->msg( 'showhideselectedversions' )->text()
1016 ) . "\n";
1017 }
1018
1019 $history .= Html::openElement( 'ul', [ 'class' => 'mw-undelete-revlist' ] );
1020 $remaining = $revisions->numRows();
1021 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1022 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() : null;
1023
1024 foreach ( $revisions as $row ) {
1025 $remaining--;
1026 $history .= $this->formatRevisionRow( $row, $earliestLiveTime, $remaining );
1027 }
1028 $revisions->free();
1029 $history .= Html::closeElement( 'ul' );
1030 } else {
1031 $out->addWikiMsg( 'nohistory' );
1032 }
1033
1034 if ( $haveFiles ) {
1035 $history .= Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n";
1036 $history .= Html::openElement( 'ul', [ 'class' => 'mw-undelete-revlist' ] );
1037 foreach ( $files as $row ) {
1038 $history .= $this->formatFileRow( $row );
1039 }
1040 $files->free();
1041 $history .= Html::closeElement( 'ul' );
1042 }
1043
1044 if ( $this->mAllowed ) {
1045 # Slip in the hidden controls here
1046 $misc = Html::hidden( 'target', $this->mTarget );
1047 $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
1048 $history .= $misc;
1049
1050 $form->appendContent( new OOUI\HtmlSnippet( $history ) );
1051 $out->addHTML( $form );
1052 } else {
1053 $out->addHTML( $history );
1054 }
1055
1056 return true;
1057 }
1058
1059 protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
1060 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1061 $row,
1062 RevisionStore::READ_NORMAL,
1063 $this->mTargetObj
1064 );
1065
1066 $revTextSize = '';
1067 $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1068 // Build checkboxen...
1069 if ( $this->mAllowed ) {
1070 if ( $this->mInvert ) {
1071 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1072 $checkBox = Xml::check( "ts$ts" );
1073 } else {
1074 $checkBox = Xml::check( "ts$ts", true );
1075 }
1076 } else {
1077 $checkBox = Xml::check( "ts$ts" );
1078 }
1079 } else {
1080 $checkBox = '';
1081 }
1082
1083 // Build page & diff links...
1084 $user = $this->getUser();
1085 if ( $this->mCanView ) {
1086 $titleObj = $this->getPageTitle();
1087 # Last link
1088 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1089 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1090 $last = $this->msg( 'diff' )->escaped();
1091 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1092 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1093 $last = $this->getLinkRenderer()->makeKnownLink(
1094 $titleObj,
1095 $this->msg( 'diff' )->text(),
1096 [],
1097 [
1098 'target' => $this->mTargetObj->getPrefixedText(),
1099 'timestamp' => $ts,
1100 'diff' => 'prev'
1101 ]
1102 );
1103 } else {
1104 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1105 $last = $this->msg( 'diff' )->escaped();
1106 }
1107 } else {
1108 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1109 $last = $this->msg( 'diff' )->escaped();
1110 }
1111
1112 // User links
1113 $userLink = Linker::revUserTools( $revRecord );
1114
1115 // Minor edit
1116 $minor = $revRecord->isMinor() ? ChangesList::flag( 'minor' ) : '';
1117
1118 // Revision text size
1119 $size = $row->ar_len;
1120 if ( $size !== null ) {
1121 $revTextSize = Linker::formatRevisionSize( $size );
1122 }
1123
1124 // Edit summary
1125 $comment = Linker::revComment( $revRecord );
1126
1127 // Tags
1128 $attribs = [];
1129 list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
1130 $row->ts_tags,
1131 'deletedhistory',
1132 $this->getContext()
1133 );
1134 if ( $classes ) {
1135 $attribs['class'] = implode( ' ', $classes );
1136 }
1137
1138 $revisionRow = $this->msg( 'undelete-revision-row2' )
1139 ->rawParams(
1140 $checkBox,
1141 $last,
1142 $pageLink,
1143 $userLink,
1144 $minor,
1145 $revTextSize,
1146 $comment,
1147 $tagSummary
1148 )
1149 ->escaped();
1150
1151 return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1152 }
1153
1154 private function formatFileRow( $row ) {
1156 $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1157 $user = $this->getUser();
1158
1159 $checkBox = '';
1160 if ( $this->mCanView && $row->fa_storage_key ) {
1161 if ( $this->mAllowed ) {
1162 $checkBox = Xml::check( 'fileid' . $row->fa_id );
1163 }
1164 $key = urlencode( $row->fa_storage_key );
1165 $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1166 } else {
1167 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1168 }
1169 $userLink = $this->getFileUser( $file );
1170 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1171 $bytes = $this->msg( 'parentheses' )
1172 ->plaintextParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1173 ->plain();
1174 $data = htmlspecialchars( $data . ' ' . $bytes );
1175 $comment = $this->getFileComment( $file );
1176
1177 // Add show/hide deletion links if available
1178 $canHide = $this->isAllowed( 'deleterevision' );
1179 if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1180 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1181 // Revision was hidden from sysops
1182 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1183 } else {
1184 $query = [
1185 'type' => 'filearchive',
1186 'target' => $this->mTargetObj->getPrefixedDBkey(),
1187 'ids' => $row->fa_id
1188 ];
1189 $revdlink = Linker::revDeleteLink( $query,
1190 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1191 }
1192 } else {
1193 $revdlink = '';
1194 }
1195
1196 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1197 }
1198
1207 private function getPageLink( RevisionRecord $revRecord, $titleObj, $ts ) {
1208 $user = $this->getUser();
1209 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1210
1211 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1212 // TODO The condition cannot be true when the function is called
1213 // TODO use Html::element and let it handle escaping
1214 return Html::rawElement(
1215 'span',
1216 [ 'class' => 'history-deleted' ],
1217 htmlspecialchars( $time )
1218 );
1219 }
1220
1221 $link = $this->getLinkRenderer()->makeKnownLink(
1222 $titleObj,
1223 $time,
1224 [],
1225 [
1226 'target' => $this->mTargetObj->getPrefixedText(),
1227 'timestamp' => $ts
1228 ]
1229 );
1230
1231 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1232 $class = Linker::getRevisionDeletedClass( $revRecord );
1233 $link = '<span class="' . $class . '">' . $link . '</span>';
1234 }
1235
1236 return $link;
1237 }
1238
1249 private function getFileLink( $file, $titleObj, $ts, $key ) {
1250 $user = $this->getUser();
1251 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1252
1253 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1254 // TODO use Html::element and let it handle escaping
1255 return Html::rawElement(
1256 'span',
1257 [ 'class' => 'history-deleted' ],
1258 htmlspecialchars( $time )
1259 );
1260 }
1261
1262 $link = $this->getLinkRenderer()->makeKnownLink(
1263 $titleObj,
1264 $time,
1265 [],
1266 [
1267 'target' => $this->mTargetObj->getPrefixedText(),
1268 'file' => $key,
1269 'token' => $user->getEditToken( $key )
1270 ]
1271 );
1272
1273 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1274 $link = '<span class="history-deleted">' . $link . '</span>';
1275 }
1276
1277 return $link;
1278 }
1279
1286 private function getFileUser( $file ) {
1287 $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() );
1288 if ( !$uploader ) {
1289 return Html::rawElement(
1290 'span',
1291 [ 'class' => 'history-deleted' ],
1292 $this->msg( 'rev-deleted-user' )->escaped()
1293 );
1294 }
1295
1296 $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1297 Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1298
1299 if ( $file->isDeleted( File::DELETED_USER ) ) {
1300 $link = Html::rawElement(
1301 'span',
1302 [ 'class' => 'history-deleted' ],
1303 $link
1304 );
1305 }
1306
1307 return $link;
1308 }
1309
1316 private function getFileComment( $file ) {
1317 if ( !$file->userCan( File::DELETED_COMMENT, $this->getAuthority() ) ) {
1318 return Html::rawElement(
1319 'span',
1320 [ 'class' => 'history-deleted' ],
1321 Html::rawElement(
1322 'span',
1323 [ 'class' => 'comment' ],
1324 $this->msg( 'rev-deleted-comment' )->escaped()
1325 )
1326 );
1327 }
1328
1329 $comment = $file->getDescription( File::FOR_THIS_USER, $this->getAuthority() );
1330 $link = Linker::commentBlock( $comment );
1331
1332 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1333 $link = Html::rawElement(
1334 'span',
1335 [ 'class' => 'history-deleted' ],
1336 $link
1337 );
1338 }
1339
1340 return $link;
1341 }
1342
1343 private function undelete() {
1344 if ( $this->getConfig()->get( 'UploadMaintenance' )
1345 && $this->mTargetObj->getNamespace() === NS_FILE
1346 ) {
1347 throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1348 }
1349
1350 $this->checkReadOnly();
1351
1352 $out = $this->getOutput();
1353 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1354 $this->getHookRunner()->onUndeleteForm__undelete( $archive, $this->mTargetObj );
1355 $ok = $archive->undeleteAsUser(
1356 $this->mTargetTimestamp,
1357 $this->getUser(),
1358 $this->mComment,
1359 $this->mFileVersions,
1360 $this->mUnsuppress
1361 );
1362
1363 if ( is_array( $ok ) ) {
1364 if ( $ok[1] ) { // Undeleted file count
1365 $this->getHookRunner()->onFileUndeleteComplete(
1366 $this->mTargetObj, $this->mFileVersions, $this->getUser(), $this->mComment );
1367 }
1368
1369 $link = $this->getLinkRenderer()->makeKnownLink( $this->mTargetObj );
1370 $out->addWikiMsg( 'undeletedpage', Message::rawParam( $link ) );
1371 } else {
1372 $out->setPageTitle( $this->msg( 'undelete-error' ) );
1373 }
1374
1375 // Show revision undeletion warnings and errors
1376 $status = $archive->getRevisionStatus();
1377 if ( $status && !$status->isGood() ) {
1378 $out->wrapWikiTextAsInterface(
1379 'error',
1380 '<div id="mw-error-cannotundelete">' .
1381 $status->getWikiText(
1382 'cannotundelete',
1383 'cannotundelete',
1384 $this->getLanguage()
1385 ) . '</div>'
1386 );
1387 }
1388
1389 // Show file undeletion warnings and errors
1390 $status = $archive->getFileStatus();
1391 if ( $status && !$status->isGood() ) {
1392 $out->wrapWikiTextAsInterface(
1393 'error',
1394 $status->getWikiText(
1395 'undelete-error-short',
1396 'undelete-error-long',
1397 $this->getLanguage()
1398 )
1399 );
1400 }
1401 }
1402
1411 public function prefixSearchSubpages( $search, $limit, $offset ) {
1412 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
1413 }
1414
1415 protected function getGroupName() {
1416 return 'pagetools';
1417 }
1418}
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:1064
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1299
static revComment(RevisionRecord $revRecord, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1782
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2436
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2360
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:185
static formatRevisionSize( $size)
Definition Linker.php:1820
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition Linker.php:1749
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1319
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:1109
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2412
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:1090
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:33
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.
int[] null $mFileVersions
IContentHandlerFactory $contentHandlerFactory
showFileConfirmationForm( $key)
Show a form confirming whether a tokenless user really wants to see a file.
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 $mSearchPrefix
Search prefix.
execute( $par)
Default execute method Checks user permissions.
showRevision( $timestamp)
getFileLink( $file, $titleObj, $ts, $key)
Fetch image view link if it's available to all users.
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...
getPageLink(RevisionRecord $revRecord, $titleObj, $ts)
Fetch revision text link if it's available to all users.
formatRevisionRow( $row, $earliestLiveTime, $remaining)
__construct(PermissionManager $permissionManager, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, IContentHandlerFactory $contentHandlerFactory, NameTableStore $changeTagDefStore, LinkBatchFactory $linkBatchFactory, RepoGroup $repoGroup, ILoadBalancer $loadBalancer, UserOptionsLookup $userOptionsLookup, WikiPageFactory $wikiPageFactory, SearchEngineFactory $searchEngineFactory)
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:48
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:69
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