Completed
Branch master (d7c4e6)
by
unknown
29:20
created

FileDeleteForm   C

Complexity

Total Complexity 45

Size/Duplication

Total Lines 383
Duplicated Lines 4.96 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 19
loc 383
rs 5.0166
wmc 45
lcom 1
cbo 20

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C execute() 9 81 14
C doDelete() 0 63 9
B showForm() 10 89 5
A showLogEntries() 0 6 1
A prepareMessage() 0 18 2
A setHeaders() 0 6 1
A isValidOldSpec() 0 5 3
B haveDeletableFile() 0 5 6
A getAction() 0 10 2
A getTimestamp() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FileDeleteForm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileDeleteForm, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * File deletion user interface.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @author Rob Church <[email protected]>
22
 * @ingroup Media
23
 */
24
25
/**
26
 * File deletion user interface
27
 *
28
 * @ingroup Media
29
 */
30
class FileDeleteForm {
31
32
	/**
33
	 * @var Title
34
	 */
35
	private $title = null;
36
37
	/**
38
	 * @var File
39
	 */
40
	private $file = null;
41
42
	/**
43
	 * @var File
44
	 */
45
	private $oldfile = null;
46
	private $oldimage = '';
47
48
	/**
49
	 * Constructor
50
	 *
51
	 * @param File $file File object we're deleting
52
	 */
53
	public function __construct( $file ) {
54
		$this->title = $file->getTitle();
55
		$this->file = $file;
56
	}
57
58
	/**
59
	 * Fulfil the request; shows the form or deletes the file,
60
	 * pending authentication, confirmation, etc.
61
	 */
62
	public function execute() {
63
		global $wgOut, $wgRequest, $wgUser, $wgUploadMaintenance;
64
65
		$permissionErrors = $this->title->getUserPermissionsErrors( 'delete', $wgUser );
66
		if ( count( $permissionErrors ) ) {
67
			throw new PermissionsError( 'delete', $permissionErrors );
68
		}
69
70
		if ( wfReadOnly() ) {
71
			throw new ReadOnlyError;
72
		}
73
74
		if ( $wgUploadMaintenance ) {
75
			throw new ErrorPageError( 'filedelete-maintenance-title', 'filedelete-maintenance' );
76
		}
77
78
		$this->setHeaders();
79
80
		$this->oldimage = $wgRequest->getText( 'oldimage', false );
81
		$token = $wgRequest->getText( 'wpEditToken' );
82
		# Flag to hide all contents of the archived revisions
83
		$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
84
85
		if ( $this->oldimage ) {
86
			$this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName(
87
				$this->title,
88
				$this->oldimage
89
			);
90
		}
91
92
		if ( !self::haveDeletableFile( $this->file, $this->oldfile, $this->oldimage ) ) {
93
			$wgOut->addHTML( $this->prepareMessage( 'filedelete-nofile' ) );
94
			$wgOut->addReturnTo( $this->title );
95
			return;
96
		}
97
98
		// Perform the deletion if appropriate
99
		if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) {
100
			$deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList' );
101
			$deleteReason = $wgRequest->getText( 'wpReason' );
102
103 View Code Duplication
			if ( $deleteReasonList == 'other' ) {
104
				$reason = $deleteReason;
105
			} elseif ( $deleteReason != '' ) {
106
				// Entry from drop down menu + additional comment
107
				$reason = $deleteReasonList . wfMessage( 'colon-separator' )
108
					->inContentLanguage()->text() . $deleteReason;
109
			} else {
110
				$reason = $deleteReasonList;
111
			}
112
113
			$status = self::doDelete(
114
				$this->title,
115
				$this->file,
116
				$this->oldimage,
117
				$reason,
118
				$suppress,
119
				$wgUser
120
			);
121
122
			if ( !$status->isGood() ) {
123
				$wgOut->addHTML( '<h2>' . $this->prepareMessage( 'filedeleteerror-short' ) . "</h2>\n" );
124
				$wgOut->addWikiText( '<div class="error">' .
125
					$status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' )
126
					. '</div>' );
127
			}
128
			if ( $status->ok ) {
129
				$wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
130
				$wgOut->addHTML( $this->prepareMessage( 'filedelete-success' ) );
131
				// Return to the main page if we just deleted all versions of the
132
				// file, otherwise go back to the description page
133
				$wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() );
134
135
				WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'wpWatch' ), $this->title, $wgUser );
136
			}
137
			return;
138
		}
139
140
		$this->showForm();
141
		$this->showLogEntries();
142
	}
143
144
	/**
145
	 * Really delete the file
146
	 *
147
	 * @param Title $title
148
	 * @param File $file
149
	 * @param string $oldimage Archive name
150
	 * @param string $reason Reason of the deletion
151
	 * @param bool $suppress Whether to mark all deleted versions as restricted
152
	 * @param User $user User object performing the request
153
	 * @throws MWException
154
	 * @return bool|Status
155
	 */
