Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileDeleteAction
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 10
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getPageTitle
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 tempDelete
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 tempExecute
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
182
 showFormWarnings
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 showConfirm
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 prepareMessage
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 getFormAction
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 checkCanExecute
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getFormMessages
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 * @ingroup Actions
6 */
7
8namespace MediaWiki\Actions;
9
10use MediaWiki\Context\IContextSource;
11use MediaWiki\Exception\ErrorPageError;
12use MediaWiki\Exception\PermissionsError;
13use MediaWiki\FileRepo\File\File;
14use MediaWiki\FileRepo\File\LocalFile;
15use MediaWiki\FileRepo\File\OldLocalFile;
16use MediaWiki\Html\Html;
17use MediaWiki\MainConfigNames;
18use MediaWiki\MediaWikiServices;
19use MediaWiki\Page\Article;
20use MediaWiki\Page\File\FileDeleteForm;
21use MediaWiki\Permissions\PermissionStatus;
22use MediaWiki\Title\Title;
23use MediaWiki\User\User;
24
25/**
26 * Handle file deletion
27 *
28 * @ingroup Actions
29 */
30class FileDeleteAction extends DeleteAction {
31    /** @var File */
32    private $file;
33    /** @var string Descriptor for the old version of the image, if applicable */
34    private $oldImage;
35    /** @var OldLocalFile|null Corresponding to oldImage, if applicable */
36    private $oldFile;
37
38    /**
39     * @inheritDoc
40     */
41    public function __construct( Article $article, IContextSource $context ) {
42        parent::__construct( $article, $context );
43        $services = MediaWikiServices::getInstance();
44        $this->file = $this->getArticle()->getFile();
45        $this->oldImage = $this->getRequest()->getText( 'oldimage', '' );
46        if ( $this->oldImage !== '' ) {
47            $this->oldFile = $services->getRepoGroup()->getLocalRepo()->newFromArchiveName(
48                $this->getTitle(),
49                $this->oldImage
50            );
51        }
52    }
53
54    /** @inheritDoc */
55    protected function getPageTitle() {
56        $title = $this->getTitle();
57        return $this->msg( 'filedelete' )->plaintextParams( $title->getText() );
58    }
59
60    protected function tempDelete() {
61        $file = $this->file;
62        /** @var LocalFile $file */'@phan-var LocalFile $file';
63        $this->tempExecute( $file );
64    }
65
66    private function tempExecute( LocalFile $file ): void {
67        $context = $this->getContext();
68        $title = $this->getTitle();
69        $article = $this->getArticle();
70        $outputPage = $context->getOutput();
71        $request = $context->getRequest();
72
73        $checkFile = $this->oldFile ?: $file;
74        if ( !$checkFile->exists() || !$checkFile->isLocal() ) {
75            $outputPage->addHTML( $this->prepareMessage( 'filedelete-nofile' ) );
76            $outputPage->addReturnTo( $title );
77            return;
78        }
79
80        // Perform the deletion if appropriate
81        $token = $request->getVal( 'wpEditToken' );
82        if (
83            !$request->wasPosted() ||
84            !$context->getUser()->matchEditToken( $token, [ 'delete', $title->getPrefixedText() ] )
85        ) {
86            $this->showConfirm();
87            return;
88        }
89
90        // Check to make sure the page has not been edited while the deletion was being confirmed
91        if ( $article->getRevIdFetched() !== $request->getIntOrNull( 'wpConfirmationRevId' ) ) {
92            $this->showEditedWarning();
93            $this->showConfirm();
94            return;
95        }
96
97        $permissionStatus = PermissionStatus::newEmpty();
98        if ( !$context->getAuthority()->authorizeWrite(
99            'delete', $title, $permissionStatus
100        ) ) {
101            throw new PermissionsError( 'delete', $permissionStatus );
102        }
103
104        $reason = $this->getDeleteReason();
105
106        # Flag to hide all contents of the archived revisions
107        $suppress = $request->getCheck( 'wpSuppress' ) &&
108            $context->getAuthority()->isAllowed( 'suppressrevision' );
109
110        $status = FileDeleteForm::doDelete(
111            $title,
112            $file,
113            $this->oldImage,
114            $reason,
115            $suppress,
116            $context->getUser(),
117            [],
118            $request->getCheck( 'wpDeleteTalk' )
119        );
120
121        if ( !$status->isGood() ) {
122            $outputPage->setPageTitleMsg(
123                $this->msg( 'cannotdelete-title' )->plaintextParams( $title->getPrefixedText() )
124            );
125            $outputPage->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
126            foreach ( $status->getMessages() as $msg ) {
127                $outputPage->addHTML( Html::errorBox(
128                    $context->msg( $msg )->parse()
129                ) );
130            }
131        }
132        if ( $status->isOK() ) {
133            $outputPage->setPageTitleMsg( $context->msg( 'actioncomplete' ) );
134            $outputPage->addHTML( $this->prepareMessage( 'filedelete-success' ) );
135            // Return to the main page if we just deleted all versions of the
136            // file, otherwise go back to the description page
137            $outputPage->addReturnTo( $this->oldImage ? $title : Title::newMainPage() );
138
139            $this->watchlistManager->setWatch(
140                $request->getCheck( 'wpWatch' ),
141                $context->getAuthority(),
142                $title
143            );
144        }
145    }
146
147    protected function showFormWarnings(): void {
148        $this->getOutput()->addHTML( $this->prepareMessage( 'filedelete-intro' ) );
149        $this->showSubpagesWarnings();
150    }
151
152    /**
153     * Show the confirmation form
154     */
155    private function showConfirm() {
156        $this->prepareOutputForForm();
157        $context = $this->getContext();
158        $article = $this->getArticle();
159
160        // oldid is set to the revision id of the page when the page was displayed.
161        // Check to make sure the page has not been edited between loading the page
162        // and clicking the delete link
163        $oldid = $context->getRequest()->getIntOrNull( 'oldid' );
164        if ( $oldid !== null && $oldid !== $article->getRevIdFetched() ) {
165            $this->showEditedWarning();
166        }
167
168        $this->showFormWarnings();
169        $form = $this->getForm();
170        if ( $form->show() ) {
171            $this->onSuccess();
172        }
173        $this->showEditReasonsLinks();
174        $this->showLogEntries();
175    }
176
177    /**
178     * Prepare a message referring to the file being deleted,
179     * showing an appropriate message depending upon whether
180     * it's a current file or an old version
181     *
182     * @param string $message Message base
183     * @return string
184     */
185    private function prepareMessage( string $message ) {
186        if ( $this->oldFile ) {
187            $lang = $this->getContext()->getLanguage();
188            # Message keys used:
189            # 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
190            return $this->getContext()->msg(
191                "{$message}-old",
192                wfEscapeWikiText( $this->getTitle()->getText() ),
193                $lang->date( $this->oldFile->getTimestamp(), true ),
194                $lang->time( $this->oldFile->getTimestamp(), true ),
195                (string)MediaWikiServices::getInstance()->getUrlUtils()->expand(
196                    $this->file->getArchiveUrl( $this->oldImage ),
197                    PROTO_CURRENT
198                )
199            )->parseAsBlock();
200        } else {
201            return $this->getContext()->msg(
202                $message,
203                wfEscapeWikiText( $this->getTitle()->getText() )
204            )->parseAsBlock();
205        }
206    }
207
208    protected function getFormAction(): string {
209        $q = [];
210        $q['action'] = 'delete';
211
212        if ( $this->oldImage ) {
213            $q['oldimage'] = $this->oldImage;
214        }
215
216        return $this->getTitle()->getLocalURL( $q );
217    }
218
219    protected function checkCanExecute( User $user ) {
220        parent::checkCanExecute( $user );
221
222        if ( $this->getContext()->getConfig()->get( MainConfigNames::UploadMaintenance ) ) {
223            throw new ErrorPageError( 'filedelete-maintenance-title', 'filedelete-maintenance' );
224        }
225    }
226
227    /**
228     * TODO Do we need all these messages to be different?
229     * @return string[]
230     */
231    protected function getFormMessages(): array {
232        return [
233            self::MSG_REASON_DROPDOWN => 'filedelete-reason-dropdown',
234            self::MSG_REASON_DROPDOWN_SUPPRESS => 'filedelete-reason-dropdown-suppress',
235            self::MSG_REASON_DROPDOWN_OTHER => 'filedelete-reason-otherlist',
236            self::MSG_COMMENT => 'filedelete-comment',
237            self::MSG_REASON_OTHER => 'filedelete-otherreason',
238            self::MSG_SUBMIT => 'filedelete-submit',
239            self::MSG_LEGEND => 'filedelete-legend',
240            self::MSG_EDIT_REASONS => 'filedelete-edit-reasonlist',
241            self::MSG_EDIT_REASONS_SUPPRESS => 'filedelete-edit-reasonlist-suppress',
242        ];
243    }
244}