Passed
Push — release-2.1 ( 0c2197...207d2d )
by Jeremy
05:47
created

attachmentChecks()   F

Complexity

Conditions 47
Paths > 20000

Size

Total Lines 160
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 47
eloc 83
c 1
b 0
f 0
nop 1
dl 0
loc 160
rs 0
nc 252020

How to fix   Long Method    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 handles the uploading and creation of attachments
5
 * as well as the auto management of the attachment directories.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2018 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * Check if the current directory is still valid or not.
22
 * If not creates the new directory
23
 *
24
 * @return void|bool False if any error occurred
25
 */
26
function automanage_attachments_check_directory()
27
{
28
	global $smcFunc, $boarddir, $modSettings, $context;
29
30
	// Not pretty, but since we don't want folders created for every post. It'll do unless a better solution can be found.
31
	if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'admin')
32
		$doit = true;
33
	elseif (empty($modSettings['automanage_attachments']))
34
		return;
35
	elseif (!isset($_FILES))
36
		return;
37
	elseif (isset($_FILES['attachment']))
38
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
39
			if (!empty($dummy))
40
			{
41
				$doit = true;
42
				break;
43
			}
44
45
	if (!isset($doit))
46
		return;
47
48
	$year = date('Y');
49
	$month = date('m');
50
51
	$rand = md5(mt_rand());
52
	$rand1 = $rand[1];
53
	$rand = $rand[0];
54
55
	if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
56
	{
57
			if (!is_array($modSettings['attachment_basedirectories']))
58
				$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
59
			$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
60
	}
61
	else
62
		$base_dir = 0;
63
64
	if ($modSettings['automanage_attachments'] == 1)
65
	{
66
		if (!isset($modSettings['last_attachments_directory']))
67
			$modSettings['last_attachments_directory'] = array();
68
		if (!is_array($modSettings['last_attachments_directory']))
69
			$modSettings['last_attachments_directory'] = $smcFunc['json_decode']($modSettings['last_attachments_directory'], true);
70
		if (!isset($modSettings['last_attachments_directory'][$base_dir]))
71
			$modSettings['last_attachments_directory'][$base_dir] = 0;
72
	}
73
74
	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
75
	//Just to be sure: I don't want directory separators at the end
76
	$sep = (DIRECTORY_SEPARATOR === '\\') ? '\/' : DIRECTORY_SEPARATOR;
77
	$basedirectory = rtrim($basedirectory, $sep);
78
79
	switch ($modSettings['automanage_attachments'])
80
	{
81
		case 1:
82
			$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
83
			break;
84
		case 2:
85
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
86
			break;
87
		case 3:
88
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
89
			break;
90
		case 4:
91
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
92
			break;
93
		case 5:
94
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
95
			break;
96
		default :
97
			$updir = '';
98
	}
99
100
	if (!is_array($modSettings['attachmentUploadDir']))
101
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
102
	if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
103
		$outputCreation = automanage_attachments_create_directory($updir);
104
	elseif (in_array($updir, $modSettings['attachmentUploadDir']))
105
		$outputCreation = true;
106
107
	if ($outputCreation)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $outputCreation does not seem to be defined for all execution paths leading up to this point.
Loading history...
108
	{
109
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
110
		$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
111
112
		updateSettings(array(
113
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
114
		));
115
	}
116
117
	return $outputCreation;
118
}
119
120
/**
121
 * Creates a directory
122
 *
123
 * @param string $updir The directory to be created
124
 *
125
 * @return bool False on errors
126
 */
127
function automanage_attachments_create_directory($updir)
128
{
129
	global $smcFunc, $modSettings, $context, $boarddir;
130
131
	$tree = get_directory_tree_elements($updir);
132
	$count = count($tree);
133
134
	$directory = attachments_init_dir($tree, $count);
135
	if ($directory === false)
136
	{
137
		// Maybe it's just the folder name
138
		$tree = get_directory_tree_elements($boarddir . DIRECTORY_SEPARATOR . $updir);
139
		$count = count($tree);
140
141
		$directory = attachments_init_dir($tree, $count);
142
		if ($directory === false)
143
			return false;
144
	}
145
146
	$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
147
148
	while (!@is_dir($directory) || $count != -1)
149
	{
150
		if (!@is_dir($directory))
151
		{
152
			if (!@mkdir($directory, 0755))
153
			{
154
				$context['dir_creation_error'] = 'attachments_no_create';
155
				return false;
156
			}
157
		}
158
159
		$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
160
		$count--;
161
	}
162
163
	// Check if the dir is writable.
164
	if (!smf_chmod($directory))
165
	{
166
		$context['dir_creation_error'] = 'attachments_no_write';
167
		return false;
168
	}
169
170
	// Everything seems fine...let's create the .htaccess
171
	if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htaccess'))
172
		secureDirectory($updir, true);
173
174
	$sep = (DIRECTORY_SEPARATOR === '\\') ? '\/' : DIRECTORY_SEPARATOR;
175
	$updir = rtrim($updir, $sep);
176
177
	// Only update if it's a new directory
178
	if (!in_array($updir, $modSettings['attachmentUploadDir']))
179
	{
180
		$modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) + 1;
181
		$modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
182
183
		updateSettings(array(
184
			'attachmentUploadDir' => $smcFunc['json_encode']($modSettings['attachmentUploadDir']),
185
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
186
		), true);
187
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
188
	}
189
190
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
191
	return true;
192
}
193
194
/**
195
 * Called when a directory space limit is reached.
196
 * Creates a new directory and increments the directory suffix number.
197
 *
198
 * @return void|bool False on errors, true if successful, nothing if auto-management of attachments is disabled
199
 */
200
function automanage_attachments_by_space()
201
{
202
	global $smcFunc, $modSettings, $boarddir;
203
204
	if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
205
		return;
206
207
	$basedirectory = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : $boarddir;
208
	// Just to be sure: I don't want directory separators at the end
209
	$sep = (DIRECTORY_SEPARATOR === '\\') ? '\/' : DIRECTORY_SEPARATOR;
210
	$basedirectory = rtrim($basedirectory, $sep);
211
212
	// Get the current base directory
213
	if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
214
	{
215
		$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
216
		$base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
217
	}
218
	else
219
		$base_dir = 0;
220
221
	// Get the last attachment directory for that base directory
222
	if (empty($modSettings['last_attachments_directory'][$base_dir]))
223
		$modSettings['last_attachments_directory'][$base_dir] = 0;
224
	// And increment it.
225
	$modSettings['last_attachments_directory'][$base_dir]++;
226
227
	$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
228
	if (automanage_attachments_create_directory($updir))
229
	{
230
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
231
		updateSettings(array(
232
			'last_attachments_directory' => $smcFunc['json_encode']($modSettings['last_attachments_directory']),
233
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
234
		));
235
		$modSettings['last_attachments_directory'] = $smcFunc['json_decode']($modSettings['last_attachments_directory'], true);
236
237
		return true;
238
	}
239
	else
240
		return false;
241
}
242
243
/**
244
 * Split a path into a list of all directories and subdirectories
245
 *
246
 * @param string $directory A path
247
 *
248
 * @return array|bool An array of all the directories and subdirectories or false on failure
249
 */
250
function get_directory_tree_elements($directory)
251
{
252
	/*
253
		In Windows server both \ and / can be used as directory separators in paths
254
		In Linux (and presumably *nix) servers \ can be part of the name
255
		So for this reasons:
256
			* in Windows we need to explode for both \ and /
257
			* while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
258
	*/
259
	if (DIRECTORY_SEPARATOR === '\\')
260
		$tree = preg_split('#[\\\/]#', $directory);
261
	else
262
	{
263
		if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR)
264
			return false;
265
266
		$tree = explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR));
267
	}
268
	return $tree;
269
}
270
271
/**
272
 * Return the first part of a path (i.e. c:\ or / + the first directory), used by automanage_attachments_create_directory
273
 *
274
 * @param array $tree An array
275
 * @param int $count The number of elements in $tree
276
 *
277
 * @return string|bool The first part of the path or false on error
278
 */
279
function attachments_init_dir(&$tree, &$count)
280
{
281
	$directory = '';
282
	// If on Windows servers the first part of the path is the drive (e.g. "C:")
283
	if (DIRECTORY_SEPARATOR === '\\')
284
	{
285
		 //Better be sure that the first part of the path is actually a drive letter...
286
		 //...even if, I should check this in the admin page...isn't it?
287
		 //...NHAAA Let's leave space for users' complains! :P
288
		if (preg_match('/^[a-z]:$/i', $tree[0]))
289
			$directory = array_shift($tree);
290
		else
291
			return false;
292
293
		$count--;
294
	}
295
	return $directory;
296
}
297
298
/**
299
 * Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments']
300
 */
301
function processAttachments()
302
{
303
	global $context, $modSettings, $smcFunc, $txt, $user_info;
304
305
	// Make sure we're uploading to the right place.
306
	if (!empty($modSettings['automanage_attachments']))
307
		automanage_attachments_check_directory();
308
309
	if (!is_array($modSettings['attachmentUploadDir']))
310
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
311
312
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
313
314
	// Is the attachments folder actualy there?
315
	if (!empty($context['dir_creation_error']))
316
		$initial_error = $context['dir_creation_error'];
317
	elseif (!is_dir($context['attach_dir']))
318
	{
319
		$initial_error = 'attach_folder_warning';
320
		log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
321
	}
322
323
	if (!isset($initial_error) && !isset($context['attachments']))
324
	{
325
		// If this isn't a new post, check the current attachments.
326
		if (isset($_REQUEST['msg']))
327
		{
328
			$request = $smcFunc['db_query']('', '
329
				SELECT COUNT(*), SUM(size)
330
				FROM {db_prefix}attachments
331
				WHERE id_msg = {int:id_msg}
332
					AND attachment_type = {int:attachment_type}',
333
				array(
334
					'id_msg' => (int) $_REQUEST['msg'],
335
					'attachment_type' => 0,
336
				)
337
			);
338
			list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
339
			$smcFunc['db_free_result']($request);
340
		}
341
		else
342
			$context['attachments'] = array(
343
				'quantity' => 0,
344
				'total_size' => 0,
345
			);
346
	}
347
348
	// Hmm. There are still files in session.
349
	$ignore_temp = false;
350
	if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1)
351
	{
352
		// Let's try to keep them. But...
353
		$ignore_temp = true;
354
		// If new files are being added. We can't ignore those
355
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
356
			if (!empty($dummy))
357
			{
358
				$ignore_temp = false;
359
				break;
360
			}
361
362
		// Need to make space for the new files. So, bye bye.
363
		if (!$ignore_temp)
364
		{
365
			foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
366
				if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
367
					unlink($attachment['tmp_name']);
368
369
			$context['we_are_history'] = $txt['error_temp_attachments_flushed'];
370
			$_SESSION['temp_attachments'] = array();
371
		}
372
	}
373
374
	if (!isset($_FILES['attachment']['name']))
375
		$_FILES['attachment']['tmp_name'] = array();
376
377
	if (!isset($_SESSION['temp_attachments']))
378
		$_SESSION['temp_attachments'] = array();
379
380
	// Remember where we are at. If it's anywhere at all.
381
	if (!$ignore_temp)
382
		$_SESSION['temp_attachments']['post'] = array(
383
			'msg' => !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0,
384
			'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
385
			'topic' => !empty($topic) ? $topic : 0,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $topic seems to never exist and therefore empty should always be true.
Loading history...
386
			'board' => !empty($board) ? $board : 0,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $board seems to never exist and therefore empty should always be true.
Loading history...
387
		);
388
389
	// If we have an initial error, lets just display it.
390
	if (!empty($initial_error))
391
	{
392
		$_SESSION['temp_attachments']['initial_error'] = $initial_error;
393
394
		// And delete the files 'cos they ain't going nowhere.
395
		foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
396
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
397
				unlink($_FILES['attachment']['tmp_name'][$n]);
398
399
		$_FILES['attachment']['tmp_name'] = array();
400
	}
401
402
	// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
403
	foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
404
	{
405
		if ($_FILES['attachment']['name'][$n] == '')
406
			continue;
407
408
		// First, let's first check for PHP upload errors.
409
		$errors = array();
410
		if (!empty($_FILES['attachment']['error'][$n]))
411
		{
412
			if ($_FILES['attachment']['error'][$n] == 2)
413
				$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
414
			elseif ($_FILES['attachment']['error'][$n] == 6)
415
				log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
416
			else
417
				log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
418
			if (empty($errors))
419
				$errors[] = 'attach_php_error';
420
		}
421
422
		// Try to move and rename the file before doing any more checks on it.
423
		$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
424
		$destName = $context['attach_dir'] . '/' . $attachID;
425
		if (empty($errors))
426
		{
427
			// The reported MIME type of the attachment might not be reliable.
428
			// Fortunately, PHP 5.3+ lets us easily verify the real MIME type.
429
			if (function_exists('mime_content_type'))
430
				$_FILES['attachment']['type'][$n] = mime_content_type($_FILES['attachment']['tmp_name'][$n]);
431
432
			$_SESSION['temp_attachments'][$attachID] = array(
433
				'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
434
				'tmp_name' => $destName,
435
				'size' => $_FILES['attachment']['size'][$n],
436
				'type' => $_FILES['attachment']['type'][$n],
437
				'id_folder' => $modSettings['currentAttachmentUploadDir'],
438
				'errors' => array(),
439
			);
440
441
			// Move the file to the attachments folder with a temp name for now.
442
			if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
443
				smf_chmod($destName, 0644);
444
			else
445
			{
446
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
447
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
448
					unlink($_FILES['attachment']['tmp_name'][$n]);
449
			}
450
		}
451
		else
452
		{
453
			$_SESSION['temp_attachments'][$attachID] = array(
454
				'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
455
				'tmp_name' => $destName,
456
				'errors' => $errors,
457
			);
458
459
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
460
				unlink($_FILES['attachment']['tmp_name'][$n]);
461
		}
462
		// If there's no errors to this point. We still do need to apply some additional checks before we are finished.
463
		if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
464
			attachmentChecks($attachID);
0 ignored issues
show
Bug introduced by
$attachID of type string is incompatible with the type integer expected by parameter $attachID of attachmentChecks(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

464
			attachmentChecks(/** @scrutinizer ignore-type */ $attachID);
Loading history...
465
	}
466
	// Mod authors, finally a hook to hang an alternate attachment upload system upon
467
	// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
468
	// Populate $_SESSION['temp_attachments'][$attachID] with the following:
469
	//   name => The file name
470
	//   tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
471
	//   size => File size (required).
472
	//   type => MIME type (optional if not available on upload).
473
	//   id_folder => $modSettings['currentAttachmentUploadDir']
474
	//   errors => An array of errors (use the index of the $txt variable for that error).
475
	// Template changes can be done using "integrate_upload_template".
476
	call_integration_hook('integrate_attachment_upload', array());
477
}
478
479
/**
480
 * Performs various checks on an uploaded file.
481
 * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
482
 *
483
 * @param int $attachID The ID of the attachment
484
 * @return bool Whether the attachment is OK
485
 */
486
function attachmentChecks($attachID)
487
{
488
	global $modSettings, $context, $sourcedir, $smcFunc;
489
490
	// No data or missing data .... Not necessarily needed, but in case a mod author missed something.
491
	if (empty($_SESSION['temp_attachments'][$attachID]))
492
		$error = '$_SESSION[\'temp_attachments\'][$attachID]';
493
494
	elseif (empty($attachID))
495
		$error = '$attachID';
496
497
	elseif (empty($context['attachments']))
498
		$error = '$context[\'attachments\']';
499
500
	elseif (empty($context['attach_dir']))
501
		$error = '$context[\'attach_dir\']';
502
503
	// Let's get their attention.
504
	if (!empty($error))
505
		fatal_lang_error('attach_check_nag', 'debug', array($error));
506
507
	// Just in case this slipped by the first checks, we stop it here and now
508
	if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
509
	{
510
		$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
511
		return false;
512
	}
513
514
	// First, the dreaded security check. Sorry folks, but this shouldn't be avoided.
515
	$size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
516
	if (isset($context['valid_image_types'][$size[2]]))
517
	{
518
		require_once($sourcedir . '/Subs-Graphics.php');
519
		if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
520
		{
521
			// It's bad. Last chance, maybe we can re-encode it?
522
			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
523
			{
524
				// Nothing to do: not allowed or not successful re-encoding it.
525
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
526
				return false;
527
			}
528
			// Success! However, successes usually come for a price:
529
			// we might get a new format for our image...
530
			$old_format = $size[2];
531
			$size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
532
			if (!(empty($size)) && ($size[2] != $old_format))
533
			{
534
				if (isset($context['valid_image_types'][$size[2]]))
535
					$_SESSION['temp_attachments'][$attachID]['type'] = 'image/' . $context['valid_image_types'][$size[2]];
536
			}
537
		}
538
	}
539
540
	// Is there room for this sucker?
541
	if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
542
	{
543
		// Check the folder size and count. If it hasn't been done already.
544
		if (empty($context['dir_size']) || empty($context['dir_files']))
545
		{
546
			$request = $smcFunc['db_query']('', '
547
				SELECT COUNT(*), SUM(size)
548
				FROM {db_prefix}attachments
549
				WHERE id_folder = {int:folder_id}
550
					AND attachment_type != {int:type}',
551
				array(
552
					'folder_id' => $modSettings['currentAttachmentUploadDir'],
553
					'type' => 1,
554
				)
555
			);
556
			list ($context['dir_files'], $context['dir_size']) = $smcFunc['db_fetch_row']($request);
557
			$smcFunc['db_free_result']($request);
558
		}
559
		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
560
		$context['dir_files']++;
561
562
		// Are we about to run out of room? Let's notify the admin then.
563
		if (empty($modSettings['attachment_full_notified']) && !empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (empty($modSettings['att...entDirFileLimit'] > 500, Probably Intended Meaning: empty($modSettings['atta...ntDirFileLimit'] > 500)
Loading history...
564
			|| (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
565
		{
566
			require_once($sourcedir . '/Subs-Admin.php');
567
			emailAdmins('admin_attachments_full');
568
			updateSettings(array('attachment_full_notified' => 1));
569
		}
570
571
		// // No room left.... What to do now???
572
		if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] > $modSettings['attachmentDirFileLimit']
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($modSettings['a...ntDirSizeLimit'] * 1024, Probably Intended Meaning: ! empty($modSettings['at...tDirSizeLimit'] * 1024)
Loading history...
573
			|| (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
574
		{
575
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
576
			{
577
				// Move it to the new folder if we can.
578
				if (automanage_attachments_by_space())
579
				{
580
					rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
581
					$_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
582
					$_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
583
					$context['dir_size'] = 0;
584
					$context['dir_files'] = 0;
585
				}
586
				// Or, let the user know that it ain't gonna happen.
587
				else
588
				{
589
					if (isset($context['dir_creation_error']))
590
						$_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
591
					else
592
						$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
593
				}
594
			}
595
			else
596
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
597
		}
598
	}
599
600
	// Is the file too big?
601
	$context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
602
	if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
603
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
604
605
	// Check the total upload size for this post...
606
	if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
607
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attach_max_total_file_size', array(comma_format($modSettings['attachmentPostLimit'], 0), comma_format($modSettings['attachmentPostLimit'] - (($context['attachments']['total_size'] - $_SESSION['temp_attachments'][$attachID]['size']) / 1024), 0)));
608
609
	// Have we reached the maximum number of files we are allowed?
610
	$context['attachments']['quantity']++;
611
612
	// Set a max limit if none exists
613
	if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
614
		$modSettings['attachmentNumPerPostLimit'] = 50;
615
616
	if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
617
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
618
619
	// File extension check
620
	if (!empty($modSettings['attachmentCheckExtensions']))
621
	{
622
		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
623
		foreach ($allowed as $k => $dummy)
624
			$allowed[$k] = trim($dummy);
625
626
		if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
627
		{
628
			$allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
629
			$_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
630
		}
631
	}
632
633
	// Undo the math if there's an error
634
	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
635
	{
636
		if (isset($context['dir_size']))
637
			$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
638
		if (isset($context['dir_files']))
639
			$context['dir_files']--;
640
		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
641
		$context['attachments']['quantity']--;
642
		return false;
643
	}
644
645
	return true;
646
}
647
648
/**
649
 * Create an attachment, with the given array of parameters.
650
 * - Adds any additional or missing parameters to $attachmentOptions.
651
 * - Renames the temporary file.
652
 * - Creates a thumbnail if the file is an image and the option enabled.
653
 *
654
 * @param array $attachmentOptions An array of attachment options
655
 * @return bool Whether the attachment was created successfully
656
 */
657
function createAttachment(&$attachmentOptions)
658
{
659
	global $modSettings, $sourcedir, $smcFunc, $context, $txt;
660
661
	require_once($sourcedir . '/Subs-Graphics.php');
662
663
	// If this is an image we need to set a few additional parameters.
664
	$size = @getimagesize($attachmentOptions['tmp_name']);
665
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
666
667
	// If it's an image get the mime type right.
668
	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
669
	{
670
		// Got a proper mime type?
671
		if (!empty($size['mime']))
672
			$attachmentOptions['mime_type'] = $size['mime'];
673
674
		// Otherwise a valid one?
675
		elseif (isset($context['valid_image_types'][$size[2]]))
676
			$attachmentOptions['mime_type'] = 'image/' . $context['valid_image_types'][$size[2]];
677
	}
678
679
	// It is possible we might have a MIME type that isn't actually an image but still have a size.
680
	// For example, Shockwave files will be able to return size but be 'application/shockwave' or similar.
681
	if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0)
682
	{
683
		$attachmentOptions['width'] = 0;
684
		$attachmentOptions['height'] = 0;
685
	}
686
687
	// Get the hash if no hash has been given yet.
688
	if (empty($attachmentOptions['file_hash']))
689
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $attachment_id of getAttachmentFilename(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

689
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], /** @scrutinizer ignore-type */ false, null, true);
Loading history...
690
691
	// Assuming no-one set the extension let's take a look at it.
692
	if (empty($attachmentOptions['fileext']))
693
	{
694
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
695
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
696
			$attachmentOptions['fileext'] = '';
697
	}
698
699
	// Last chance to change stuff!
700
	call_integration_hook('integrate_createAttachment', array(&$attachmentOptions));
701
702
	// Make sure the folder is valid...
703
	$tmp = is_array($modSettings['attachmentUploadDir']) ? $modSettings['attachmentUploadDir'] : $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
704
	$folders = array_keys($tmp);
705
	if (empty($attachmentOptions['id_folder']) || !in_array($attachmentOptions['id_folder'], $folders))
706
		$attachmentOptions['id_folder'] = $modSettings['currentAttachmentUploadDir'];
707
708
	$attachmentOptions['id'] = $smcFunc['db_insert']('',
709
		'{db_prefix}attachments',
710
		array(
711
			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
712
			'size' => 'int', 'width' => 'int', 'height' => 'int',
713
			'mime_type' => 'string-20', 'approved' => 'int',
714
		),
715
		array(
716
			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
717
			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
718
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
719
		),
720
		array('id_attach'),
721
		1
722
	);
723
724
	// Attachment couldn't be created.
725
	if (empty($attachmentOptions['id']))
726
	{
727
		loadLanguage('Errors');
728
		log_error($txt['attachment_not_created'], 'general');
729
		return false;
730
	}
731
732
	// Now that we have the attach id, let's rename this sucker and finish up.
733
	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
734
	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
735
736
	// If it's not approved then add to the approval queue.
737
	if (!$attachmentOptions['approved'])
738
		$smcFunc['db_insert']('',
739
			'{db_prefix}approval_queue',
740
			array(
741
				'id_attach' => 'int', 'id_msg' => 'int',
742
			),
743
			array(
744
				$attachmentOptions['id'], (int) $attachmentOptions['post'],
745
			),
746
			array()
747
		);
748
749
	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
750
		return true;
751
752
	// Like thumbnails, do we?
753
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
754
	{
755
		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
756
		{
757
			// Figure out how big we actually made it.
758
			$size = @getimagesize($attachmentOptions['destination'] . '_thumb');
759
			list ($thumb_width, $thumb_height) = $size;
760
761
			if (!empty($size['mime']))
762
				$thumb_mime = $size['mime'];
763
			elseif (isset($context['valid_image_types'][$size[2]]))
764
				$thumb_mime = 'image/' . $context['valid_image_types'][$size[2]];
765
			// Lord only knows how this happened...
766
			else
767
				$thumb_mime = '';
768
769
			$thumb_filename = $attachmentOptions['name'] . '_thumb';
770
			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
771
			$thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
772
			$thumb_path = $attachmentOptions['destination'] . '_thumb';
773
774
			// We should check the file size and count here since thumbs are added to the existing totals.
775
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($modSettings['a...tachmentDirFileLimit']), Probably Intended Meaning: ! empty($modSettings['au...achmentDirFileLimit']))
Loading history...
776
			{
777
				$context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
778
				$context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
779
780
				// If the folder is full, try to create a new one and move the thumb to it.
781
				if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
782
				{
783
					if (automanage_attachments_by_space())
784
					{
785
						rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
786
						$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
787
						$context['dir_size'] = 0;
788
						$context['dir_files'] = 0;
789
					}
790
				}
791
			}
792
			// If a new folder has been already created. Gotta move this thumb there then.
793
			if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
794
			{
795
				rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
796
				$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
797
			}
798
799
			// To the database we go!
800
			$attachmentOptions['thumb'] = $smcFunc['db_insert']('',
801
				'{db_prefix}attachments',
802
				array(
803
					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
804
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
805
				),
806
				array(
807
					$modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
808
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
809
				),
810
				array('id_attach'),
811
				1
812
			);
813
814
			if (!empty($attachmentOptions['thumb']))
815
			{
816
				$smcFunc['db_query']('', '
817
					UPDATE {db_prefix}attachments
818
					SET id_thumb = {int:id_thumb}
819
					WHERE id_attach = {int:id_attach}',
820
					array(
821
						'id_thumb' => $attachmentOptions['thumb'],
822
						'id_attach' => $attachmentOptions['id'],
823
					)
824
				);
825
826
				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
827
			}
828
		}
829
	}
830
831
	return true;
832
}
833
834
/**
835
 * Assigns the given attachments to the given message ID.
836
 *
837
 * @param $attachIDs array of attachment IDs to assign.
838
 * @param $msgID integer the message ID.
839
 *
840
 * @return boolean false on error or missing params.
841
 */
842
function assignAttachments($attachIDs = array(), $msgID = 0)
843
{
844
	global $smcFunc;
845
846
	// Oh, come on!
847
	if (empty($attachIDs) || empty($msgID))
848
		return false;
849
850
	// "I see what is right and approve, but I do what is wrong."
851
	call_integration_hook('integrate_assign_attachments', array(&$attachIDs, &$msgID));
852
853
	// One last check
854
	if (empty($attachIDs))
855
		return false;
856
857
	// Perform.
858
	$smcFunc['db_query']('', '
859
		UPDATE {db_prefix}attachments
860
		SET id_msg = {int:id_msg}
861
		WHERE id_attach IN ({array_int:attach_ids})',
862
		array(
863
			'id_msg' => $msgID,
864
			'attach_ids' => $attachIDs,
865
		)
866
	);
867
868
	return true;
869
}
870
871
/**
872
 * Gets an attach ID and tries to load all its info.
873
 *
874
 * @param int $attachID the attachment ID to load info from.
875
 *
876
 * @return mixed If succesful, it will return an array of loaded data. String, most likely a $txt key if there was some error.
877
 */
878
function parseAttachBBC($attachID = 0)
879
{
880
	global $board, $modSettings, $context, $scripturl, $smcFunc;
881
882
	// Meh...
883
	if (empty($attachID))
884
		return 'attachments_no_data_loaded';
885
886
	// Make it easy.
887
	$msgID = !empty($_REQUEST['msg']) ? (int) $_REQUEST['msg'] : 0;
888
889
	// Perhaps someone else wants to do the honors? Yes, this also includes dealing with previews ;)
890
	$externalParse = call_integration_hook('integrate_pre_parseAttachBBC', array($attachID, $msgID));
891
892
	// "I am innocent of the blood of this just person: see ye to it."
893
	if (!empty($externalParse) && (is_string($externalParse) || is_array($externalParse)))
894
		return $externalParse;
895
896
	//Are attachments enable?
897
	if (empty($modSettings['attachmentEnable']))
898
		return 'attachments_not_enable';
899
900
	// Previewing much? no msg ID has been set yet.
901
	if (!empty($context['preview_message']))
902
	{
903
		$allAttachments = getAttachsByMsg(0);
904
905
		if (empty($allAttachments[0][$attachID]))
906
			return 'attachments_no_data_loaded';
907
908
		$attachLoaded = loadAttachmentContext(0, $allAttachments);
909
910
		$attachContext = $attachLoaded[$attachID];
911
912
		// Fix the url to point out to showAvatar().
913
		$attachContext['href'] = $scripturl . '?action=dlattach;attach=' . $attachID . ';type=preview';
914
915
		$attachContext['link'] = '<a href="' . $scripturl . '?action=dlattach;attach=' . $attachID . ';type=preview' . (empty($attachContext['is_image']) ? ';file' : '') . '">' . $smcFunc['htmlspecialchars']($attachContext['name']) . '</a>';
916
917
		// Fix the thumbnail too, if the image has one.
918
		if (!empty($attachContext['thumbnail']) && !empty($attachContext['thumbnail']['has_thumb']))
919
			$attachContext['thumbnail']['href'] = $scripturl . '?action=dlattach;attach=' . $attachContext['thumbnail']['id'] . ';image;type=preview';
920
921
		return $attachContext;
922
	}
923
924
	// There is always the chance someone else has already done our dirty work...
925
	// If so, all pertinent checks were already done. Hopefully...
926
	if (!empty($context['current_attachments']) && !empty($context['current_attachments'][$attachID]))
927
		return $context['current_attachments'][$attachID];
928
929
	// If we are lucky enough to be in $board's scope then check it!
930
	if (!empty($board) && !allowedTo('view_attachments', $board))
931
		return 'attachments_not_allowed_to_see';
932
933
	// Get the message info associated with this particular attach ID.
934
	$attachInfo = getAttachMsgInfo($attachID);
935
936
	// There is always the chance this attachment no longer exists or isn't associated to a message anymore...
937
	if (empty($attachInfo) || empty($attachInfo['msg']))
938
		return 'attachments_no_msg_associated';
939
940
	// Hold it! got the info now check if you can see this attachment.
941
	if (!allowedTo('view_attachments', $attachInfo['board']))
942
		return 'attachments_not_allowed_to_see';
943
944
	$allAttachments = getAttachsByMsg($attachInfo['msg']);
945
	$attachContext = $allAttachments[$attachInfo['msg']][$attachID];
946
947
	// No point in keep going further.
948
	if (!allowedTo('view_attachments', $attachContext['board']))
949
		return 'attachments_not_allowed_to_see';
950
951
	// Load this particular attach's context.
952
	if (!empty($attachContext))
953
		$attachLoaded = loadAttachmentContext($attachContext['id_msg'], $allAttachments);
954
955
	// One last check, you know, gotta be paranoid...
956
	else
957
		return 'attachments_no_data_loaded';
958
959
	// This is the last "if" I promise!
960
	if (empty($attachLoaded))
961
		return 'attachments_no_data_loaded';
962
963
	else
964
		$attachContext = $attachLoaded[$attachID];
965
966
	// You may or may not want to show this under the post.
967
	if (!empty($modSettings['dont_show_attach_under_post']) && !isset($context['show_attach_under_post'][$attachID]))
968
		$context['show_attach_under_post'][$attachID] = $attachID;
969
970
	// Last minute changes?
971
	call_integration_hook('integrate_post_parseAttachBBC', array(&$attachContext));
972
973
	// Don't do any logic with the loaded data, leave it to whoever called this function.
974
	return $attachContext;
975
}
976
977
/**
978
 * Gets raw info directly from the attachments table.
979
 *
980
 * @param array $attachIDs An array of attachments IDs.
981
 *
982
 * @return array.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array. at position 0 could not be parsed: Unknown type name 'array.' at position 0 in array..
Loading history...
983
 */
984
function getRawAttachInfo($attachIDs)
985
{
986
	global $smcFunc, $modSettings;
987
988
	if (empty($attachIDs))
989
		return array();
990
991
	$return = array();
992
993
	$request = $smcFunc['db_query']('', '
994
		SELECT a.id_attach, a.id_msg, a.id_member, a.size, a.mime_type, a.id_folder, a.filename' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
995
				COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
996
		FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
997
				LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
998
		WHERE a.id_attach IN ({array_int:attach_ids})
999
		LIMIT 1',
1000
		array(
1001
			'attach_ids' => (array) $attachIDs,
1002
		)
1003
	);
1004
1005
	if ($smcFunc['db_num_rows']($request) != 1)
1006
		return array();
1007
1008
	while ($row = $smcFunc['db_fetch_assoc']($request))
1009
		$return[$row['id_attach']] = array(
1010
			'name' => $smcFunc['htmlspecialchars']($row['filename']),
1011
			'size' => $row['size'],
1012
			'attachID' => $row['id_attach'],
1013
			'unchecked' => false,
1014
			'approved' => 1,
1015
			'mime_type' => $row['mime_type'],
1016
			'thumb' => $row['id_thumb'],
1017
		);
1018
	$smcFunc['db_free_result']($request);
1019
1020
	return $return;
1021
}
1022
1023
/**
1024
 * Gets all needed message data associated with an attach ID
1025
 *
1026
 * @param int $attachID the attachment ID to load info from.
1027
 *
1028
 * @return array.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array. at position 0 could not be parsed: Unknown type name 'array.' at position 0 in array..
Loading history...
1029
 */
1030
function getAttachMsgInfo($attachID)
1031
{
1032
	global $smcFunc;
1033
1034
	if (empty($attachID))
1035
		return array();
1036
1037
	$request = $smcFunc['db_query']('', '
1038
		SELECT a.id_msg AS msg, m.id_topic AS topic, m.id_board AS board
1039
		FROM {db_prefix}attachments AS a
1040
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1041
		WHERE id_attach = {int:id_attach}
1042
		LIMIT 1',
1043
		array(
1044
			'id_attach' => (int) $attachID,
1045
		)
1046
	);
1047
1048
	if ($smcFunc['db_num_rows']($request) != 1)
1049
		return array();
1050
1051
	$row = $smcFunc['db_fetch_assoc']($request);
1052
	$smcFunc['db_free_result']($request);
1053
1054
	return $row;
1055
}
1056
1057
/**
1058
 * Gets attachment info associated with a message ID
1059
 *
1060
 * @param int $msgID the message ID to load info from.
1061
 *
1062
 * @return array.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array. at position 0 could not be parsed: Unknown type name 'array.' at position 0 in array..
Loading history...
1063
 */
1064
function getAttachsByMsg($msgID = 0)
1065
{
1066
	global $modSettings, $smcFunc;
1067
	static $attached = array();
1068
1069
	if (!isset($attached[$msgID]))
1070
	{
1071
		$request = $smcFunc['db_query']('', '
1072
			SELECT
1073
				a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved, m.id_topic AS topic, m.id_board AS board,
1074
				a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
1075
				COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
1076
			FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
1077
				LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
1078
				LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1079
			WHERE a.attachment_type = {int:attachment_type}
1080
				'. (!empty($msgID) ? 'AND a.id_msg = {int:message_id}' : '') . '',
1081
			array(
1082
				'message_id' => $msgID,
1083
				'attachment_type' => 0,
1084
				'is_approved' => 1,
1085
			)
1086
		);
1087
		$temp = array();
1088
		while ($row = $smcFunc['db_fetch_assoc']($request))
1089
		{
1090
			if (!$row['approved'] && $modSettings['postmod_active'] && !allowedTo('approve_posts') && (!isset($all_posters[$row['id_msg']]) || $all_posters[$row['id_msg']] != $user_info['id']))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $user_info seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $all_posters seems to never exist and therefore isset should always be false.
Loading history...
1091
				continue;
1092
1093
			$temp[$row['id_attach']] = $row;
1094
		}
1095
		$smcFunc['db_free_result']($request);
1096
1097
		// This is better than sorting it with the query...
1098
		ksort($temp);
1099
1100
		$attached[$msgID] = $temp;
1101
	}
1102
1103
	return $attached;
1104
}
1105
1106
/**
1107
 * This loads an attachment's contextual data including, most importantly, its size if it is an image.
1108
 * It requires the view_attachments permission to calculate image size.
1109
 * It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by
1110
 * the max_image_width and max_image_height settings.
1111
 *
1112
 * @param int $id_msg ID of the post to load attachments for
1113
 * @param array $attachments  An array of already loaded attachments. This function no longer depends on having $topic declared, thus, you need to load the actual topic ID for each attachment.
1114
 * @return array An array of attachment info
1115
 */
1116
function loadAttachmentContext($id_msg, $attachments)
1117
{
1118
	global $modSettings, $txt, $scripturl, $sourcedir, $smcFunc;
1119
1120
	if (empty($attachments) || empty($attachments[$id_msg]))
1121
		return array();
1122
1123
	// Set up the attachment info - based on code by Meriadoc.
1124
	$attachmentData = array();
1125
	$have_unapproved = false;
1126
	if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable']))
1127
	{
1128
		foreach ($attachments[$id_msg] as $i => $attachment)
1129
		{
1130
			$attachmentData[$i] = array(
1131
				'id' => $attachment['id_attach'],
1132
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1133
				'downloads' => $attachment['downloads'],
1134
				'size' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1135
				'byte_size' => $attachment['filesize'],
1136
				'href' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1137
				'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'] . '">' . $smcFunc['htmlspecialchars']($attachment['filename']) . '</a>',
1138
				'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1139
				'is_approved' => $attachment['approved'],
1140
				'topic' => $attachment['topic'],
1141
				'board' => $attachment['board'],
1142
			);
1143
1144
			// If something is unapproved we'll note it so we can sort them.
1145
			if (!$attachment['approved'])
1146
				$have_unapproved = true;
1147
1148
			if (!$attachmentData[$i]['is_image'])
1149
				continue;
1150
1151
			$attachmentData[$i]['real_width'] = $attachment['width'];
1152
			$attachmentData[$i]['width'] = $attachment['width'];
1153
			$attachmentData[$i]['real_height'] = $attachment['height'];
1154
			$attachmentData[$i]['height'] = $attachment['height'];
1155
1156
			// Let's see, do we want thumbs?
1157
			if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1158
			{
1159
				// A proper thumb doesn't exist yet? Create one!
1160
				if (empty($attachment['id_thumb']) || $attachment['thumb_width'] > $modSettings['attachmentThumbWidth'] || $attachment['thumb_height'] > $modSettings['attachmentThumbHeight'] || ($attachment['thumb_width'] < $modSettings['attachmentThumbWidth'] && $attachment['thumb_height'] < $modSettings['attachmentThumbHeight']))
1161
				{
1162
					$filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder']);
1163
1164
					require_once($sourcedir . '/Subs-Graphics.php');
1165
					if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
1166
					{
1167
						// So what folder are we putting this image in?
1168
						if (!empty($modSettings['currentAttachmentUploadDir']))
1169
						{
1170
							if (!is_array($modSettings['attachmentUploadDir']))
1171
								$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
1172
							$id_folder_thumb = $modSettings['currentAttachmentUploadDir'];
1173
						}
1174
						else
1175
						{
1176
							$id_folder_thumb = 1;
1177
						}
1178
1179
						// Calculate the size of the created thumbnail.
1180
						$size = @getimagesize($filename . '_thumb');
1181
						list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
1182
						$thumb_size = filesize($filename . '_thumb');
1183
1184
						// What about the extension?
1185
						$thumb_ext = isset($context['valid_image_types'][$size[2]]) ? $context['valid_image_types'][$size[2]] : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $context seems to never exist and therefore isset should always be false.
Loading history...
1186
1187
						// Figure out the mime type.
1188
						if (!empty($size['mime']))
1189
							$thumb_mime = $size['mime'];
1190
						else
1191
							$thumb_mime = 'image/' . $thumb_ext;
1192
1193
						$thumb_filename = $attachment['filename'] . '_thumb';
1194
						$thumb_hash = getAttachmentFilename($thumb_filename, false, null, true);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $attachment_id of getAttachmentFilename(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1194
						$thumb_hash = getAttachmentFilename($thumb_filename, /** @scrutinizer ignore-type */ false, null, true);
Loading history...
1195
						$old_id_thumb = $attachment['id_thumb'];
1196
1197
						// Add this beauty to the database.
1198
						$attachment['id_thumb'] = $smcFunc['db_insert']('',
1199
							'{db_prefix}attachments',
1200
							array('id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'size' => 'int', 'width' => 'int', 'height' => 'int', 'fileext' => 'string', 'mime_type' => 'string'),
1201
							array($id_folder_thumb, $id_msg, 3, $thumb_filename, $thumb_hash, (int) $thumb_size, (int) $attachment['thumb_width'], (int) $attachment['thumb_height'], $thumb_ext, $thumb_mime),
1202
							array('id_attach'),
1203
							1
1204
						);
1205
1206
						if (!empty($attachment['id_thumb']))
1207
						{
1208
							$smcFunc['db_query']('', '
1209
								UPDATE {db_prefix}attachments
1210
								SET id_thumb = {int:id_thumb}
1211
								WHERE id_attach = {int:id_attach}',
1212
								array(
1213
									'id_thumb' => $attachment['id_thumb'],
1214
									'id_attach' => $attachment['id_attach'],
1215
								)
1216
							);
1217
1218
							$thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
1219
							rename($filename . '_thumb', $thumb_realname);
1220
1221
							// Do we need to remove an old thumbnail?
1222
							if (!empty($old_id_thumb))
1223
							{
1224
								require_once($sourcedir . '/ManageAttachments.php');
1225
								removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1226
							}
1227
						}
1228
					}
1229
				}
1230
1231
				// Only adjust dimensions on successful thumbnail creation.
1232
				if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1233
				{
1234
					$attachmentData[$i]['width'] = $attachment['thumb_width'];
1235
					$attachmentData[$i]['height'] = $attachment['thumb_height'];
1236
				}
1237
			}
1238
1239
			if (!empty($attachment['id_thumb']))
1240
				$attachmentData[$i]['thumbnail'] = array(
1241
					'id' => $attachment['id_thumb'],
1242
					'href' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_thumb'] . ';image',
1243
				);
1244
			$attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1245
1246
			// If thumbnails are disabled, check the maximum size of the image.
1247
			if (!$attachmentData[$i]['thumbnail']['has_thumb'] && ((!empty($modSettings['max_image_width']) && $attachment['width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachment['height'] > $modSettings['max_image_height'])))
1248
			{
1249
				if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1250
				{
1251
					$attachmentData[$i]['width'] = $modSettings['max_image_width'];
1252
					$attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1253
				}
1254
				elseif (!empty($modSettings['max_image_width']))
1255
				{
1256
					$attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1257
					$attachmentData[$i]['height'] = $modSettings['max_image_height'];
1258
				}
1259
			}
1260
			elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1261
			{
1262
				// If the image is too large to show inline, make it a popup.
1263
				if (((!empty($modSettings['max_image_width']) && $attachmentData[$i]['real_width'] > $modSettings['max_image_width']) || (!empty($modSettings['max_image_height']) && $attachmentData[$i]['real_height'] > $modSettings['max_image_height'])))
1264
					$attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);';
1265
				else
1266
					$attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1267
			}
1268
1269
			if (!$attachmentData[$i]['thumbnail']['has_thumb'])
1270
				$attachmentData[$i]['downloads']++;
1271
		}
1272
	}
1273
1274
	// Do we need to instigate a sort?
1275
	if ($have_unapproved)
1276
		usort($attachmentData, 'approved_attach_sort');
1277
1278
	return $attachmentData;
1279
}
1280
1281
?>