156
	public static function doDelete( &$title, &$file, &$oldimage, $reason,
157
		$suppress, User $user = null
158
	) {
159
		if ( $user === null ) {
160
			global $wgUser;
161
			$user = $wgUser;
162
		}
163
164
		if ( $oldimage ) {
165
			$page = null;
166
			$status = $file->deleteOld( $oldimage, $reason, $suppress, $user );
0 ignored issues
show
Bug introduced by
The method deleteOld() does not exist on File. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
167
			if ( $status->ok ) {
168
				// Need to do a log item
169
				$logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text();
170
				if ( trim( $reason ) != '' ) {
171
					$logComment .= wfMessage( 'colon-separator' )
172
						->inContentLanguage()->text() . $reason;
173
				}
174
175
				$logtype = $suppress ? 'suppress' : 'delete';
176
177
				$logEntry = new ManualLogEntry( $logtype, 'delete' );
178
				$logEntry->setPerformer( $user );
179
				$logEntry->setTarget( $title );
180
				$logEntry->setComment( $logComment );
181
				$logid = $logEntry->insert();
182
				$logEntry->publish( $logid );
183
184
				$status->value = $logid;
185
			}
186
		} else {
187
			$status = Status::newFatal( 'cannotdelete',
188
				wfEscapeWikiText( $title->getPrefixedText() )
189
			);
190
			$page = WikiPage::factory( $title );
191
			$dbw = wfGetDB( DB_MASTER );
192
			$dbw->startAtomic( __METHOD__ );
193
			// delete the associated article first
194
			$error = '';
195
			$deleteStatus = $page->doDeleteArticleReal( $reason, $suppress, 0, false, $error, $user );
196
			// doDeleteArticleReal() returns a non-fatal error status if the page
197
			// or revision is missing, so check for isOK() rather than isGood()
198
			if ( $deleteStatus->isOK() ) {
199
				$status = $file->delete( $reason, $suppress, $user );
200
				if ( $status->isOK() ) {
201
					$status->value = $deleteStatus->value; // log id
202
					$dbw->endAtomic( __METHOD__ );
203
				} else {
204
					// Page deleted but file still there? rollback page delete
205
					wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLBFactory() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
206
				}
207
			} else {
208
				// Done; nothing changed
209
				$dbw->endAtomic( __METHOD__ );
210
			}
211
		}
212
213
		if ( $status->isOK() ) {
214
			Hooks::run( 'FileDeleteComplete', [ &$file, &$oldimage, &$page, &$user, &$reason ] );
215
		}
216
217
		return $status;
218
	}
219
220
	/**
221
	 * Show the confirmation form
222
	 */
223
	private function showForm() {
224
		global $wgOut, $wgUser, $wgRequest;
225
226
		if ( $wgUser->isAllowed( 'suppressrevision' ) ) {
227
			$suppress = "<tr id=\"wpDeleteSuppressRow\">
228
					<td></td>
229
					<td class='mw-input'><strong>" .
230
						Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
231
							'wpSuppress', 'wpSuppress', false, [ 'tabindex' => '3' ] ) .
232
					"</strong></td>
233
				</tr>";
234
		} else {
235
			$suppress = '';
236
		}
237
238
		$checkWatch = $wgUser->getBoolOption( 'watchdeletion' ) || $wgUser->isWatched( $this->title );
239
		$form = Xml::openElement( 'form', [ 'method' => 'post', 'action' => $this->getAction(),
240
			'id' => 'mw-img-deleteconfirm' ] ) .
241
			Xml::openElement( 'fieldset' ) .
242
			Xml::element( 'legend', null, wfMessage( 'filedelete-legend' )->text() ) .
243
			Html::hidden( 'wpEditToken', $wgUser->getEditToken( $this->oldimage ) ) .
244
			$this->prepareMessage( 'filedelete-intro' ) .
245
			Xml::openElement( 'table', [ 'id' => 'mw-img-deleteconfirm-table' ] ) .
246
			"<tr>
247
				<td class='mw-label'>" .
248
					Xml::label( wfMessage( 'filedelete-comment' )->text(), 'wpDeleteReasonList' ) .
249
				"</td>
250
				<td class='mw-input'>" .
251
					Xml::listDropDown(
252
						'wpDeleteReasonList',
253
						wfMessage( 'filedelete-reason-dropdown' )->inContentLanguage()->text(),
254
						wfMessage( 'filedelete-reason-otherlist' )->inContentLanguage()->text(),
255
						'',
256
						'wpReasonDropDown',
257
						1
258
					) .
259
				"</td>
260
			</tr>
261
			<tr>
262
				<td class='mw-label'>" .
263
					Xml::label( wfMessage( 'filedelete-otherreason' )->text(), 'wpReason' ) .
264
				"</td>
265
				<td class='mw-input'>" .
266
					Xml::input( 'wpReason', 60, $wgRequest->getText( 'wpReason' ),
267
						[ 'type' => 'text', 'maxlength' => '255', 'tabindex' => '2', 'id' => 'wpReason' ] ) .
268
				"</td>
