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