57 private $submitClicked;
95 private const UI_LABELS = [
97 'check-label' =>
'revdelete-hide-text',
98 'success' =>
'revdelete-success',
99 'failure' =>
'revdelete-failure',
100 'text' =>
'revdelete-text-text',
101 'selected' =>
'revdelete-selected-text',
104 'check-label' =>
'revdelete-hide-text',
105 'success' =>
'revdelete-success',
106 'failure' =>
'revdelete-failure',
107 'text' =>
'revdelete-text-text',
108 'selected' =>
'revdelete-selected-text',
111 'check-label' =>
'revdelete-hide-image',
112 'success' =>
'revdelete-success',
113 'failure' =>
'revdelete-failure',
114 'text' =>
'revdelete-text-file',
115 'selected' =>
'revdelete-selected-file',
118 'check-label' =>
'revdelete-hide-image',
119 'success' =>
'revdelete-success',
120 'failure' =>
'revdelete-failure',
121 'text' =>
'revdelete-text-file',
122 'selected' =>
'revdelete-selected-file',
125 'check-label' =>
'revdelete-hide-name',
126 'success' =>
'logdelete-success',
127 'failure' =>
'logdelete-failure',
128 'text' =>
'logdelete-text',
129 'selected' =>
'logdelete-selected',
140 parent::__construct(
'Revisiondelete' );
142 $this->permissionManager = $permissionManager;
143 $this->repoGroup = $repoGroup;
162 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
163 # Handle our many different possible input types.
164 $ids = $request->getVal(
'ids' );
165 if ( $ids !==
null ) {
166 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
167 $this->ids = explode(
',', $ids );
170 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
173 $this->ids = array_unique( array_filter( $this->ids ) );
175 $this->typeName = $request->getVal(
'type' );
176 $this->targetObj = Title::newFromText( $request->getText(
'target' ) );
178 # For reviewing deleted files...
179 $this->archiveName = $request->getVal(
'file' );
180 $this->token = $request->getVal(
'token' );
181 if ( $this->archiveName && $this->targetObj ) {
187 $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
190 if ( !$this->typeName || count( $this->ids ) == 0 ) {
191 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
194 $restriction = RevisionDeleter::getRestriction( $this->typeName );
196 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
200 # Allow the list type to adjust the passed target
201 $this->targetObj = RevisionDeleter::suggestTarget(
207 # We need a target page!
208 if ( $this->targetObj ===
null ) {
209 $output->addWikiMsg(
'undelete-header' );
215 $checkReplica = !$this->submitClicked;
217 $this->permissionManager->isBlockedFrom(
227 $this->getLanguage(),
232 $this->typeLabels = self::UI_LABELS[$this->typeName];
235 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
236 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
237 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
238 $pageIsSuppressed = $list->areAnySuppressed();
239 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
241 $this->otherReason = $request->getVal(
'wpReason',
'' );
242 # Give a link to the logs/hist for this page
245 # Initialise checkboxes
247 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
248 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
249 RevisionDeleter::getRevdelConstant( $this->typeName )
251 [
'revdelete-hide-comment',
'wpHideComment', RevisionRecord::DELETED_COMMENT ],
252 [
'revdelete-hide-user',
'wpHideUser', RevisionRecord::DELETED_USER ]
254 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
255 $this->checks[] = [
'revdelete-hide-restricted',
256 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
259 # Either submit or create our form
260 if ( $this->mIsAllowed && $this->submitClicked ) {
266 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
267 # Show relevant lines from the deletion log
268 $deleteLogPage =
new LogPage(
'delete' );
269 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
270 LogEventsList::showLogExtract(
275 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
278 # Show relevant lines from the suppression log
279 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
280 $suppressLogPage =
new LogPage(
'suppress' );
281 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
282 LogEventsList::showLogExtract(
287 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
297 # Give a link to the logs/hist for this page
298 if ( $this->targetObj ) {
300 $this->
getSkin()->setRelevantTitle( $this->targetObj );
303 $links[] = $linkRenderer->makeKnownLink(
305 $this->
msg(
'viewpagelogs' )->text(),
307 [
'page' => $this->targetObj->getPrefixedText() ]
309 if ( !$this->targetObj->isSpecialPage() ) {
310 # Give a link to the page history
311 $links[] = $linkRenderer->makeKnownLink(
313 $this->
msg(
'pagehist' )->text(),
315 [
'action' =>
'history' ]
317 # Link to deleted edits
318 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
320 $links[] = $linkRenderer->makeKnownLink(
322 $this->
msg(
'deletedhist' )->text(),
324 [
'target' => $this->targetObj->getPrefixedDBkey() ]
328 # Logs themselves don't have histories or archived revisions
340 $conds[
'log_type'] = [
'delete',
'suppress' ];
341 $conds[
'log_action'] = $this->
getList()->getLogAction();
342 $conds[
'ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
345 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
356 $repo = $this->repoGroup->getLocalRepo();
357 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
360 if ( !$oimage->exists() ) {
361 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
366 if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
367 if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
373 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
375 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
376 $this->targetObj->getText(),
377 $lang->userDate( $oimage->getTimestamp(), $user ),
378 $lang->userTime( $oimage->getTimestamp(), $user ) );
380 Xml::openElement(
'form', [
383 'target' => $this->targetObj->getPrefixedDBkey(),
384 'file' => $archiveName,
385 'token' => $user->getEditToken( $archiveName ),
389 Xml::submitButton( $this->msg(
'revdelete-show-file-submit' )->text() ) .
396 # We mustn't allow the output to be CDN cached, otherwise
397 # if an admin previews a deleted image, and it's cached, then
398 # a user without appropriate permissions can toddle off and
399 # nab the image, and CDN will serve it
400 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
402 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
405 $key = $oimage->getStorageKey();
406 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
407 $repo->streamFileWithStatus(
$path );
415 if ( $this->revDelList ===
null ) {
416 $this->revDelList = RevisionDeleter::createList(
417 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
421 return $this->revDelList;
433 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
434 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
437 $out->addHTML(
"<ul>" );
442 for ( $list->reset(); $list->current(); $list->next() ) {
443 $item = $list->current();
445 if ( !$item->canView() ) {
446 if ( !$this->submitClicked ) {
449 $userAllowed =
false;
453 $out->addHTML( $item->getHTML() );
456 if ( !$numRevisions ) {
457 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
460 $out->addHTML(
"</ul>" );
465 if ( !$userAllowed ) {
470 if ( $this->mIsAllowed ) {
471 $suppressAllowed = $this->permissionManager
472 ->userHasRight( $this->
getUser(),
'suppressrevision' );
473 $out->addModules( [
'mediawiki.misc-authed-ooui' ] );
474 $out->addModuleStyles( [
'mediawiki.special',
475 'mediawiki.interface.helpers.styles' ] );
477 $dropdownReason = $this->
msg(
'revdelete-reason-dropdown' )
478 ->page( $this->targetObj )->inContentLanguage()->text();
480 if ( $suppressAllowed ) {
481 $dropdownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
482 ->page( $this->targetObj )->inContentLanguage()->text();
489 'label' => $this->
msg(
'revdelete-log' )->text(),
490 'cssclass' =>
'wpReasonDropDown',
491 'id' =>
'wpRevDeleteReasonList',
492 'name' =>
'wpRevDeleteReasonList',
493 'options' => Html::listDropdownOptions(
495 [
'other' => $this->
msg(
'revdelete-reasonotherlist' )->text() ]
497 'default' => $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' )
502 'label' => $this->
msg(
'revdelete-otherreason' )->text(),
503 'name' =>
'wpReason',
509 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
514 'name' =>
'wpEditToken',
515 'default' => $this->
getUser()->getEditToken()
521 'default' => $this->targetObj->getPrefixedText()
527 'default' => $this->typeName
533 'default' => implode(
',', $this->ids )
536 $htmlForm = HTMLForm::factory(
'ooui', $fields, $this->
getContext() );
538 ->setSubmitText( $this->
msg(
'revdelete-submit', $numRevisions )->text() )
539 ->setSubmitName(
'wpSubmit' )
540 ->setWrapperLegend( $this->
msg(
'revdelete-legend' )->text() )
541 ->setAction( $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) )
544 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
547 if ( $suppressAllowed ) {
548 $link .= $linkRenderer->makeKnownLink(
549 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->
getTitle(),
550 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
552 [
'action' =>
'edit' ]
554 $link .= $this->
msg(
'pipe-separator' )->escaped();
556 $link .= $linkRenderer->makeKnownLink(
557 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->
getTitle(),
558 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
560 [
'action' =>
'edit' ]
562 $htmlForm->setPostHtml( Xml::tags(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) );
564 $out->addHTML( $htmlForm->getHTML(
false ) );
575 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
576 'revdelete-text-others'
579 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
580 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
583 if ( $this->mIsAllowed ) {
584 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
599 if ( $list->length() == 1 ) {
605 foreach ( $this->checks as $item ) {
608 [ $message, $name, $bitField ] = $item;
612 'label-raw' => $this->
msg( $message )->escaped(),
616 'default' => $list->length() == 1 ? $list->current()->getBits() & $bitField : null
619 if ( $bitField == RevisionRecord::DELETED_RESTRICTED ) {
620 $field[
'label-raw'] =
"<b>" . $field[
'label-raw'] .
"</b>";
621 if ( $type ===
'radio' ) {
622 $field[
'options-messages'] = [
623 'revdelete-radio-same' => -1,
624 'revdelete-radio-unset-suppress' => 0,
625 'revdelete-radio-set-suppress' => 1
628 } elseif ( $type ===
'radio' ) {
629 $field[
'options-messages'] = [
630 'revdelete-radio-same' => -1,
631 'revdelete-radio-unset' => 0,
632 'revdelete-radio-set' => 1
648 # Check edit token on submission
649 $token = $this->
getRequest()->getVal(
'wpEditToken' );
650 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
651 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
657 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
658 $comment = $listReason;
659 if ( $comment ===
'other' ) {
660 $comment = $this->otherReason;
661 } elseif ( $this->otherReason !==
'' ) {
663 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
664 . $this->otherReason;
666 # Can the user set this field?
667 if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
668 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
672 # If the save went through, go to success message...
673 $status = $this->
save( $bitParams, $comment );
674 if ( $status->isGood() ) {
679 # ...otherwise, bounce back to form...
692 $out->setPageTitleMsg( $this->
msg(
'actioncomplete' ) );
695 $out->msg( $this->typeLabels[
'success'] )->parse()
698 $this->wasSaved =
true;
699 $this->revDelList->reloadFromPrimary();
710 $out->setPageTitleMsg( $this->
msg(
'actionfailed' ) );
713 $out->parseAsContent(
714 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
728 foreach ( $this->checks as [ , $name, $field ] ) {
729 $val = $this->
getRequest()->getInt( $name, 0 );
730 if ( $val < -1 || $val > 1 ) {
733 $bitfield[$field] = $val;
735 if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
736 $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
748 protected function save( array $bitPars, $reason ) {
749 return $this->
getList()->setVisibility(
750 [
'value' => $bitPars,
'comment' => $reason ]
763class_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 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.
Show an error when the user tries to do something whilst blocked.
Module of static functions for generating XML.