269
			</tr>
270
			{$suppress}";
271 View Code Duplication
		if ( $wgUser->isLoggedIn() ) {
272
			$form .= "
273
			<tr>
274
				<td></td>
275
				<td class='mw-input'>" .
276
					Xml::checkLabel( wfMessage( 'watchthis' )->text(),
277
						'wpWatch', 'wpWatch', $checkWatch, [ 'tabindex' => '3' ] ) .
278
				"</td>
279
			</tr>";
280
		}
281
		$form .= "
282
			<tr>
283
				<td></td>
284
				<td class='mw-submit'>" .
285
					Xml::submitButton(
286
						wfMessage( 'filedelete-submit' )->text(),
287
						[
288
							'name' => 'mw-filedelete-submit',
289
							'id' => 'mw-filedelete-submit',
290
							'tabindex' => '4'
291
						]
292
					) .
293
				"</td>
294
			</tr>" .
295
			Xml::closeElement( 'table' ) .
296
			Xml::closeElement( 'fieldset' ) .
297
			Xml::closeElement( 'form' );
298
299
			if ( $wgUser->isAllowed( 'editinterface' ) ) {
300
				$title = wfMessage( 'filedelete-reason-dropdown' )->inContentLanguage()->getTitle();
301
				$link = Linker::linkKnown(
302
					$title,
303
					wfMessage( 'filedelete-edit-reasonlist' )->escaped(),
304
					[],
305
					[ 'action' => 'edit' ]
306
				);
307
				$form .= '<p class="mw-filedelete-editreasons">' . $link . '</p>';
308
			}
309
310
		$wgOut->addHTML( $form );
311
	}
312
313
	/**
314
	 * Show deletion log fragments pertaining to the current file
315
	 */
316
	private function showLogEntries() {
317
		global $wgOut;
318
		$deleteLogPage = new LogPage( 'delete' );
319
		$wgOut->addHTML( '<h2>' . $deleteLogPage->getName()->escaped() . "</h2>\n" );
320
		LogEventsList::showLogExtract( $wgOut, 'delete', $this->title );
321
	}
322
323
	/**
324
	 * Prepare a message referring to the file being deleted,
325
	 * showing an appropriate message depending upon whether
326
	 * it's a current file or an old version
327
	 *
328
	 * @param string $message Message base
329
	 * @return string
330
	 */
331
	private function prepareMessage( $message ) {
332
		global $wgLang;
333
		if ( $this->oldimage ) {
334
			# Message keys used:
335
			# 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
336
			return wfMessage(
337
				"{$message}-old",
338
				wfEscapeWikiText( $this->title->getText() ),
339
				$wgLang->date( $this->getTimestamp(), true ),
340
				$wgLang->time( $this->getTimestamp(), true ),
341
				wfExpandUrl( $this->file->getArchiveUrl( $this->oldimage ), PROTO_CURRENT ) )->parseAsBlock();
342
		} else {
343
			return wfMessage(
344
				$message,
345
				wfEscapeWikiText( $this->title->getText() )
346
			)->parseAsBlock();
347
		}
348
	}
349
350
	/**
351
	 * Set headers, titles and other bits
352
	 */
353
	private function setHeaders() {
354
		global $wgOut;
355
		$wgOut->setPageTitle( wfMessage( 'filedelete', $this->title->getText() ) );
356
		$wgOut->setRobotPolicy( 'noindex,nofollow' );
357
		$wgOut->addBacklinkSubtitle( $this->title );
358
	}
359
360
	/**
361
	 * Is the provided `oldimage` value valid?
362
	 *
363
	 * @param string $oldimage
364
	 * @return bool
365
	 */
366
	public static function isValidOldSpec( $oldimage ) {
367
		return strlen( $oldimage ) >= 16
368
			&& strpos( $oldimage, '/' ) === false
369
			&& strpos( $oldimage, '\\' ) === false;
370
	}
371
372
	/**
373
	 * Could we delete the file specified? If an `oldimage`
374
	 * value was provided, does it correspond to an
375
	 * existing, local, old version of this file?
376
	 *
377
	 * @param File $file
378
	 * @param File $oldfile
379
	 * @param File $oldimage
380
	 * @return bool
381
	 */
382
	public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
383
		return $oldimage
384
			? $oldfile && $oldfile->exists() && $oldfile->isLocal()
385
			: $file && $file->exists() && $file->isLocal();
386
	}
387
388
	/**
389
	 * Prepare the form action
390
	 *
391
	 * @return string
392
	 */
393
	private function getAction() {
394
		$q = [];
395
		$q['action'] = 'delete';
396
397
		if ( $this->oldimage ) {
398
			$q['oldimage'] = $this->oldimage;
399
		}
400
401
		return $this->title->getLocalURL( $q );
402
	}
403
404
	/**
405
	 * Extract the timestamp of the old version
406
	 *
407
	 * @return string
408
	 */
409
	private function getTimestamp() {
410
		return $this->oldfile->getTimestamp();
411
	}
412
}
413