41 private $submitClicked;
74 private $permissionManager;
82 private const UI_LABELS = [
84 'check-label' =>
'revdelete-hide-text',
85 'success' =>
'revdelete-success',
86 'failure' =>
'revdelete-failure',
87 'text' =>
'revdelete-text-text',
88 'selected' =>
'revdelete-selected-text',
91 'check-label' =>
'revdelete-hide-text',
92 'success' =>
'revdelete-success',
93 'failure' =>
'revdelete-failure',
94 'text' =>
'revdelete-text-text',
95 'selected' =>
'revdelete-selected-text',
98 'check-label' =>
'revdelete-hide-image',
99 'success' =>
'revdelete-success',
100 'failure' =>
'revdelete-failure',
101 'text' =>
'revdelete-text-file',
102 'selected' =>
'revdelete-selected-file',
105 'check-label' =>
'revdelete-hide-image',
106 'success' =>
'revdelete-success',
107 'failure' =>
'revdelete-failure',
108 'text' =>
'revdelete-text-file',
109 'selected' =>
'revdelete-selected-file',
112 'check-label' =>
'revdelete-hide-name',
113 'success' =>
'logdelete-success',
114 'failure' =>
'logdelete-failure',
115 'text' =>
'logdelete-text',
116 'selected' =>
'logdelete-selected',
127 parent::__construct(
'Revisiondelete' );
129 $this->permissionManager = $permissionManager;
130 $this->repoGroup = $repoGroup;
149 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
150 # Handle our many different possible input types.
151 $ids = $request->getVal(
'ids' );
152 if ( $ids !==
null ) {
153 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
154 $this->ids = explode(
',', $ids );
157 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
160 $this->ids = array_unique( array_filter( $this->ids ) );
162 $this->typeName = $request->getVal(
'type' );
163 $this->targetObj = Title::newFromText( $request->getText(
'target' ) );
165 # For reviewing deleted files...
166 $this->archiveName = $request->getVal(
'file' );
167 $this->token = $request->getVal(
'token' );
168 if ( $this->archiveName && $this->targetObj ) {
177 if ( !$this->typeName || count( $this->ids ) == 0 ) {
178 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
183 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
187 # Allow the list type to adjust the passed target
194 # We need a target page!
195 if ( $this->targetObj ===
null ) {
196 $output->addWikiMsg(
'undelete-header' );
202 $checkReplica = !$this->submitClicked;
204 $this->permissionManager->isBlockedFrom(
214 $this->getLanguage(),
219 $this->typeLabels = self::UI_LABELS[$this->typeName];
222 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
223 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
224 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
225 $pageIsSuppressed = $list->areAnySuppressed();
226 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
228 $this->otherReason = $request->getVal(
'wpReason',
'' );
229 # Give a link to the logs/hist for this page
232 # Initialise checkboxes
234 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
235 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
238 [
'revdelete-hide-comment',
'wpHideComment', RevisionRecord::DELETED_COMMENT ],
239 [
'revdelete-hide-user',
'wpHideUser', RevisionRecord::DELETED_USER ]
241 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
242 $this->checks[] = [
'revdelete-hide-restricted',
243 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
246 # Either submit or create our form
247 if ( $this->mIsAllowed && $this->submitClicked ) {
253 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
254 # Show relevant lines from the deletion log
255 $deleteLogPage =
new LogPage(
'delete' );
256 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
257 LogEventsList::showLogExtract(
262 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
265 # Show relevant lines from the suppression log
266 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
267 $suppressLogPage =
new LogPage(
'suppress' );
268 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
269 LogEventsList::showLogExtract(
274 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
284 # Give a link to the logs/hist for this page
285 if ( $this->targetObj ) {
287 $this->
getSkin()->setRelevantTitle( $this->targetObj );
290 $links[] = $linkRenderer->makeKnownLink(
292 $this->
msg(
'viewpagelogs' )->text(),
294 [
'page' => $this->targetObj->getPrefixedText() ]
296 if ( !$this->targetObj->isSpecialPage() ) {
297 # Give a link to the page history
298 $links[] = $linkRenderer->makeKnownLink(
300 $this->
msg(
'pagehist' )->text(),
302 [
'action' =>
'history' ]
304 # Link to deleted edits
305 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
307 $links[] = $linkRenderer->makeKnownLink(
309 $this->
msg(
'deletedhist' )->text(),
311 [
'target' => $this->targetObj->getPrefixedDBkey() ]
315 # Logs themselves don't have histories or archived revisions
327 $conds[
'log_type'] = [
'delete',
'suppress' ];
328 $conds[
'log_action'] = $this->
getList()->getLogAction();
332 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
345 $repo = $this->repoGroup->getLocalRepo();
346 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
349 if ( !$oimage->exists() ) {
350 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
355 if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
356 if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
362 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
364 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
365 $this->targetObj->getText(),
366 $lang->userDate( $oimage->getTimestamp(), $user ),
367 $lang->userTime( $oimage->getTimestamp(), $user ) );
369 Xml::openElement(
'form', [
372 'target' => $this->targetObj->getPrefixedDBkey(),
373 'file' => $archiveName,
374 'token' => $user->getEditToken( $archiveName ),
378 Xml::submitButton( $this->msg(
'revdelete-show-file-submit' )->text() ) .
385 # We mustn't allow the output to be CDN cached, otherwise
386 # if an admin previews a deleted image, and it's cached, then
387 # a user without appropriate permissions can toddle off and
388 # nab the image, and CDN will serve it
389 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
391 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
393 $this->
getRequest()->response()->header(
'Pragma: no-cache' );
395 $key = $oimage->getStorageKey();
396 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
397 $repo->streamFileWithStatus(
$path );
405 if ( $this->revDelList ===
null ) {
407 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
411 return $this->revDelList;
423 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
424 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
427 $out->addHTML(
"<ul>" );
432 for ( $list->reset(); $list->current(); $list->next() ) {
433 $item = $list->current();
435 if ( !$item->canView() ) {
436 if ( !$this->submitClicked ) {
439 $userAllowed =
false;
443 $out->addHTML( $item->getHTML() );
446 if ( !$numRevisions ) {
447 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
450 $out->addHTML(
"</ul>" );
455 if ( !$userAllowed ) {
460 if ( $this->mIsAllowed ) {
461 $suppressAllowed = $this->permissionManager
462 ->userHasRight( $this->
getUser(),
'suppressrevision' );
463 $out->addModules( [
'mediawiki.special.revisionDelete' ] );
464 $out->addModuleStyles( [
'mediawiki.special',
465 'mediawiki.interface.helpers.styles' ] );
467 $dropDownReason = $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->text();
469 if ( $suppressAllowed ) {
470 $dropDownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
471 ->inContentLanguage()->text();
478 'label' => $this->
msg(
'revdelete-log' )->text(),
479 'cssclass' =>
'wpReasonDropDown',
480 'id' =>
'wpRevDeleteReasonList',
481 'name' =>
'wpRevDeleteReasonList',
482 'options' => Xml::listDropDownOptions(
484 [
'other' => $this->
msg(
'revdelete-reasonotherlist' )->text() ]
486 'default' => $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' )
491 'label' => $this->
msg(
'revdelete-otherreason' )->text(),
492 'name' =>
'wpReason',
498 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
503 'name' =>
'wpEditToken',
504 'default' => $this->
getUser()->getEditToken()
510 'default' => $this->targetObj->getPrefixedText()
516 'default' => $this->typeName
522 'default' => implode(
',', $this->ids )
525 $htmlForm = HTMLForm::factory(
'ooui', $fields, $this->
getContext() );
527 ->setSubmitText( $this->
msg(
'revdelete-submit', $numRevisions )->text() )
528 ->setSubmitName(
'wpSubmit' )
529 ->setWrapperLegend( $this->
msg(
'revdelete-legend' )->text() )
530 ->setAction( $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) )
533 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
536 if ( $suppressAllowed ) {
537 $link .= $linkRenderer->makeKnownLink(
538 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->
getTitle(),
539 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
541 [
'action' =>
'edit' ]
543 $link .= $this->
msg(
'pipe-separator' )->escaped();
545 $link .= $linkRenderer->makeKnownLink(
546 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->
getTitle(),
547 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
549 [
'action' =>
'edit' ]
551 $htmlForm->setPostHtml( Xml::tags(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) );
553 $out->addHTML( $htmlForm->getHTML(
false ) );
564 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
565 'revdelete-text-others'
568 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
569 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
572 if ( $this->mIsAllowed ) {
573 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
588 if ( $list->length() == 1 ) {
594 foreach ( $this->checks as $item ) {
597 [ $message, $name, $bitField ] = $item;
601 'label-raw' => $this->
msg( $message )->escaped(),
605 'default' => $list->length() == 1 ? $list->current()->getBits() & $bitField : null
608 if ( $bitField == RevisionRecord::DELETED_RESTRICTED ) {
609 $field[
'label-raw'] =
"<b>" . $field[
'label-raw'] .
"</b>";
610 if (
$type ===
'radio' ) {
611 $field[
'options-messages'] = [
612 'revdelete-radio-same' => -1,
613 'revdelete-radio-unset-suppress' => 0,
614 'revdelete-radio-set-suppress' => 1
617 } elseif (
$type ===
'radio' ) {
618 $field[
'options-messages'] = [
619 'revdelete-radio-same' => -1,
620 'revdelete-radio-unset' => 0,
621 'revdelete-radio-set' => 1
637 # Check edit token on submission
638 $token = $this->
getRequest()->getVal(
'wpEditToken' );
639 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
640 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
646 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
647 $comment = $listReason;
648 if ( $comment ===
'other' ) {
649 $comment = $this->otherReason;
650 } elseif ( $this->otherReason !==
'' ) {
652 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
653 . $this->otherReason;
655 # Can the user set this field?
656 if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
657 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
661 # If the save went through, go to success message...
662 $status = $this->
save( $bitParams, $comment );
663 if ( $status->isGood() ) {
668 # ...otherwise, bounce back to form...
681 $out->setPageTitle( $this->
msg(
'actioncomplete' ) );
684 $out->msg( $this->typeLabels[
'success'] )->parse()
687 $this->wasSaved =
true;
688 $this->revDelList->reloadFromPrimary();
699 $out->setPageTitle( $this->
msg(
'actionfailed' ) );
702 $out->parseAsContent(
703 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
717 foreach ( $this->checks as $item ) {
718 [ , $name, $field ] = $item;
719 $val = $this->
getRequest()->getInt( $name, 0 );
720 if ( $val < -1 || $val > 1 ) {
723 $bitfield[$field] = $val;
725 if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
726 $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
738 protected function save( array $bitPars, $reason ) {
739 return $this->
getList()->setVisibility(
740 [
'value' => $bitPars,
'comment' => $reason ]
An error page which can definitely be safely rendered using the OutputPage.
Class to simplify the use of log pages.
Show an error when a user tries to do something they do not have the necessary permissions for.
Prioritized list of file repositories.
static getCanonicalTypeName( $typeName)
Gets the canonical type name, if any.
static getRelationType( $typeName)
Get DB field name for URL param... Future code for other things may also track other types of revisio...
static suggestTarget( $typeName, $target, array $ids)
Suggest a target for the revision deletion.
static getRevdelConstant( $typeName)
Get the revision deletion constant for the RevDel type.
static getRestriction( $typeName)
Get the user right required for the RevDel type.
static createList( $typeName, IContextSource $context, PageIdentity $page, array $ids)
Instantiate the appropriate list class for a given list of IDs.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
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,...
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getAuthority()
Shortcut to get the Authority executing this instance.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Special page allowing users with the appropriate permissions to view and hide revisions.
showForm()
Show a list of items that we will operate on, and show a form with checkboxes which will allow the us...
tryShowFile( $archiveName)
Show a deleted file version requested by the visitor.
save(array $bitPars, $reason)
Do the write operations.
success()
Report that the submit operation succeeded.
addUsageText()
Show some introductory text.
bool $wasSaved
Was the DB modified in this request.
getLogQueryCond()
Get the condition used for fetching log snippets.
showConvenienceLinks()
Show some useful links in the subtitle.
doesWrites()
Indicates whether this special page may perform database writes.
execute( $par)
Default execute method Checks user permissions.
failure( $status)
Report that the submit operation failed.
__construct(PermissionManager $permissionManager, RepoGroup $repoGroup)
submit()
UI entry point for form submission.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
extractBitParams()
Put together an array that contains -1, 0, or the *_deleted const for each bit.
getList()
Get the list object for this request.
Shortcut to construct a special page which is unlisted by default.
Show an error when the user tries to do something whilst blocked.
if(!isset( $args[0])) $lang