67 parent::__construct(
'Undelete',
'deletedhistory' );
78 $this->mAction = $request->getVal(
'action' );
79 if ( $par !==
null && $par !==
'' ) {
80 $this->mTarget = $par;
82 $this->mTarget = $request->getVal(
'target' );
85 $this->mTargetObj =
null;
87 if ( $this->mTarget !==
null && $this->mTarget !==
'' ) {
91 $this->mSearchPrefix = $request->getText(
'prefix' );
92 $time = $request->getVal(
'timestamp' );
93 $this->mTimestamp = $time ?
wfTimestamp( TS_MW, $time ) :
'';
94 $this->mFilename = $request->getVal(
'file' );
96 $posted = $request->wasPosted() &&
97 $user->matchEditToken( $request->getVal(
'wpEditToken' ) );
98 $this->mRestore = $request->getCheck(
'restore' ) && $posted;
99 $this->mRevdel = $request->getCheck(
'revdel' ) && $posted;
100 $this->mInvert = $request->getCheck(
'invert' ) && $posted;
101 $this->mPreview = $request->getCheck(
'preview' ) && $posted;
102 $this->mDiff = $request->getCheck(
'diff' );
103 $this->mDiffOnly = $request->getBool(
'diffonly', $this->
getUser()->getOption(
'diffonly' ) );
104 $this->mComment = $request->getText(
'wpComment' );
105 $this->mUnsuppress = $request->getVal(
'wpUnsuppress' ) && MediaWikiServices::getInstance()
106 ->getPermissionManager()
107 ->userHasRight( $user,
'suppressrevision' );
108 $this->mToken = $request->getVal(
'token' );
110 $block = $user->getBlock();
111 if ( $this->
isAllowed(
'undelete' ) && !( $block && $block->isSitewide() ) ) {
112 $this->mAllowed =
true;
113 $this->mCanView =
true;
114 } elseif ( $this->
isAllowed(
'deletedtext' ) ) {
115 $this->mAllowed =
false;
116 $this->mCanView =
true;
117 $this->mRestore =
false;
119 $this->mAllowed =
false;
120 $this->mCanView =
false;
121 $this->mTimestamp =
'';
122 $this->mRestore =
false;
125 if ( $this->mRestore || $this->mInvert ) {
127 $this->mFileVersions = [];
128 foreach ( $request->getValues() as $key => $val ) {
130 if ( preg_match(
'/^ts(\d{14})$/', $key,
$matches ) ) {
131 array_push( $timestamps,
$matches[1] );
134 if ( preg_match(
'/^fileid(\d+)$/', $key,
$matches ) ) {
135 $this->mFileVersions[] = intval(
$matches[1] );
138 rsort( $timestamps );
139 $this->mTargetTimestamp = $timestamps;
152 $user = $user ?: $this->
getUser();
153 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
155 if ( $this->mTargetObj !==
null ) {
156 return $permissionManager->userCan( $permission, $user, $this->mTargetObj );
158 return $permissionManager->userHasRight( $user, $permission );
163 return $this->
isAllowed( $this->mRestriction, $user );
173 $this->
addHelpLink(
'Help:Deletion_and_undeletion' );
180 if ( is_null( $this->mTargetObj ) ) {
181 $out->addWikiMsg(
'undelete-header' );
183 # Not all users can just browse every deleted page from the list
184 if ( MediaWikiServices::getInstance()
186 ->userHasRight( $user,
'browsearchive' )
195 if ( $this->mAllowed ) {
196 $out->setPageTitle( $this->
msg(
'undeletepage' ) );
198 $out->setPageTitle( $this->
msg(
'viewdeletedpage' ) );
201 $this->
getSkin()->setRelevantTitle( $this->mTargetObj );
203 if ( $this->mTimestamp !==
'' ) {
205 } elseif ( $this->mFilename !==
null && $this->mTargetObj->inNamespace(
NS_FILE ) ) {
208 if ( !
$file->exists() ) {
209 $out->addWikiMsg(
'filedelete-nofile', $this->mFilename );
216 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
219 $this->
showFile( $this->mFilename );
221 } elseif ( $this->mAction ===
'submit' ) {
222 if ( $this->mRestore ) {
224 } elseif ( $this->mRevdel ) {
242 foreach ( $this->
getRequest()->getValues() as $key => $val ) {
244 if ( preg_match(
"/^ts(\d{14})$/", $key,
$matches ) ) {
245 $revisions[$archive->getRevision(
$matches[1] )->getId()] = 1;
250 'type' =>
'revision',
252 'target' => $this->mTargetObj->getPrefixedText()
260 $out->setPageTitle( $this->
msg(
'undelete-search-title' ) );
261 $fuzzySearch = $this->
getRequest()->getVal(
'fuzzy',
true );
266 $fields[] =
new OOUI\ActionFieldLayout(
267 new OOUI\TextInputWidget( [
269 'inputId' =>
'prefix',
271 'value' => $this->mSearchPrefix,
274 new OOUI\ButtonInputWidget( [
275 'label' => $this->
msg(
'undelete-search-submit' )->text(),
276 'flags' => [
'primary',
'progressive' ],
277 'inputId' =>
'searchUndelete',
281 'label' =>
new OOUI\HtmlSnippet(
283 $fuzzySearch ?
'undelete-search-full' :
'undelete-search-prefix'
290 $fieldset =
new OOUI\FieldsetLayout( [
291 'label' => $this->
msg(
'undelete-search-box' )->text(),
295 $form =
new OOUI\FormLayout( [
300 $form->appendContent(
302 new OOUI\HtmlSnippet(
303 Html::hidden(
'title', $this->
getPageTitle()->getPrefixedDBkey() ) .
304 Html::hidden(
'fuzzy', $fuzzySearch )
309 new OOUI\PanelLayout( [
317 # List undeletable articles
318 if ( $this->mSearchPrefix ) {
321 if ( $fuzzySearch ) {
339 if ( $result->numRows() == 0 ) {
340 $out->addWikiMsg(
'undelete-no-results' );
345 $out->addWikiMsg(
'undeletepagetext', $this->
getLanguage()->formatNum( $result->numRows() ) );
349 $out->addHTML(
"<ul id='undeleteResultsList'>\n" );
350 foreach ( $result as $row ) {
355 $title->getPrefixedText(),
357 [
'target' =>
$title->getPrefixedText() ]
361 $item = Html::element(
363 [
'class' =>
'mw-invalidtitle' ],
371 $revs = $this->
msg(
'undeleterevisions' )->numParams( $row->count )->parse();
375 [
'class' =>
'undeleteResult' ],
381 $out->addHTML(
"</ul>\n" );
387 if ( !preg_match(
'/[0-9]{14}/', $timestamp ) ) {
392 if ( !
Hooks::run(
'UndeleteForm::showRevision', [ &$archive, $this->mTargetObj ] ) ) {
395 $rev = $archive->getRevision( $timestamp );
401 $out->addWikiMsg(
'undeleterevision-missing' );
406 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
407 if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
409 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
410 $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
411 'rev-suppressed-text-permission' :
'rev-deleted-text-permission'
418 "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
419 $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
420 'rev-suppressed-text-view' :
'rev-deleted-text-view'
422 $out->addHTML(
'<br />' );
426 if ( $this->mDiff ) {
427 $previousRev = $archive->getPreviousRevision( $timestamp );
428 if ( $previousRev ) {
429 $this->
showDiff( $previousRev, $rev );
430 if ( $this->mDiffOnly ) {
434 $out->addHTML(
'<hr />' );
436 $out->addWikiMsg(
'undelete-nodiff' );
441 $this->
getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
442 $this->mTargetObj->getPrefixedText()
449 $time =
$lang->userTimeAndDate( $timestamp, $user );
450 $d =
$lang->userDate( $timestamp, $user );
451 $t =
$lang->userTime( $timestamp, $user );
454 $content = $rev->getContent( RevisionRecord::FOR_THIS_USER, $user );
459 if ( $this->mPreview || $isText ) {
460 $openDiv =
'<div id="mw-undelete-revision" class="mw-warning">';
462 $openDiv =
'<div id="mw-undelete-revision">';
464 $out->addHTML( $openDiv );
467 if ( !$this->mDiff ) {
470 $out->addHTML(
"$revdel " );
476 Message::rawParam( $link ), $time,
477 Message::rawParam( $userLink ), $d,
$t
479 $out->addHTML(
'</div>' );
481 if ( !
Hooks::run(
'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) {
485 if ( $this->mPreview || !$isText ) {
488 $popts = $out->parserOptions();
489 $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
491 $rendered = $renderer->getRenderedRevision(
492 $rev->getRevisionRecord(),
495 [
'audience' => RevisionRecord::FOR_THIS_USER ]
500 $pout = $rendered->getRevisionParserOutput();
502 $out->addParserOutput( $pout, [
503 'enableSectionEditLinks' =>
false,
511 '@phan-var TextContent $content';
515 'readonly' =>
'readonly',
520 $buttonFields[] =
new OOUI\ButtonInputWidget( [
523 'label' => $this->
msg(
'showpreview' )->text()
529 $buttonFields[] =
new OOUI\ButtonInputWidget( [
532 'label' => $this->
msg(
'showdiff' )->text()
538 'style' =>
'clear: both' ] ) .
541 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) ] ) .
545 'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
548 'name' =>
'timestamp',
549 'value' => $timestamp ] ) .
552 'name' =>
'wpEditToken',
553 'value' => $user->getEditToken() ] ) .
554 new OOUI\FieldLayout(
556 'content' =>
new OOUI\HorizontalLayout( [
557 'items' => $buttonFields
575 $diffContext->setTitle( $currentRev->getTitle() );
578 $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
579 $diffEngine->setRevisions( $previousRev->getRevisionRecord(), $currentRev->getRevisionRecord() );
580 $diffEngine->showDiffStyle();
581 $formattedDiff = $diffEngine->getDiff(
586 $this->
getOutput()->addHTML(
"<div>$formattedDiff</div>\n" );
595 $isDeleted = !( $rev->getId() && $rev->getTitle() );
600 'target' => $this->mTargetObj->getPrefixedText(),
601 'timestamp' =>
wfTimestamp( TS_MW, $rev->getTimestamp() )
605 $targetPage = $rev->getTitle();
606 $targetQuery = [
'oldid' => $rev->getId() ];
623 [
'ct_rev_id' => $rev->getId() ],
627 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
628 foreach ( $tagIds as $tagId ) {
630 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
635 $tags = implode(
',', $tags );
640 return '<div id="mw-diff-' . $prefix .
'title1"><strong>' .
645 $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
646 $lang->userDate( $rev->getTimestamp(), $user ),
647 $lang->userTime( $rev->getTimestamp(), $user )
653 '<div id="mw-diff-' . $prefix .
'title2">' .
656 '<div id="mw-diff-' . $prefix .
'title3">' .
659 '<div id="mw-diff-' . $prefix .
'title5">' .
660 $tagSummary[0] .
'<br />' .
673 $out->addWikiMsg(
'undelete-show-file-confirm',
674 $this->mTargetObj->getText(),
675 $lang->userDate(
$file->getTimestamp(), $user ),
676 $lang->userTime(
$file->getTimestamp(), $user ) );
681 'target' => $this->mTarget,
683 'token' => $user->getEditToken( $key ),
699 # We mustn't allow the output to be CDN cached, otherwise
700 # if an admin previews a deleted image, and it's cached, then
701 # a user without appropriate permissions can toddle off and
702 # nab the image, and CDN will serve it
704 $response->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
705 $response->header(
'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
709 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
710 $repo->streamFileWithStatus(
$path );
717 if ( $this->mAllowed ) {
718 $out->addModules(
'mediawiki.special.undelete' );
721 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
722 [
'undeletepagetitle',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
726 Hooks::run(
'UndeleteForm::showHistory', [ &$archive, $this->mTargetObj ] );
728 $out->addHTML(
'<div class="mw-undelete-history">' );
729 if ( $this->mAllowed ) {
730 $out->addWikiMsg(
'undeletehistory' );
731 $out->addWikiMsg(
'undeleterevdel' );
733 $out->addWikiMsg(
'undeletehistorynoadmin' );
735 $out->addHTML(
'</div>' );
737 # List all stored revisions
738 $revisions = $archive->listRevisions();
739 $files = $archive->listFiles();
741 $haveRevisions = $revisions && $revisions->numRows() > 0;
742 $haveFiles = $files && $files->numRows() > 0;
744 # Batch existence check on user and talk pages
745 if ( $haveRevisions ) {
747 foreach ( $revisions as $row ) {
752 $revisions->seek( 0 );
756 foreach ( $files as $row ) {
764 if ( $this->mAllowed ) {
767 $action = $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] );
768 # Start the form here
769 $form =
new OOUI\FormLayout( [
776 # Show relevant lines from the deletion log:
777 $deleteLogPage =
new LogPage(
'delete' );
778 $out->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) .
"\n" );
780 # Show relevant lines from the suppression log:
781 $suppressLogPage =
new LogPage(
'suppress' );
782 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
783 if ( $permissionManager->userHasRight( $this->getUser(),
'suppressionlog' ) ) {
784 $out->addHTML(
Xml::element(
'h2',
null, $suppressLogPage->getName()->text() ) .
"\n" );
788 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
790 $fields[] =
new OOUI\Layout( [
791 'content' =>
new OOUI\HtmlSnippet( $this->
msg(
'undeleteextrahelp' )->parseAsBlock() )
794 $fields[] =
new OOUI\FieldLayout(
795 new OOUI\TextInputWidget( [
796 'name' =>
'wpComment',
797 'inputId' =>
'wpComment',
799 'value' => $this->mComment,
807 'label' => $this->
msg(
'undeletecomment' )->text(),
812 $fields[] =
new OOUI\FieldLayout(
814 'content' =>
new OOUI\HorizontalLayout( [
816 new OOUI\ButtonInputWidget( [
818 'inputId' =>
'mw-undelete-submit',
820 'label' => $this->
msg(
'undeletebtn' )->text(),
821 'flags' => [
'primary',
'progressive' ],
824 new OOUI\ButtonInputWidget( [
826 'inputId' =>
'mw-undelete-invert',
828 'label' => $this->
msg(
'undeleteinvert' )->text()
835 if ( $permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
836 $fields[] =
new OOUI\FieldLayout(
837 new OOUI\CheckboxInputWidget( [
838 'name' =>
'wpUnsuppress',
839 'inputId' =>
'mw-undelete-unsuppress',
843 'label' => $this->
msg(
'revdelete-unsuppress' )->text(),
849 $fieldset =
new OOUI\FieldsetLayout( [
850 'label' => $this->
msg(
'undelete-fieldset-title' )->text(),
851 'id' =>
'mw-undelete-table',
855 $form->appendContent(
856 new OOUI\PanelLayout( [
860 'content' => $fieldset,
862 new OOUI\HtmlSnippet(
863 Html::hidden(
'target', $this->mTarget ) .
864 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() )
870 $history .=
Xml::element(
'h2',
null, $this->
msg(
'history' )->text() ) .
"\n";
872 if ( $haveRevisions ) {
873 # Show the page's stored (deleted) history
875 if ( $permissionManager->userHasRight( $this->getUser(),
'deleterevision' ) ) {
876 $history .= Html::element(
881 'class' =>
'deleterevision-log-submit mw-log-deleterevision-button'
883 $this->
msg(
'showhideselectedversions' )->text()
887 $history .=
'<ul class="mw-undelete-revlist">';
888 $remaining = $revisions->numRows();
889 $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
891 foreach ( $revisions as $row ) {
898 $out->addWikiMsg(
'nohistory' );
902 $history .=
Xml::element(
'h2',
null, $this->
msg(
'filehist' )->text() ) .
"\n";
903 $history .=
'<ul class="mw-undelete-revlist">';
904 foreach ( $files as $row ) {
911 if ( $this->mAllowed ) {
912 # Slip in the hidden controls here
913 $misc = Html::hidden(
'target', $this->mTarget );
914 $misc .= Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() );
917 $form->appendContent(
new OOUI\HtmlSnippet( $history ) );
918 $out->addHTML( $form );
920 $out->addHTML( $history );
929 'title' => $this->mTargetObj
935 if ( $this->mAllowed ) {
936 if ( $this->mInvert ) {
937 if ( in_array( $ts, $this->mTargetTimestamp ) ) {
951 if ( $this->mCanView ) {
954 if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getUser() ) ) {
955 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
956 $last = $this->
msg(
'diff' )->escaped();
957 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
958 $pageLink = $this->
getPageLink( $rev, $titleObj, $ts );
961 $this->
msg(
'diff' )->text(),
964 'target' => $this->mTargetObj->getPrefixedText(),
970 $pageLink = $this->
getPageLink( $rev, $titleObj, $ts );
971 $last = $this->
msg(
'diff' )->escaped();
974 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
975 $last = $this->
msg(
'diff' )->escaped();
985 $size = $row->ar_len;
986 if ( !is_null( $size ) ) {
1001 $attribs[
'class'] = implode(
' ', $classes );
1004 $revisionRow = $this->
msg(
'undelete-revision-row2' )
1017 return Xml::tags(
'li', $attribs, $revisionRow ) .
"\n";
1026 if ( $this->mCanView && $row->fa_storage_key ) {
1027 if ( $this->mAllowed ) {
1028 $checkBox =
Xml::check(
'fileid' . $row->fa_id );
1030 $key = urlencode( $row->fa_storage_key );
1033 $pageLink = htmlspecialchars( $this->
getLanguage()->userTimeAndDate( $ts, $user ) );
1036 $data = $this->
msg(
'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1037 $bytes = $this->
msg(
'parentheses' )
1038 ->plaintextParams( $this->
msg(
'nbytes' )->numParams( $row->fa_size )->text() )
1040 $data = htmlspecialchars( $data .
' ' . $bytes );
1044 $canHide = $this->
isAllowed(
'deleterevision' );
1045 if ( $canHide || (
$file->getVisibility() && $this->
isAllowed(
'deletedhistory' ) ) ) {
1051 'type' =>
'filearchive',
1052 'target' => $this->mTargetObj->getPrefixedDBkey(),
1053 'ids' => $row->fa_id
1062 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1075 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1077 if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
1078 return '<span class="history-deleted">' . $time .
'</span>';
1086 'target' => $this->mTargetObj->getPrefixedText(),
1091 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1092 $link =
'<span class="history-deleted">' . $link .
'</span>';
1110 $time = $this->
getLanguage()->userTimeAndDate( $ts, $user );
1113 return '<span class="history-deleted">' . htmlspecialchars( $time ) .
'</span>';
1121 'target' => $this->mTargetObj->getPrefixedText(),
1123 'token' => $user->getEditToken( $key )
1128 $link =
'<span class="history-deleted">' . $link .
'</span>';
1142 return '<span class="history-deleted">' .
1143 $this->
msg(
'rev-deleted-user' )->escaped() .
1151 $link =
'<span class="history-deleted">' . $link .
'</span>';
1165 return '<span class="history-deleted"><span class="comment">' .
1166 $this->
msg(
'rev-deleted-comment' )->escaped() .
'</span></span>';
1172 $link =
'<span class="history-deleted">' . $link .
'</span>';
1179 if ( $this->
getConfig()->
get(
'UploadMaintenance' )
1180 && $this->mTargetObj->getNamespace() ==
NS_FILE
1182 throw new ErrorPageError(
'undelete-error',
'filedelete-maintenance' );
1189 Hooks::run(
'UndeleteForm::undelete', [ &$archive, $this->mTargetObj ] );
1190 $ok = $archive->undelete(
1191 $this->mTargetTimestamp,
1193 $this->mFileVersions,
1198 if ( is_array( $ok ) ) {
1201 $this->mTargetObj, $this->mFileVersions,
1202 $this->
getUser(), $this->mComment ] );
1206 $out->addWikiMsg(
'undeletedpage', Message::rawParam( $link ) );
1208 $out->setPageTitle( $this->
msg(
'undelete-error' ) );
1212 $status = $archive->getRevisionStatus();
1214 $out->wrapWikiTextAsInterface(
1216 '<div id="mw-error-cannotundelete">' .
1220 $this->getLanguage()
1226 $status = $archive->getFileStatus();
1228 $out->wrapWikiTextAsInterface(
1231 'undelete-error-short',
1232 'undelete-error-long',
1233 $this->getLanguage()