39 private $submitClicked;
77 private const UI_LABELS = [
79 'check-label' =>
'revdelete-hide-text',
80 'success' =>
'revdelete-success',
81 'failure' =>
'revdelete-failure',
82 'text' =>
'revdelete-text-text',
83 'selected' =>
'revdelete-selected-text',
86 'check-label' =>
'revdelete-hide-text',
87 'success' =>
'revdelete-success',
88 'failure' =>
'revdelete-failure',
89 'text' =>
'revdelete-text-text',
90 'selected' =>
'revdelete-selected-text',
93 'check-label' =>
'revdelete-hide-image',
94 'success' =>
'revdelete-success',
95 'failure' =>
'revdelete-failure',
96 'text' =>
'revdelete-text-file',
97 'selected' =>
'revdelete-selected-file',
100 'check-label' =>
'revdelete-hide-image',
101 'success' =>
'revdelete-success',
102 'failure' =>
'revdelete-failure',
103 'text' =>
'revdelete-text-file',
104 'selected' =>
'revdelete-selected-file',
107 'check-label' =>
'revdelete-hide-name',
108 'success' =>
'logdelete-success',
109 'failure' =>
'logdelete-failure',
110 'text' =>
'logdelete-text',
111 'selected' =>
'logdelete-selected',
116 parent::__construct(
'Revisiondelete' );
118 $this->permissionManager = $permissionManager;
119 $this->repoGroup = $repoGroup;
140 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
141 # Handle our many different possible input types.
142 $ids = $request->getVal(
'ids' );
143 if ( $ids !==
null ) {
144 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
145 $this->ids = explode(
',', $ids );
148 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
151 $this->ids = array_unique( array_filter( $this->ids ) );
153 $this->typeName = $request->getVal(
'type' );
154 $this->targetObj = Title::newFromText( $request->getText(
'target' ) );
156 # For reviewing deleted files...
157 $this->archiveName = $request->getVal(
'file' );
158 $this->token = $request->getVal(
'token' );
159 if ( $this->archiveName && $this->targetObj ) {
165 $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName );
168 if ( !$this->typeName || count( $this->ids ) == 0 ) {
169 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
172 $restriction = RevisionDeleter::getRestriction( $this->typeName );
174 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
178 # Allow the list type to adjust the passed target
179 $this->targetObj = RevisionDeleter::suggestTarget(
185 # We need a target page!
186 if ( $this->targetObj ===
null ||
187 ( $this->typeName !==
'logging' && !$this->targetObj->canExist() )
189 $output->addWikiMsg(
'undelete-header' );
195 $checkReplica = !$this->submitClicked;
197 $this->permissionManager->isBlockedFrom(
207 $this->getLanguage(),
212 $this->typeLabels = self::UI_LABELS[$this->typeName];
215 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
216 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
217 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
218 $pageIsSuppressed = $list->areAnySuppressed();
219 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
221 $this->otherReason = $request->getVal(
'wpReason',
'' );
222 # Give a link to the logs/hist for this page
225 # Initialise checkboxes
227 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
228 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
229 RevisionDeleter::getRevdelConstant( $this->typeName )
231 [
'revdelete-hide-comment',
'wpHideComment', RevisionRecord::DELETED_COMMENT ],
232 [
'revdelete-hide-user',
'wpHideUser', RevisionRecord::DELETED_USER ]
234 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
235 $this->checks[] = [
'revdelete-hide-restricted',
236 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
239 # Either submit or create our form
240 if ( $this->mIsAllowed && $this->submitClicked ) {
246 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
247 # Show relevant lines from the deletion log
248 $deleteLogPage =
new LogPage(
'delete' );
249 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
250 LogEventsList::showLogExtract(
255 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
258 # Show relevant lines from the suppression log
259 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
260 $suppressLogPage =
new LogPage(
'suppress' );
261 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
262 LogEventsList::showLogExtract(
267 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
277 # Give a link to the logs/hist for this page
278 if ( $this->targetObj ) {
280 $this->
getSkin()->setRelevantTitle( $this->targetObj );
283 $links[] = $linkRenderer->makeKnownLink(
285 $this->
msg(
'viewpagelogs' )->text(),
287 [
'page' => $this->targetObj->getPrefixedText() ]
289 if ( !$this->targetObj->isSpecialPage() ) {
290 # Give a link to the page history
291 $links[] = $linkRenderer->makeKnownLink(
293 $this->
msg(
'pagehist' )->text(),
295 [
'action' =>
'history' ]
297 # Link to deleted edits
298 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
300 $links[] = $linkRenderer->makeKnownLink(
302 $this->
msg(
'deletedhist' )->text(),
304 [
'target' => $this->targetObj->getPrefixedDBkey() ]
308 # Logs themselves don't have histories or archived revisions
320 $conds[
'log_type'] = [
'delete',
'suppress' ];
321 $conds[
'log_action'] = $this->
getList()->getLogAction();
322 $conds[
'ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
325 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
336 if ( $this->targetObj->getNamespace() !==
NS_FILE ) {
337 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
341 $repo = $this->repoGroup->getLocalRepo();
342 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
345 if ( !$oimage->exists() ) {
346 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
351 if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
352 if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
358 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
360 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
361 $this->targetObj->getText(),
362 $lang->userDate( $oimage->getTimestamp(), $user ),
363 $lang->userTime( $oimage->getTimestamp(), $user ) );
365 Html::rawElement(
'form', [
368 'target' => $this->targetObj->getPrefixedDBkey(),
369 'file' => $archiveName,
370 'token' => $user->getEditToken( $archiveName ),
373 Html::submitButton( $this->msg(
'revdelete-show-file-submit' )->text() )
380 # We mustn't allow the output to be CDN cached, otherwise
381 # if an admin previews a deleted image, and it's cached, then
382 # a user without appropriate permissions can toddle off and
383 # nab the image, and CDN will serve it
384 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
386 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
389 $key = $oimage->getStorageKey();
390 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
391 $repo->streamFileWithStatus(
$path );
399 if ( $this->revDelList ===
null ) {
400 $this->revDelList = RevisionDeleter::createList(
401 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
405 return $this->revDelList;
417 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
418 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
421 $out->addHTML(
"<ul>" );
426 foreach ( $list as $item ) {
427 if ( !$item->canView() ) {
428 if ( !$this->submitClicked ) {
431 $userAllowed =
false;
435 $out->addHTML( $item->getHTML() );
438 if ( !$numRevisions ) {
439 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
442 $out->addHTML(
"</ul>" );
447 if ( !$userAllowed ) {
452 if ( $this->mIsAllowed ) {
453 $suppressAllowed = $this->permissionManager
454 ->userHasRight( $this->
getUser(),
'suppressrevision' );
455 $out->addModules( [
'mediawiki.misc-authed-ooui' ] );
456 $out->addModuleStyles( [
'mediawiki.special',
457 'mediawiki.interface.helpers.styles' ] );
459 $dropdownReason = $this->
msg(
'revdelete-reason-dropdown' )
460 ->page( $this->targetObj )->inContentLanguage()->text();
462 if ( $suppressAllowed ) {
463 $dropdownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
464 ->page( $this->targetObj )->inContentLanguage()->text();
471 'label' => $this->
msg(
'revdelete-log' )->text(),
472 'cssclass' =>
'wpReasonDropDown',
473 'id' =>
'wpRevDeleteReasonList',
474 'name' =>
'wpRevDeleteReasonList',
475 'options' => Html::listDropdownOptions(
477 [
'other' => $this->
msg(
'revdelete-reasonotherlist' )->text() ]
479 'default' => $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' )
484 'label' => $this->
msg(
'revdelete-otherreason' )->text(),
485 'name' =>
'wpReason',
491 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
496 'name' =>
'wpEditToken',
497 'default' => $this->
getUser()->getEditToken()
503 'default' => $this->targetObj->getPrefixedText()
509 'default' => $this->typeName
515 'default' => implode(
',', $this->ids )
518 $htmlForm = HTMLForm::factory(
'ooui', $fields, $this->
getContext() );
520 ->setSubmitText( $this->
msg(
'revdelete-submit', $numRevisions )->text() )
521 ->setSubmitName(
'wpSubmit' )
522 ->setWrapperLegend( $this->
msg(
'revdelete-legend' )->text() )
523 ->setAction( $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ) )
526 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
529 if ( $suppressAllowed ) {
530 $link .= $linkRenderer->makeKnownLink(
531 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->getTitle(),
532 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
534 [
'action' =>
'edit' ]
536 $link .= $this->
msg(
'pipe-separator' )->escaped();
538 $link .= $linkRenderer->makeKnownLink(
539 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
540 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
542 [
'action' =>
'edit' ]
544 $htmlForm->setPostHtml( Html::rawElement(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) );
546 $out->addHTML( $htmlForm->getHTML(
false ) );
557 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
558 'revdelete-text-others'
561 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
562 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
565 if ( $this->mIsAllowed ) {
566 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
581 if ( $list->length() == 1 ) {
587 foreach ( $this->checks as $item ) {
590 [ $message, $name, $bitField ] = $item;
594 'label-raw' => $this->
msg( $message )->escaped(),
598 'default' => $list->length() == 1 ? $list->current()->getBits() & $bitField : null
601 if ( $bitField == RevisionRecord::DELETED_RESTRICTED ) {
602 $field[
'label-raw'] =
"<b>" . $field[
'label-raw'] .
"</b>";
603 if ( $type ===
'radio' ) {
604 $field[
'options-messages'] = [
605 'revdelete-radio-same' => -1,
606 'revdelete-radio-unset-suppress' => 0,
607 'revdelete-radio-set-suppress' => 1
610 } elseif ( $type ===
'radio' ) {
611 $field[
'options-messages'] = [
612 'revdelete-radio-same' => -1,
613 'revdelete-radio-unset' => 0,
614 'revdelete-radio-set' => 1
630 # Check edit token on submission
631 $token = $this->
getRequest()->getVal(
'wpEditToken' );
632 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
633 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
639 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
640 $comment = $listReason;
641 if ( $comment ===
'other' ) {
642 $comment = $this->otherReason;
643 } elseif ( $this->otherReason !==
'' ) {
645 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
646 . $this->otherReason;
648 # Can the user set this field?
649 if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
650 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
654 # If the save went through, go to success message...
655 $status = $this->
save( $bitParams, $comment );
656 if ( $status->isGood() ) {
661 # ...otherwise, bounce back to form...
674 $out->setPageTitleMsg( $this->
msg(
'actioncomplete' ) );
677 $out->msg( $this->typeLabels[
'success'] )->parse()
680 $this->wasSaved =
true;
681 $this->revDelList->reloadFromPrimary();
692 $out->setPageTitleMsg( $this->
msg(
'actionfailed' ) );
693 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
696 $out->parseAsContent(
697 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
711 foreach ( $this->checks as [ , $name, $field ] ) {
712 $val = $this->
getRequest()->getInt( $name, 0 );
713 if ( $val < -1 || $val > 1 ) {
716 $bitfield[$field] = $val;
718 if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
719 $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
731 protected function save( array $bitPars, $reason ) {
732 return $this->
getList()->setVisibility(
733 [
'value' => $bitPars,
'comment' => $reason ]
747class_alias( SpecialRevisionDelete::class,
'SpecialRevisionDelete' );
An error page which can definitely be safely rendered using the OutputPage.
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.
General controller for RevDel, used by both SpecialRevisiondelete and ApiRevisionDelete.