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