Make DeleteAction and FileDeleteAction share showForm

Use getTitle() in FileDeleteAction, it's the same as using the old
$this->file.

Use a single showForm method for both classes, and a separate method to
add handy links. When switching to FormAction, the former will be
replaced by getFormFields(), the latter by postText(). However, before
doing that, the following things should be addressed:
- The onArticleConfirmDelete hook, which allows changing the reason.
  This hook should be replaced in the long term, but for now it might be
  sufficient to add it to the common UI logic and only run it for
  non-file pages.
- The horrible $hasHistory hack. This is going to be a tad more
  complicated because we need to remove it from WikiPage and
  ContentHandler, and replace it with something else.

Bug: T288282
Change-Id: I965c32457b1426ece9781221a95337d295b9e1a2
This commit is contained in:
Daimona Eaytoy 2021-08-16 19:34:00 +02:00
parent 1c009126c5
commit 6cbff5bbac
2 changed files with 77 additions and 227 deletions

View file

@ -21,8 +21,6 @@
use MediaWiki\Cache\BacklinkCacheFactory;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageReference;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\User\UserOptionsLookup;
use MediaWiki\Watchlist\WatchlistManager;
@ -97,8 +95,8 @@ class DeleteAction extends FormlessAction {
$request = $context->getRequest();
$outputPage = $context->getOutput();
$this->runExecuteChecks( $title );
$this->prepareOutput( $context->msg( 'delete-confirm', $title->getPrefixedText() ), $title );
$this->runExecuteChecks();
$this->prepareOutput( $context->msg( 'delete-confirm', $title->getPrefixedText() ) );
# Better double-check that it hasn't been deleted yet!
$article->getPage()->loadPageData(
@ -109,7 +107,7 @@ class DeleteAction extends FormlessAction {
$outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
[ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
);
$this->showLogEntries( $title );
$this->showLogEntries();
return;
}
@ -159,7 +157,7 @@ class DeleteAction extends FormlessAction {
'error mw-error-cannotdelete',
$status->getWikiText( false, false, $context->getLanguage() )
);
$this->showLogEntries( $this->getTitle() );
$this->showLogEntries();
} else {
$outputPage->addHTML( $error );
}
@ -239,7 +237,6 @@ class DeleteAction extends FormlessAction {
private function tempConfirmDelete(): void {
$this->prepareOutputForForm();
$title = $this->getTitle();
$ctx = $this->getContext();
$outputPage = $ctx->getOutput();
@ -255,16 +252,56 @@ class DeleteAction extends FormlessAction {
// FIXME: Replace (or at least rename) this hook
$this->getHookRunner()->onArticleConfirmDelete( $this->getArticle(), $outputPage, $reason );
$user = $ctx->getUser();
$this->showForm( $reason );
$this->showEditReasonsLinks();
$this->showLogEntries();
}
protected function showEditReasonsLinks(): void {
if ( $this->getContext()->getAuthority()->isAllowed( 'editinterface' ) ) {
$link = '';
if ( $this->isSuppressionAllowed() ) {
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS_SUPPRESS )->text(),
[],
[ 'action' => 'edit' ]
);
$link .= $this->msg( 'pipe-separator' )->escaped();
}
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS )->text(),
[],
[ 'action' => 'edit' ]
);
$this->getOutput()->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
}
}
/**
* @return bool
*/
protected function isSuppressionAllowed(): bool {
return $this->getContext()->getAuthority()->isAllowed( 'suppressrevision' );
}
/**
* @param string $reason
*/
protected function showForm( string $reason ): void {
$outputPage = $this->getOutput();
$user = $this->getUser();
$title = $this->getTitle();
$checkWatch = $this->userOptionsLookup->getBoolOption( $user, 'watchdeletion' ) ||
$this->watchlistManager->isWatched( $user, $title );
$fields = [];
$suppressAllowed = $ctx->getAuthority()->isAllowed( 'suppressrevision' );
$dropDownReason = $this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->text();
// Add additional specific reasons for suppress
if ( $suppressAllowed ) {
if ( $this->isSuppressionAllowed() ) {
$dropDownReason .= "\n" . $this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )
->inContentLanguage()->text();
}
@ -318,13 +355,13 @@ class DeleteAction extends FormlessAction {
'selected' => $checkWatch,
] ),
[
'label' => $ctx->msg( 'watchthis' )->text(),
'label' => $this->msg( 'watchthis' )->text(),
'align' => 'inline',
'infusable' => true,
]
);
}
if ( $suppressAllowed ) {
if ( $this->isSuppressionAllowed() ) {
$fields[] = new OOUI\FieldLayout(
new OOUI\CheckboxInputWidget( [
'name' => 'wpSuppress',
@ -333,7 +370,7 @@ class DeleteAction extends FormlessAction {
'selected' => false,
] ),
[
'label' => $ctx->msg( 'revdelete-suppress' )->text(),
'label' => $this->msg( 'revdelete-suppress' )->text(),
'align' => 'inline',
'infusable' => true,
]
@ -382,36 +419,14 @@ class DeleteAction extends FormlessAction {
'content' => $form,
] )
);
if ( $ctx->getAuthority()->isAllowed( 'editinterface' ) ) {
$link = '';
if ( $suppressAllowed ) {
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS_SUPPRESS )->text(),
[],
[ 'action' => 'edit' ]
);
$link .= $ctx->msg( 'pipe-separator' )->escaped();
}
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS )->text(),
[],
[ 'action' => 'edit' ]
);
$outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
}
$this->showLogEntries( $title );
}
/**
* @param PageIdentity $title
* @todo Should use Action::checkCanExecute instead
*/
protected function runExecuteChecks( PageIdentity $title ): void {
protected function runExecuteChecks(): void {
$permissionStatus = PermissionStatus::newEmpty();
if ( !$this->getContext()->getAuthority()->definitelyCan( 'delete', $title, $permissionStatus ) ) {
if ( !$this->getContext()->getAuthority()->definitelyCan( 'delete', $this->getTitle(), $permissionStatus ) ) {
throw new PermissionsError( 'delete', $permissionStatus );
}
@ -440,23 +455,21 @@ class DeleteAction extends FormlessAction {
/**
* Show deletion log fragments pertaining to the current page
* @param PageReference $title
*/
protected function showLogEntries( PageReference $title ): void {
protected function showLogEntries(): void {
$deleteLogPage = new LogPage( 'delete' );
$outputPage = $this->getContext()->getOutput();
$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
LogEventsList::showLogExtract( $outputPage, 'delete', $title );
LogEventsList::showLogExtract( $outputPage, 'delete', $this->getTitle() );
}
/**
* @param Message $pageTitle
* @param PageReference $backlinkTitle
*/
protected function prepareOutput( Message $pageTitle, PageReference $backlinkTitle ): void {
protected function prepareOutput( Message $pageTitle ): void {
$outputPage = $this->getOutput();
$outputPage->setPageTitle( $pageTitle );
$outputPage->addBacklinkSubtitle( $backlinkTitle );
$outputPage->addBacklinkSubtitle( $this->getTitle() );
$outputPage->setRobotPolicy( 'noindex,nofollow' );
}

View file

@ -20,22 +20,18 @@
namespace MediaWiki\Actions;
use CommentStore;
use DeleteAction;
use ErrorPageError;
use File;
use FileDeleteForm;
use Html;
use IContextSource;
use LocalFile;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\PermissionStatus;
use OldLocalFile;
use Page;
use PermissionsError;
use Title;
use Xml;
/**
* Handle file deletion
@ -45,11 +41,6 @@ use Xml;
class FileDeleteAction extends DeleteAction {
/** @var File */
private $file;
/**
* XXX Can we use $this->getTitle() instead?
* @var Title
*/
private $title;
/** @var string Descriptor for the old version of the image, if applicable */
private $oldImage;
/** @var OldLocalFile|null Corresponding to oldImage, if applicable */
@ -62,11 +53,10 @@ class FileDeleteAction extends DeleteAction {
parent::__construct( $page, $context );
$services = MediaWikiServices::getInstance();
$this->file = $this->getArticle()->getFile();
$this->title = $this->file->getTitle();
$this->oldImage = $this->getRequest()->getText( 'oldimage', '' );
if ( $this->oldImage !== '' ) {
$this->oldFile = $services->getRepoGroup()->getLocalRepo()->newFromArchiveName(
$this->title,
$this->getTitle(),
$this->oldImage
);
}
@ -86,18 +76,19 @@ class FileDeleteAction extends DeleteAction {
private function tempExecute( LocalFile $file ): void {
$context = $this->getContext();
$title = $this->getTitle();
$this->runExecuteChecks( $this->title );
$this->runExecuteChecks();
$outputPage = $context->getOutput();
$this->prepareOutput( $context->msg( 'filedelete', $this->title->getText() ), $this->title );
$this->prepareOutput( $context->msg( 'filedelete', $title->getText() ) );
$request = $context->getRequest();
$checkFile = $this->oldFile ?: $file;
if ( !$checkFile->exists() || !$checkFile->isLocal() ) {
$outputPage->addHTML( $this->prepareMessage( 'filedelete-nofile' ) );
$outputPage->addReturnTo( $this->title );
$outputPage->addReturnTo( $title );
return;
}
@ -105,15 +96,15 @@ class FileDeleteAction extends DeleteAction {
$token = $request->getVal( 'wpEditToken' );
if (
!$request->wasPosted() ||
!$context->getUser()->matchEditToken( $token, [ 'delete', $this->title->getPrefixedText() ] )
!$context->getUser()->matchEditToken( $token, [ 'delete', $title->getPrefixedText() ] )
) {
$this->showForm();
$this->showConfirm();
return;
}
$permissionStatus = PermissionStatus::newEmpty();
if ( !$context->getAuthority()->authorizeWrite(
'delete', $this->title, $permissionStatus
'delete', $title, $permissionStatus
) ) {
throw new PermissionsError( 'delete', $permissionStatus );
}
@ -125,7 +116,7 @@ class FileDeleteAction extends DeleteAction {
$context->getAuthority()->isAllowed( 'suppressrevision' );
$status = FileDeleteForm::doDelete(
$this->title,
$title,
$file,
$this->oldImage,
$reason,
@ -144,12 +135,12 @@ class FileDeleteAction extends DeleteAction {
$outputPage->addHTML( $this->prepareMessage( 'filedelete-success' ) );
// Return to the main page if we just deleted all versions of the
// file, otherwise go back to the description page
$outputPage->addReturnTo( $this->oldImage ? $this->title : Title::newMainPage() );
$outputPage->addReturnTo( $this->oldImage ? $title : Title::newMainPage() );
$this->watchlistManager->setWatch(
$request->getCheck( 'wpWatch' ),
$context->getAuthority(),
$this->title
$title
);
}
}
@ -161,166 +152,12 @@ class FileDeleteAction extends DeleteAction {
/**
* Show the confirmation form
*/
private function showForm() {
private function showConfirm() {
$this->prepareOutputForForm();
$ctx = $this->getContext();
$outputPage = $ctx->getOutput();
$this->showFormWarnings();
$user = $ctx->getUser();
$checkWatch = $this->userOptionsLookup->getBoolOption( $user, 'watchdeletion' ) ||
$this->watchlistManager->isWatched( $user, $this->title );
$fields = [];
$suppressAllowed = $ctx->getAuthority()->isAllowed( 'suppressrevision' );
$dropDownReason = $this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->text();
// Add additional specific reasons for suppress
if ( $suppressAllowed ) {
$dropDownReason .= "\n" . $this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )
->inContentLanguage()->text();
}
$options = Xml::listDropDownOptions(
$dropDownReason,
[ 'other' => $this->getFormMsg( self::MSG_REASON_DROPDOWN_OTHER )->inContentLanguage()->text() ]
);
$options = Xml::listDropDownOptionsOoui( $options );
$fields[] = new \OOUI\FieldLayout(
new \OOUI\DropdownInputWidget( [
'name' => 'wpDeleteReasonList',
'inputId' => 'wpDeleteReasonList',
'tabIndex' => 1,
'infusable' => true,
'value' => '',
'options' => $options,
] ),
[
'label' => $this->getFormMsg( self::MSG_COMMENT )->text(),
'align' => 'top',
]
);
// HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
// (e.g. emojis) count for two each. This limit is overridden in JS to instead count
// Unicode codepoints.
$fields[] = new \OOUI\FieldLayout(
new \OOUI\TextInputWidget( [
'name' => 'wpReason',
'inputId' => 'wpReason',
'tabIndex' => 2,
'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
'infusable' => true,
'value' => $this->getDefaultReason(),
'autofocus' => true,
] ),
[
'label' => $this->getFormMsg( self::MSG_REASON_OTHER )->text(),
'align' => 'top',
]
);
if ( $user->isRegistered() ) {
$fields[] = new \OOUI\FieldLayout(
new \OOUI\CheckboxInputWidget( [
'name' => 'wpWatch',
'inputId' => 'wpWatch',
'tabIndex' => 3,
'selected' => $checkWatch,
] ),
[
'label' => $ctx->msg( 'watchthis' )->text(),
'align' => 'inline',
'infusable' => true,
]
);
}
if ( $suppressAllowed ) {
$fields[] = new \OOUI\FieldLayout(
new \OOUI\CheckboxInputWidget( [
'name' => 'wpSuppress',
'inputId' => 'wpSuppress',
'tabIndex' => 4,
'selected' => false,
] ),
[
'label' => $ctx->msg( 'revdelete-suppress' )->text(),
'align' => 'inline',
'infusable' => true,
]
);
}
$fields[] = new \OOUI\FieldLayout(
new \OOUI\ButtonInputWidget( [
'name' => 'wpConfirmB',
'inputId' => 'wpConfirmB',
'tabIndex' => 5,
'value' => $this->getFormMsg( self::MSG_SUBMIT )->text(),
'label' => $this->getFormMsg( self::MSG_SUBMIT )->text(),
'flags' => [ 'primary', 'destructive' ],
'type' => 'submit',
] ),
[
'align' => 'top',
]
);
$fieldset = new \OOUI\FieldsetLayout( [
'label' => $this->getFormMsg( self::MSG_LEGEND )->text(),
'id' => 'mw-delete-table',
'items' => $fields,
] );
$form = new \OOUI\FormLayout( [
'method' => 'post',
'action' => $this->getFormAction(),
'id' => 'deleteconfirm',
] );
$form->appendContent(
$fieldset,
new \OOUI\HtmlSnippet(
Html::hidden(
'wpEditToken',
$user->getEditToken( [ 'delete', $this->title->getPrefixedText() ] )
)
)
);
$outputPage->addHTML(
new \OOUI\PanelLayout( [
'classes' => [ 'deletepage-wrapper' ],
'expanded' => false,
'padded' => true,
'framed' => true,
'content' => $form,
] )
);
if ( $ctx->getAuthority()->isAllowed( 'editinterface' ) ) {
$link = '';
if ( $suppressAllowed ) {
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS_SUPPRESS )->text(),
[],
[ 'action' => 'edit' ]
);
$link .= $ctx->msg( 'pipe-separator' )->escaped();
}
$link .= $this->linkRenderer->makeKnownLink(
$this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->getTitle(),
$this->getFormMsg( self::MSG_EDIT_REASONS )->text(),
[],
[ 'action' => 'edit' ]
);
$outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
}
$this->showLogEntries( $this->title );
$this->showForm( $this->getDefaultReason() );
$this->showEditReasonsLinks();
$this->showLogEntries();
}
/**
@ -338,7 +175,7 @@ class FileDeleteAction extends DeleteAction {
# 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
return $this->getContext()->msg(
"{$message}-old",
wfEscapeWikiText( $this->title->getText() ),
wfEscapeWikiText( $this->getTitle()->getText() ),
$lang->date( $this->oldFile->getTimestamp(), true ),
$lang->time( $this->oldFile->getTimestamp(), true ),
wfExpandUrl( $this->file->getArchiveUrl( $this->oldImage ), PROTO_CURRENT )
@ -346,7 +183,7 @@ class FileDeleteAction extends DeleteAction {
} else {
return $this->getContext()->msg(
$message,
wfEscapeWikiText( $this->title->getText() )
wfEscapeWikiText( $this->getTitle()->getText() )
)->parseAsBlock();
}
}
@ -362,14 +199,14 @@ class FileDeleteAction extends DeleteAction {
$q['oldimage'] = $this->oldImage;
}
return $this->title->getLocalURL( $q );
return $this->getTitle()->getLocalURL( $q );
}
/**
* @inheritDoc
*/
protected function runExecuteChecks( PageIdentity $title ): void {
parent::runExecuteChecks( $title );
protected function runExecuteChecks(): void {
parent::runExecuteChecks();
if ( $this->getContext()->getConfig()->get( 'UploadMaintenance' ) ) {
throw new ErrorPageError( 'filedelete-maintenance-title', 'filedelete-maintenance' );