97 private const REVISION_HISTORY_LIMIT = 500;
114 private $mTargetTimestamp = [];
120 private $mComment =
'';
130 private $mUnsuppress;
132 private $mFileVersions = [];
134 private $mUndeleteTalk;
136 private $mHistoryOffset;
143 private $mSearchPrefix;
195 parent::__construct(
'Undelete',
'deletedhistory' );
196 $this->permissionManager = $permissionManager;
197 $this->revisionStore = $revisionStore;
198 $this->revisionRenderer = $revisionRenderer;
199 $this->contentHandlerFactory = $contentHandlerFactory;
200 $this->changeTagDefStore = $changeTagDefStore;
201 $this->linkBatchFactory = $linkBatchFactory;
203 $this->dbProvider = $dbProvider;
204 $this->userOptionsLookup = $userOptionsLookup;
205 $this->wikiPageFactory = $wikiPageFactory;
206 $this->searchEngineFactory = $searchEngineFactory;
207 $this->undeletePageFactory = $undeletePageFactory;
208 $this->archivedRevisionLookup = $archivedRevisionLookup;
209 $this->commentFormatter = $commentFormatter;
210 $this->watchlistManager = $watchlistManager;
217 private function loadRequest( $par ) {
221 $this->mAction = $request->getRawVal(
'action' );
222 if ( $par !==
null && $par !==
'' ) {
223 $this->mTarget = $par;
225 $this->mTarget = $request->getVal(
'target' );
228 $this->mTargetObj =
null;
230 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
231 $this->mTargetObj = Title::newFromText( $this->mTarget );
234 $this->mSearchPrefix = $request->getText(
'prefix' );
235 $time = $request->getVal(
'timestamp' );
236 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
237 $this->mFilename = $request->getVal(
'file' );
239 $posted = $request->wasPosted() &&
240 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
241 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
242 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
243 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
244 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
245 $this->mDiff = $request->getCheck(
'diff' );
246 $this->mDiffOnly = $request->getBool(
'diffonly',
247 $this->userOptionsLookup->getOption( $this->getUser(),
'diffonly' ) );
248 $commentList = $request->getText(
'wpCommentList',
'other' );
249 $comment = $request->getText(
'wpComment' );
250 if ( $commentList ===
'other' ) {
251 $this->mComment = $comment;
252 } elseif ( $comment !==
'' ) {
253 $this->mComment = $commentList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() . $comment;
255 $this->mComment = $commentList;
257 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) &&
258 $this->permissionManager->userHasRight( $user,
'suppressrevision' );
259 $this->mToken = $request->getVal(
'token' );
260 $this->mUndeleteTalk = $request->getCheck(
'undeletetalk' );
261 $this->mHistoryOffset = $request->getVal(
'historyoffset' );
264 $this->mAllowed =
true;
265 $this->mCanView =
true;
266 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
267 $this->mAllowed =
false;
268 $this->mCanView =
true;
269 $this->mRestore =
false;
271 $this->mAllowed =
false;
272 $this->mCanView =
false;
273 $this->mTimestamp =
'';
274 $this->mRestore =
false;
277 if ( $this->mRestore || $this->mInvert ) {
279 $this->mFileVersions = [];
280 foreach ( $request->getValues() as $key => $val ) {
282 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
286 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
287 $this->mFileVersions[] = intval(
$matches[1] );
290 rsort( $timestamps );
291 $this->mTargetTimestamp = $timestamps;
305 $block = $user->getBlock();
307 if ( $this->mTargetObj !==
null ) {
308 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
310 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
311 $sitewideBlock = $block && $block->isSitewide();
312 return $permission ===
'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
317 return $this->
isAllowed( $this->mRestriction, $user );
328 if ( !parent::userCanExecute( $user ) ) {
335 $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
353 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
355 $this->loadRequest( $par );
360 if ( $this->mTargetObj ===
null ) {
361 $out->addWikiMsg(
'undelete-header' );
363 # Not all users can just browse every deleted page from the list
364 if ( $this->permissionManager->userHasRight( $user,
'browsearchive' ) ) {
365 $this->showSearchForm();
372 if ( $this->mAllowed ) {
373 $out->setPageTitleMsg( $this->
msg(
'undeletepage' ) );
375 $out->setPageTitleMsg( $this->
msg(
'viewdeletedpage' ) );
378 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
380 if ( $this->mTimestamp !==
'' ) {
381 $this->showRevision( $this->mTimestamp );
382 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
383 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
385 if ( !$file->exists() ) {
386 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
387 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
388 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
393 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
394 $this->showFileConfirmationForm( $this->mFilename );
396 $this->showFile( $this->mFilename );
398 } elseif ( $this->mAction ===
'submit' ) {
399 if ( $this->mRestore ) {
401 } elseif ( $this->mRevdel ) {
402 $this->redirectToRevDel();
404 } elseif ( $this->mAction ===
'render' ) {
415 private function redirectToRevDel() {
418 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
420 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
421 $revisionRecord = $this->archivedRevisionLookup
422 ->getRevisionRecordByTimestamp( $this->mTargetObj,
$matches[1] );
423 if ( $revisionRecord ) {
425 $revisions[ $revisionRecord->getId() ] = 1;
431 'type' =>
'revision',
433 'target' => $this->mTargetObj->getPrefixedText()
439 private function showSearchForm() {
441 $out->setPageTitleMsg( $this->
msg(
'undelete-search-title' ) );
442 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
'1' );
447 $fields[] =
new ActionFieldLayout(
448 new TextInputWidget( [
450 'inputId' =>
'prefix',
452 'value' => $this->mSearchPrefix,
455 new ButtonInputWidget( [
456 'label' => $this->
msg(
'undelete-search-submit' )->text(),
457 'flags' => [
'primary',
'progressive' ],
458 'inputId' =>
'searchUndelete',
462 'label' =>
new HtmlSnippet(
464 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
471 $fieldset =
new FieldsetLayout( [
472 'label' => $this->
msg(
'undelete-search-box' )->text(),
476 $form =
new FormLayout( [
481 $form->appendContent(
484 Html::hidden(
'title', $this->
getPageTitle()->getPrefixedDBkey() ) .
485 Html::hidden(
'fuzzy', $fuzzySearch )
498 # List undeletable articles
499 if ( $this->mSearchPrefix ) {
502 if ( $fuzzySearch ) {
507 $this->showList( $result );
517 private function showList( $result ) {
520 if ( $result->numRows() == 0 ) {
521 $out->addWikiMsg(
'undelete-no-results' );
526 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
530 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
531 foreach ( $result as $row ) {
532 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
533 if ( $title !==
null ) {
534 $item = $linkRenderer->makeKnownLink(
536 $title->getPrefixedText(),
538 [
'target' => $title->getPrefixedText() ]
544 [
'class' =>
'mw-invalidtitle' ],
545 Linker::getInvalidTitleDescription(
552 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
556 [
'class' =>
'undeleteResult' ],
557 $item . $this->
msg(
'word-separator' )->escaped() .
558 $this->
msg(
'parentheses' )->rawParams( $revs )->escaped()
563 $out->addHTML(
"</ul>\n" );
568 private function showRevision( $timestamp ) {
569 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
573 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
579 $this->
msg(
'undelete-back-to-list' )->text(),
581 [
'target' => $this->mTargetObj->getPrefixedText() ]
584 $subtitle =
"< $listLink";
585 $out->setSubtitle( $subtitle );
590 $archive, $this->mTargetObj )
594 $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
599 $out->addWikiMsg(
'undeleterevision-missing' );
603 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
605 $titleText = $this->mTargetObj->getPrefixedDBkey();
606 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
607 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
608 ? [
'rev-suppressed-text-permission', $titleText ]
609 : [
'rev-deleted-text-permission', $titleText ];
612 $this->
msg( $msg[0], $msg[1] )->parse(),
619 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
620 ? [
'rev-suppressed-text-view', $titleText ]
621 : [
'rev-deleted-text-view', $titleText ];
624 $this->
msg( $msg[0], $msg[1] )->parse(),
631 if ( $this->mDiff ) {
632 $previousRevRecord = $this->archivedRevisionLookup
633 ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
634 if ( $previousRevRecord ) {
635 $this->showDiff( $previousRevRecord, $revRecord );
636 if ( $this->mDiffOnly ) {
640 $out->addHTML(
'<hr />' );
642 $out->addWikiMsg(
'undelete-nodiff' );
647 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
648 $this->mTargetObj->getPrefixedText()
655 $time = $lang->userTimeAndDate( $timestamp, $user );
656 $d = $lang->userDate( $timestamp, $user );
657 $t = $lang->userTime( $timestamp, $user );
658 $userLink = Linker::revUserTools( $revRecord );
661 $content = $revRecord->getContent(
663 RevisionRecord::FOR_THIS_USER,
666 }
catch ( RevisionAccessException $e ) {
671 $isText = ( $content instanceof TextContent );
673 $undeleteRevisionContent =
'';
675 if ( !$this->mDiff ) {
676 $revdel = Linker::getRevDeleteLink(
682 $undeleteRevisionContent = $revdel .
' ';
686 $undeleteRevisionContent .= $out->msg(
695 if ( $this->mPreview || $isText ) {
698 $undeleteRevisionContent,
699 'mw-undelete-revision'
706 [
'class' =>
'mw-undelete-revision', ],
707 $undeleteRevisionContent
712 if ( $this->mPreview || !$isText ) {
715 $popts = $out->parserOptions();
718 $rendered = $this->revisionRenderer->getRenderedRevision(
722 [
'audience' => RevisionRecord::FOR_THIS_USER,
'causeAction' =>
'undelete-preview' ]
727 $pout = $rendered->getRevisionParserOutput();
729 $out->addParserOutput( $pout, [
730 'enableSectionEditLinks' =>
false,
732 }
catch ( RevisionAccessException $e ) {
740 '@phan-var TextContent $content';
744 'readonly' =>
'readonly',
747 ], $content->getText() .
"\n" );
749 $buttonFields[] =
new ButtonInputWidget( [
752 'label' => $this->
msg(
'showpreview' )->text()
758 $buttonFields[] =
new ButtonInputWidget( [
761 'label' => $this->
msg(
'showdiff' )->text()
767 'style' =>
'clear: both' ] ) .
770 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
774 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
777 'name' =>
'timestamp',
778 'value' => $timestamp ] ) .
781 'name' =>
'wpEditToken',
782 'value' => $user->getEditToken() ] ) .
785 'content' =>
new HorizontalLayout( [
786 'items' => $buttonFields
802 private function showDiff(
803 RevisionRecord $previousRevRecord,
804 RevisionRecord $currentRevRecord
806 $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
808 $diffContext =
new DerivativeContext( $this->
getContext() );
809 $diffContext->setTitle( $currentTitle );
810 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
812 $contentModel = $currentRevRecord->getSlot(
817 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
818 ->createDifferenceEngine( $diffContext );
820 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
821 $diffEngine->showDiffStyle();
822 $formattedDiff = $diffEngine->getDiff(
823 $this->diffHeader( $previousRevRecord,
'o' ),
824 $this->diffHeader( $currentRevRecord,
'n' )
827 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
835 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
836 if ( $revRecord instanceof RevisionArchiveRecord ) {
840 'target' => $this->mTargetObj->getPrefixedText(),
841 'timestamp' =>
wfTimestamp( TS_MW, $revRecord->getTimestamp() )
845 $targetPage = $revRecord->getPageAsLinkTarget();
846 $targetQuery = [
'oldid' => $revRecord->getId() ];
852 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
860 $dbr = $this->dbProvider->getReplicaDatabase();
861 $tagIds = $dbr->newSelectQueryBuilder()
862 ->select(
'ct_tag_id' )
863 ->from(
'change_tag' )
864 ->where( [
'ct_rev_id' => $revRecord->getId() ] )
865 ->caller( __METHOD__ )->fetchFieldValues();
867 foreach ( $tagIds as $tagId ) {
869 $tags[] = $this->changeTagDefStore->getName( (
int)$tagId );
870 }
catch ( NameTableAccessException $exception ) {
874 $tags = implode(
',', $tags );
880 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
881 $lang->userDate( $revRecord->getTimestamp(), $user ),
882 $lang->userTime( $revRecord->getTimestamp(), $user )
887 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
888 $asof = Html::rawElement(
890 [
'class' => Linker::getRevisionDeletedClass( $revRecord ) ],
897 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
900 '<div id="mw-diff-' . $prefix .
'title2">' .
901 Linker::revUserTools( $revRecord ) .
'<br />' .
903 '<div id="mw-diff-' . $prefix .
'title3">' .
904 $minor . $this->commentFormatter->formatRevision( $revRecord, $user ) . $rdel .
'<br />' .
906 '<div id="mw-diff-' . $prefix .
'title5">' .
907 $tagSummary[0] .
'<br />' .
915 private function showFileConfirmationForm( $key ) {
919 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
920 $out->addWikiMsg(
'undelete-show-file-confirm',
921 $this->mTargetObj->getText(),
922 $lang->userDate( $file->getTimestamp(), $user ),
923 $lang->userTime( $file->getTimestamp(), $user ) );
925 Html::rawElement(
'form', [
928 'target' => $this->mTarget,
930 'token' => $user->getEditToken( $key ),
942 private function showFile( $key ) {
945 # We mustn't allow the output to be CDN cached, otherwise
946 # if an admin previews a deleted image, and it's cached, then
947 # a user without appropriate permissions can toddle off and
948 # nab the image, and CDN will serve it
950 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
951 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
953 $path = $this->localRepo->getZonePath(
'deleted' ) .
'/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
954 $this->localRepo->streamFileWithStatus(
$path );
961 private function addRevisionsToBatch( LinkBatch $batch, IResultWrapper $revisions ) {
962 foreach ( $revisions as $row ) {
963 $batch->add(
NS_USER, $row->ar_user_text );
972 private function addFilesToBatch( LinkBatch $batch, IResultWrapper $files ) {
973 foreach ( $files as $row ) {
974 $batch->add(
NS_USER, $row->fa_user_text );
984 $out->setArticleBodyOnly(
true );
985 $dbr = $this->dbProvider->getReplicaDatabase();
986 if ( $this->mHistoryOffset ) {
987 $extraConds = [ $dbr->expr(
'ar_timestamp',
'<', $dbr->timestamp( $this->mHistoryOffset ) ) ];
991 $revisions = $this->archivedRevisionLookup->listRevisions(
994 self::REVISION_HISTORY_LIMIT + 1
996 $batch = $this->linkBatchFactory->newLinkBatch();
997 $this->addRevisionsToBatch( $batch, $revisions );
1001 if ( $revisions->numRows() > self::REVISION_HISTORY_LIMIT ) {
1003 $out->setStatusCode( 206 );
1014 $history = Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1017 $numRevisions = $revisions->
numRows();
1018 $displayCount = min( $numRevisions, self::REVISION_HISTORY_LIMIT );
1019 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1020 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() :
null;
1022 $revisions->rewind();
1023 for ( $i = 0; $i < $displayCount; $i++ ) {
1027 $history .= $this->
formatRevisionRow( $row, $earliestLiveTime, $numRevisions - $i );
1029 $history .= Html::closeElement(
'ul' );
1037 if ( $this->mAllowed ) {
1038 $out->addModules(
'mediawiki.misc-authed-ooui' );
1039 $out->addModuleStyles(
'mediawiki.special' );
1041 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
1043 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1044 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
1049 $this->
getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
1051 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-undelete-history' ] ) );
1052 if ( $this->mAllowed ) {
1053 $out->addWikiMsg(
'undeletehistory' );
1054 $out->addWikiMsg(
'undeleterevdel' );
1056 $out->addWikiMsg(
'undeletehistorynoadmin' );
1058 $out->addHTML( Html::closeElement(
'div' ) );
1060 # List all stored revisions
1061 $revisions = $this->archivedRevisionLookup->listRevisions(
1064 self::REVISION_HISTORY_LIMIT + 1
1066 $files = $archive->listFiles();
1067 $numRevisions = $revisions->numRows();
1068 $showLoadMore = $numRevisions > self::REVISION_HISTORY_LIMIT;
1069 $haveRevisions = $numRevisions > 0;
1070 $haveFiles = $files && $files->numRows() > 0;
1072 # Batch existence check on user and talk pages
1073 if ( $haveRevisions || $haveFiles ) {
1074 $batch = $this->linkBatchFactory->newLinkBatch();
1075 $this->addRevisionsToBatch( $batch, $revisions );
1078 $this->addFilesToBatch( $batch, $files );
1083 if ( $this->mAllowed ) {
1086 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
1087 # Start the form here
1088 $form =
new FormLayout( [
1090 'action' => $action,
1095 # Show relevant lines from the deletion log:
1096 $deleteLogPage =
new LogPage(
'delete' );
1097 $out->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
1098 LogEventsList::showLogExtract( $out,
'delete', $this->mTargetObj );
1099 # Show relevant lines from the suppression log:
1100 $suppressLogPage =
new LogPage(
'suppress' );
1101 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
1102 $out->addHTML(
Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
1103 LogEventsList::showLogExtract( $out,
'suppress', $this->mTargetObj );
1106 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1107 $unsuppressAllowed = $this->permissionManager->userHasRight( $this->
getUser(),
'suppressrevision' );
1109 $fields[] =
new Layout( [
1110 'content' =>
new HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
1113 $dropdownComment = $this->
msg(
'undelete-comment-dropdown' )
1114 ->page( $this->mTargetObj )->inContentLanguage()->text();
1116 if ( $unsuppressAllowed ) {
1117 $dropdownComment .=
"\n" . $this->
msg(
'undelete-comment-dropdown-unsuppress' )
1118 ->page( $this->mTargetObj )->inContentLanguage()->text();
1122 [
'other' => $this->
msg(
'undeletecommentotherlist' )->text() ]
1126 $fields[] =
new FieldLayout(
1127 new DropdownInputWidget( [
1128 'name' =>
'wpCommentList',
1129 'inputId' =>
'wpCommentList',
1130 'infusable' =>
true,
1131 'value' => $this->
getRequest()->getText(
'wpCommentList',
'other' ),
1132 'options' => $options,
1135 'label' => $this->
msg(
'undeletecomment' )->text(),
1140 $fields[] =
new FieldLayout(
1141 new TextInputWidget( [
1142 'name' =>
'wpComment',
1143 'inputId' =>
'wpComment',
1144 'infusable' =>
true,
1145 'value' => $this->
getRequest()->getText(
'wpComment' ),
1146 'autofocus' =>
true,
1150 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
1153 'label' => $this->
msg(
'undeleteothercomment' )->text(),
1158 if ( $this->
getUser()->isRegistered() ) {
1159 $checkWatch = $this->watchlistManager->isWatched( $this->
getUser(), $this->mTargetObj )
1160 || $this->
getRequest()->getText(
'wpWatch' );
1161 $fields[] =
new FieldLayout(
1162 new CheckboxInputWidget( [
1163 'name' =>
'wpWatch',
1164 'inputId' =>
'mw-undelete-watch',
1166 'selected' => $checkWatch,
1169 'label' => $this->
msg(
'watchthis' )->text(),
1170 'align' =>
'inline',
1175 if ( $unsuppressAllowed ) {
1176 $fields[] =
new FieldLayout(
1177 new CheckboxInputWidget( [
1178 'name' =>
'wpUnsuppress',
1179 'inputId' =>
'mw-undelete-unsuppress',
1183 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
1184 'align' =>
'inline',
1189 $undelPage = $this->undeletePageFactory->newUndeletePage(
1190 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1191 $this->getContext()->getAuthority()
1193 if ( $undelPage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1194 $fields[] =
new FieldLayout(
1195 new CheckboxInputWidget( [
1196 'name' =>
'undeletetalk',
1197 'inputId' =>
'mw-undelete-undeletetalk',
1198 'selected' =>
false,
1201 'label' => $this->
msg(
'undelete-undeletetalk' )->text(),
1202 'align' =>
'inline',
1207 $fields[] =
new FieldLayout(
1209 'content' =>
new HorizontalLayout( [
1211 new ButtonInputWidget( [
1212 'name' =>
'restore',
1213 'inputId' =>
'mw-undelete-submit',
1215 'label' => $this->
msg(
'undeletebtn' )->text(),
1216 'flags' => [
'primary',
'progressive' ],
1219 new ButtonInputWidget( [
1221 'inputId' =>
'mw-undelete-invert',
1223 'label' => $this->
msg(
'undeleteinvert' )->text()
1230 $fieldset =
new FieldsetLayout( [
1231 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
1232 'id' =>
'mw-undelete-table',
1238 if ( $unsuppressAllowed ) {
1240 $this->
msg(
'undelete-comment-dropdown-unsuppress' )->inContentLanguage()->
getTitle(),
1241 $this->
msg(
'undelete-edit-commentlist-unsuppress' )->text(),
1243 [
'action' =>
'edit' ]
1245 $link .= $this->
msg(
'pipe-separator' )->escaped();
1248 $this->
msg(
'undelete-comment-dropdown' )->inContentLanguage()->
getTitle(),
1249 $this->
msg(
'undelete-edit-commentlist' )->text(),
1251 [
'action' =>
'edit' ]
1254 $link = Html::rawElement(
'p', [
'class' =>
'mw-undelete-editcomments' ], $link );
1258 $form->appendContent(
1260 'expanded' =>
false,
1263 'content' => $fieldset,
1267 Html::hidden(
'target', $this->mTarget ) .
1268 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() )
1274 $history .=
Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
1276 if ( $haveRevisions ) {
1277 # Show the page's stored (deleted) history
1279 if ( $this->permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
1285 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
1287 $this->
msg(
'showhideselectedversions' )->text()
1293 if ( $showLoadMore ) {
1295 Html::openElement(
'div' ) .
1298 [
'id' =>
'mw-load-more-revisions' ],
1299 $this->
msg(
'undelete-load-more-revisions' )->text()
1301 Html::closeElement(
'div' ) .
1305 $out->addWikiMsg(
'nohistory' );
1309 $history .=
Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
1310 $history .= Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1311 foreach ( $files as $row ) {
1312 $history .= $this->formatFileRow( $row );
1315 $history .= Html::closeElement(
'ul' );
1318 if ( $this->mAllowed ) {
1319 # Slip in the hidden controls here
1320 $misc = Html::hidden(
'target', $this->mTarget );
1321 $misc .= Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() );
1325 $form->appendContent(
new HtmlSnippet( $history ) );
1327 $out->addHTML( (
string)$form );
1329 $out->addHTML( $history );
1336 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1338 IDBAccessObject::READ_NORMAL,
1345 if ( $this->mAllowed ) {
1346 if ( $this->mInvert ) {
1347 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1361 if ( $this->mCanView ) {
1364 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1365 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1366 $last = $this->
msg(
'diff' )->escaped();
1367 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1368 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1371 $this->
msg(
'diff' )->text(),
1374 'target' => $this->mTargetObj->getPrefixedText(),
1380 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1381 $last = $this->
msg(
'diff' )->escaped();
1384 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1385 $last = $this->
msg(
'diff' )->escaped();
1389 $userLink = Linker::revUserTools( $revRecord );
1392 $minor = $revRecord->isMinor() ? ChangesList::flag(
'minor' ) :
'';
1395 $size = $row->ar_len;
1396 if ( $size !==
null ) {
1397 $revTextSize = Linker::formatRevisionSize( $size );
1401 $comment = $this->commentFormatter->formatRevision( $revRecord, $user );
1411 $attribs[
'class'] = implode(
' ', $classes );
1414 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1427 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1430 private function formatFileRow( $row ) {
1431 $file = ArchivedFile::newFromRow( $row );
1436 if ( $this->mCanView && $row->fa_storage_key ) {
1437 if ( $this->mAllowed ) {
1438 $checkBox =
Xml::check(
'fileid' . $row->fa_id );
1440 $key = urlencode( $row->fa_storage_key );
1441 $pageLink = $this->getFileLink( $file, $this->
getPageTitle(), $ts, $key );
1443 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1445 $userLink = $this->getFileUser( $file );
1446 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1447 $bytes = $this->
msg(
'parentheses' )
1448 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1450 $data = htmlspecialchars( $data .
' ' . $bytes );
1451 $comment = $this->getFileComment( $file );
1454 $canHide = $this->
isAllowed(
'deleterevision' );
1455 if ( $canHide || ( $file->getVisibility() && $this->isAllowed(
'deletedhistory' ) ) ) {
1456 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1458 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1461 'type' =>
'filearchive',
1462 'target' => $this->mTargetObj->getPrefixedDBkey(),
1463 'ids' => $row->fa_id
1465 $revdlink = Linker::revDeleteLink( $query,
1466 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1472 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1483 private function getPageLink( RevisionRecord $revRecord, LinkTarget $target, $ts ) {
1485 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1487 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1491 [
'class' =>
'history-deleted' ],
1501 'target' => $this->mTargetObj->getPrefixedText(),
1506 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1507 $class = Linker::getRevisionDeletedClass( $revRecord );
1508 $link =
'<span class="' . $class .
'">' . $link .
'</span>';
1524 private function getFileLink( $file, LinkTarget $target, $ts, $key ) {
1526 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1528 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1531 [
'class' =>
'history-deleted' ],
1536 if ( $file->exists() ) {
1542 'target' => $this->mTargetObj->getPrefixedText(),
1544 'token' => $user->getEditToken( $key )
1548 $link = htmlspecialchars( $time );
1551 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1552 $link =
'<span class="history-deleted">' . $link .
'</span>';
1564 private function getFileUser( $file ) {
1565 $uploader = $file->getUploader( File::FOR_THIS_USER, $this->
getAuthority() );
1567 return Html::rawElement(
1569 [
'class' =>
'history-deleted' ],
1570 $this->
msg(
'rev-deleted-user' )->escaped()
1574 $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1575 Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1577 if ( $file->isDeleted( File::DELETED_USER ) ) {
1578 $link = Html::rawElement(
1580 [
'class' =>
'history-deleted' ],
1594 private function getFileComment( $file ) {
1595 if ( !$file->userCan( File::DELETED_COMMENT, $this->getAuthority() ) ) {
1596 return Html::rawElement(
1598 [
'class' =>
'history-deleted' ],
1601 [
'class' =>
'comment' ],
1602 $this->
msg(
'rev-deleted-comment' )->escaped()
1607 $comment = $file->getDescription( File::FOR_THIS_USER, $this->
getAuthority() );
1608 $link = $this->commentFormatter->formatBlock( $comment );
1610 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1611 $link = Html::rawElement(
1613 [
'class' =>
'history-deleted' ],
1621 private function undelete() {
1623 && $this->mTargetObj->getNamespace() ===
NS_FILE
1625 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1631 $undeletePage = $this->undeletePageFactory->newUndeletePage(
1632 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1633 $this->getAuthority()
1635 if ( $this->mUndeleteTalk && $undeletePage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1636 $undeletePage->setUndeleteAssociatedTalk(
true );
1638 $status = $undeletePage
1639 ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1640 ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1641 ->setUnsuppress( $this->mUnsuppress )
1643 ->undeleteIfAllowed( $this->mComment );
1645 if ( !$status->isGood() ) {
1646 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1647 $out->wrapWikiTextAsInterface(
1649 Status::wrap( $status )->getWikiText(
1658 $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
1659 $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
1661 if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1663 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1665 if ( $status->getValue()[UndeletePage::FILES_RESTORED] !== 0 ) {
1667 $this->mTargetObj, $this->mFileVersions, $this->
getUser(), $this->mComment );
1673 $this->watchlistManager->setWatch(
1690 return $this->
prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );