96 private const REVISION_HISTORY_LIMIT = 500;
106 private $mTargetTimestamp = [];
110 private $mComment =
'';
119 private $mUnsuppress;
121 private $mFileVersions = [];
123 private $mUndeleteTalk;
125 private $mHistoryOffset;
132 private $mSearchPrefix;
184 parent::__construct(
'Undelete',
'deletedhistory' );
185 $this->permissionManager = $permissionManager;
186 $this->revisionStore = $revisionStore;
187 $this->revisionRenderer = $revisionRenderer;
188 $this->contentHandlerFactory = $contentHandlerFactory;
189 $this->changeTagDefStore = $changeTagDefStore;
190 $this->linkBatchFactory = $linkBatchFactory;
192 $this->dbProvider = $dbProvider;
193 $this->userOptionsLookup = $userOptionsLookup;
194 $this->wikiPageFactory = $wikiPageFactory;
195 $this->searchEngineFactory = $searchEngineFactory;
196 $this->undeletePageFactory = $undeletePageFactory;
197 $this->archivedRevisionLookup = $archivedRevisionLookup;
198 $this->commentFormatter = $commentFormatter;
199 $this->watchlistManager = $watchlistManager;
206 private function loadRequest( $par ) {
210 $this->mAction = $request->getRawVal(
'action' );
211 if ( $par !==
null && $par !==
'' ) {
212 $this->mTarget = $par;
214 $this->mTarget = $request->getVal(
'target' );
217 $this->mTargetObj =
null;
219 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
220 $this->mTargetObj = Title::newFromText( $this->mTarget );
223 $this->mSearchPrefix = $request->getText(
'prefix' );
224 $time = $request->getVal(
'timestamp' );
225 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
226 $this->mFilename = $request->getVal(
'file' );
228 $posted = $request->wasPosted() &&
229 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
230 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
231 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
232 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
233 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
234 $this->mDiff = $request->getCheck(
'diff' );
235 $this->mDiffOnly = $request->getBool(
'diffonly',
236 $this->userOptionsLookup->getOption( $this->getUser(),
'diffonly' ) );
237 $commentList = $request->getText(
'wpCommentList',
'other' );
238 $comment = $request->getText(
'wpComment' );
239 if ( $commentList ===
'other' ) {
240 $this->mComment = $comment;
241 } elseif ( $comment !==
'' ) {
242 $this->mComment = $commentList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() . $comment;
244 $this->mComment = $commentList;
246 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) &&
247 $this->permissionManager->userHasRight( $user,
'suppressrevision' );
248 $this->mToken = $request->getVal(
'token' );
249 $this->mUndeleteTalk = $request->getCheck(
'undeletetalk' );
250 $this->mHistoryOffset = $request->getVal(
'historyoffset' );
253 $this->mAllowed =
true;
254 $this->mCanView =
true;
255 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
256 $this->mAllowed =
false;
257 $this->mCanView =
true;
258 $this->mRestore =
false;
260 $this->mAllowed =
false;
261 $this->mCanView =
false;
262 $this->mTimestamp =
'';
263 $this->mRestore =
false;
266 if ( $this->mRestore || $this->mInvert ) {
268 $this->mFileVersions = [];
269 foreach ( $request->getValues() as $key => $val ) {
271 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
275 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
276 $this->mFileVersions[] = intval(
$matches[1] );
279 rsort( $timestamps );
280 $this->mTargetTimestamp = $timestamps;
294 $block = $user->getBlock();
296 if ( $this->mTargetObj !==
null ) {
297 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
299 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
300 $sitewideBlock = $block && $block->isSitewide();
301 return $permission ===
'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
306 return $this->
isAllowed( $this->mRestriction, $user );
317 if ( !parent::userCanExecute( $user ) ) {
324 $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
342 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
344 $this->loadRequest( $par );
349 if ( $this->mTargetObj ===
null ) {
350 $out->addWikiMsg(
'undelete-header' );
352 # Not all users can just browse every deleted page from the list
353 if ( $this->permissionManager->userHasRight( $user,
'browsearchive' ) ) {
354 $this->showSearchForm();
361 if ( $this->mAllowed ) {
362 $out->setPageTitleMsg( $this->
msg(
'undeletepage' ) );
364 $out->setPageTitleMsg( $this->
msg(
'viewdeletedpage' ) );
367 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
369 if ( $this->mTimestamp !==
'' ) {
370 $this->showRevision( $this->mTimestamp );
371 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
372 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
374 if ( !$file->exists() ) {
375 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
376 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
377 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
382 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
383 $this->showFileConfirmationForm( $this->mFilename );
385 $this->showFile( $this->mFilename );
387 } elseif ( $this->mAction ===
'submit' ) {
388 if ( $this->mRestore ) {
390 } elseif ( $this->mRevdel ) {
391 $this->redirectToRevDel();
393 } elseif ( $this->mAction ===
'render' ) {
404 private function redirectToRevDel() {
407 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
409 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
410 $revisionRecord = $this->archivedRevisionLookup
411 ->getRevisionRecordByTimestamp( $this->mTargetObj,
$matches[1] );
412 if ( $revisionRecord ) {
414 $revisions[ $revisionRecord->getId() ] = 1;
420 'type' =>
'revision',
422 'target' => $this->mTargetObj->getPrefixedText()
428 private function showSearchForm() {
430 $out->setPageTitleMsg( $this->
msg(
'undelete-search-title' ) );
431 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
'1' );
436 $fields[] =
new ActionFieldLayout(
437 new TextInputWidget( [
439 'inputId' =>
'prefix',
441 'value' => $this->mSearchPrefix,
444 new ButtonInputWidget( [
445 'label' => $this->
msg(
'undelete-search-submit' )->text(),
446 'flags' => [
'primary',
'progressive' ],
447 'inputId' =>
'searchUndelete',
451 'label' =>
new HtmlSnippet(
453 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
460 $fieldset =
new FieldsetLayout( [
461 'label' => $this->
msg(
'undelete-search-box' )->text(),
465 $form =
new FormLayout( [
470 $form->appendContent(
473 Html::hidden(
'title', $this->
getPageTitle()->getPrefixedDBkey() ) .
474 Html::hidden(
'fuzzy', $fuzzySearch )
487 # List undeletable articles
488 if ( $this->mSearchPrefix ) {
491 if ( $fuzzySearch ) {
496 $this->showList( $result );
506 private function showList( $result ) {
509 if ( $result->numRows() == 0 ) {
510 $out->addWikiMsg(
'undelete-no-results' );
515 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
519 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
520 foreach ( $result as $row ) {
521 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
522 if ( $title !==
null ) {
523 $item = $linkRenderer->makeKnownLink(
525 $title->getPrefixedText(),
527 [
'target' => $title->getPrefixedText() ]
533 [
'class' =>
'mw-invalidtitle' ],
534 Linker::getInvalidTitleDescription(
541 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
545 [
'class' =>
'undeleteResult' ],
546 $item . $this->
msg(
'word-separator' )->escaped() .
547 $this->
msg(
'parentheses' )->rawParams( $revs )->escaped()
552 $out->addHTML(
"</ul>\n" );
557 private function showRevision( $timestamp ) {
558 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
562 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
568 $this->
msg(
'undelete-back-to-list' )->text(),
570 [
'target' => $this->mTargetObj->getPrefixedText() ]
573 $subtitle =
"< $listLink";
574 $out->setSubtitle( $subtitle );
579 $archive, $this->mTargetObj )
583 $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
588 $out->addWikiMsg(
'undeleterevision-missing' );
592 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
594 $titleText = $this->mTargetObj->getPrefixedDBkey();
595 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
596 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
597 ? [
'rev-suppressed-text-permission', $titleText ]
598 : [
'rev-deleted-text-permission', $titleText ];
601 $this->
msg( $msg[0], $msg[1] )->parse(),
608 $msg = $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
609 ? [
'rev-suppressed-text-view', $titleText ]
610 : [
'rev-deleted-text-view', $titleText ];
613 $this->
msg( $msg[0], $msg[1] )->parse(),
620 if ( $this->mDiff ) {
621 $previousRevRecord = $this->archivedRevisionLookup
622 ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
623 if ( $previousRevRecord ) {
624 $this->showDiff( $previousRevRecord, $revRecord );
625 if ( $this->mDiffOnly ) {
629 $out->addHTML(
'<hr />' );
631 $out->addWikiMsg(
'undelete-nodiff' );
636 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
637 $this->mTargetObj->getPrefixedText()
644 $time = $lang->userTimeAndDate( $timestamp, $user );
645 $d = $lang->userDate( $timestamp, $user );
646 $t = $lang->userTime( $timestamp, $user );
647 $userLink = Linker::revUserTools( $revRecord );
649 $content = $revRecord->getContent(
651 RevisionRecord::FOR_THIS_USER,
656 $isText = ( $content instanceof TextContent );
658 $undeleteRevisionContent =
'';
660 if ( !$this->mDiff ) {
661 $revdel = Linker::getRevDeleteLink(
667 $undeleteRevisionContent = $revdel .
' ';
671 $undeleteRevisionContent .= $out->msg(
680 if ( $this->mPreview || $isText ) {
683 $undeleteRevisionContent,
684 'mw-undelete-revision'
691 [
'class' =>
'mw-undelete-revision', ],
692 $undeleteRevisionContent
697 if ( $this->mPreview || !$isText ) {
700 $popts = $out->parserOptions();
702 $rendered = $this->revisionRenderer->getRenderedRevision(
706 [
'audience' => RevisionRecord::FOR_THIS_USER,
'causeAction' =>
'undelete-preview' ]
711 $pout = $rendered->getRevisionParserOutput();
713 $out->addParserOutput( $pout, [
714 'enableSectionEditLinks' =>
false,
722 '@phan-var TextContent $content';
726 'readonly' =>
'readonly',
729 ], $content->getText() .
"\n" );
731 $buttonFields[] =
new ButtonInputWidget( [
734 'label' => $this->
msg(
'showpreview' )->text()
740 $buttonFields[] =
new ButtonInputWidget( [
743 'label' => $this->
msg(
'showdiff' )->text()
749 'style' =>
'clear: both' ] ) .
752 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
756 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
759 'name' =>
'timestamp',
760 'value' => $timestamp ] ) .
763 'name' =>
'wpEditToken',
764 'value' => $user->getEditToken() ] ) .
767 'content' =>
new HorizontalLayout( [
768 'items' => $buttonFields
784 private function showDiff(
785 RevisionRecord $previousRevRecord,
786 RevisionRecord $currentRevRecord
788 $currentTitle = Title::newFromLinkTarget( $currentRevRecord->getPageAsLinkTarget() );
790 $diffContext =
new DerivativeContext( $this->
getContext() );
791 $diffContext->setTitle( $currentTitle );
792 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
794 $contentModel = $currentRevRecord->getSlot(
799 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
800 ->createDifferenceEngine( $diffContext );
802 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
803 $diffEngine->showDiffStyle();
804 $formattedDiff = $diffEngine->getDiff(
805 $this->diffHeader( $previousRevRecord,
'o' ),
806 $this->diffHeader( $currentRevRecord,
'n' )
809 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
817 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
818 if ( $revRecord instanceof RevisionArchiveRecord ) {
822 'target' => $this->mTargetObj->getPrefixedText(),
823 'timestamp' =>
wfTimestamp( TS_MW, $revRecord->getTimestamp() )
827 $targetPage = $revRecord->getPageAsLinkTarget();
828 $targetQuery = [
'oldid' => $revRecord->getId() ];
834 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
842 $dbr = $this->dbProvider->getReplicaDatabase();
843 $tagIds = $dbr->newSelectQueryBuilder()
844 ->select(
'ct_tag_id' )
845 ->from(
'change_tag' )
846 ->where( [
'ct_rev_id' => $revRecord->getId() ] )
847 ->caller( __METHOD__ )->fetchFieldValues();
849 foreach ( $tagIds as $tagId ) {
851 $tags[] = $this->changeTagDefStore->getName( (
int)$tagId );
852 }
catch ( NameTableAccessException $exception ) {
856 $tags = implode(
',', $tags );
862 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
863 $lang->userDate( $revRecord->getTimestamp(), $user ),
864 $lang->userTime( $revRecord->getTimestamp(), $user )
869 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
870 $asof = Html::rawElement(
872 [
'class' => Linker::getRevisionDeletedClass( $revRecord ) ],
879 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
882 '<div id="mw-diff-' . $prefix .
'title2">' .
883 Linker::revUserTools( $revRecord ) .
'<br />' .
885 '<div id="mw-diff-' . $prefix .
'title3">' .
886 $minor . $this->commentFormatter->formatRevision( $revRecord, $user ) . $rdel .
'<br />' .
888 '<div id="mw-diff-' . $prefix .
'title5">' .
889 $tagSummary[0] .
'<br />' .
897 private function showFileConfirmationForm( $key ) {
901 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
902 $out->addWikiMsg(
'undelete-show-file-confirm',
903 $this->mTargetObj->getText(),
904 $lang->userDate( $file->getTimestamp(), $user ),
905 $lang->userTime( $file->getTimestamp(), $user ) );
907 Html::rawElement(
'form', [
910 'target' => $this->mTarget,
912 'token' => $user->getEditToken( $key ),
924 private function showFile( $key ) {
927 # We mustn't allow the output to be CDN cached, otherwise
928 # if an admin previews a deleted image, and it's cached, then
929 # a user without appropriate permissions can toddle off and
930 # nab the image, and CDN will serve it
932 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
933 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
935 $path = $this->localRepo->getZonePath(
'deleted' ) .
'/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
936 $this->localRepo->streamFileWithStatus(
$path );
943 private function addRevisionsToBatch( LinkBatch $batch, IResultWrapper $revisions ) {
944 foreach ( $revisions as $row ) {
945 $batch->add(
NS_USER, $row->ar_user_text );
954 private function addFilesToBatch( LinkBatch $batch, IResultWrapper $files ) {
955 foreach ( $files as $row ) {
956 $batch->add(
NS_USER, $row->fa_user_text );
966 $out->setArticleBodyOnly(
true );
967 $dbr = $this->dbProvider->getReplicaDatabase();
968 if ( $this->mHistoryOffset ) {
969 $extraConds = [ $dbr->expr(
'ar_timestamp',
'<', $dbr->timestamp( $this->mHistoryOffset ) ) ];
973 $revisions = $this->archivedRevisionLookup->listRevisions(
976 self::REVISION_HISTORY_LIMIT + 1
978 $batch = $this->linkBatchFactory->newLinkBatch();
979 $this->addRevisionsToBatch( $batch, $revisions );
983 if ( $revisions->numRows() > self::REVISION_HISTORY_LIMIT ) {
985 $out->setStatusCode( 206 );
996 $history = Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
999 $numRevisions = $revisions->
numRows();
1000 $displayCount = min( $numRevisions, self::REVISION_HISTORY_LIMIT );
1001 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1002 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() :
null;
1004 $revisions->rewind();
1005 for ( $i = 0; $i < $displayCount; $i++ ) {
1009 $history .= $this->
formatRevisionRow( $row, $earliestLiveTime, $numRevisions - $i );
1011 $history .= Html::closeElement(
'ul' );
1019 if ( $this->mAllowed ) {
1020 $out->addModules(
'mediawiki.misc-authed-ooui' );
1021 $out->addModuleStyles(
'mediawiki.special' );
1023 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
1025 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1026 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
1031 $this->
getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
1033 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-undelete-history' ] ) );
1034 if ( $this->mAllowed ) {
1035 $out->addWikiMsg(
'undeletehistory' );
1036 $out->addWikiMsg(
'undeleterevdel' );
1038 $out->addWikiMsg(
'undeletehistorynoadmin' );
1040 $out->addHTML( Html::closeElement(
'div' ) );
1042 # List all stored revisions
1043 $revisions = $this->archivedRevisionLookup->listRevisions(
1046 self::REVISION_HISTORY_LIMIT + 1
1048 $files = $archive->listFiles();
1049 $numRevisions = $revisions->numRows();
1050 $showLoadMore = $numRevisions > self::REVISION_HISTORY_LIMIT;
1051 $haveRevisions = $numRevisions > 0;
1052 $haveFiles = $files && $files->numRows() > 0;
1054 # Batch existence check on user and talk pages
1055 if ( $haveRevisions || $haveFiles ) {
1056 $batch = $this->linkBatchFactory->newLinkBatch();
1057 $this->addRevisionsToBatch( $batch, $revisions );
1060 $this->addFilesToBatch( $batch, $files );
1065 if ( $this->mAllowed ) {
1068 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
1069 # Start the form here
1070 $form =
new FormLayout( [
1072 'action' => $action,
1077 # Show relevant lines from the deletion log:
1078 $deleteLogPage =
new LogPage(
'delete' );
1079 $out->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
1080 LogEventsList::showLogExtract( $out,
'delete', $this->mTargetObj );
1081 # Show relevant lines from the suppression log:
1082 $suppressLogPage =
new LogPage(
'suppress' );
1083 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
1084 $out->addHTML(
Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
1085 LogEventsList::showLogExtract( $out,
'suppress', $this->mTargetObj );
1088 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1089 $unsuppressAllowed = $this->permissionManager->userHasRight( $this->
getUser(),
'suppressrevision' );
1091 $fields[] =
new Layout( [
1092 'content' =>
new HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
1095 $dropdownComment = $this->
msg(
'undelete-comment-dropdown' )
1096 ->page( $this->mTargetObj )->inContentLanguage()->text();
1098 if ( $unsuppressAllowed ) {
1099 $dropdownComment .=
"\n" . $this->
msg(
'undelete-comment-dropdown-unsuppress' )
1100 ->page( $this->mTargetObj )->inContentLanguage()->text();
1104 [
'other' => $this->
msg(
'undeletecommentotherlist' )->text() ]
1108 $fields[] =
new FieldLayout(
1109 new DropdownInputWidget( [
1110 'name' =>
'wpCommentList',
1111 'inputId' =>
'wpCommentList',
1112 'infusable' =>
true,
1113 'value' => $this->
getRequest()->getText(
'wpCommentList',
'other' ),
1114 'options' => $options,
1117 'label' => $this->
msg(
'undeletecomment' )->text(),
1122 $fields[] =
new FieldLayout(
1123 new TextInputWidget( [
1124 'name' =>
'wpComment',
1125 'inputId' =>
'wpComment',
1126 'infusable' =>
true,
1127 'value' => $this->
getRequest()->getText(
'wpComment' ),
1128 'autofocus' =>
true,
1132 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
1135 'label' => $this->
msg(
'undeleteothercomment' )->text(),
1140 if ( $this->
getUser()->isRegistered() ) {
1141 $checkWatch = $this->watchlistManager->isWatched( $this->
getUser(), $this->mTargetObj )
1142 || $this->
getRequest()->getText(
'wpWatch' );
1143 $fields[] =
new FieldLayout(
1144 new CheckboxInputWidget( [
1145 'name' =>
'wpWatch',
1146 'inputId' =>
'mw-undelete-watch',
1148 'selected' => $checkWatch,
1151 'label' => $this->
msg(
'watchthis' )->text(),
1152 'align' =>
'inline',
1157 if ( $unsuppressAllowed ) {
1158 $fields[] =
new FieldLayout(
1159 new CheckboxInputWidget( [
1160 'name' =>
'wpUnsuppress',
1161 'inputId' =>
'mw-undelete-unsuppress',
1165 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
1166 'align' =>
'inline',
1171 $undelPage = $this->undeletePageFactory->newUndeletePage(
1172 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1173 $this->getContext()->getAuthority()
1175 if ( $undelPage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1176 $fields[] =
new FieldLayout(
1177 new CheckboxInputWidget( [
1178 'name' =>
'undeletetalk',
1179 'inputId' =>
'mw-undelete-undeletetalk',
1180 'selected' =>
false,
1183 'label' => $this->
msg(
'undelete-undeletetalk' )->text(),
1184 'align' =>
'inline',
1189 $fields[] =
new FieldLayout(
1191 'content' =>
new HorizontalLayout( [
1193 new ButtonInputWidget( [
1194 'name' =>
'restore',
1195 'inputId' =>
'mw-undelete-submit',
1197 'label' => $this->
msg(
'undeletebtn' )->text(),
1198 'flags' => [
'primary',
'progressive' ],
1201 new ButtonInputWidget( [
1203 'inputId' =>
'mw-undelete-invert',
1205 'label' => $this->
msg(
'undeleteinvert' )->text()
1212 $fieldset =
new FieldsetLayout( [
1213 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
1214 'id' =>
'mw-undelete-table',
1220 if ( $unsuppressAllowed ) {
1222 $this->
msg(
'undelete-comment-dropdown-unsuppress' )->inContentLanguage()->
getTitle(),
1223 $this->
msg(
'undelete-edit-commentlist-unsuppress' )->text(),
1225 [
'action' =>
'edit' ]
1227 $link .= $this->
msg(
'pipe-separator' )->escaped();
1230 $this->
msg(
'undelete-comment-dropdown' )->inContentLanguage()->
getTitle(),
1231 $this->
msg(
'undelete-edit-commentlist' )->text(),
1233 [
'action' =>
'edit' ]
1236 $link = Html::rawElement(
'p', [
'class' =>
'mw-undelete-editcomments' ], $link );
1240 $form->appendContent(
1242 'expanded' =>
false,
1245 'content' => $fieldset,
1249 Html::hidden(
'target', $this->mTarget ) .
1250 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() )
1256 $history .=
Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
1258 if ( $haveRevisions ) {
1259 # Show the page's stored (deleted) history
1261 if ( $this->permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
1267 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
1269 $this->
msg(
'showhideselectedversions' )->text()
1275 if ( $showLoadMore ) {
1277 Html::openElement(
'div' ) .
1280 [
'id' =>
'mw-load-more-revisions' ],
1281 $this->
msg(
'undelete-load-more-revisions' )->text()
1283 Html::closeElement(
'div' ) .
1287 $out->addWikiMsg(
'nohistory' );
1291 $history .=
Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
1292 $history .= Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1293 foreach ( $files as $row ) {
1294 $history .= $this->formatFileRow( $row );
1297 $history .= Html::closeElement(
'ul' );
1300 if ( $this->mAllowed ) {
1301 # Slip in the hidden controls here
1302 $misc = Html::hidden(
'target', $this->mTarget );
1303 $misc .= Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() );
1307 $form->appendContent(
new HtmlSnippet( $history ) );
1309 $out->addHTML( (
string)$form );
1311 $out->addHTML( $history );
1318 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1320 IDBAccessObject::READ_NORMAL,
1327 if ( $this->mAllowed ) {
1328 if ( $this->mInvert ) {
1329 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1343 if ( $this->mCanView ) {
1346 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1347 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1348 $last = $this->
msg(
'diff' )->escaped();
1349 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1350 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1353 $this->
msg(
'diff' )->text(),
1356 'target' => $this->mTargetObj->getPrefixedText(),
1362 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1363 $last = $this->
msg(
'diff' )->escaped();
1366 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1367 $last = $this->
msg(
'diff' )->escaped();
1371 $userLink = Linker::revUserTools( $revRecord );
1374 $minor = $revRecord->isMinor() ? ChangesList::flag(
'minor' ) :
'';
1377 $size = $row->ar_len;
1378 if ( $size !==
null ) {
1379 $revTextSize = Linker::formatRevisionSize( $size );
1383 $comment = $this->commentFormatter->formatRevision( $revRecord, $user );
1393 $attribs[
'class'] = implode(
' ', $classes );
1396 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1409 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1412 private function formatFileRow( $row ) {
1418 if ( $this->mCanView && $row->fa_storage_key ) {
1419 if ( $this->mAllowed ) {
1420 $checkBox =
Xml::check(
'fileid' . $row->fa_id );
1422 $key = urlencode( $row->fa_storage_key );
1423 $pageLink = $this->getFileLink( $file, $this->
getPageTitle(), $ts, $key );
1425 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1427 $userLink = $this->getFileUser( $file );
1428 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1429 $bytes = $this->
msg(
'parentheses' )
1430 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1432 $data = htmlspecialchars( $data .
' ' . $bytes );
1433 $comment = $this->getFileComment( $file );
1436 $canHide = $this->
isAllowed(
'deleterevision' );
1437 if ( $canHide || ( $file->getVisibility() && $this->isAllowed(
'deletedhistory' ) ) ) {
1438 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1440 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1443 'type' =>
'filearchive',
1444 'target' => $this->mTargetObj->getPrefixedDBkey(),
1445 'ids' => $row->fa_id
1447 $revdlink = Linker::revDeleteLink( $query,
1448 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1454 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1465 private function getPageLink( RevisionRecord $revRecord, LinkTarget $target, $ts ) {
1467 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1469 if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1473 [
'class' =>
'history-deleted' ],
1483 'target' => $this->mTargetObj->getPrefixedText(),
1488 if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1489 $class = Linker::getRevisionDeletedClass( $revRecord );
1490 $link =
'<span class="' . $class .
'">' . $link .
'</span>';
1506 private function getFileLink( $file, LinkTarget $target, $ts, $key ) {
1508 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1510 if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1513 [
'class' =>
'history-deleted' ],
1518 if ( $file->exists() ) {
1524 'target' => $this->mTargetObj->getPrefixedText(),
1526 'token' => $user->getEditToken( $key )
1530 $link = htmlspecialchars( $time );
1533 if ( $file->isDeleted( File::DELETED_FILE ) ) {
1534 $link =
'<span class="history-deleted">' . $link .
'</span>';
1546 private function getFileUser( $file ) {
1547 $uploader = $file->getUploader( File::FOR_THIS_USER, $this->
getAuthority() );
1549 return Html::rawElement(
1551 [
'class' =>
'history-deleted' ],
1552 $this->
msg(
'rev-deleted-user' )->escaped()
1556 $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1557 Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1559 if ( $file->isDeleted( File::DELETED_USER ) ) {
1560 $link = Html::rawElement(
1562 [
'class' =>
'history-deleted' ],
1576 private function getFileComment( $file ) {
1577 if ( !$file->userCan( File::DELETED_COMMENT, $this->getAuthority() ) ) {
1578 return Html::rawElement(
1580 [
'class' =>
'history-deleted' ],
1583 [
'class' =>
'comment' ],
1584 $this->
msg(
'rev-deleted-comment' )->escaped()
1589 $comment = $file->getDescription( File::FOR_THIS_USER, $this->
getAuthority() );
1590 $link = $this->commentFormatter->formatBlock( $comment );
1592 if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1593 $link = Html::rawElement(
1595 [
'class' =>
'history-deleted' ],
1603 private function undelete() {
1605 && $this->mTargetObj->getNamespace() ===
NS_FILE
1607 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1613 $undeletePage = $this->undeletePageFactory->newUndeletePage(
1614 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1615 $this->getAuthority()
1617 if ( $this->mUndeleteTalk && $undeletePage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1618 $undeletePage->setUndeleteAssociatedTalk(
true );
1620 $status = $undeletePage
1621 ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1622 ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1623 ->setUnsuppress( $this->mUnsuppress )
1625 ->undeleteIfAllowed( $this->mComment );
1627 if ( !$status->isGood() ) {
1628 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1629 $out->wrapWikiTextAsInterface(
1631 Status::wrap( $status )->getWikiText(
1640 $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
1641 $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
1643 if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1645 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1647 if ( $status->getValue()[UndeletePage::FILES_RESTORED] !== 0 ) {
1649 $this->mTargetObj, $this->mFileVersions, $this->
getUser(), $this->mComment );
1655 $this->watchlistManager->setWatch(
1672 return $this->
prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );