24 namespace MediaWiki\Specials;
58 private $submitClicked;
96 private const UI_LABELS = [
98 'check-label' =>
'revdelete-hide-text',
99 'success' =>
'revdelete-success',
100 'failure' =>
'revdelete-failure',
101 'text' =>
'revdelete-text-text',
102 'selected' =>
'revdelete-selected-text',
105 'check-label' =>
'revdelete-hide-text',
106 'success' =>
'revdelete-success',
107 'failure' =>
'revdelete-failure',
108 'text' =>
'revdelete-text-text',
109 'selected' =>
'revdelete-selected-text',
112 'check-label' =>
'revdelete-hide-image',
113 'success' =>
'revdelete-success',
114 'failure' =>
'revdelete-failure',
115 'text' =>
'revdelete-text-file',
116 'selected' =>
'revdelete-selected-file',
119 'check-label' =>
'revdelete-hide-image',
120 'success' =>
'revdelete-success',
121 'failure' =>
'revdelete-failure',
122 'text' =>
'revdelete-text-file',
123 'selected' =>
'revdelete-selected-file',
126 'check-label' =>
'revdelete-hide-name',
127 'success' =>
'logdelete-success',
128 'failure' =>
'logdelete-failure',
129 'text' =>
'logdelete-text',
130 'selected' =>
'logdelete-selected',
141 parent::__construct(
'Revisiondelete' );
143 $this->permissionManager = $permissionManager;
144 $this->repoGroup = $repoGroup;
163 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
164 # Handle our many different possible input types.
165 $ids = $request->getVal(
'ids' );
166 if ( $ids !==
null ) {
167 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
168 $this->ids = explode(
',', $ids );
171 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
174 $this->ids = array_unique( array_filter( $this->ids ) );
176 $this->typeName = $request->getVal(
'type' );
179 # For reviewing deleted files...
180 $this->archiveName = $request->getVal(
'file' );
181 $this->token = $request->getVal(
'token' );
182 if ( $this->archiveName && $this->targetObj ) {
191 if ( !$this->typeName || count( $this->ids ) == 0 ) {
192 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
197 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
201 # Allow the list type to adjust the passed target
208 # We need a target page!
209 if ( $this->targetObj ===
null ) {
210 $output->addWikiMsg(
'undelete-header' );
216 $checkReplica = !$this->submitClicked;
218 $this->permissionManager->isBlockedFrom(
228 $this->getLanguage(),
233 $this->typeLabels = self::UI_LABELS[$this->typeName];
236 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
237 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
238 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
239 $pageIsSuppressed = $list->areAnySuppressed();
240 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
242 $this->otherReason = $request->getVal(
'wpReason',
'' );
243 # Give a link to the logs/hist for this page
246 # Initialise checkboxes
248 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
249 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
255 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
256 $this->checks[] = [
'revdelete-hide-restricted',
260 # Either submit or create our form
261 if ( $this->mIsAllowed && $this->submitClicked ) {
267 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
268 # Show relevant lines from the deletion log
269 $deleteLogPage =
new LogPage(
'delete' );
270 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
276 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
279 # Show relevant lines from the suppression log
280 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
281 $suppressLogPage =
new LogPage(
'suppress' );
282 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
288 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
298 # Give a link to the logs/hist for this page
299 if ( $this->targetObj ) {
301 $this->
getSkin()->setRelevantTitle( $this->targetObj );
304 $links[] = $linkRenderer->makeKnownLink(
306 $this->
msg(
'viewpagelogs' )->text(),
308 [
'page' => $this->targetObj->getPrefixedText() ]
310 if ( !$this->targetObj->isSpecialPage() ) {
311 # Give a link to the page history
312 $links[] = $linkRenderer->makeKnownLink(
314 $this->
msg(
'pagehist' )->text(),
316 [
'action' =>
'history' ]
318 # Link to deleted edits
319 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
321 $links[] = $linkRenderer->makeKnownLink(
323 $this->
msg(
'deletedhist' )->text(),
325 [
'target' => $this->targetObj->getPrefixedDBkey() ]
329 # Logs themselves don't have histories or archived revisions
341 $conds[
'log_type'] = [
'delete',
'suppress' ];
342 $conds[
'log_action'] = $this->
getList()->getLogAction();
346 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
359 $repo = $this->repoGroup->getLocalRepo();
360 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
363 if ( !$oimage->exists() ) {
364 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
376 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
378 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
379 $this->targetObj->getText(),
380 $lang->userDate( $oimage->getTimestamp(), $user ),
381 $lang->userTime( $oimage->getTimestamp(), $user ) );
386 'target' => $this->targetObj->getPrefixedDBkey(),
387 'file' => $archiveName,
388 'token' => $user->getEditToken( $archiveName ),
399 # We mustn't allow the output to be CDN cached, otherwise
400 # if an admin previews a deleted image, and it's cached, then
401 # a user without appropriate permissions can toddle off and
402 # nab the image, and CDN will serve it
403 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
405 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
408 $key = $oimage->getStorageKey();
409 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
410 $repo->streamFileWithStatus(
$path );
418 if ( $this->revDelList ===
null ) {
420 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
424 return $this->revDelList;
436 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
437 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
440 $out->addHTML(
"<ul>" );
445 for ( $list->reset(); $list->current(); $list->next() ) {
446 $item = $list->current();
448 if ( !$item->canView() ) {
449 if ( !$this->submitClicked ) {
452 $userAllowed =
false;
456 $out->addHTML( $item->getHTML() );
459 if ( !$numRevisions ) {
460 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
463 $out->addHTML(
"</ul>" );
468 if ( !$userAllowed ) {
473 if ( $this->mIsAllowed ) {
474 $suppressAllowed = $this->permissionManager
475 ->userHasRight( $this->
getUser(),
'suppressrevision' );
476 $out->addModules( [
'mediawiki.misc-authed-ooui' ] );
477 $out->addModuleStyles( [
'mediawiki.special',
478 'mediawiki.interface.helpers.styles' ] );
480 $dropDownReason = $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->text();
482 if ( $suppressAllowed ) {
483 $dropDownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
484 ->inContentLanguage()->text();
491 'label' => $this->
msg(
'revdelete-log' )->text(),
492 'cssclass' =>
'wpReasonDropDown',
493 'id' =>
'wpRevDeleteReasonList',
494 'name' =>
'wpRevDeleteReasonList',
497 [
'other' => $this->
msg(
'revdelete-reasonotherlist' )->text() ]
499 'default' => $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' )
504 'label' => $this->
msg(
'revdelete-otherreason' )->text(),
505 'name' =>
'wpReason',
516 'name' =>
'wpEditToken',
517 'default' => $this->
getUser()->getEditToken()
523 'default' => $this->targetObj->getPrefixedText()
529 'default' => $this->typeName
535 'default' => implode(
',', $this->ids )
540 ->setSubmitText( $this->
msg(
'revdelete-submit', $numRevisions )->text() )
541 ->setSubmitName(
'wpSubmit' )
542 ->setWrapperLegend( $this->
msg(
'revdelete-legend' )->text() )
543 ->setAction( $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) )
546 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
549 if ( $suppressAllowed ) {
550 $link .= $linkRenderer->makeKnownLink(
551 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->
getTitle(),
552 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
554 [
'action' =>
'edit' ]
556 $link .= $this->
msg(
'pipe-separator' )->escaped();
558 $link .= $linkRenderer->makeKnownLink(
559 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->
getTitle(),
560 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
562 [
'action' =>
'edit' ]
564 $htmlForm->setPostHtml(
Xml::tags(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) );
566 $out->addHTML( $htmlForm->getHTML(
false ) );
577 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
578 'revdelete-text-others'
581 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
582 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
585 if ( $this->mIsAllowed ) {
586 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
601 if ( $list->length() == 1 ) {
607 foreach ( $this->checks as $item ) {
610 [ $message, $name, $bitField ] = $item;
614 'label-raw' => $this->
msg( $message )->escaped(),
618 'default' => $list->length() == 1 ? $list->current()->getBits() & $bitField : null
622 $field[
'label-raw'] =
"<b>" . $field[
'label-raw'] .
"</b>";
623 if ( $type ===
'radio' ) {
624 $field[
'options'] = [
625 $this->
msg(
'revdelete-radio-same' )->text() => -1,
626 $this->
msg(
'revdelete-radio-unset-suppress' )->text() => 0,
627 $this->
msg(
'revdelete-radio-set-suppress' )->text() => 1
630 } elseif ( $type ===
'radio' ) {
631 $field[
'options'] = [
632 $this->
msg(
'revdelete-radio-same' )->text() => -1,
633 $this->
msg(
'revdelete-radio-unset' )->text() => 0,
634 $this->
msg(
'revdelete-radio-set' )->text() => 1
650 # Check edit token on submission
651 $token = $this->
getRequest()->getVal(
'wpEditToken' );
652 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
653 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
659 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
660 $comment = $listReason;
661 if ( $comment ===
'other' ) {
662 $comment = $this->otherReason;
663 } elseif ( $this->otherReason !==
'' ) {
665 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
666 . $this->otherReason;
668 # Can the user set this field?
670 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
674 # If the save went through, go to success message...
675 $status = $this->
save( $bitParams, $comment );
676 if ( $status->isGood() ) {
681 # ...otherwise, bounce back to form...
694 $out->setPageTitleMsg( $this->
msg(
'actioncomplete' ) );
697 $out->msg( $this->typeLabels[
'success'] )->parse()
700 $this->wasSaved =
true;
701 $this->revDelList->reloadFromPrimary();
712 $out->setPageTitleMsg( $this->
msg(
'actionfailed' ) );
715 $out->parseAsContent(
716 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
730 foreach ( $this->checks as [ , $name, $field ] ) {
731 $val = $this->
getRequest()->getInt( $name, 0 );
732 if ( $val < -1 || $val > 1 ) {
735 $bitfield[$field] = $val;
750 protected function save( array $bitPars, $reason ) {
751 return $this->
getList()->setVisibility(
752 [
'value' => $bitPars,
'comment' => $reason ]
765 class_alias( SpecialRevisionDelete::class,
'SpecialRevisionDelete' );
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...
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
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,...
getUser()
Shortcut to get the User executing this instance.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getPageTitle( $subpage=false)
Get a self-referential title object.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
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.
Shortcut to construct a special page which is unlisted by default.
Show an error when a user tries to do something they do not have the necessary permissions for.
Prioritized list of file repositories.
General controller for RevDel, used by both SpecialRevisiondelete and ApiRevisionDelete.
static getCanonicalTypeName( $typeName)
Gets the canonical type name, if any.
static getRelationType( $typeName)
Get DB field name for URL param...
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.
Show an error when the user tries to do something whilst blocked.
Module of static functions for generating XML.
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
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.