63 private $mTargetTimestamp = [];
67 private $mComment =
'';
78 private $mFileVersions = [];
80 private $mUndeleteTalk;
87 private $mSearchPrefix;
90 private $permissionManager;
93 private $revisionStore;
96 private $revisionRenderer;
99 private $contentHandlerFactory;
102 private $changeTagDefStore;
105 private $linkBatchFactory;
111 private $loadBalancer;
114 private $userOptionsLookup;
117 private $wikiPageFactory;
120 private $searchEngineFactory;
123 private $undeletePageFactory;
126 private $archivedRevisionLookup;
129 private $commentFormatter;
163 parent::__construct(
'Undelete',
'deletedhistory' );
164 $this->permissionManager = $permissionManager;
165 $this->revisionStore = $revisionStore;
166 $this->revisionRenderer = $revisionRenderer;
167 $this->contentHandlerFactory = $contentHandlerFactory;
168 $this->changeTagDefStore = $changeTagDefStore;
169 $this->linkBatchFactory = $linkBatchFactory;
171 $this->loadBalancer = $loadBalancer;
172 $this->userOptionsLookup = $userOptionsLookup;
173 $this->wikiPageFactory = $wikiPageFactory;
174 $this->searchEngineFactory = $searchEngineFactory;
175 $this->undeletePageFactory = $undeletePageFactory;
176 $this->archivedRevisionLookup = $archivedRevisionLookup;
177 $this->commentFormatter = $commentFormatter;
184 private function loadRequest( $par ) {
188 $this->mAction = $request->getRawVal(
'action' );
189 if ( $par !==
null && $par !==
'' ) {
190 $this->mTarget = $par;
192 $this->mTarget = $request->getVal(
'target' );
195 $this->mTargetObj =
null;
197 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
198 $this->mTargetObj = Title::newFromText( $this->mTarget );
201 $this->mSearchPrefix = $request->getText(
'prefix' );
202 $time = $request->getVal(
'timestamp' );
203 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
204 $this->mFilename = $request->getVal(
'file' );
206 $posted = $request->wasPosted() &&
207 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
208 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
209 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
210 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
211 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
212 $this->mDiff = $request->getCheck(
'diff' );
213 $this->mDiffOnly = $request->getBool(
'diffonly',
214 $this->userOptionsLookup->getOption( $this->getUser(),
'diffonly' ) );
215 $this->mComment = $request->getText(
'wpComment' );
216 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) &&
217 $this->permissionManager->userHasRight( $user,
'suppressrevision' );
218 $this->mToken = $request->getVal(
'token' );
219 $this->mUndeleteTalk = $request->getCheck(
'undeletetalk' );
222 $this->mAllowed =
true;
223 $this->mCanView =
true;
224 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
225 $this->mAllowed =
false;
226 $this->mCanView =
true;
227 $this->mRestore =
false;
229 $this->mAllowed =
false;
230 $this->mCanView =
false;
231 $this->mTimestamp =
'';
232 $this->mRestore =
false;
235 if ( $this->mRestore || $this->mInvert ) {
237 $this->mFileVersions = [];
238 foreach ( $request->getValues() as $key => $val ) {
240 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
241 array_push( $timestamps,
$matches[1] );
244 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
245 $this->mFileVersions[] = intval(
$matches[1] );
248 rsort( $timestamps );
249 $this->mTargetTimestamp = $timestamps;
262 $user = $user ?: $this->
getUser();
263 $block = $user->getBlock();
265 if ( $this->mTargetObj !==
null ) {
266 return $this->permissionManager->userCan( $permission, $user, $this->mTargetObj );
268 $hasRight = $this->permissionManager->userHasRight( $user, $permission );
269 $sitewideBlock = $block && $block->isSitewide();
270 return $permission ===
'undelete' ? ( $hasRight && !$sitewideBlock ) : $hasRight;
275 return $this->
isAllowed( $this->mRestriction, $user );
286 if ( !parent::userCanExecute( $user ) ) {
293 $this->mTargetObj && $this->permissionManager->isBlockedFrom( $user, $this->mTargetObj ) ) {
311 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
313 $this->loadRequest( $par );
318 if ( $this->mTargetObj ===
null ) {
319 $out->addWikiMsg(
'undelete-header' );
321 # Not all users can just browse every deleted page from the list
322 if ( $this->permissionManager->userHasRight( $user,
'browsearchive' ) ) {
323 $this->showSearchForm();
330 if ( $this->mAllowed ) {
331 $out->setPageTitle( $this->
msg(
'undeletepage' ) );
333 $out->setPageTitle( $this->
msg(
'viewdeletedpage' ) );
336 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
338 if ( $this->mTimestamp !==
'' ) {
339 $this->showRevision( $this->mTimestamp );
340 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
343 if ( !
$file->exists() ) {
344 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
345 } elseif ( !
$file->userCan( File::DELETED_FILE, $user ) ) {
346 if (
$file->isDeleted( File::DELETED_RESTRICTED ) ) {
351 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
352 $this->showFileConfirmationForm( $this->mFilename );
354 $this->showFile( $this->mFilename );
356 } elseif ( $this->mAction ===
'submit' ) {
357 if ( $this->mRestore ) {
359 } elseif ( $this->mRevdel ) {
360 $this->redirectToRevDel();
372 private function redirectToRevDel() {
375 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
377 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
378 $revisionRecord = $this->archivedRevisionLookup
379 ->getRevisionRecordByTimestamp( $this->mTargetObj,
$matches[1] );
380 if ( $revisionRecord ) {
382 $revisions[ $revisionRecord->getId() ] = 1;
388 'type' =>
'revision',
390 'target' => $this->mTargetObj->getPrefixedText()
396 private function showSearchForm() {
398 $out->setPageTitle( $this->
msg(
'undelete-search-title' ) );
399 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
'1' );
404 $fields[] =
new OOUI\ActionFieldLayout(
405 new OOUI\TextInputWidget( [
407 'inputId' =>
'prefix',
409 'value' => $this->mSearchPrefix,
412 new OOUI\ButtonInputWidget( [
413 'label' => $this->
msg(
'undelete-search-submit' )->text(),
414 'flags' => [
'primary',
'progressive' ],
415 'inputId' =>
'searchUndelete',
419 'label' =>
new OOUI\HtmlSnippet(
421 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
428 $fieldset =
new OOUI\FieldsetLayout( [
429 'label' => $this->
msg(
'undelete-search-box' )->text(),
433 $form =
new OOUI\FormLayout( [
438 $form->appendContent(
440 new OOUI\HtmlSnippet(
441 Html::hidden(
'title', $this->
getPageTitle()->getPrefixedDBkey() ) .
442 Html::hidden(
'fuzzy', $fuzzySearch )
447 new OOUI\PanelLayout( [
455 # List undeletable articles
456 if ( $this->mSearchPrefix ) {
459 if ( $fuzzySearch ) {
464 $this->showList( $result );
474 private function showList( $result ) {
477 if ( $result->numRows() == 0 ) {
478 $out->addWikiMsg(
'undelete-no-results' );
483 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
487 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
488 foreach ( $result as $row ) {
489 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
491 $item = $linkRenderer->makeKnownLink(
493 $title->getPrefixedText(),
495 [
'target' =>
$title->getPrefixedText() ]
499 $item = Html::element(
501 [
'class' =>
'mw-invalidtitle' ],
502 Linker::getInvalidTitleDescription(
509 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
513 [
'class' =>
'undeleteResult' ],
514 $item . $this->
msg(
'word-separator' )->escaped() .
515 $this->
msg(
'parentheses' )->rawParams( $revs )->escaped()
520 $out->addHTML(
"</ul>\n" );
525 private function showRevision( $timestamp ) {
526 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
530 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
536 $this->
msg(
'undelete-back-to-list' )->text(),
538 [
'target' => $this->mTargetObj->getPrefixedText() ]
541 $subtitle =
"< $listLink";
542 $out->setSubtitle( $subtitle );
547 $archive, $this->mTargetObj )
551 $revRecord = $this->archivedRevisionLookup->getRevisionRecordByTimestamp( $this->mTargetObj, $timestamp );
556 $out->addWikiMsg(
'undeleterevision-missing' );
560 if ( $revRecord->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
562 $titleText = $this->mTargetObj->getPrefixedDBkey();
563 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
564 $msg = $revRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED )
565 ? [
'rev-suppressed-text-permission', $titleText ]
566 : [
'rev-deleted-text-permission', $titleText ];
569 $this->
msg( $msg[0], $msg[1] )->parse(),
576 $msg = $revRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED )
577 ? [
'rev-suppressed-text-view', $titleText ]
578 : [
'rev-deleted-text-view', $titleText ];
581 $this->
msg( $msg[0], $msg[1] )->parse(),
588 if ( $this->mDiff ) {
589 $previousRevRecord = $this->archivedRevisionLookup
590 ->getPreviousRevisionRecord( $this->mTargetObj, $timestamp );
591 if ( $previousRevRecord ) {
592 $this->showDiff( $previousRevRecord, $revRecord );
593 if ( $this->mDiffOnly ) {
597 $out->addHTML(
'<hr />' );
599 $out->addWikiMsg(
'undelete-nodiff' );
604 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
605 $this->mTargetObj->getPrefixedText()
612 $time =
$lang->userTimeAndDate( $timestamp, $user );
613 $d =
$lang->userDate( $timestamp, $user );
614 $t =
$lang->userTime( $timestamp, $user );
615 $userLink = Linker::revUserTools( $revRecord );
619 RevisionRecord::FOR_THIS_USER,
626 $undeleteRevisionContent =
'';
628 if ( !$this->mDiff ) {
629 $revdel = Linker::getRevDeleteLink(
635 $undeleteRevisionContent = $revdel .
' ';
639 $undeleteRevisionContent .= $out->msg(
648 if ( $this->mPreview || $isText ) {
651 $undeleteRevisionContent,
652 'mw-undelete-revision'
659 [
'class' =>
'mw-undelete-revision', ],
660 $undeleteRevisionContent
665 if ( $this->mPreview || !$isText ) {
668 $popts = $out->parserOptions();
670 $rendered = $this->revisionRenderer->getRenderedRevision(
674 [
'audience' => RevisionRecord::FOR_THIS_USER,
'causeAction' =>
'undelete-preview' ]
679 $pout = $rendered->getRevisionParserOutput();
681 $out->addParserOutput( $pout, [
682 'enableSectionEditLinks' =>
false,
690 '@phan-var TextContent $content';
694 'readonly' =>
'readonly',
699 $buttonFields[] =
new OOUI\ButtonInputWidget( [
702 'label' => $this->
msg(
'showpreview' )->text()
708 $buttonFields[] =
new OOUI\ButtonInputWidget( [
711 'label' => $this->
msg(
'showdiff' )->text()
716 Xml::openElement(
'div', [
717 'style' =>
'clear: both' ] ) .
718 Xml::openElement(
'form', [
720 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
721 Xml::element(
'input', [
724 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
725 Xml::element(
'input', [
727 'name' =>
'timestamp',
728 'value' => $timestamp ] ) .
729 Xml::element(
'input', [
731 'name' =>
'wpEditToken',
732 'value' => $user->getEditToken() ] ) .
733 new OOUI\FieldLayout(
735 'content' =>
new OOUI\HorizontalLayout( [
736 'items' => $buttonFields
740 Xml::closeElement(
'form' ) .
741 Xml::closeElement(
'div' )
752 private function showDiff(
759 $diffContext->setTitle( $currentTitle );
760 $diffContext->setWikiPage( $this->wikiPageFactory->newFromTitle( $currentTitle ) );
762 $contentModel = $currentRevRecord->
getSlot(
767 $diffEngine = $this->contentHandlerFactory->getContentHandler( $contentModel )
768 ->createDifferenceEngine( $diffContext );
770 $diffEngine->setRevisions( $previousRevRecord, $currentRevRecord );
771 $diffEngine->showDiffStyle();
772 $formattedDiff = $diffEngine->getDiff(
773 $this->diffHeader( $previousRevRecord,
'o' ),
774 $this->diffHeader( $currentRevRecord,
'n' )
777 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
785 private function diffHeader(
RevisionRecord $revRecord, $prefix ) {
790 'target' => $this->mTargetObj->getPrefixedText(),
796 $targetQuery = [
'oldid' => $revRecord->
getId() ];
802 $rdel = Linker::getRevDeleteLink( $user, $revRecord, $this->mTargetObj );
810 $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
811 $tagIds =
$dbr->selectFieldValues(
814 [
'ct_rev_id' => $revRecord->
getId() ],
818 foreach ( $tagIds as $tagId ) {
820 $tags[] = $this->changeTagDefStore->getName( (
int)$tagId );
825 $tags = implode(
',', $tags );
830 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
843 '<div id="mw-diff-' . $prefix .
'title2">' .
844 Linker::revUserTools( $revRecord ) .
'<br />' .
846 '<div id="mw-diff-' . $prefix .
'title3">' .
847 $minor . $this->commentFormatter->formatRevision( $revRecord, $user ) . $rdel .
'<br />' .
849 '<div id="mw-diff-' . $prefix .
'title5">' .
850 $tagSummary[0] .
'<br />' .
858 private function showFileConfirmationForm( $key ) {
863 $out->addWikiMsg(
'undelete-show-file-confirm',
864 $this->mTargetObj->getText(),
865 $lang->userDate(
$file->getTimestamp(), $user ),
866 $lang->userTime(
$file->getTimestamp(), $user ) );
868 Xml::openElement(
'form', [
871 'target' => $this->mTarget,
873 'token' => $user->getEditToken( $key ),
877 Xml::submitButton( $this->msg(
'undelete-show-file-submit' )->text() ) .
886 private function showFile( $key ) {
889 # We mustn't allow the output to be CDN cached, otherwise
890 # if an admin previews a deleted image, and it's cached, then
891 # a user without appropriate permissions can toddle off and
892 # nab the image, and CDN will serve it
894 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
895 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
896 $response->header(
'Pragma: no-cache' );
898 $path = $this->localRepo->getZonePath(
'deleted' ) .
'/' . $this->localRepo->getDeletedHashPath( $key ) . $key;
899 $this->localRepo->streamFileWithStatus(
$path );
906 if ( $this->mAllowed ) {
907 $out->addModules(
'mediawiki.misc-authed-ooui' );
909 $out->addModuleStyles(
'mediawiki.interface.helpers.styles' );
911 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
912 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
917 $this->
getHookRunner()->onUndeleteForm__showHistory( $archive, $this->mTargetObj );
919 $out->addHTML( Html::openElement(
'div', [
'class' =>
'mw-undelete-history' ] ) );
920 if ( $this->mAllowed ) {
921 $out->addWikiMsg(
'undeletehistory' );
922 $out->addWikiMsg(
'undeleterevdel' );
924 $out->addWikiMsg(
'undeletehistorynoadmin' );
926 $out->addHTML( Html::closeElement(
'div' ) );
928 # List all stored revisions
929 $revisions = $this->archivedRevisionLookup->listRevisions( $this->mTargetObj );
930 $files = $archive->listFiles();
932 $haveRevisions = $revisions && $revisions->numRows() > 0;
933 $haveFiles = $files && $files->numRows() > 0;
935 # Batch existence check on user and talk pages
936 if ( $haveRevisions || $haveFiles ) {
937 $batch = $this->linkBatchFactory->newLinkBatch();
938 if ( $haveRevisions ) {
939 foreach ( $revisions as $row ) {
940 $batch->add(
NS_USER, $row->ar_user_text );
943 $revisions->seek( 0 );
946 foreach ( $files as $row ) {
947 $batch->add(
NS_USER, $row->fa_user_text );
955 if ( $this->mAllowed ) {
958 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
959 # Start the form here
960 $form =
new OOUI\FormLayout( [
967 # Show relevant lines from the deletion log:
968 $deleteLogPage =
new LogPage(
'delete' );
969 $out->addHTML( Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
970 LogEventsList::showLogExtract( $out,
'delete', $this->mTargetObj );
971 # Show relevant lines from the suppression log:
972 $suppressLogPage =
new LogPage(
'suppress' );
973 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
974 $out->addHTML( Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
975 LogEventsList::showLogExtract( $out,
'suppress', $this->mTargetObj );
978 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
980 $fields[] =
new OOUI\Layout( [
981 'content' =>
new OOUI\HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
984 $fields[] =
new OOUI\FieldLayout(
985 new OOUI\TextInputWidget( [
986 'name' =>
'wpComment',
987 'inputId' =>
'wpComment',
989 'value' => $this->mComment,
994 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
997 'label' => $this->
msg(
'undeletecomment' )->text(),
1002 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
1003 $fields[] =
new OOUI\FieldLayout(
1004 new OOUI\CheckboxInputWidget( [
1005 'name' =>
'wpUnsuppress',
1006 'inputId' =>
'mw-undelete-unsuppress',
1010 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
1011 'align' =>
'inline',
1016 $undelPage = $this->undeletePageFactory->newUndeletePage(
1017 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1018 $this->getContext()->getAuthority()
1020 if ( $undelPage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1021 $fields[] =
new OOUI\FieldLayout(
1022 new OOUI\CheckboxInputWidget( [
1023 'name' =>
'undeletetalk',
1024 'inputId' =>
'mw-undelete-undeletetalk',
1025 'selected' =>
false,
1028 'label' => $this->
msg(
'undelete-undeletetalk' )->text(),
1029 'align' =>
'inline',
1034 $fields[] =
new OOUI\FieldLayout(
1036 'content' =>
new OOUI\HorizontalLayout( [
1038 new OOUI\ButtonInputWidget( [
1039 'name' =>
'restore',
1040 'inputId' =>
'mw-undelete-submit',
1042 'label' => $this->
msg(
'undeletebtn' )->text(),
1043 'flags' => [
'primary',
'progressive' ],
1046 new OOUI\ButtonInputWidget( [
1048 'inputId' =>
'mw-undelete-invert',
1050 'label' => $this->
msg(
'undeleteinvert' )->text()
1057 $fieldset =
new OOUI\FieldsetLayout( [
1058 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
1059 'id' =>
'mw-undelete-table',
1064 $form->appendContent(
1065 new OOUI\PanelLayout( [
1066 'expanded' =>
false,
1069 'content' => $fieldset,
1071 new OOUI\HtmlSnippet(
1072 Html::hidden(
'target', $this->mTarget ) .
1073 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() )
1079 $history .= Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
1081 if ( $haveRevisions ) {
1082 # Show the page's stored (deleted) history
1084 if ( $this->permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
1085 $history .= Html::element(
1090 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
1092 $this->
msg(
'showhideselectedversions' )->text()
1096 $history .= Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1097 $remaining = $revisions->numRows();
1098 $firstRev = $this->revisionStore->getFirstRevision( $this->mTargetObj );
1099 $earliestLiveTime = $firstRev ? $firstRev->getTimestamp() :
null;
1101 foreach ( $revisions as $row ) {
1106 $history .= Html::closeElement(
'ul' );
1108 $out->addWikiMsg(
'nohistory' );
1112 $history .= Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
1113 $history .= Html::openElement(
'ul', [
'class' =>
'mw-undelete-revlist' ] );
1114 foreach ( $files as $row ) {
1115 $history .= $this->formatFileRow( $row );
1118 $history .= Html::closeElement(
'ul' );
1121 if ( $this->mAllowed ) {
1122 # Slip in the hidden controls here
1123 $misc = Html::hidden(
'target', $this->mTarget );
1124 $misc .= Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() );
1128 $form->appendContent(
new OOUI\HtmlSnippet( $history ) );
1130 $out->addHTML( (
string)$form );
1132 $out->addHTML( $history );
1139 $revRecord = $this->revisionStore->newRevisionFromArchiveRow(
1141 RevisionStore::READ_NORMAL,
1148 if ( $this->mAllowed ) {
1149 if ( $this->mInvert ) {
1150 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1151 $checkBox = Xml::check(
"ts$ts" );
1153 $checkBox = Xml::check(
"ts$ts",
true );
1156 $checkBox = Xml::check(
"ts$ts" );
1164 if ( $this->mCanView ) {
1167 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1168 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1169 $last = $this->
msg(
'diff' )->escaped();
1170 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1171 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1174 $this->
msg(
'diff' )->text(),
1177 'target' => $this->mTargetObj->getPrefixedText(),
1183 $pageLink = $this->getPageLink( $revRecord, $titleObj, $ts );
1184 $last = $this->
msg(
'diff' )->escaped();
1187 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1188 $last = $this->
msg(
'diff' )->escaped();
1192 $userLink = Linker::revUserTools( $revRecord );
1195 $minor = $revRecord->
isMinor() ? ChangesList::flag(
'minor' ) :
'';
1198 $size = $row->ar_len;
1199 if ( $size !==
null ) {
1200 $revTextSize = Linker::formatRevisionSize( $size );
1204 $comment = $this->commentFormatter->formatRevision( $revRecord, $user );
1214 $attribs[
'class'] = implode(
' ', $classes );
1217 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1230 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1233 private function formatFileRow( $row ) {
1239 if ( $this->mCanView && $row->fa_storage_key ) {
1240 if ( $this->mAllowed ) {
1241 $checkBox = Xml::check(
'fileid' . $row->fa_id );
1243 $key = urlencode( $row->fa_storage_key );
1246 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1248 $userLink = $this->getFileUser(
$file );
1249 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1250 $bytes = $this->
msg(
'parentheses' )
1251 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1253 $data = htmlspecialchars( $data .
' ' . $bytes );
1254 $comment = $this->getFileComment(
$file );
1257 $canHide = $this->
isAllowed(
'deleterevision' );
1258 if ( $canHide || (
$file->getVisibility() && $this->isAllowed(
'deletedhistory' ) ) ) {
1259 if ( !
$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1261 $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1264 'type' =>
'filearchive',
1265 'target' => $this->mTargetObj->getPrefixedDBkey(),
1266 'ids' => $row->fa_id
1268 $revdlink = Linker::revDeleteLink( $query,
1269 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1275 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1286 private function getPageLink(
RevisionRecord $revRecord, $titleObj, $ts ) {
1288 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1290 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1292 return Html::element(
1294 [
'class' =>
'history-deleted' ],
1304 'target' => $this->mTargetObj->getPrefixedText(),
1309 if ( $revRecord->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1310 $class = Linker::getRevisionDeletedClass( $revRecord );
1311 $link =
'<span class="' . $class .
'">' . $link .
'</span>';
1327 private function getFileLink(
$file, $titleObj, $ts, $key ) {
1329 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1331 if ( !
$file->userCan( File::DELETED_FILE, $user ) ) {
1332 return Html::element(
1334 [
'class' =>
'history-deleted' ],
1344 'target' => $this->mTargetObj->getPrefixedText(),
1346 'token' => $user->getEditToken( $key )
1350 if (
$file->isDeleted( File::DELETED_FILE ) ) {
1351 $link =
'<span class="history-deleted">' . $link .
'</span>';
1363 private function getFileUser(
$file ) {
1366 return Html::rawElement(
1368 [
'class' =>
'history-deleted' ],
1369 $this->
msg(
'rev-deleted-user' )->escaped()
1373 $link = Linker::userLink( $uploader->getId(), $uploader->getName() ) .
1374 Linker::userToolLinks( $uploader->getId(), $uploader->getName() );
1376 if (
$file->isDeleted( File::DELETED_USER ) ) {
1377 $link = Html::rawElement(
1379 [
'class' =>
'history-deleted' ],
1393 private function getFileComment(
$file ) {
1394 if ( !
$file->userCan( File::DELETED_COMMENT, $this->getAuthority() ) ) {
1395 return Html::rawElement(
1397 [
'class' =>
'history-deleted' ],
1400 [
'class' =>
'comment' ],
1401 $this->
msg(
'rev-deleted-comment' )->escaped()
1407 $link = $this->commentFormatter->formatBlock( $comment );
1409 if (
$file->isDeleted( File::DELETED_COMMENT ) ) {
1410 $link = Html::rawElement(
1412 [
'class' =>
'history-deleted' ],
1420 private function undelete() {
1421 if ( $this->
getConfig()->
get( MainConfigNames::UploadMaintenance )
1422 && $this->mTargetObj->getNamespace() ===
NS_FILE
1424 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1430 $undeletePage = $this->undeletePageFactory->newUndeletePage(
1431 $this->wikiPageFactory->newFromTitle( $this->mTargetObj ),
1432 $this->getAuthority()
1434 if ( $this->mUndeleteTalk && $undeletePage->canProbablyUndeleteAssociatedTalk()->isGood() ) {
1435 $undeletePage->setUndeleteAssociatedTalk(
true );
1437 $status = $undeletePage
1438 ->setUndeleteOnlyTimestamps( $this->mTargetTimestamp )
1439 ->setUndeleteOnlyFileVersions( $this->mFileVersions )
1440 ->setUnsuppress( $this->mUnsuppress )
1442 ->undeleteIfAllowed( $this->mComment );
1444 if ( !$status->isGood() ) {
1445 $out->setPageTitle( $this->
msg(
'undelete-error' ) );
1446 $out->wrapWikiTextAsInterface(
1448 Status::wrap( $status )->getWikiText(
1457 $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED];
1458 $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED];
1460 if ( $restoredRevs === 0 && $restoredFiles === 0 ) {
1462 $out->setPageTitle( $this->
msg(
'undelete-error' ) );
1464 if ( $status->getValue()[UndeletePage::FILES_RESTORED] !== 0 ) {
1466 $this->mTargetObj, $this->mFileVersions, $this->
getUser(), $this->mComment );
1483 return $this->
prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );