Completed
Push — release-2.1 ( 9ac149...d770f4 )
by Mert
07:54
created

Attachments::setResponse()   C

Complexity

Conditions 12
Paths 19

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 37
rs 5.1612
cc 12
eloc 20
nc 19
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains handling attachments.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2015 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
class Attachments
20
{
21
	protected $_msg = 0;
22
	protected $_board = null;
23
	protected $_attachmentUploadDir = false;
24
	protected $_attchDir = '';
25
	protected $_currentAttachmentUploadDir;
26
	protected $_canPostAttachment;
27
	protected $_generalErrors = array();
28
	protected $_initialError;
29
	protected $_attachments = array();
30
	protected $_attachResults = array();
31
	protected $_attachSuccess = array();
32
	protected $_response = array(
33
		'error' => true,
34
		'data' => array(),
35
		'extra' => '',
36
	);
37
	protected $_subActions = array(
38
		'add',
39
		'delete',
40
	);
41
	protected $_sa = false;
42
43
	public function __construct()
44
	{
45
		global $modSettings, $context;
46
47
		$this->_msg = (int) !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0;
48
		$this->_board = (int) !empty($_REQUEST['board']) ? $_REQUEST['board'] : null;
49
50
		$this->_currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir'];
51
52
		if (!is_array($modSettings['attachmentUploadDir']))
53
			$this->_attachmentUploadDir = json_decode($modSettings['attachmentUploadDir'], true);
54
55
		$this->_attchDir = $context['attach_dir'] = $this->_attachmentUploadDir[$modSettings['currentAttachmentUploadDir']];
56
57
		$this->_canPostAttachment = $context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment', $this->_board) || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments', $this->_board)));
58
	}
59
60
	public function call()
61
	{
62
		global $smcFunc, $sourcedir;
63
64
		require_once($sourcedir . '/Subs-Attachments.php');
65
66
		// Guest aren't welcome, sorry.
67
		is_not_guest();
68
69
		$this->_sa = !empty($_REQUEST['sa']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_REQUEST['sa'])) : false;
70
71
		if ($this->_canPostAttachment && $this->_sa && in_array($this->_sa, $this->_subActions))
72
			$this->{$this->_sa}();
73
74
		// Just send a generic message.
75
		else
76
			$this->setResponse(array(
77
				'text' => 'attach_error_title',
78
				'type' => 'error',
79
				'data' => false,
80
			));
81
82
		// Back to the future, oh, to the browser!
83
		$this->sendResponse();
84
	}
85
86
	public function delete()
87
	{
88
		global $sourcedir;
89
90
		// Need this, don't ask why just nod your head.
91
		require_once($sourcedir . '/ManageAttachments.php');
92
93
		$attachID = !empty($_REQUEST['attach']) && is_numeric($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : 0;
94
95
		// Need something to work with.
96
		if (!$attachID || !is_int($attachID) || (!empty($_SESSION['already_attached']) && !isset($_SESSION['already_attached'][$attachID])))
97
			return $this->setResponse(array(
98
				'text' => 'attached_file_deleted_error',
99
				'type' => 'error',
100
				'data' => false,
101
			));
102
103
		// Lets pass some params and see what happens :P
104
		$affectedMessage = removeAttachments(array('id_attach' => $attachID), '', true, true);
105
106
		// Gotta also remove the attachment from the session var.
107
		unset($_SESSION['already_attached'][$attachID]);
108
109
		// $affectedMessage returns an empty array array(0) which php treats as non empty... awesome...
110
		$this->setResponse(array(
111
			'text' => !empty($affectedMessage) ? 'attached_file_deleted' : 'attached_file_deleted_error',
112
			'type' => !empty($affectedMessage) ? 'info' : 'warning',
113
			'data' => $affectedMessage,
114
		));
115
	}
116
117
	public function add()
118
	{
119
		$result = array();
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
120
121
		// You gotta be able to post attachments.
122
		if (!$this->_canPostAttachment)
123
			return $this->setResponse(array(
124
				'text' => 'attached_file_cannot',
125
				'type' => 'error',
126
				'data' => false,
127
			));
128
129
		// Process them at once!
130
		$this->processAttachments();
131
132
		// The attachments was created and moved the the right folder, time to update the DB.
133
		if (!empty($_SESSION['temp_attachments']))
134
			$this->createAtttach();
135
136
		// Set the response.
137
		$this->setResponse();
138
	}
139
140
	/**
141
	 * Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments']
142
	 */
143
	protected function processAttachments()
144
	{
145
		global $context, $modSettings, $smcFunc, $user_info, $txt;
146
147
		if (!isset($_FILES['attachment']['name']))
148
			$_FILES['attachment']['tmp_name'] = array();
149
150
		// If there are attachments, calculate the total size and how many.
151
		$context['attachments']['total_size'] = 0;
152
		$context['attachments']['quantity'] = 0;
153
154
		// If this isn't a new post, check the current attachments.
155 View Code Duplication
		if (isset($_REQUEST['msg']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
		{
157
			$context['attachments']['quantity'] = count($context['current_attachments']);
158
			foreach ($context['current_attachments'] as $attachment)
159
				$context['attachments']['total_size'] += $attachment['size'];
160
		}
161
162
		// A bit of house keeping first.
163 View Code Duplication
		if (!empty($_SESSION['temp_attachments']) && count($_SESSION['temp_attachments']) == 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
			unset($_SESSION['temp_attachments']);
165
166
		// Our infamous SESSION var, we are gonna have soo much fun with it!
167
		if (!isset($_SESSION['temp_attachments']))
168
			$_SESSION['temp_attachments'] = array();
169
170
		// Make sure we're uploading to the right place.
171
		if (!empty($modSettings['automanage_attachments']))
172
			automanage_attachments_check_directory();
173
174
		// Is the attachments folder actually there?
175
		if (!empty($context['dir_creation_error']))
176
			$this->_generalErrors[] = $context['dir_creation_error'];
177
178
		// The current attach folder ha some issues...
179
		elseif (!is_dir($this->_attchDir))
180
		{
181
			$this->_generalErrors[] = 'attach_folder_warning';
182
			log_error(sprintf($txt['attach_folder_admin_warning'], $this->_attchDir), 'critical');
183
		}
184
185
		// If this isn't a new post, check the current attachments.
186
		if (empty($this->_generalErrors) && $this->_msg)
187
		{
188
			$context['attachments'] = array();
189
			$request = $smcFunc['db_query']('', '
190
				SELECT COUNT(*), SUM(size)
191
				FROM {db_prefix}attachments
192
				WHERE id_msg = {int:id_msg}
193
					AND attachment_type = {int:attachment_type}',
194
				array(
195
					'id_msg' => (int) $this->_msg,
196
					'attachment_type' => 0,
197
				)
198
			);
199
			list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
200
			$smcFunc['db_free_result']($request);
201
		}
202
203
		else
204
			$context['attachments'] = array(
205
				'quantity' => 0,
206
				'total_size' => 0,
207
			);
208
209
		// Check for other general errors here.
210
211
		// If we have an initial error, delete the files.
212
		if (!empty($this->_generalErrors))
213
		{
214
			// And delete the files 'cos they ain't going nowhere.
215 View Code Duplication
			foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
217
					unlink($_FILES['attachment']['tmp_name'][$n]);
218
219
			$_FILES['attachment']['tmp_name'] = array();
220
221
			// No point in going further with this.
222
			return;
223
		}
224
225
		// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
226 View Code Duplication
		foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
		{
228
			if ($_FILES['attachment']['name'][$n] == '')
229
				continue;
230
231
			// First, let's first check for PHP upload errors.
232
			$errors = array();
233
			if (!empty($_FILES['attachment']['error'][$n]))
234
			{
235
				if ($_FILES['attachment']['error'][$n] == 2)
236
					$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
237
238
				else
239
					log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
240
241
				// Log this one, because...
242
				if ($_FILES['attachment']['error'][$n] == 6)
243
					log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
244
245
				// Weird, no errors were cached, still fill out a generic one.
246
				if (empty($errors))
247
					$errors[] = 'attach_php_error';
248
			}
249
250
			// Try to move and rename the file before doing any more checks on it.
251
			$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
252
			$destName = $this->_attchDir . '/' . $attachID;
253
254
			// No errors, YAY!
255
			if (empty($errors))
256
			{
257
				$_SESSION['temp_attachments'][$attachID] = array(
258
					'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
259
					'tmp_name' => $destName,
260
					'size' => $_FILES['attachment']['size'][$n],
261
					'type' => $_FILES['attachment']['type'][$n],
262
					'id_folder' => $modSettings['currentAttachmentUploadDir'],
263
					'errors' => array(),
264
				);
265
266
				// Move the file to the attachments folder with a temp name for now.
267
				if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
268
					@chmod($destName, 0644);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
269
270
				// This is madness!!
271
				else
272
				{
273
					// File couldn't be moved.
274
					$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
275
					if (file_exists($_FILES['attachment']['tmp_name'][$n]))
276
						unlink($_FILES['attachment']['tmp_name'][$n]);
277
				}
278
			}
279
280
			// Fill up a nice array with some data from the file and the errors encountered so far.
281
			else
282
			{
283
				$_SESSION['temp_attachments'][$attachID] = array(
284
					'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
285
					'tmp_name' => $destName,
286
					'errors' => $errors,
287
				);
288
289
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
290
					unlink($_FILES['attachment']['tmp_name'][$n]);
291
			}
292
293
			// If there's no errors to this point. We still do need to apply some additional checks before we are finished.
294
			if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
295
				attachmentChecks($attachID);
296
		}
297
298
		// Mod authors, finally a hook to hang an alternate attachment upload system upon
299
		// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
300
		// Populate $_SESSION['temp_attachments'][$attachID] with the following:
301
		//   name => The file name
302
		//   tmp_name => Path to the temp file ($this->_attchDir . '/' . $attachID).
303
		//   size => File size (required).
304
		//   type => MIME type (optional if not available on upload).
305
		//   id_folder => $modSettings['currentAttachmentUploadDir']
306
		//   errors => An array of errors (use the index of the $txt variable for that error).
307
		// Template changes can be done using "integrate_upload_template".
308
		call_integration_hook('integrate_attachment_upload', array());
309
	}
310
311
	protected function createAtttach()
312
	{
313
		global $txt, $user_info, $modSettings;
314
315
		$attachIDs = array();
316
317
		// Create an empty session var to keep track of all the files we attached.
318
		$SESSION['already_attached'] = array();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$SESSION was never initialized. Although not strictly required by PHP, it is generally a good practice to add $SESSION = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
319
320
		foreach ($_SESSION['temp_attachments'] as  $attachID => $attachment)
321
		{
322
			$attachmentOptions = array(
323
				'post' => $this->_msg,
324
				'poster' => $user_info['id'],
325
				'name' => $attachment['name'],
326
				'tmp_name' => $attachment['tmp_name'],
327
				'size' => isset($attachment['size']) ? $attachment['size'] : 0,
328
				'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
329
				'id_folder' => isset($attachment['id_folder']) ? $attachment['id_folder'] : $modSettings['currentAttachmentUploadDir'],
330
				'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
331
				'errors' => $attachment['errors'],
332
			);
333
334
			if (empty($attachment['errors']))
335
				if (createAttachment($attachmentOptions))
336
				{
337
					// Avoid JS getting confused.
338
					$attachmentOptions['attachID'] = $attachmentOptions['id'];
339
					unset($attachmentOptions['id']);
340
					$attachIDs[] = $attachmentOptions['attachID'];
341
					if (!empty($attachmentOptions['thumb']))
342
						$attachIDs[] = $attachmentOptions['thumb'];
343
344
					// Super duper important! pass the already attached files if this was a newly created message.
345
					if (!$this->_msg)
346
						$_SESSION['already_attached'][$attachmentOptions['attachID']] = $attachmentOptions;
347
348
					else
349
						assignAttachments($attachmentOptions, $this->_msg);
350
				}
351
352
			elseif (!empty($attachmentOptions['errors']))
353
			{
354
				// Sort out the errors for display and delete any associated files.
355
				$log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file');
356
357
				foreach ($attachmentOptions['errors'] as $error)
358
				{
359
					$attachmentOptions['errors'][] = vsprintf($txt['attach_warning'], $attachment['name']);
360
361
					if (!is_array($error))
362
					{
363
						$attachmentOptions['errors'][] = $txt[$error];
364 View Code Duplication
						if (in_array($error, $log_these))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
365
							log_error($attachment['name'] . ': ' . $txt[$error], 'critical');
366
					}
367
					else
368
						$attachmentOptions['errors'][] = vsprintf($txt[$error[0]], $error[1]);
369
				}
370
				if (file_exists($attachment['tmp_name']))
371
					unlink($attachment['tmp_name']);
372
			}
373
374
			// Regardless of errors, pass the results.
375
			$this->_attachResults[] = $attachmentOptions;
376
		}
377
378
		// Temp save this on the db.
379
		if (!empty($_SESSION['already_attached']))
380
			$this->_attachSuccess = $_SESSION['already_attached'];
381
382
		unset($_SESSION['temp_attachments']);
383
	}
384
385
	protected function setResponse($data = array())
386
	{
387
		global $txt;
388
389
		loadLanguage('Post');
390
391
		// Some default values in case something is missed or neglected :P
392
		$this->_response = array(
393
			'text' => 'attach_php_error',
394
			'type' => 'error',
395
			'data' => false,
396
		);
397
398
		// Adding needs some VIP treatment.
399
		if ($this->_sa == 'add')
400
		{
401
			// Is there any generic errors? made some sense out of them!
402
			if ($this->_generalErrors)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_generalErrors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
403
				foreach ($this->_generalErrors as $k => $v)
404
					$this->_generalErrors[$k] = (is_array($v) ? vsprintf($txt[$v[0]], $v[1]) : $txt[$v]);
405
406
			// Gotta urlencode the filename.
407
			if ($this->_attachResults)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_attachResults of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
408
				foreach ($this->_attachResults as $k => $v)
409
					$this->_attachResults[$k]['name'] =  urlencode($this->_attachResults[$k]['name']);
410
411
			$this->_response = array(
412
				'files' => $this->_attachResults ? $this->_attachResults : false,
413
				'generalErrors' => $this->_generalErrors ? $this->_generalErrors : false,
414
			);
415
		}
416
417
		// Rest of us mere mortals gets no special treatment...
418
		elseif (!empty($data))
419
			if (!empty($data['text']) && !empty($txt[$data['text']]))
420
				$this->_response['text'] = $txt[$data['text']];
421
	}
422
423
	protected function sendResponse()
424
	{
425
		global $modSettings;
426
427
		ob_end_clean();
428
429
		if (!empty($modSettings['CompressedOutput']))
430
			@ob_start('ob_gzhandler');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
431
432
		else
433
			ob_start();
434
435
		// Set the header.
436
		header('Content-Type: application/json');
437
438
		echo json_encode($this->_response ? $this->_response : array());
439
440
		// Done.
441
		obExit(false);
442
		die;
443
	}
444
}
445
446
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
447