96 private const REVISION_HISTORY_LIMIT = 500;
113 private $mTargetTimestamp = [];
119 private $mComment =
'';
129 private $mUnsuppress;
131 private $mFileVersions = [];
133 private $mUndeleteTalk;
135 private $mHistoryOffset;
142 private $mSearchPrefix;
194 parent::__construct(
'Undelete',
'deletedhistory' );
195 $this->permissionManager = $permissionManager;
196 $this->revisionStore = $revisionStore;
197 $this->revisionRenderer = $revisionRenderer;
198 $this->contentHandlerFactory = $contentHandlerFactory;
199 $this->changeTagDefStore = $changeTagDefStore;
200 $this->linkBatchFactory = $linkBatchFactory;
202 $this->dbProvider = $dbProvider;
203 $this->userOptionsLookup = $userOptionsLookup;
204 $this->wikiPageFactory = $wikiPageFactory;
205 $this->searchEngineFactory = $searchEngineFactory;
206 $this->undeletePageFactory = $undeletePageFactory;
207 $this->archivedRevisionLookup = $archivedRevisionLookup;
208 $this->commentFormatter = $commentFormatter;
209 $this->watchlistManager = $watchlistManager;
216 private function loadRequest( $par ) {
220 $this->mAction = $request->getRawVal(
'action' );
221 if ( $par !==
null && $par !==
'' ) {
222 $this->mTarget = $par;
224 $this->mTarget = $request->getVal(
'target' );
227 $this->mTargetObj =
null;
229 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
230 $this->mTargetObj = Title::newFromText( $this->mTarget );
233 $this->mSearchPrefix = $request->getText(
'prefix' );
234 $time = $request->getVal(
'timestamp' );
235 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
236 $this->mFilename = $request->getVal(
'file' );
238 $posted = $request->wasPosted() &&
239 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
240 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
241 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
242 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
243 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
244 $this->mDiff = $request->getCheck(
'diff' );
245 $this->mDiffOnly = $request->getBool(
'diffonly',
246 $this->userOptionsLookup->getOption( $this->getUser(),
'diffonly' ) );
247 $commentList = $request->getText(
'wpCommentList',
'other' );
248 $comment = $request->getText(
'wpComment' );
249 if ( $commentList ===
'other' ) {
250 $this->mComment = $comment;
251 } elseif ( $comment !==
'' ) {
252 $this->mComment = $commentList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() . $comment;
254 $this->mComment = $commentList;
256 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) &&
257 $this->permissionManager->userHasRight( $user,
'suppressrevision' );
258 $this->mToken = $request->getVal(
'token' );
259 $this->mUndeleteTalk = $request->getCheck(
'undeletetalk' );
260 $this->mHistoryOffset = $request->getVal(
'historyoffset' );
263 $this->mAllowed =
true;
264 $this->mCanView =
true;
265 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
266 $this->mAllowed =
false;
267 $this->mCanView =
true;
268 $this->mRestore =
false;
270 $this->mAllowed =
false;
271 $this->mCanView =
false;
272 $this->mTimestamp =
'';
273 $this->mRestore =
false;
276 if ( $this->mRestore || $this->mInvert ) {
278 $this->mFileVersions = [];
279 foreach ( $request->getValues() as $key => $val ) {
281 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
285 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
286 $this->mFileVersions[] = intval(
$matches[1] );
289 rsort( $timestamps );
290 $this->mTargetTimestamp = $timestamps;
304 $block = $user->getBlock();
306 if ( $this->mTargetObj !==
null ) {
307 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
309 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
310 $sitewideBlock = $block && $block->isSitewide();
311 return $permission ===
'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
316 return $this->
isAllowed( $this->mRestriction, $user );
327 if ( !parent::userCanExecute( $user ) ) {
334 $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
352 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
354 $this->loadRequest( $par );
359 if ( $this->mTargetObj ===
null ) {
360 $out->addWikiMsg(
'undelete-header' );
362 # Not all users can just browse every deleted page from the list
363 if ( $this->permissionManager->userHasRight( $user,
'browsearchive' ) ) {
364 $this->showSearchForm();
371 if ( $this->mAllowed ) {
372 $out->setPageTitleMsg( $this->
msg(
'undeletepage' ) );
374 $out->setPageTitleMsg( $this->
msg(
'viewdeletedpage' ) );
377 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
379 if ( $this->mTimestamp !==
'' ) {
380 $this->showRevision( $this->mTimestamp );
381 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
382 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
384 if ( !$file->exists() ) {
385 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
386 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
387 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
392 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
393 $this->showFileConfirmationForm( $this->mFilename );
395 $this->showFile( $this->mFilename );
397 } elseif ( $this->mAction ===
'submit' ) {
398 if ( $this->mRestore ) {
400 } elseif ( $this->mRevdel ) {
401 $this->redirectToRevDel();
403 } elseif ( $this->mAction ===
'render' ) {
414 private function redirectToRevDel() {
417 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
419 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
420 $revisionRecord = $this->archivedRevisionLookup
421 ->getRevisionRecordByTimestamp( $this->mTargetObj,
$matches[1] );
422 if ( $revisionRecord ) {
424 $revisions[ $revisionRecord->getId() ] = 1;
430 'type' =>
'revision',
432 'target' => $this->mTargetObj->getPrefixedText()
438 private function showSearchForm() {
440 $out->setPageTitleMsg( $this->
msg(
'undelete-search-title' ) );
441 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
'1' );
446 $fields[] =
new ActionFieldLayout(
447 new TextInputWidget( [
449 'inputId' =>
'prefix',
451 'value' => $this->mSearchPrefix,
454 new ButtonInputWidget( [
455 'label' => $this->
msg(
'undelete-search-submit' )->text(),
456 'flags' => [
'primary',
'progressive' ],
457 'inputId' =>
'searchUndelete',
461 'label' =>
new HtmlSnippet(
463 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
470 $fieldset =
new FieldsetLayout( [
471 'label' => $this->
msg(
'undelete-search-box' )->text(),
475 $form =
new FormLayout( [
480 $form->appendContent(
483 Html::hidden(
'title', $this->
getPageTitle()->getPrefixedDBkey() ) .
484 Html::hidden(
'fuzzy', $fuzzySearch )
497 # List undeletable articles
498 if ( $this->mSearchPrefix ) {
501 if ( $fuzzySearch ) {
506 $this->showList( $result );
516 private function showList( $result ) {
519 if ( $result->numRows() == 0 ) {
520 $out->addWikiMsg(
'undelete-no-results' );
525 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
529 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
530 foreach ( $result as $row ) {
531 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
532 if ( $title !==
null ) {
533 $item = $linkRenderer->makeKnownLink(
535 $title->getPrefixedText(),
537 [
'target' => $title->getPrefixedText() ]
543 [
'class' =>
'mw-invalidtitle' ],
544 Linker::getInvalidTitleDescription(
551 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
555 [
'class' =>
'undeleteResult' ],
556 $item . $this->
msg(
'word-separator' )->escaped() .
557 $this->
msg(
'parentheses' )->rawParams( $revs )->escaped()
562 $out->addHTML(
"</ul>\n" );
567 private function showRevision( $timestamp ) {
568 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
572 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
578 $this->
msg(
'undelete-back-to-list' )->text(),
580 [
'target' => $this->mTargetObj->getPrefixedText() ]
583 $subtitle =
"< $listLink";
584 $out->setSubtitle( $subtitle );
589 $archive, $this->mTargetObj )
593 $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
598 $out->addWikiMsg(
'undeleterevision-missing' );
602 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
604 $titleText = $this->mTargetObj->getPrefixedDBkey();
605 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
606 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
607 ? [
'rev-suppressed-text-permission', $titleText ]
608 : [
'rev-deleted-text-permission', $titleText ];
611 $this->
msg( $msg[0], $msg[1] )->parse(),
618 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
619 ? [
'rev-suppressed-text-view', $titleText ]
620 : [
'rev-deleted-text-view', $titleText ];
623 $this->
msg( $msg[0], $msg[1] )->parse(),
630 if ( $this->mDiff ) {
631 $previousRevRecord = $this->archivedRevisionLookup
632 ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
633 if ( $previousRevRecord ) {
634 $this->showDiff( $previousRevRecord, $revRecord );
635 if ( $this->mDiffOnly ) {
639 $out->addHTML(
'<hr />' );
641 $out->addWikiMsg(
'undelete-nodiff' );
646 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
647 $this->mTargetObj->getPrefixedText()
654 $time = $lang->userTimeAndDate( $timestamp, $user );
655 $d = $lang->userDate( $timestamp, $user );
656 $t = $lang->userTime( $timestamp, $user );
657 $userLink = Linker::revUserTools( $revRecord );
660 $content = $revRecord->getContent(
662 RevisionRecord::FOR_THIS_USER,
665 }
catch ( RevisionAccessException $e ) {
670 $isText = ( $content instanceof TextContent );
672 $undeleteRevisionContent =
'';
674 if ( !$this->mDiff ) {
675 $revdel = Linker::getRevDeleteLink(
681 $undeleteRevisionContent = $revdel .
' ';
685 $undeleteRevisionContent .= $out->msg(
694 if ( $this->mPreview || $isText ) {
697 $undeleteRevisionContent,
698 'mw-undelete-revision'
705 [
'class' =>
'mw-undelete-revision', ],
706 $undeleteRevisionContent
711 if ( $this->mPreview || !$isText ) {
714 $popts = $out->parserOptions();
717 $rendered = $this->revisionRenderer->getRenderedRevision(
721 [
'audience' => RevisionRecord::FOR_THIS_USER,
'causeAction' =>
'undelete-preview' ]
726 $pout = $rendered->getRevisionParserOutput();
728 $out->addParserOutput( $pout, [
729 'enableSectionEditLinks' =>
false,
731 }
catch ( RevisionAccessException $e ) {
739 '@phan-var TextContent $content';
743 'readonly' =>
'readonly',
746 ], $content->getText() .
"\n" );
748 $buttonFields[] =
new ButtonInputWidget( [
751 'label' => $this->
msg(
'showpreview' )->text()
757 $buttonFields[] =
new ButtonInputWidget( [
760 'label' => $this->
msg(
'showdiff' )->text()
766 'style' =>
'clear: both' ] ) .
769 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
773 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
776 'name' =>
'timestamp',
777 'value' => $timestamp ] ) .
780 'name' =>
'wpEditToken',
781 'value' => $user->getEditToken() ] ) .
784 'content' =>
new HorizontalLayout( [
785 'items' => $buttonFields
801 private function showDiff(
802 RevisionRecord $previousRevRecord,
803 RevisionRecord $currentRevRecord
805 $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
807 $diffContext =
new DerivativeContext( $this->
getContext() );
808 $diffContext->setTitle( $currentTitle );
809 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
811 $contentModel = $currentRevRecord->getSlot(
816 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
817 ->createDifferenceEngine( $diffContext );
819 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
820 $diffEngine->showDiffStyle();
821 $formattedDiff = $diffEngine->getDiff(
822 $this->diffHeader( $previousRevRecord,
'o' ),
823 $this->diffHeader( $currentRevRecord,
'n' )
826 if ( $formattedDiff ===
false ) {
827 if ( $diffEngine->hasSuppressedRevision() ) {
828 $error =
'rev-suppressed-no-diff';
829 } elseif ( $diffEngine->hasDeletedRevision() ) {
830 $error =
'rev-deleted-no-diff';
833 $error =
'undelete-error-loading-diff';
835 $this->
getOutput()->addHTML( $this->
msg( $error )->parse() );
837 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
846 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
847 if ( $revRecord instanceof RevisionArchiveRecord ) {
851 'target' => $this->mTargetObj->getPrefixedText(),
852 'timestamp' =>
wfTimestamp( TS_MW, $revRecord->getTimestamp() )
856 $targetPage = $revRecord->getPageAsLinkTarget();
857 $targetQuery = [
'oldid' => $revRecord->getId() ];
863 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
871 $dbr = $this->dbProvider->getReplicaDatabase();
872 $tagIds = $dbr->newSelectQueryBuilder()
873 ->select(
'ct_tag_id' )
874 ->from(
'change_tag' )
875 ->where( [
'ct_rev_id' => $revRecord->getId() ] )
876 ->caller( __METHOD__ )->fetchFieldValues();
878 foreach ( $tagIds as $tagId ) {
880 $tags[] = $this->changeTagDefStore->getName( (
int)$tagId );
881 }
catch ( NameTableAccessException $exception ) {
885 $tags = implode(
',', $tags );
891 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
892 $lang->userDate( $revRecord->getTimestamp(), $user ),
893 $lang->userTime( $revRecord->getTimestamp(), $user )
898 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
899 $asof = Html::rawElement(
901 [
'class' => Linker::getRevisionDeletedClass( $revRecord ) ],
908 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
911 '<div id="mw-diff-' . $prefix .
'title2">' .
912 Linker::revUserTools( $revRecord ) .
'<br />' .
914 '<div id="mw-diff-' . $prefix .
'title3">' .
915 $minor . $this->commentFormatter->formatRevision( $revRecord, $user ) . $rdel .
'<br />' .
917 '<div id="mw-diff-' . $prefix .
'title5">' .
918 $tagSummary[0] .
'<br />' .
926 private function showFileConfirmationForm( $key ) {
930 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
931 $out->addWikiMsg(
'undelete-show-file-confirm',
932 $this->mTargetObj->getText(),
933 $lang->userDate( $file->getTimestamp(), $user ),
934 $lang->userTime( $file->getTimestamp(), $user ) );
936 Html::rawElement(
'form', [
939 'target' => $this->mTarget,
941 'token' => $user->getEditToken( $key ),
953 private function showFile( $key ) {
956 # We mustn't allow the output to be CDN cached, otherwise
957 # if an admin previews a deleted image, and it's cached, then
958 # a user without appropriate permissions can toddle off and
959 # nab the image, and CDN will serve it
961 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
962 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
964 $path = $this->localRepo->getZonePath(
'deleted' ) .
'/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
965 $this->localRepo->streamFileWithStatus(
$path );
968 private function addRevisionsToBatch( LinkBatch $batch, IResultWrapper $revisions ) {
969 foreach ( $revisions as $row ) {
970 $batch->add(
NS_USER, $row->ar_user_text );
975 private function addFilesToBatch( LinkBatch $batch, IResultWrapper $files ) {
976 foreach ( $files as $row ) {
977 $batch->add(
NS_USER, $row->fa_user_text );
987 $out->setArticleBodyOnly(
true );
988 $dbr = $this->dbProvider->getReplicaDatabase();
989 if ( $this->mHistoryOffset ) {
990 $extraConds = [ $dbr->expr(
'ar_timestamp',
'<', $dbr->timestamp( $this->mHistoryOffset ) ) ];
994 $revisions = $this->archivedRevisionLookup->listRevisions(
997 self::REVISION_HISTORY_LIMIT + 1
999 $batch = $this->linkBatchFactory->newLinkBatch();
1000 $this->addRevisionsToBatch( $batch, $revisions );
1004 if ( $revisions->numRows() > self::REVISION_HISTORY_LIMIT ) {
1006 $out->setStatusCode( 206 );
1017 $history = Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1020 $numRevisions = $revisions->
numRows();
1021 $displayCount = min( $numRevisions, self::REVISION_HISTORY_LIMIT );
1022 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1023 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() :
null;
1025 $revisions->rewind();
1026 for ( $i = 0; $i < $displayCount; $i++ ) {
1030 $history .= $this->
formatRevisionRow( $row, $earliestLiveTime, $numRevisions - $i );
1032 $history .= Html::closeElement(
'ul' );
1040 if ( $this->mAllowed ) {
1041 $out->addModules(
'mediawiki.misc-authed-ooui' );
1042 $out->addModuleStyles(
'mediawiki.special' );
1044 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
1046 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1047 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
1052 $this->
getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
1054 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-undelete-history' ] ) );
1055 if ( $this->mAllowed ) {
1056 $out->addWikiMsg(
'undeletehistory' );
1057 $out->addWikiMsg(
'undeleterevdel' );
1059 $out->addWikiMsg(
'undeletehistorynoadmin' );
1061 $out->addHTML( Html::closeElement(
'div' ) );
1063 # List all stored revisions
1064 $revisions = $this->archivedRevisionLookup->listRevisions(
1067 self::REVISION_HISTORY_LIMIT + 1
1069 $files = $archive->listFiles();
1070 $numRevisions = $revisions->numRows();
1071 $showLoadMore = $numRevisions > self::REVISION_HISTORY_LIMIT;
1072 $haveRevisions = $numRevisions > 0;
1073 $haveFiles = $files && $files->numRows() > 0;
1075 # Batch existence check on user and talk pages
1076 if ( $haveRevisions || $haveFiles ) {
1077 $batch = $this->linkBatchFactory->newLinkBatch();
1078 $this->addRevisionsToBatch( $batch, $revisions );
1081 $this->addFilesToBatch( $batch, $files );
1086 if ( $this->mAllowed ) {
1089 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
1090 # Start the form here
1091 $form =
new FormLayout( [
1093 'action' => $action,
1098 # Show relevant lines from the deletion log:
1099 $deleteLogPage =
new LogPage(
'delete' );
1100 $out->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
1101 LogEventsList::showLogExtract( $out,
'delete', $this->mTargetObj );
1102 # Show relevant lines from the suppression log:
1103 $suppressLogPage =
new LogPage(
'suppress' );
1104 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
1105 $out->addHTML(
Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
1106 LogEventsList::showLogExtract( $out,
'suppress', $this->mTargetObj );
1109 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1110 $unsuppressAllowed = $this->permissionManager->userHasRight( $this->
getUser(),
'suppressrevision' );
1112 $fields[] =
new Layout( [
1113 'content' =>
new HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
1116 $dropdownComment = $this->
msg(
'undelete-comment-dropdown' )
1117 ->page( $this->mTargetObj )->inContentLanguage()->text();
1119 if ( $unsuppressAllowed ) {
1120 $dropdownComment .=
"\n" . $this->
msg(
'undelete-comment-dropdown-unsuppress' )
1121 ->page( $this->mTargetObj )->inContentLanguage()->text();
1125 [
'other' => $this->
msg(
'undeletecommentotherlist' )->text() ]
1129 $fields[] =
new FieldLayout(
1130 new DropdownInputWidget( [
1131 'name' =>
'wpCommentList',
1132 'inputId' =>
'wpCommentList',
1133 'infusable' =>
true,
1134 'value' => $this->
getRequest()->getText(
'wpCommentList',
'other' ),
1135 'options' => $options,
1138 'label' => $this->
msg(
'undeletecomment' )->text(),
1143 $fields[] =
new FieldLayout(
1144 new TextInputWidget( [
1145 'name' =>
'wpComment',
1146 'inputId' =>
'wpComment',
1147 'infusable' =>
true,
1148 'value' => $this->
getRequest()->getText(
'wpComment' ),
1149 'autofocus' =>
true,
1153 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
1156 'label' => $this->
msg(
'undeleteothercomment' )->text(),
1161 if ( $this->
getUser()->isRegistered() ) {
1162 $checkWatch = $this->watchlistManager->isWatched( $this->
getUser(), $this->mTargetObj )
1163 || $this->
getRequest()->getText(
'wpWatch' );
1164 $fields[] =
new FieldLayout(
1165 new CheckboxInputWidget( [
1166 'name' =>
'wpWatch',
1167 'inputId' =>
'mw-undelete-watch',
1169 'selected' => $checkWatch,
1172 'label' => $this->
msg(
'watchthis' )->text(),
1173 'align' =>
'inline',
1178 if ( $unsuppressAllowed ) {
1179 $fields[] =
new FieldLayout(
1180 new CheckboxInputWidget( [
1181 'name' =>
'wpUnsuppress',
1182 'inputId' =>
'mw-undelete-unsuppress',
1186 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
1187 'align' =>
'inline',
1192 $undelPage = $this->undeletePageFactory->newUndeletePage(
1193 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1194 $this->getContext()->getAuthority()
1196 if ( $undelPage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1197 $fields[] =
new FieldLayout(
1198 new CheckboxInputWidget( [
1199 'name' =>
'undeletetalk',
1200 'inputId' =>
'mw-undelete-undeletetalk',
1201 'selected' =>
false,
1204 'label' => $this->
msg(
'undelete-undeletetalk' )->text(),
1205 'align' =>
'inline',
1210 $fields[] =
new FieldLayout(
1212 'content' =>
new HorizontalLayout( [
1214 new ButtonInputWidget( [
1215 'name' =>
'restore',
1216 'inputId' =>
'mw-undelete-submit',
1218 'label' => $this->
msg(
'undeletebtn' )->text(),
1219 'flags' => [
'primary',
'progressive' ],
1222 new ButtonInputWidget( [
1224 'inputId' =>
'mw-undelete-invert',
1226 'label' => $this->
msg(
'undeleteinvert' )->text()
1233 $fieldset =
new FieldsetLayout( [
1234 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
1235 'id' =>
'mw-undelete-table',
1241 if ( $unsuppressAllowed ) {
1243 $this->
msg(
'undelete-comment-dropdown-unsuppress' )->inContentLanguage()->
getTitle(),
1244 $this->
msg(
'undelete-edit-commentlist-unsuppress' )->text(),
1246 [
'action' =>
'edit' ]
1248 $link .= $this->
msg(
'pipe-separator' )->escaped();
1251 $this->
msg(
'undelete-comment-dropdown' )->inContentLanguage()->
getTitle(),
1252 $this->
msg(
'undelete-edit-commentlist' )->text(),
1254 [
'action' =>
'edit' ]
1257 $link = Html::rawElement(
'p', [
'class' =>
'mw-undelete-editcomments' ], $link );
1261 $form->appendContent(
1263 'expanded' =>
false,
1266 'content' => $fieldset,
1270 Html::hidden(
'target', $this->mTarget ) .
1271 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() )
1277 $history .=
Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
1279 if ( $haveRevisions ) {
1280 # Show the page's stored (deleted) history
1282 if ( $this->mAllowed && $this->permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
1288 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
1290 $this->
msg(
'showhideselectedversions' )->text()
1296 if ( $showLoadMore ) {
1298 Html::openElement(
'div' ) .
1301 [
'id' =>
'mw-load-more-revisions' ],
1302 $this->
msg(
'undelete-load-more-revisions' )->text()
1304 Html::closeElement(
'div' ) .
1308 $out->addWikiMsg(
'nohistory' );
1312 $history .=
Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
1313 $history .= Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1314 foreach ( $files as $row ) {
1315 $history .= $this->formatFileRow( $row );
1318 $history .= Html::closeElement(
'ul' );
1321 if ( $this->mAllowed ) {
1322 # Slip in the hidden controls here
1323 $misc = Html::hidden(
'target', $this->mTarget );
1324 $misc .= Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() );
1328 $form->appendContent(
new HtmlSnippet( $history ) );
1330 $out->addHTML( (
string)$form );
1332 $out->addHTML( $history );
1339 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1341 IDBAccessObject::READ_NORMAL,
1348 if ( $this->mAllowed ) {
1349 if ( $this->mInvert ) {
1350 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1364 if ( $this->mCanView ) {
1367 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1368 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1369 $last = $this->
msg(
'diff' )->escaped();
1370 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1371 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1374 $this->
msg(
'diff' )->text(),
1377 'target' => $this->mTargetObj->getPrefixedText(),
1383 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1384 $last = $this->
msg(
'diff' )->escaped();
1387 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1388 $last = $this->
msg(
'diff' )->escaped();
1392 $userLink = Linker::revUserTools( $revRecord );
1395 $minor = $revRecord->isMinor() ? ChangesList::flag(
'minor' ) :
'';
1398 $size = $row->ar_len;
1399 if ( $size !==
null ) {
1400 $revTextSize = Linker::formatRevisionSize( $size );
1404 $comment = $this->commentFormatter->formatRevision( $revRecord, $user );
1414 $attribs[
'class'] = implode(
' ', $classes );
1417 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1430 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1433 private function formatFileRow( $row ) {
1434 $file = ArchivedFile::newFromRow( $row );
1439 if ( $this->mCanView && $row->fa_storage_key ) {
1440 if ( $this->mAllowed ) {
1441 $checkBox =
Xml::check(
'fileid' . $row->fa_id );
1443 $key = urlencode( $row->fa_storage_key );
1444 $pageLink = $this->getFileLink( $file, $this->
getPageTitle(), $ts, $key );
1446 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1448 $userLink = $this->getFileUser( $file );
1449 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1450 $bytes = $this->
msg(
'parentheses' )
1451 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1453 $data = htmlspecialchars( $data .
' ' . $bytes );
1454 $comment = $this->getFileComment( $file );
1457 $canHide = $this->
isAllowed(
'deleterevision' );
1458 if ( $canHide || ( $file->getVisibility() && $this->isAllowed(
'deletedhistory' ) ) ) {
1459 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1461 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1464 'type' =>
'filearchive',
1465 'target' => $this->mTargetObj->getPrefixedDBkey(),
1466 'ids' => $row->fa_id
1468 $revdlink = Linker::revDeleteLink( $query,
1469 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1475 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1486 private function getPageLink( RevisionRecord $revRecord, LinkTarget $target, $ts ) {
1488 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1490 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1494 [
'class' =>
'history-deleted' ],
1504 'target' => $this->mTargetObj->getPrefixedText(),
1509 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1510 $class = Linker::getRevisionDeletedClass( $revRecord );
1511 $link =
'<span class="' . $class .
'">' . $link .
'</span>';
1527 private function getFileLink( $file, LinkTarget $target, $ts, $key ) {
1529 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1531 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1534 [
'class' =>
'history-deleted' ],
1539 if ( $file->exists() ) {
1545 'target' => $this->mTargetObj->getPrefixedText(),
1547 'token' => $user->getEditToken( $key )
1551 $link = htmlspecialchars( $time );
1554 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1555 $link =
'<span class="history-deleted">' . $link .
'</span>';
1567 private function getFileUser( $file ) {
1568 $uploader = $file->getUploader( File::FOR_THIS_USER, $this->
getAuthority() );
1570 return Html::rawElement(
1572 [
'class' =>
'history-deleted' ],
1573 $this->
msg(
'rev-deleted-user' )->escaped()
1577 $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1578 Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1580 if ( $file->isDeleted( File::DELETED_USER ) ) {
1581 $link = Html::rawElement(
1583 [
'class' =>
'history-deleted' ],
1597 private function getFileComment( $file ) {
1598 if ( !$file->userCan( File::DELETED_COMMENT, $this->getAuthority() ) ) {
1599 return Html::rawElement(
1601 [
'class' =>
'history-deleted' ],
1604 [
'class' =>
'comment' ],
1605 $this->
msg(
'rev-deleted-comment' )->escaped()
1610 $comment = $file->getDescription( File::FOR_THIS_USER, $this->
getAuthority() );
1611 $link = $this->commentFormatter->formatBlock( $comment );
1613 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1614 $link = Html::rawElement(
1616 [
'class' =>
'history-deleted' ],
1624 private function undelete() {
1626 && $this->mTargetObj->getNamespace() ===
NS_FILE
1628 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1634 $undeletePage = $this->undeletePageFactory->newUndeletePage(
1635 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1636 $this->getAuthority()
1638 if ( $this->mUndeleteTalk && $undeletePage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1639 $undeletePage->setUndeleteAssociatedTalk(
true );
1641 $status = $undeletePage
1642 ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1643 ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1644 ->setUnsuppress( $this->mUnsuppress )
1646 ->undeleteIfAllowed( $this->mComment );
1648 if ( !$status->isGood() ) {
1649 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1650 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1651 foreach ( $status->getMessages() as $msg ) {
1652 $out->addHTML( Html::errorBox(
1653 $this->
msg( $msg )->parse()
1659 $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
1660 $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
1662 if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1664 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1666 if ( $status->getValue()[UndeletePage::FILES_RESTORED] !== 0 ) {
1668 $this->mTargetObj, $this->mFileVersions, $this->
getUser(), $this->mComment );
1674 $this->watchlistManager->setWatch(
1691 return $this->
prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );