54 private $submitClicked;
92 private const UI_LABELS = [
94 'check-label' =>
'revdelete-hide-text',
95 'success' =>
'revdelete-success',
96 'failure' =>
'revdelete-failure',
97 'text' =>
'revdelete-text-text',
98 'selected' =>
'revdelete-selected-text',
101 'check-label' =>
'revdelete-hide-text',
102 'success' =>
'revdelete-success',
103 'failure' =>
'revdelete-failure',
104 'text' =>
'revdelete-text-text',
105 'selected' =>
'revdelete-selected-text',
108 'check-label' =>
'revdelete-hide-image',
109 'success' =>
'revdelete-success',
110 'failure' =>
'revdelete-failure',
111 'text' =>
'revdelete-text-file',
112 'selected' =>
'revdelete-selected-file',
115 'check-label' =>
'revdelete-hide-image',
116 'success' =>
'revdelete-success',
117 'failure' =>
'revdelete-failure',
118 'text' =>
'revdelete-text-file',
119 'selected' =>
'revdelete-selected-file',
122 'check-label' =>
'revdelete-hide-name',
123 'success' =>
'logdelete-success',
124 'failure' =>
'logdelete-failure',
125 'text' =>
'logdelete-text',
126 'selected' =>
'logdelete-selected',
137 parent::__construct(
'Revisiondelete' );
139 $this->permissionManager = $permissionManager;
140 $this->repoGroup = $repoGroup;
159 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
160 # Handle our many different possible input types.
161 $ids = $request->getVal(
'ids' );
162 if ( $ids !==
null ) {
163 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
164 $this->ids = explode(
',', $ids );
167 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
170 $this->ids = array_unique( array_filter( $this->ids ) );
172 $this->typeName = $request->getVal(
'type' );
173 $this->targetObj = Title::newFromText( $request->getText(
'target' ) );
175 # For reviewing deleted files...
176 $this->archiveName = $request->getVal(
'file' );
177 $this->token = $request->getVal(
'token' );
178 if ( $this->archiveName && $this->targetObj ) {
184 $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
187 if ( !$this->typeName || count( $this->ids ) == 0 ) {
188 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
191 $restriction = RevisionDeleter::getRestriction( $this->typeName );
193 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
197 # Allow the list type to adjust the passed target
198 $this->targetObj = RevisionDeleter::suggestTarget(
204 # We need a target page!
205 if ( $this->targetObj ===
null ) {
206 $output->addWikiMsg(
'undelete-header' );
212 $checkReplica = !$this->submitClicked;
214 $this->permissionManager->isBlockedFrom(
224 $this->getLanguage(),
229 $this->typeLabels = self::UI_LABELS[$this->typeName];
232 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
233 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
234 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
235 $pageIsSuppressed = $list->areAnySuppressed();
236 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
238 $this->otherReason = $request->getVal(
'wpReason',
'' );
239 # Give a link to the logs/hist for this page
242 # Initialise checkboxes
244 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
245 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
246 RevisionDeleter::getRevdelConstant( $this->typeName )
248 [
'revdelete-hide-comment',
'wpHideComment', RevisionRecord::DELETED_COMMENT ],
249 [
'revdelete-hide-user',
'wpHideUser', RevisionRecord::DELETED_USER ]
251 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
252 $this->checks[] = [
'revdelete-hide-restricted',
253 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
256 # Either submit or create our form
257 if ( $this->mIsAllowed && $this->submitClicked ) {
263 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
264 # Show relevant lines from the deletion log
265 $deleteLogPage =
new LogPage(
'delete' );
266 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
267 LogEventsList::showLogExtract(
272 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
275 # Show relevant lines from the suppression log
276 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
277 $suppressLogPage =
new LogPage(
'suppress' );
278 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
279 LogEventsList::showLogExtract(
284 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
294 # Give a link to the logs/hist for this page
295 if ( $this->targetObj ) {
297 $this->
getSkin()->setRelevantTitle( $this->targetObj );
300 $links[] = $linkRenderer->makeKnownLink(
302 $this->
msg(
'viewpagelogs' )->text(),
304 [
'page' => $this->targetObj->getPrefixedText() ]
306 if ( !$this->targetObj->isSpecialPage() ) {
307 # Give a link to the page history
308 $links[] = $linkRenderer->makeKnownLink(
310 $this->
msg(
'pagehist' )->text(),
312 [
'action' =>
'history' ]
314 # Link to deleted edits
315 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
317 $links[] = $linkRenderer->makeKnownLink(
319 $this->
msg(
'deletedhist' )->text(),
321 [
'target' => $this->targetObj->getPrefixedDBkey() ]
325 # Logs themselves don't have histories or archived revisions
337 $conds[
'log_type'] = [
'delete',
'suppress' ];
338 $conds[
'log_action'] = $this->
getList()->getLogAction();
339 $conds[
'ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
342 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
353 $repo = $this->repoGroup->getLocalRepo();
354 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
357 if ( !$oimage->exists() ) {
358 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
363 if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
364 if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
370 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
372 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
373 $this->targetObj->getText(),
374 $lang->userDate( $oimage->getTimestamp(), $user ),
375 $lang->userTime( $oimage->getTimestamp(), $user ) );
377 Html::rawElement(
'form', [
380 'target' => $this->targetObj->getPrefixedDBkey(),
381 'file' => $archiveName,
382 'token' => $user->getEditToken( $archiveName ),
392 # We mustn't allow the output to be CDN cached, otherwise
393 # if an admin previews a deleted image, and it's cached, then
394 # a user without appropriate permissions can toddle off and
395 # nab the image, and CDN will serve it
396 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
398 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
401 $key = $oimage->getStorageKey();
402 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
403 $repo->streamFileWithStatus(
$path );
411 if ( $this->revDelList ===
null ) {
412 $this->revDelList = RevisionDeleter::createList(
413 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
417 return $this->revDelList;
429 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
430 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
433 $out->addHTML(
"<ul>" );
438 for ( $list->reset(); $list->current(); $list->next() ) {
439 $item = $list->current();
441 if ( !$item->canView() ) {
442 if ( !$this->submitClicked ) {
445 $userAllowed =
false;
449 $out->addHTML( $item->getHTML() );
452 if ( !$numRevisions ) {
453 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
456 $out->addHTML(
"</ul>" );
461 if ( !$userAllowed ) {
466 if ( $this->mIsAllowed ) {
467 $suppressAllowed = $this->permissionManager
468 ->userHasRight( $this->
getUser(),
'suppressrevision' );
469 $out->addModules( [
'mediawiki.misc-authed-ooui' ] );
470 $out->addModuleStyles( [
'mediawiki.special',
471 'mediawiki.interface.helpers.styles' ] );
473 $dropdownReason = $this->
msg(
'revdelete-reason-dropdown' )
474 ->page( $this->targetObj )->inContentLanguage()->text();
476 if ( $suppressAllowed ) {
477 $dropdownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
478 ->page( $this->targetObj )->inContentLanguage()->text();
485 'label' => $this->
msg(
'revdelete-log' )->text(),
486 'cssclass' =>
'wpReasonDropDown',
487 'id' =>
'wpRevDeleteReasonList',
488 'name' =>
'wpRevDeleteReasonList',
489 'options' => Html::listDropdownOptions(
491 [
'other' => $this->
msg(
'revdelete-reasonotherlist' )->text() ]
493 'default' => $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' )
498 'label' => $this->
msg(
'revdelete-otherreason' )->text(),
499 'name' =>
'wpReason',
505 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
510 'name' =>
'wpEditToken',
511 'default' => $this->
getUser()->getEditToken()
517 'default' => $this->targetObj->getPrefixedText()
523 'default' => $this->typeName
529 'default' => implode(
',', $this->ids )
532 $htmlForm = HTMLForm::factory(
'ooui', $fields, $this->
getContext() );
534 ->setSubmitText( $this->
msg(
'revdelete-submit', $numRevisions )->text() )
535 ->setSubmitName(
'wpSubmit' )
536 ->setWrapperLegend( $this->
msg(
'revdelete-legend' )->text() )
537 ->setAction( $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) )
540 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
543 if ( $suppressAllowed ) {
544 $link .= $linkRenderer->makeKnownLink(
545 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->
getTitle(),
546 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
548 [
'action' =>
'edit' ]
550 $link .= $this->
msg(
'pipe-separator' )->escaped();
552 $link .= $linkRenderer->makeKnownLink(
553 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->
getTitle(),
554 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
556 [
'action' =>
'edit' ]
558 $htmlForm->setPostHtml( Xml::tags(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) );
560 $out->addHTML( $htmlForm->getHTML(
false ) );
571 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
572 'revdelete-text-others'
575 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
576 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
579 if ( $this->mIsAllowed ) {
580 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
595 if ( $list->length() == 1 ) {
601 foreach ( $this->checks as $item ) {
604 [ $message, $name, $bitField ] = $item;
608 'label-raw' => $this->
msg( $message )->escaped(),
612 'default' => $list->length() == 1 ? $list->current()->getBits() & $bitField : null
615 if ( $bitField == RevisionRecord::DELETED_RESTRICTED ) {
616 $field[
'label-raw'] =
"<b>" . $field[
'label-raw'] .
"</b>";
617 if ( $type ===
'radio' ) {
618 $field[
'options-messages'] = [
619 'revdelete-radio-same' => -1,
620 'revdelete-radio-unset-suppress' => 0,
621 'revdelete-radio-set-suppress' => 1
624 } elseif ( $type ===
'radio' ) {
625 $field[
'options-messages'] = [
626 'revdelete-radio-same' => -1,
627 'revdelete-radio-unset' => 0,
628 'revdelete-radio-set' => 1
644 # Check edit token on submission
645 $token = $this->
getRequest()->getVal(
'wpEditToken' );
646 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
647 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
653 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
654 $comment = $listReason;
655 if ( $comment ===
'other' ) {
656 $comment = $this->otherReason;
657 } elseif ( $this->otherReason !==
'' ) {
659 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
660 . $this->otherReason;
662 # Can the user set this field?
663 if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
664 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
668 # If the save went through, go to success message...
669 $status = $this->
save( $bitParams, $comment );
670 if ( $status->isGood() ) {
675 # ...otherwise, bounce back to form...
688 $out->setPageTitleMsg( $this->
msg(
'actioncomplete' ) );
691 $out->msg( $this->typeLabels[
'success'] )->parse()
694 $this->wasSaved =
true;
695 $this->revDelList->reloadFromPrimary();
706 $out->setPageTitleMsg( $this->
msg(
'actionfailed' ) );
709 $out->parseAsContent(
710 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
724 foreach ( $this->checks as [ , $name, $field ] ) {
725 $val = $this->
getRequest()->getInt( $name, 0 );
726 if ( $val < -1 || $val > 1 ) {
729 $bitfield[$field] = $val;
731 if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
732 $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
744 protected function save( array $bitPars, $reason ) {
745 return $this->
getList()->setVisibility(
746 [
'value' => $bitPars,
'comment' => $reason ]
759class_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...
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 By default the message key is the canonical name of...
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.
Show an error when the user tries to do something whilst blocked.