38 private $submitClicked;
71 private $permissionManager;
79 private const UI_LABELS = [
81 'check-label' =>
'revdelete-hide-text',
82 'success' =>
'revdelete-success',
83 'failure' =>
'revdelete-failure',
84 'text' =>
'revdelete-text-text',
85 'selected' =>
'revdelete-selected-text',
88 'check-label' =>
'revdelete-hide-text',
89 'success' =>
'revdelete-success',
90 'failure' =>
'revdelete-failure',
91 'text' =>
'revdelete-text-text',
92 'selected' =>
'revdelete-selected-text',
95 'check-label' =>
'revdelete-hide-image',
96 'success' =>
'revdelete-success',
97 'failure' =>
'revdelete-failure',
98 'text' =>
'revdelete-text-file',
99 'selected' =>
'revdelete-selected-file',
102 'check-label' =>
'revdelete-hide-image',
103 'success' =>
'revdelete-success',
104 'failure' =>
'revdelete-failure',
105 'text' =>
'revdelete-text-file',
106 'selected' =>
'revdelete-selected-file',
109 'check-label' =>
'revdelete-hide-name',
110 'success' =>
'logdelete-success',
111 'failure' =>
'logdelete-failure',
112 'text' =>
'logdelete-text',
113 'selected' =>
'logdelete-selected',
124 parent::__construct(
'Revisiondelete' );
126 $this->permissionManager = $permissionManager;
127 $this->repoGroup = $repoGroup;
146 $this->submitClicked = $request->wasPosted() && $request->getBool(
'wpSubmit' );
147 # Handle our many different possible input types.
148 $ids = $request->getVal(
'ids' );
149 if ( $ids !==
null ) {
150 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
151 $this->ids = explode(
',', $ids );
154 $this->ids = array_keys( $request->getArray(
'ids', [] ) );
157 $this->ids = array_unique( array_filter( $this->ids ) );
159 $this->typeName = $request->getVal(
'type' );
160 $this->targetObj = Title::newFromText( $request->getText(
'target' ) );
162 # For reviewing deleted files...
163 $this->archiveName = $request->getVal(
'file' );
164 $this->token = $request->getVal(
'token' );
165 if ( $this->archiveName && $this->targetObj ) {
174 if ( !$this->typeName || count( $this->ids ) == 0 ) {
175 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
180 if ( !$this->
getAuthority()->isAllowedAny( $restriction,
'deletedhistory' ) ) {
184 # Allow the list type to adjust the passed target
191 # We need a target page!
192 if ( $this->targetObj ===
null ) {
193 $output->addWikiMsg(
'undelete-header' );
199 $checkReplica = !$this->submitClicked;
201 $this->permissionManager->isBlockedFrom(
211 $this->getLanguage(),
216 $this->typeLabels = self::UI_LABELS[$this->typeName];
219 $this->mIsAllowed = $this->permissionManager->userHasRight( $user, $restriction );
220 $canViewSuppressedOnly = $this->permissionManager->userHasRight( $user,
'viewsuppressed' ) &&
221 !$this->permissionManager->userHasRight( $user,
'suppressrevision' );
222 $pageIsSuppressed = $list->areAnySuppressed();
223 $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
225 $this->otherReason = $request->getVal(
'wpReason',
'' );
226 # Give a link to the logs/hist for this page
229 # Initialise checkboxes
231 # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
232 [ $this->typeLabels[
'check-label'],
'wpHidePrimary',
235 [
'revdelete-hide-comment',
'wpHideComment', RevisionRecord::DELETED_COMMENT ],
236 [
'revdelete-hide-user',
'wpHideUser', RevisionRecord::DELETED_USER ]
238 if ( $this->permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
239 $this->checks[] = [
'revdelete-hide-restricted',
240 'wpHideRestricted', RevisionRecord::DELETED_RESTRICTED ];
243 # Either submit or create our form
244 if ( $this->mIsAllowed && $this->submitClicked ) {
250 if ( $this->permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
251 # Show relevant lines from the deletion log
252 $deleteLogPage =
new LogPage(
'delete' );
253 $output->addHTML(
"<h2>" . $deleteLogPage->getName()->escaped() .
"</h2>\n" );
254 LogEventsList::showLogExtract(
259 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
262 # Show relevant lines from the suppression log
263 if ( $this->permissionManager->userHasRight( $user,
'suppressionlog' ) ) {
264 $suppressLogPage =
new LogPage(
'suppress' );
265 $output->addHTML(
"<h2>" . $suppressLogPage->getName()->escaped() .
"</h2>\n" );
266 LogEventsList::showLogExtract(
271 [
'lim' => 25,
'conds' => $this->
getLogQueryCond(),
'useMaster' => $this->wasSaved ]
281 # Give a link to the logs/hist for this page
282 if ( $this->targetObj ) {
284 $this->
getSkin()->setRelevantTitle( $this->targetObj );
287 $links[] = $linkRenderer->makeKnownLink(
289 $this->
msg(
'viewpagelogs' )->text(),
291 [
'page' => $this->targetObj->getPrefixedText() ]
293 if ( !$this->targetObj->isSpecialPage() ) {
294 # Give a link to the page history
295 $links[] = $linkRenderer->makeKnownLink(
297 $this->
msg(
'pagehist' )->text(),
299 [
'action' =>
'history' ]
301 # Link to deleted edits
302 if ( $this->permissionManager->userHasRight( $this->getUser(),
'undelete' ) ) {
304 $links[] = $linkRenderer->makeKnownLink(
306 $this->
msg(
'deletedhist' )->text(),
308 [
'target' => $this->targetObj->getPrefixedDBkey() ]
312 # Logs themselves don't have histories or archived revisions
324 $conds[
'log_type'] = [
'delete',
'suppress' ];
325 $conds[
'log_action'] = $this->
getList()->getLogAction();
329 $conds[
'ls_value'] = array_map(
'strval', $this->ids );
342 $repo = $this->repoGroup->getLocalRepo();
343 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
346 if ( !$oimage->exists() ) {
347 $this->
getOutput()->addWikiMsg(
'revdelete-no-file' );
352 if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) {
353 if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
359 if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
361 $this->
getOutput()->addWikiMsg(
'revdelete-show-file-confirm',
362 $this->targetObj->getText(),
363 $lang->userDate( $oimage->getTimestamp(), $user ),
364 $lang->userTime( $oimage->getTimestamp(), $user ) );
366 Xml::openElement(
'form', [
369 'target' => $this->targetObj->getPrefixedDBkey(),
370 'file' => $archiveName,
371 'token' => $user->getEditToken( $archiveName ),
375 Xml::submitButton( $this->msg(
'revdelete-show-file-submit' )->text() ) .
382 # We mustn't allow the output to be CDN cached, otherwise
383 # if an admin previews a deleted image, and it's cached, then
384 # a user without appropriate permissions can toddle off and
385 # nab the image, and CDN will serve it
386 $this->
getRequest()->response()->header(
'Expires: ' . gmdate(
'D, d M Y H:i:s', 0 ) .
' GMT' );
388 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
390 $this->
getRequest()->response()->header(
'Pragma: no-cache' );
392 $key = $oimage->getStorageKey();
393 $path = $repo->getZonePath(
'deleted' ) .
'/' . $repo->getDeletedHashPath( $key ) . $key;
394 $repo->streamFileWithStatus(
$path );
402 if ( $this->revDelList ===
null ) {
404 $this->typeName, $this->
getContext(), $this->targetObj, $this->ids
408 return $this->revDelList;
420 $out->wrapWikiMsg(
"<strong>$1</strong>", [ $this->typeLabels[
'selected'],
421 $this->
getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] );
424 $out->addHTML(
"<ul>" );
429 for ( $list->reset(); $list->current(); $list->next() ) {
430 $item = $list->current();
432 if ( !$item->canView() ) {
433 if ( !$this->submitClicked ) {
436 $userAllowed =
false;
440 $out->addHTML( $item->getHTML() );
443 if ( !$numRevisions ) {
444 throw new ErrorPageError(
'revdelete-nooldid-title',
'revdelete-nooldid-text' );
447 $out->addHTML(
"</ul>" );
452 if ( !$userAllowed ) {
457 if ( $this->mIsAllowed ) {
458 $suppressAllowed = $this->permissionManager
459 ->userHasRight( $this->
getUser(),
'suppressrevision' );
460 $out->addModules( [
'mediawiki.special.revisionDelete' ] );
461 $out->addModuleStyles( [
'mediawiki.special',
462 'mediawiki.interface.helpers.styles' ] );
464 $dropDownReason = $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->text();
466 if ( $suppressAllowed ) {
467 $dropDownReason .=
"\n" . $this->
msg(
'revdelete-reason-dropdown-suppress' )
468 ->inContentLanguage()->text();
471 $form = Xml::openElement(
'form', [
'method' =>
'post',
472 'action' => $this->
getPageTitle()->getLocalURL( [
'action' =>
'submit' ] ),
473 'id' =>
'mw-revdel-form-revisions' ] ) .
474 Xml::fieldset( $this->
msg(
'revdelete-legend' )->text() ) .
476 Xml::openElement(
'table' ) .
478 '<td class="mw-label">' .
479 Xml::label( $this->
msg(
'revdelete-log' )->text(),
'wpRevDeleteReasonList' ) .
481 '<td class="mw-input">' .
482 Xml::listDropDown(
'wpRevDeleteReasonList',
484 $this->
msg(
'revdelete-reasonotherlist' )->inContentLanguage()->text(),
485 $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' ),
'wpReasonDropDown'
489 '<td class="mw-label">' .
490 Xml::label( $this->
msg(
'revdelete-otherreason' )->text(),
'wpReason' ) .
492 '<td class="mw-input">' .
493 Xml::input(
'wpReason', 60, $this->otherReason, [
499 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT - 155,
504 '<td class="mw-submit">' .
505 Xml::submitButton( $this->
msg(
'revdelete-submit', $numRevisions )->text(),
506 [
'name' =>
'wpSubmit' ] ) .
509 Xml::closeElement(
'table' ) .
510 Html::hidden(
'wpEditToken', $this->
getUser()->getEditToken() ) .
511 Html::hidden(
'target', $this->targetObj->getPrefixedText() ) .
512 Html::hidden(
'type', $this->typeName ) .
513 Html::hidden(
'ids', implode(
',', $this->ids ) ) .
514 Xml::closeElement(
'fieldset' ) .
"\n" .
515 Xml::closeElement(
'form' ) .
"\n";
517 if ( $this->permissionManager->userHasRight( $this->getUser(),
'editinterface' ) ) {
520 if ( $suppressAllowed ) {
521 $link .= $linkRenderer->makeKnownLink(
522 $this->
msg(
'revdelete-reason-dropdown-suppress' )->inContentLanguage()->
getTitle(),
523 $this->
msg(
'revdelete-edit-reasonlist-suppress' )->text(),
525 [
'action' =>
'edit' ]
527 $link .= $this->
msg(
'pipe-separator' )->escaped();
529 $link .= $linkRenderer->makeKnownLink(
530 $this->
msg(
'revdelete-reason-dropdown' )->inContentLanguage()->
getTitle(),
531 $this->
msg(
'revdelete-edit-reasonlist' )->text(),
533 [
'action' =>
'edit' ]
535 $form .= Xml::tags(
'p', [
'class' =>
'mw-revdel-editreasons' ], $link ) .
"\n";
540 $out->addHTML( $form );
550 "<strong>$1</strong>\n$2", $this->typeLabels[
'text'],
551 'revdelete-text-others'
554 if ( $this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' ) ) {
555 $this->
getOutput()->addWikiMsg(
'revdelete-suppress-text' );
558 if ( $this->mIsAllowed ) {
559 $this->
getOutput()->addWikiMsg(
'revdelete-confirm' );
570 if ( $list->length() == 1 ) {
572 $bitfield = $list->current()->getBits();
574 if ( $this->submitClicked ) {
578 foreach ( $this->checks as $item ) {
581 list( $message, $name, $field ) = $item;
582 $innerHTML = Xml::checkLabel(
583 $this->
msg( $message )->text(),
586 (
bool)( $bitfield & $field )
589 if ( $field == RevisionRecord::DELETED_RESTRICTED ) {
590 $innerHTML =
"<b>$innerHTML</b>";
593 $line = Xml::tags(
'td', [
'class' =>
'mw-input' ], $innerHTML );
594 $html .=
"<tr>$line</tr>\n";
599 $html .=
'<th class="mw-revdel-checkbox">'
600 . $this->
msg(
'revdelete-radio-same' )->escaped() .
'</th>';
601 $html .=
'<th class="mw-revdel-checkbox">'
602 . $this->
msg(
'revdelete-radio-unset' )->escaped() .
'</th>';
603 $html .=
'<th class="mw-revdel-checkbox">'
604 . $this->
msg(
'revdelete-radio-set' )->escaped() .
'</th>';
605 $html .=
"<th></th></tr>\n";
606 foreach ( $this->checks as $item ) {
609 list( $message, $name, $field ) = $item;
611 if ( $this->submitClicked ) {
612 $selected = $this->
getRequest()->getInt( $name, 0 );
616 $line =
'<td class="mw-revdel-checkbox">' . Xml::radio( $name,
'-1', $selected == -1 ) .
'</td>';
617 $line .=
'<td class="mw-revdel-checkbox">' . Xml::radio( $name,
'0', $selected == 0 ) .
'</td>';
618 $line .=
'<td class="mw-revdel-checkbox">' . Xml::radio( $name,
'1', $selected == 1 ) .
'</td>';
619 $label = $this->
msg( $message )->escaped();
620 if ( $field == RevisionRecord::DELETED_RESTRICTED ) {
621 $label =
"<b>$label</b>";
623 $line .=
"<td>$label</td>";
624 $html .=
"<tr>$line</tr>\n";
639 # Check edit token on submission
640 $token = $this->
getRequest()->getVal(
'wpEditToken' );
641 if ( $this->submitClicked && !$this->
getUser()->matchEditToken( $token ) ) {
642 $this->
getOutput()->addWikiMsg(
'sessionfailure' );
648 $listReason = $this->
getRequest()->getText(
'wpRevDeleteReasonList',
'other' );
649 $comment = $listReason;
650 if ( $comment ===
'other' ) {
651 $comment = $this->otherReason;
652 } elseif ( $this->otherReason !==
'' ) {
654 $comment .= $this->
msg(
'colon-separator' )->inContentLanguage()->text()
655 . $this->otherReason;
657 # Can the user set this field?
658 if ( $bitParams[RevisionRecord::DELETED_RESTRICTED] == 1
659 && !$this->permissionManager->userHasRight( $this->getUser(),
'suppressrevision' )
663 # If the save went through, go to success message...
664 $status = $this->
save( $bitParams, $comment );
665 if ( $status->isGood() ) {
670 # ...otherwise, bounce back to form...
683 $out->setPageTitle( $this->
msg(
'actioncomplete' ) );
686 $out->msg( $this->typeLabels[
'success'] )->parse()
689 $this->wasSaved =
true;
690 $this->revDelList->reloadFromPrimary();
701 $out->setPageTitle( $this->
msg(
'actionfailed' ) );
704 $out->parseAsContent(
705 $status->getWikiText( $this->typeLabels[
'failure'],
false, $this->getLanguage() )
719 foreach ( $this->checks as $item ) {
720 list( , $name, $field ) = $item;
721 $val = $this->
getRequest()->getInt( $name, 0 );
722 if ( $val < -1 || $val > 1 ) {
725 $bitfield[$field] = $val;
727 if ( !isset( $bitfield[RevisionRecord::DELETED_RESTRICTED] ) ) {
728 $bitfield[RevisionRecord::DELETED_RESTRICTED] = 0;
740 protected function save( array $bitPars, $reason ) {
741 return $this->
getList()->setVisibility(
742 [
'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 extractBitfield(array $bitPars, $oldfield)
Put together a rev_deleted bitfield.
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.
Represents a title within MediaWiki.
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