24 namespace MediaWiki\Specials;
83 private const REVISION_HISTORY_LIMIT = 500;
93 private $mTargetTimestamp = [];
97 private $mComment =
'';
106 private $mUnsuppress;
108 private $mFileVersions = [];
110 private $mUndeleteTalk;
112 private $mHistoryOffset;
119 private $mSearchPrefix;
168 parent::__construct(
'Undelete',
'deletedhistory' );
169 $this->permissionManager = $permissionManager;
170 $this->revisionStore = $revisionStore;
171 $this->revisionRenderer = $revisionRenderer;
172 $this->contentHandlerFactory = $contentHandlerFactory;
173 $this->changeTagDefStore = $changeTagDefStore;
174 $this->linkBatchFactory = $linkBatchFactory;
176 $this->dbProvider = $dbProvider;
177 $this->userOptionsLookup = $userOptionsLookup;
178 $this->wikiPageFactory = $wikiPageFactory;
179 $this->searchEngineFactory = $searchEngineFactory;
180 $this->undeletePageFactory = $undeletePageFactory;
181 $this->archivedRevisionLookup = $archivedRevisionLookup;
182 $this->commentFormatter = $commentFormatter;
189 private function loadRequest( $par ) {
193 $this->mAction = $request->getRawVal(
'action' );
194 if ( $par !==
null && $par !==
'' ) {
195 $this->mTarget = $par;
197 $this->mTarget = $request->getVal(
'target' );
200 $this->mTargetObj =
null;
202 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
206 $this->mSearchPrefix = $request->getText(
'prefix' );
207 $time = $request->getVal(
'timestamp' );
208 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
209 $this->mFilename = $request->getVal(
'file' );
211 $posted = $request->wasPosted() &&
212 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
213 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
214 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
215 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
216 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
217 $this->mDiff = $request->getCheck(
'diff' );
218 $this->mDiffOnly = $request->getBool(
'diffonly',
219 $this->userOptionsLookup->getOption( $this->getUser(),
'diffonly' ) );
220 $this->mComment = $request->getText(
'wpComment' );
221 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) &&
222 $this->permissionManager->userHasRight( $user,
'suppressrevision' );
223 $this->mToken = $request->getVal(
'token' );
224 $this->mUndeleteTalk = $request->getCheck(
'undeletetalk' );
225 $this->mHistoryOffset = $request->getVal(
'historyoffset' );
228 $this->mAllowed =
true;
229 $this->mCanView =
true;
230 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
231 $this->mAllowed =
false;
232 $this->mCanView =
true;
233 $this->mRestore =
false;
235 $this->mAllowed =
false;
236 $this->mCanView =
false;
237 $this->mTimestamp =
'';
238 $this->mRestore =
false;
241 if ( $this->mRestore || $this->mInvert ) {
243 $this->mFileVersions = [];
244 foreach ( $request->getValues() as $key => $val ) {
246 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
250 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
251 $this->mFileVersions[] = intval(
$matches[1] );
254 rsort( $timestamps );
255 $this->mTargetTimestamp = $timestamps;
268 $user = $user ?: $this->
getUser();
269 $block = $user->getBlock();
271 if ( $this->mTargetObj !==
null ) {
272 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
274 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
275 $sitewideBlock = $block && $block->isSitewide();
276 return $permission ===
'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
281 return $this->
isAllowed( $this->mRestriction, $user );
292 if ( !parent::userCanExecute( $user ) ) {
299 $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
317 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
319 $this->loadRequest( $par );
324 if ( $this->mTargetObj ===
null ) {
325 $out->addWikiMsg(
'undelete-header' );
327 # Not all users can just browse every deleted page from the list
328 if ( $this->permissionManager->userHasRight( $user,
'browsearchive' ) ) {
329 $this->showSearchForm();
336 if ( $this->mAllowed ) {
337 $out->setPageTitleMsg( $this->
msg(
'undeletepage' ) );
339 $out->setPageTitleMsg( $this->
msg(
'viewdeletedpage' ) );
342 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
344 if ( $this->mTimestamp !==
'' ) {
345 $this->showRevision( $this->mTimestamp );
346 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
349 if ( !
$file->exists() ) {
350 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
357 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
358 $this->showFileConfirmationForm( $this->mFilename );
360 $this->showFile( $this->mFilename );
362 } elseif ( $this->mAction ===
'submit' ) {
363 if ( $this->mRestore ) {
365 } elseif ( $this->mRevdel ) {
366 $this->redirectToRevDel();
368 } elseif ( $this->mAction ===
'render' ) {
379 private function redirectToRevDel() {
382 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
384 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
385 $revisionRecord = $this->archivedRevisionLookup
386 ->getRevisionRecordByTimestamp( $this->mTargetObj,
$matches[1] );
387 if ( $revisionRecord ) {
389 $revisions[ $revisionRecord->getId() ] = 1;
395 'type' =>
'revision',
397 'target' => $this->mTargetObj->getPrefixedText()
403 private function showSearchForm() {
405 $out->setPageTitleMsg( $this->
msg(
'undelete-search-title' ) );
406 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
'1' );
411 $fields[] = new \OOUI\ActionFieldLayout(
412 new \OOUI\TextInputWidget( [
414 'inputId' =>
'prefix',
416 'value' => $this->mSearchPrefix,
419 new \OOUI\ButtonInputWidget( [
420 'label' => $this->
msg(
'undelete-search-submit' )->text(),
421 'flags' => [
'primary',
'progressive' ],
422 'inputId' =>
'searchUndelete',
426 'label' =>
new \OOUI\HtmlSnippet(
428 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
435 $fieldset = new \OOUI\FieldsetLayout( [
436 'label' => $this->
msg(
'undelete-search-box' )->text(),
440 $form = new \OOUI\FormLayout( [
445 $form->appendContent(
447 new \OOUI\HtmlSnippet(
454 new \OOUI\PanelLayout( [
462 # List undeletable articles
463 if ( $this->mSearchPrefix ) {
466 if ( $fuzzySearch ) {
471 $this->showList( $result );
481 private function showList( $result ) {
484 if ( $result->numRows() == 0 ) {
485 $out->addWikiMsg(
'undelete-no-results' );
490 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
494 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
495 foreach ( $result as $row ) {
497 if ( $title !==
null ) {
498 $item = $linkRenderer->makeKnownLink(
500 $title->getPrefixedText(),
502 [
'target' => $title->getPrefixedText() ]
508 [
'class' =>
'mw-invalidtitle' ],
516 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
520 [
'class' =>
'undeleteResult' ],
521 $item . $this->
msg(
'word-separator' )->escaped() .
522 $this->
msg(
'parentheses' )->rawParams( $revs )->escaped()
527 $out->addHTML(
"</ul>\n" );
532 private function showRevision( $timestamp ) {
533 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
537 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
543 $this->
msg(
'undelete-back-to-list' )->text(),
545 [
'target' => $this->mTargetObj->getPrefixedText() ]
548 $subtitle =
"< $listLink";
549 $out->setSubtitle( $subtitle );
554 $archive, $this->mTargetObj )
558 $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
563 $out->addWikiMsg(
'undeleterevision-missing' );
569 $titleText = $this->mTargetObj->getPrefixedDBkey();
572 ? [
'rev-suppressed-text-permission', $titleText ]
573 : [
'rev-deleted-text-permission', $titleText ];
576 $this->
msg( $msg[0], $msg[1] )->parse(),
584 ? [
'rev-suppressed-text-view', $titleText ]
585 : [
'rev-deleted-text-view', $titleText ];
588 $this->
msg( $msg[0], $msg[1] )->parse(),
595 if ( $this->mDiff ) {
596 $previousRevRecord = $this->archivedRevisionLookup
597 ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
598 if ( $previousRevRecord ) {
599 $this->showDiff( $previousRevRecord, $revRecord );
600 if ( $this->mDiffOnly ) {
604 $out->addHTML(
'<hr />' );
606 $out->addWikiMsg(
'undelete-nodiff' );
611 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
612 $this->mTargetObj->getPrefixedText()
619 $time = $lang->userTimeAndDate( $timestamp, $user );
620 $d = $lang->userDate( $timestamp, $user );
621 $t = $lang->userTime( $timestamp, $user );
633 $undeleteRevisionContent =
'';
635 if ( !$this->mDiff ) {
642 $undeleteRevisionContent = $revdel .
' ';
646 $undeleteRevisionContent .= $out->msg(
655 if ( $this->mPreview || $isText ) {
658 $undeleteRevisionContent,
659 'mw-undelete-revision'
666 [
'class' =>
'mw-undelete-revision', ],
667 $undeleteRevisionContent
672 if ( $this->mPreview || !$isText ) {
675 $popts = $out->parserOptions();
677 $rendered = $this->revisionRenderer->getRenderedRevision(
686 $pout = $rendered->getRevisionParserOutput();
688 $out->addParserOutput( $pout, [
689 'enableSectionEditLinks' =>
false,
697 '@phan-var TextContent $content';
701 'readonly' =>
'readonly',
706 $buttonFields[] = new \OOUI\ButtonInputWidget( [
709 'label' => $this->
msg(
'showpreview' )->text()
715 $buttonFields[] = new \OOUI\ButtonInputWidget( [
718 'label' => $this->
msg(
'showdiff' )->text()
724 'style' =>
'clear: both' ] ) .
727 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
731 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
734 'name' =>
'timestamp',
735 'value' => $timestamp ] ) .
738 'name' =>
'wpEditToken',
739 'value' => $user->getEditToken() ] ) .
740 new \OOUI\FieldLayout(
742 'content' =>
new \OOUI\HorizontalLayout( [
743 'items' => $buttonFields
759 private function showDiff(
760 RevisionRecord $previousRevRecord,
761 RevisionRecord $currentRevRecord
766 $diffContext->setTitle( $currentTitle );
767 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
769 $contentModel = $currentRevRecord->getSlot(
774 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
775 ->createDifferenceEngine( $diffContext );
777 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
778 $diffEngine->showDiffStyle();
779 $formattedDiff = $diffEngine->getDiff(
780 $this->diffHeader( $previousRevRecord,
'o' ),
781 $this->diffHeader( $currentRevRecord,
'n' )
784 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
792 private function diffHeader( RevisionRecord $revRecord, $prefix ) {
793 if ( $revRecord instanceof RevisionArchiveRecord ) {
797 'target' => $this->mTargetObj->getPrefixedText(),
798 'timestamp' =>
wfTimestamp( TS_MW, $revRecord->getTimestamp() )
802 $targetPage = $revRecord->getPageAsLinkTarget();
803 $targetQuery = [
'oldid' => $revRecord->getId() ];
817 $dbr = $this->dbProvider->getReplicaDatabase();
818 $tagIds = $dbr->newSelectQueryBuilder()
819 ->select(
'ct_tag_id' )
820 ->from(
'change_tag' )
821 ->where( [
'ct_rev_id' => $revRecord->getId() ] )
822 ->caller( __METHOD__ )->fetchFieldValues();
824 foreach ( $tagIds as $tagId ) {
826 $tags[] = $this->changeTagDefStore->getName( (
int)$tagId );
827 }
catch ( NameTableAccessException $exception ) {
831 $tags = implode(
',', $tags );
837 $lang->userTimeAndDate( $revRecord->getTimestamp(), $user ),
838 $lang->userDate( $revRecord->getTimestamp(), $user ),
839 $lang->userTime( $revRecord->getTimestamp(), $user )
854 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
857 '<div id="mw-diff-' . $prefix .
'title2">' .
860 '<div id="mw-diff-' . $prefix .
'title3">' .
861 $minor . $this->commentFormatter->formatRevision( $revRecord, $user ) . $rdel .
'<br />' .
863 '<div id="mw-diff-' . $prefix .
'title5">' .
864 $tagSummary[0] .
'<br />' .
872 private function showFileConfirmationForm( $key ) {
876 $file =
new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
877 $out->addWikiMsg(
'undelete-show-file-confirm',
878 $this->mTargetObj->getText(),
879 $lang->userDate(
$file->getTimestamp(), $user ),
880 $lang->userTime(
$file->getTimestamp(), $user ) );
885 'target' => $this->mTarget,
887 'token' => $user->getEditToken( $key ),
900 private function showFile( $key ) {
903 # We mustn't allow the output to be CDN cached, otherwise
904 # if an admin previews a deleted image, and it's cached, then
905 # a user without appropriate permissions can toddle off and
906 # nab the image, and CDN will serve it
908 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
909 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
911 $path = $this->localRepo->getZonePath(
'deleted' ) .
'/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
912 $this->localRepo->streamFileWithStatus(
$path );
919 private function addRevisionsToBatch(
LinkBatch $batch, IResultWrapper $revisions ) {
920 foreach ( $revisions as $row ) {
930 private function addFilesToBatch(
LinkBatch $batch, IResultWrapper $files ) {
931 foreach ( $files as $row ) {
942 $out->setArticleBodyOnly(
true );
943 $dbr = $this->dbProvider->getReplicaDatabase();
944 if ( $this->mHistoryOffset ) {
945 $encOffset = $dbr->addQuotes( $dbr->timestamp( $this->mHistoryOffset ) );
946 $extraConds = [
"ar_timestamp < $encOffset" ];
950 $revisions = $this->archivedRevisionLookup->listRevisions(
953 self::REVISION_HISTORY_LIMIT + 1
955 $batch = $this->linkBatchFactory->newLinkBatch();
956 $this->addRevisionsToBatch( $batch, $revisions );
960 if ( $revisions->numRows() > self::REVISION_HISTORY_LIMIT ) {
962 $out->setStatusCode( 206 );
976 $numRevisions = $revisions->
numRows();
977 $displayCount = min( $numRevisions, self::REVISION_HISTORY_LIMIT );
978 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
979 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() :
null;
981 $revisions->rewind();
982 for ( $i = 0; $i < $displayCount; $i++ ) {
986 $history .= $this->
formatRevisionRow( $row, $earliestLiveTime, $numRevisions - $i );
996 if ( $this->mAllowed ) {
997 $out->addModules(
'mediawiki.misc-authed-ooui' );
999 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
1001 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1002 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
1007 $this->
getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
1009 $out->addHTML(
Html::openElement(
'div', [
'class' =>
'mw-undelete-history' ] ) );
1010 if ( $this->mAllowed ) {
1011 $out->addWikiMsg(
'undeletehistory' );
1012 $out->addWikiMsg(
'undeleterevdel' );
1014 $out->addWikiMsg(
'undeletehistorynoadmin' );
1018 # List all stored revisions
1019 $revisions = $this->archivedRevisionLookup->listRevisions(
1022 self::REVISION_HISTORY_LIMIT + 1
1024 $files = $archive->listFiles();
1025 $numRevisions = $revisions->numRows();
1026 $showLoadMore = $numRevisions > self::REVISION_HISTORY_LIMIT;
1027 $haveRevisions = $numRevisions > 0;
1028 $haveFiles = $files && $files->numRows() > 0;
1030 # Batch existence check on user and talk pages
1031 if ( $haveRevisions || $haveFiles ) {
1032 $batch = $this->linkBatchFactory->newLinkBatch();
1033 $this->addRevisionsToBatch( $batch, $revisions );
1036 $this->addFilesToBatch( $batch, $files );
1041 if ( $this->mAllowed ) {
1044 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
1045 # Start the form here
1046 $form = new \OOUI\FormLayout( [
1048 'action' => $action,
1053 # Show relevant lines from the deletion log:
1054 $deleteLogPage =
new LogPage(
'delete' );
1055 $out->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
1057 # Show relevant lines from the suppression log:
1058 $suppressLogPage =
new LogPage(
'suppress' );
1059 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
1060 $out->addHTML(
Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
1064 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1066 $fields[] = new \OOUI\Layout( [
1067 'content' =>
new \OOUI\HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
1070 $fields[] = new \OOUI\FieldLayout(
1071 new \OOUI\TextInputWidget( [
1072 'name' =>
'wpComment',
1073 'inputId' =>
'wpComment',
1074 'infusable' =>
true,
1075 'value' => $this->mComment,
1076 'autofocus' =>
true,
1083 'label' => $this->
msg(
'undeletecomment' )->text(),
1088 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
1089 $fields[] = new \OOUI\FieldLayout(
1090 new \OOUI\CheckboxInputWidget( [
1091 'name' =>
'wpUnsuppress',
1092 'inputId' =>
'mw-undelete-unsuppress',
1096 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
1097 'align' =>
'inline',
1102 $undelPage = $this->undeletePageFactory->newUndeletePage(
1103 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1104 $this->getContext()->getAuthority()
1106 if ( $undelPage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1107 $fields[] = new \OOUI\FieldLayout(
1108 new \OOUI\CheckboxInputWidget( [
1109 'name' =>
'undeletetalk',
1110 'inputId' =>
'mw-undelete-undeletetalk',
1111 'selected' =>
false,
1114 'label' => $this->
msg(
'undelete-undeletetalk' )->text(),
1115 'align' =>
'inline',
1120 $fields[] = new \OOUI\FieldLayout(
1122 'content' =>
new \OOUI\HorizontalLayout( [
1124 new \OOUI\ButtonInputWidget( [
1125 'name' =>
'restore',
1126 'inputId' =>
'mw-undelete-submit',
1128 'label' => $this->
msg(
'undeletebtn' )->text(),
1129 'flags' => [
'primary',
'progressive' ],
1132 new \OOUI\ButtonInputWidget( [
1134 'inputId' =>
'mw-undelete-invert',
1136 'label' => $this->
msg(
'undeleteinvert' )->text()
1143 $fieldset = new \OOUI\FieldsetLayout( [
1144 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
1145 'id' =>
'mw-undelete-table',
1150 $form->appendContent(
1151 new \OOUI\PanelLayout( [
1152 'expanded' =>
false,
1155 'content' => $fieldset,
1157 new \OOUI\HtmlSnippet(
1165 $history .=
Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
1167 if ( $haveRevisions ) {
1168 # Show the page's stored (deleted) history
1170 if ( $this->permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
1176 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
1178 $this->
msg(
'showhideselectedversions' )->text()
1184 if ( $showLoadMore ) {
1189 [
'id' =>
'mw-load-more-revisions' ],
1190 $this->
msg(
'undelete-load-more-revisions' )->text()
1196 $out->addWikiMsg(
'nohistory' );
1200 $history .=
Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
1202 foreach ( $files as $row ) {
1203 $history .= $this->formatFileRow( $row );
1209 if ( $this->mAllowed ) {
1210 # Slip in the hidden controls here
1216 $form->appendContent(
new \OOUI\HtmlSnippet( $history ) );
1218 $out->addHTML( (
string)$form );
1220 $out->addHTML( $history );
1227 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1229 RevisionStore::READ_NORMAL,
1236 if ( $this->mAllowed ) {
1237 if ( $this->mInvert ) {
1238 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1252 if ( $this->mCanView ) {
1256 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1257 $last = $this->
msg(
'diff' )->escaped();
1258 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1259 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1262 $this->
msg(
'diff' )->text(),
1265 'target' => $this->mTargetObj->getPrefixedText(),
1271 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1272 $last = $this->
msg(
'diff' )->escaped();
1275 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1276 $last = $this->
msg(
'diff' )->escaped();
1286 $size = $row->ar_len;
1287 if ( $size !==
null ) {
1292 $comment = $this->commentFormatter->formatRevision( $revRecord, $user );
1302 $attribs[
'class'] = implode(
' ', $classes );
1305 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1318 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1321 private function formatFileRow( $row ) {
1327 if ( $this->mCanView && $row->fa_storage_key ) {
1328 if ( $this->mAllowed ) {
1329 $checkBox =
Xml::check(
'fileid' . $row->fa_id );
1331 $key = urlencode( $row->fa_storage_key );
1334 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1336 $userLink = $this->getFileUser(
$file );
1337 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1338 $bytes = $this->
msg(
'parentheses' )
1339 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1341 $data = htmlspecialchars( $data .
' ' . $bytes );
1342 $comment = $this->getFileComment(
$file );
1345 $canHide = $this->
isAllowed(
'deleterevision' );
1346 if ( $canHide || (
$file->getVisibility() && $this->isAllowed(
'deletedhistory' ) ) ) {
1352 'type' =>
'filearchive',
1353 'target' => $this->mTargetObj->getPrefixedDBkey(),
1354 'ids' => $row->fa_id
1363 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1374 private function getPageLink( RevisionRecord $revRecord, $titleObj, $ts ) {
1376 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1382 [
'class' =>
'history-deleted' ],
1392 'target' => $this->mTargetObj->getPrefixedText(),
1399 $link =
'<span class="' . $class .
'">' . $link .
'</span>';
1415 private function getFileLink(
$file, $titleObj, $ts, $key ) {
1417 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1422 [
'class' =>
'history-deleted' ],
1427 if (
$file->exists() ) {
1433 'target' => $this->mTargetObj->getPrefixedText(),
1435 'token' => $user->getEditToken( $key )
1439 $link = htmlspecialchars( $time );
1443 $link =
'<span class="history-deleted">' . $link .
'</span>';
1455 private function getFileUser(
$file ) {
1460 [
'class' =>
'history-deleted' ],
1461 $this->
msg(
'rev-deleted-user' )->escaped()
1471 [
'class' =>
'history-deleted' ],
1485 private function getFileComment(
$file ) {
1489 [
'class' =>
'history-deleted' ],
1492 [
'class' =>
'comment' ],
1493 $this->
msg(
'rev-deleted-comment' )->escaped()
1499 $link = $this->commentFormatter->formatBlock( $comment );
1504 [
'class' =>
'history-deleted' ],
1512 private function undelete() {
1514 && $this->mTargetObj->getNamespace() ===
NS_FILE
1516 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1522 $undeletePage = $this->undeletePageFactory->newUndeletePage(
1523 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1524 $this->getAuthority()
1526 if ( $this->mUndeleteTalk && $undeletePage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1527 $undeletePage->setUndeleteAssociatedTalk(
true );
1529 $status = $undeletePage
1530 ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1531 ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1532 ->setUnsuppress( $this->mUnsuppress )
1534 ->undeleteIfAllowed( $this->mComment );
1536 if ( !$status->isGood() ) {
1537 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1538 $out->wrapWikiTextAsInterface(
1552 if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1554 $out->setPageTitleMsg( $this->
msg(
'undelete-error' ) );
1558 $this->mTargetObj, $this->mFileVersions, $this->
getUser(), $this->mComment );
1575 return $this->
prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
1587 class_alias( SpecialUndelete::class,
'SpecialUndelete' );
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
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 flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
Implements some public methods and some protected utility functions which are required by multiple ch...
Class representing a list of titles The execute() method checks them all for existence and adds them ...
execute()
Do the query and add the results to the LinkCache object.
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
A class containing constants representing the names of configuration variables.
const UploadMaintenance
Name constant for the UploadMaintenance setting, for use with Config::get()
Backend logic for performing a page undelete action.
Service for creating WikiPage objects.
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
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,...
displayRestrictionError()
Output an error message telling the user what access level they have to have.
getUser()
Shortcut to get the User executing this instance.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
prefixSearchString( $search, $limit, $offset, SearchEngineFactory $searchEngineFactory=null)
Perform a regular substring search for prefixSearchSubpages.
getPageTitle( $subpage=false)
Get a self-referential title object.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getLanguage()
Shortcut to get user's language.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
The Message class deals with fetching and processing of interface message into a variety of formats.
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.
getLocalRepo()
Get the local repository, i.e.
Factory class for SearchEngine.
Content object implementation for representing flat text.
Show an error when the user tries to do something whilst blocked.
Module of static functions for generating XML.
static closeElement( $element)
Shortcut to close an XML element.
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
static openElement( $element, $attribs=null)
This opens an XML element.
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Service for page undelete actions.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.