Passed
Push — patch_1-1-7 ( 90a951...ab62ad )
by Emanuele
04:23 queued 03:46
created

getAttachments()   C

Complexity

Conditions 12
Paths 8

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 25
nc 8
nop 4
dl 0
loc 41
rs 6.9666
c 0
b 0
f 0

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 handles the uploading and creation of attachments
5
 * as well as the auto management of the attachment directories.
6
 * Note to enhance documentation later:
7
 * attachment_type = 3 is a thumbnail, etc.
8
 *
9
 * @name      ElkArte Forum
10
 * @copyright ElkArte Forum contributors
11
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
12
 *
13
 * This file contains code covered by:
14
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
15
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
16
 *
17
 * @version 1.1.7
18
 *
19
 */
20
21
/**
22
 * Check and create a directory automatically.
23
 *
24
 * @package Attachments
25
 */
26
function automanage_attachments_check_directory()
27
{
28
	global $modSettings, $context;
29
30
	// Not pretty, but since we don't want folders created for every post.
31
	// It'll do unless a better solution can be found.
32
	if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'admin')
33
		$doit = true;
34
	elseif (empty($modSettings['automanage_attachments']))
35
		return;
36
	elseif (!isset($_FILES))
37
		return;
38
	elseif (isset($_FILES['attachment']))
39
	{
40
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
41
		{
42
			if (!empty($dummy))
43
			{
44
				$doit = true;
45
				break;
46
			}
47
		}
48
	}
49
50
	if (!isset($doit))
51
		return;
52
53
	// Get our date and random numbers for the directory choices
54
	$year = date('Y');
55
	$month = date('m');
56
57
	$rand = md5(mt_rand());
58
	$rand1 = $rand[1];
59
	$rand = $rand[0];
60
61
	if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
62
	{
63
		if (!is_array($modSettings['attachment_basedirectories']))
64
			$modSettings['attachment_basedirectories'] = Util::unserialize($modSettings['attachment_basedirectories']);
65
66
		$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
67
	}
68
	else
69
		$base_dir = 0;
70
71
	if ($modSettings['automanage_attachments'] == 1)
72
	{
73
		if (!isset($modSettings['last_attachments_directory']))
74
			$modSettings['last_attachments_directory'] = array();
75
		if (!is_array($modSettings['last_attachments_directory']))
76
			$modSettings['last_attachments_directory'] = Util::unserialize($modSettings['last_attachments_directory']);
77
		if (!isset($modSettings['last_attachments_directory'][$base_dir]))
78
			$modSettings['last_attachments_directory'][$base_dir] = 0;
79
	}
80
81
	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : BOARDDIR);
82
83
	// Just to be sure: I don't want directory separators at the end
84
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
85
	$basedirectory = rtrim($basedirectory, $sep);
86
87
	switch ($modSettings['automanage_attachments'])
88
	{
89
		case 1:
90
			$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
91
			break;
92
		case 2:
93
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
94
			break;
95
		case 3:
96
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
97
			break;
98
		case 4:
99
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
100
			break;
101
		case 5:
102
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
103
			break;
104
		default:
105
			$updir = '';
106
	}
107
108
	if (!is_array($modSettings['attachmentUploadDir']))
109
		$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
110
111
	if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
112
		$outputCreation = automanage_attachments_create_directory($updir);
113
	elseif (in_array($updir, $modSettings['attachmentUploadDir']))
114
		$outputCreation = true;
115
116
	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...
117
	{
118
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
119
		$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
120
121
		updateSettings(array(
122
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
123
		));
124
	}
125
126
	return $outputCreation;
127
}
128
129
/**
130
 * Creates a directory as defined by the admin attach options
131
 *
132
 * What it does:
133
 *
134
 * - Attempts to make the directory writable
135
 * - Places an .htaccess in new directories for security
136
 *
137
 * @package Attachments
138
 * @param string $updir
139
 */
140
function automanage_attachments_create_directory($updir)
141
{
142
	global $modSettings, $context;
143
144
	$tree = get_directory_tree_elements($updir);
145
	$count = count($tree);
0 ignored issues
show
Bug introduced by
It seems like $tree can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

145
	$count = count(/** @scrutinizer ignore-type */ $tree);
Loading history...
146
147
	$directory = !empty($tree) ? attachments_init_dir($tree, $count) : false;
148
	if ($directory === false)
149
	{
150
		// Maybe it's just the folder name
151
		$tree = get_directory_tree_elements(BOARDDIR . DIRECTORY_SEPARATOR . $updir);
152
		$count = count($tree);
153
154
		$directory = !empty($tree) ? attachments_init_dir($tree, $count) : false;
155
		if ($directory === false)
156
			return false;
157
	}
158
159
	$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
160
161
	while (!@is_dir($directory) || $count != -1)
162
	{
163
		if (!@is_dir($directory))
164
		{
165
			if (!@mkdir($directory, 0755))
166
			{
167
				$context['dir_creation_error'] = 'attachments_no_create';
168
				return false;
169
			}
170
		}
171
172
		$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
173
		$count--;
174
	}
175
176
	// Try to make it writable
177
	if (!is_writable($directory))
178
	{
179
		chmod($directory, 0755);
180
		if (!is_writable($directory))
181
		{
182
			chmod($directory, 0775);
183
			if (!is_writable($directory))
184
			{
185
				chmod($directory, 0777);
186
				if (!is_writable($directory))
187
				{
188
					$context['dir_creation_error'] = 'attachments_no_write';
189
					return false;
190
				}
191
			}
192
		}
193
	}
194
195
	// Everything seems fine...let's create the .htaccess
196
	if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htaccess'))
197
		secureDirectory($updir, true);
198
199
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
200
	$updir = rtrim($updir, $sep);
201
202
	// Only update if it's a new directory
203
	if (!in_array($updir, $modSettings['attachmentUploadDir']))
204
	{
205
		$modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) + 1;
206
		$modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
207
208
		updateSettings(array(
209
			'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
210
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
211
		), true);
212
		$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
213
	}
214
215
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
216
	return true;
217
}
218
219
/**
220
 * Determines the current base directory and attachment directory
221
 *
222
 * What it does:
223
 *
224
 * - Increments the above directory to the next available slot
225
 * - Uses automanage_attachments_create_directory to create the incremental directory
226
 *
227
 * @package Attachments
228
 */
229
function automanage_attachments_by_space()
230
{
231
	global $modSettings;
232
233
	if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
234
		return;
235
236
	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : BOARDDIR);
237
238
	// Just to be sure: I don't want directory separators at the end
239
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
240
	$basedirectory = rtrim($basedirectory, $sep);
241
242
	// Get the current base directory
243
	if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
244
	{
245
		$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
246
		$base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
247
	}
248
	else
249
		$base_dir = 0;
250
251
	// Get the last attachment directory for that base directory
252
	if (empty($modSettings['last_attachments_directory'][$base_dir]))
253
		$modSettings['last_attachments_directory'][$base_dir] = 0;
254
255
	// And increment it.
256
	$modSettings['last_attachments_directory'][$base_dir]++;
257
258
	$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
259
260
	// make sure it exists and is writable
261
	if (automanage_attachments_create_directory($updir))
262
	{
263
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
264
		updateSettings(array(
265
			'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
266
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
267
		));
268
		$modSettings['last_attachments_directory'] = Util::unserialize($modSettings['last_attachments_directory']);
269
270
		return true;
271
	}
272
	else
273
		return false;
274
}
275
276
/**
277
 * Finds the current directory tree for the supplied base directory
278
 *
279
 * @package Attachments
280
 * @param string $directory
281
 * @return string[]|boolean on fail else array of directory names
282
 */
283
function get_directory_tree_elements($directory)
284
{
285
	/*
286
		In Windows server both \ and / can be used as directory separators in paths
287
		In Linux (and presumably *nix) servers \ can be part of the name
288
		So for this reasons:
289
			* in Windows we need to explode for both \ and /
290
			* while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
291
	*/
292
	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
293
		$tree = preg_split('#[\\\/]#', $directory);
294
	else
295
	{
296
		if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR)
297
			return false;
298
299
		$tree = explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR));
300
	}
301
302
	return $tree;
303
}
304
305
/**
306
 * Helper function for automanage_attachments_create_directory
307
 *
308
 * What it does:
309
 *
310
 * - Gets the directory w/o drive letter for windows
311
 *
312
 * @package Attachments
313
 * @param string[] $tree
314
 * @param int $count
315
 */
316
function attachments_init_dir(&$tree, &$count)
317
{
318
	$directory = '';
319
320
	// If on Windows servers the first part of the path is the drive (e.g. "C:")
321
	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
322
	{
323
		// Better be sure that the first part of the path is actually a drive letter...
324
		// ...even if, I should check this in the admin page...isn't it?
325
		// ...NHAAA Let's leave space for users' complains! :P
326
		if (preg_match('/^[a-z]:$/i', $tree[0]))
327
			$directory = array_shift($tree);
328
		else
329
			return false;
330
331
		$count--;
332
	}
333
334
	return $directory;
335
}
336
337
/**
338
 * Handles the actual saving of attachments to a directory.
339
 *
340
 * What it does:
341
 *
342
 * - Loops through $_FILES['attachment'] array and saves each file to the current attachments folder.
343
 * - Validates the save location actually exists.
344
 *
345
 * @package Attachments
346
 * @param int|null $id_msg = null or id of the message with attachments, if any.
347
 *                  If null, this is an upload in progress for a new post.
348
 * @throws Elk_Exception
349
 */
350
function processAttachments($id_msg = null)
351
{
352
	global $context, $modSettings, $txt, $user_info, $ignore_temp, $topic, $board;
353
354
	$attach_errors = ElkArte\Errors\AttachmentErrorContext::context();
355
	$added_initial_error = false;
356
357
	// Make sure we're uploading to the right place.
358
	if (!empty($modSettings['automanage_attachments']))
359
		automanage_attachments_check_directory();
360
361
	if (!is_array($modSettings['attachmentUploadDir']))
362
	{
363
		$attachmentUploadDir = Util::unserialize($modSettings['attachmentUploadDir']);
364
		if (!empty($attachmentUploadDir))
365
		{
366
			$modSettings['attachmentUploadDir'] = $attachmentUploadDir;
367
		}
368
	}
369
370
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
371
372
	// Is the attachments folder actually there?
373
	if (!empty($context['dir_creation_error']))
374
		$initial_error = $context['dir_creation_error'];
375
	elseif (!is_dir($context['attach_dir']))
376
	{
377
		$initial_error = 'attach_folder_warning';
378
		Errors::instance()->log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
379
	}
380
381
	if (!isset($initial_error) && !isset($context['attachments']['quantity']))
382
	{
383
		// If this isn't a new post, check the current attachments.
384
		if (!empty($id_msg))
385
			list ($context['attachments']['quantity'], $context['attachments']['total_size']) = attachmentsSizeForMessage($id_msg);
386
		else
387
		{
388
			$context['attachments']['quantity'] = 0;
389
			$context['attachments']['total_size'] = 0;
390
		}
391
	}
392
393
	// Hmm. There are still files in session.
394
	$ignore_temp = false;
395
	if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1)
396
	{
397
		// Let's try to keep them. But...
398
		$ignore_temp = true;
399
400
		// If new files are being added. We can't ignore those
401
		if (!empty($_FILES['attachment']['tmp_name']))
402
		{
403
			foreach ($_FILES['attachment']['tmp_name'] as $dummy)
404
			{
405
				if (!empty($dummy))
406
				{
407
					$ignore_temp = false;
408
					break;
409
				}
410
			}
411
		}
412
413
		// Need to make space for the new files. So, bye bye.
414
		if (!$ignore_temp)
415
		{
416
			foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
417
			{
418
				if (strpos($attachID, 'post_tmp_' . $user_info['id'] . '_') !== false)
419
					@unlink($attachment['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

419
					/** @scrutinizer ignore-unhandled */ @unlink($attachment['tmp_name']);

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...
420
			}
421
422
			$attach_errors->activate()->addError('temp_attachments_flushed');
423
			$_SESSION['temp_attachments'] = array();
424
		}
425
	}
426
427
	if (!isset($_FILES['attachment']['name']))
428
		$_FILES['attachment']['tmp_name'] = array();
429
430
	if (!isset($_SESSION['temp_attachments']))
431
		$_SESSION['temp_attachments'] = array();
432
433
	// Remember where we are at. If it's anywhere at all.
434
	if (!$ignore_temp)
435
		$_SESSION['temp_attachments']['post'] = array(
436
			'msg' => !empty($id_msg) ? $id_msg : 0,
437
			'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
438
			'topic' => !empty($topic) ? $topic : 0,
439
			'board' => !empty($board) ? $board : 0,
440
		);
441
442
	// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
443
	foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
444
	{
445
		if ($_FILES['attachment']['name'][$n] == '')
446
			continue;
447
448
		// If we have an initial error, lets just display it.
449
		if (!empty($initial_error) && $added_initial_error === false)
450
		{
451
			$added_initial_error = true;
452
			$_SESSION['temp_attachments']['initial_error'] = $initial_error;
453
454
			// This is a generic error
455
			$attach_errors->activate();
456
			$attach_errors->addError('attach_no_upload');
457
			// @todo This is likely the result of some refactoring, verify when $attachment is not set and why
458
			if (isset($attachment))
459
			{
460
				$attach_errors->addError(is_array($attachment) ? array($attachment[0], $attachment[1]) : $attachment);
0 ignored issues
show
Bug introduced by
It seems like is_array($attachment) ? ...hment[1]) : $attachment can also be of type array; however, parameter $error of ElkArte\Errors\AttachmentErrorContext::addError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

460
				$attach_errors->addError(/** @scrutinizer ignore-type */ is_array($attachment) ? array($attachment[0], $attachment[1]) : $attachment);
Loading history...
461
			}
462
463
			// And delete the files 'cos they ain't going nowhere.
464
			foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
0 ignored issues
show
Comprehensibility Bug introduced by
$n is overwriting a variable from outer foreach loop.
Loading history...
Comprehensibility Bug introduced by
$dummy is overwriting a variable from outer foreach loop.
Loading history...
465
			{
466
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
467
					unlink($_FILES['attachment']['tmp_name'][$n]);
468
			}
469
470
			$_FILES['attachment']['tmp_name'] = array();
471
		}
472
473
		// First, let's first check for PHP upload errors.
474
		$errors = attachmentUploadChecks($n);
475
476
		// Set the names and destination for this file
477
		$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
478
		$destName = $context['attach_dir'] . '/' . $attachID;
479
480
		// If we are error free, Try to move and rename the file before doing more checks on it.
481
		if (empty($errors))
482
		{
483
			$_SESSION['temp_attachments'][$attachID] = array(
484
				'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'),
485
				'tmp_name' => $destName,
486
				'attachid' => $attachID,
487
				'public_attachid' => 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()),
488
				'size' => $_FILES['attachment']['size'][$n],
489
				'type' => $_FILES['attachment']['type'][$n],
490
				'id_folder' => $modSettings['currentAttachmentUploadDir'],
491
				'errors' => array(),
492
			);
493
494
			// Move the file to the attachments folder with a temp name for now.
495
			if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
496
				@chmod($destName, 0644);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

496
				/** @scrutinizer ignore-unhandled */ @chmod($destName, 0644);

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...
497
			else
498
			{
499
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
500
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
501
					unlink($_FILES['attachment']['tmp_name'][$n]);
502
			}
503
		}
504
		// Upload error(s) were detected, flag the error, remove the file
505
		else
506
		{
507
			$_SESSION['temp_attachments'][$attachID] = array(
508
				'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'),
509
				'tmp_name' => $destName,
510
				'errors' => $errors,
511
			);
512
513
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
514
				unlink($_FILES['attachment']['tmp_name'][$n]);
515
		}
516
517
		// If there were no errors to this point, we apply some additional checks
518
		if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
519
			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

519
			attachmentChecks(/** @scrutinizer ignore-type */ $attachID);
Loading history...
520
521
		// Want to correct for phonetographer photos?
522
		if (!empty($modSettings['attachment_autorotate']) && empty($_SESSION['temp_attachments'][$attachID]['errors']) && substr($_SESSION['temp_attachments'][$attachID]['type'], 0, 5) === 'image')
523
		{
524
			autoRotateImage($_SESSION['temp_attachments'][$attachID]['tmp_name']);
525
		}
526
527
		// Sort out the errors for display and delete any associated files.
528
		if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
529
		{
530
			$attach_errors->addAttach($attachID, $_SESSION['temp_attachments'][$attachID]['name']);
531
			$log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file', 'bad_attachment');
532
533
			foreach ($_SESSION['temp_attachments'][$attachID]['errors'] as $error)
534
			{
535
				if (!is_array($error))
536
				{
537
					$attach_errors->addError($error);
538
					if (in_array($error, $log_these))
539
					{
540
						Errors::instance()->log_error($_SESSION['temp_attachments'][$attachID]['name'] . ': ' . $txt[$error], 'critical');
541
542
						// For critical errors, we don't want the file or session data to persist
543
						if (file_exists($_SESSION['temp_attachments'][$attachID]['tmp_name']))
544
						{
545
							unlink($_SESSION['temp_attachments'][$attachID]['tmp_name']);
546
						}
547
						unset($_SESSION['temp_attachments'][$attachID]);
548
					}
549
				}
550
				else
551
					$attach_errors->addError(array($error[0], $error[1]));
552
			}
553
		}
554
	}
555
556
	// Mod authors, finally a hook to hang an alternate attachment upload system upon
557
	// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
558
	// Populate $_SESSION['temp_attachments'][$attachID] with the following:
559
	//   name => The file name
560
	//   tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
561
	//   size => File size (required).
562
	//   type => MIME type (optional if not available on upload).
563
	//   id_folder => $modSettings['currentAttachmentUploadDir']
564
	//   errors => An array of errors (use the index of the $txt variable for that error).
565
	// Template changes can be done using "integrate_upload_template".
566
	call_integration_hook('integrate_attachment_upload');
567
}
568
569
/**
570
 * Deletes a temporary attachment from the $_SESSION (and the filesystem)
571
 *
572
 * @package Attachments
573
 * @param string $attach_id the temporary name generated when a file is uploaded
574
 *               and used in $_SESSION to help identify the attachment itself
575
 */
576
function removeTempAttachById($attach_id)
577
{
578
	foreach ($_SESSION['temp_attachments'] as $attachID => $attach)
579
	{
580
		if ($attachID === $attach_id)
581
		{
582
			// This file does exist, so lets terminate it!
583
			if (file_exists($attach['tmp_name']))
584
			{
585
				@unlink($attach['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

585
				/** @scrutinizer ignore-unhandled */ @unlink($attach['tmp_name']);

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...
586
				unset($_SESSION['temp_attachments'][$attachID]);
587
588
				return true;
589
			}
590
			// Nope can't delete it if we can't find it
591
			else
592
				return 'attachment_not_found';
593
		}
594
	}
595
596
	return 'attachment_not_found';
597
}
598
599
/**
600
 * Finds and return a temporary attachment by its id
601
 *
602
 * @package Attachments
603
 * @param string $attach_id the temporary name generated when a file is uploaded
604
 *  and used in $_SESSION to help identify the attachment itself
605
 *
606
 * @return mixed
607
 * @throws Exception
608
 */
609
function getTempAttachById($attach_id)
610
{
611
	global $modSettings, $user_info;
612
613
	$attach_real_id = null;
614
615
	if (empty($_SESSION['temp_attachments']))
616
	{
617
		throw new \Exception('no_access');
618
	}
619
620
	foreach ($_SESSION['temp_attachments'] as $attachID => $val)
621
	{
622
		if ($attachID === 'post')
623
		{
624
			continue;
625
		}
626
627
		if ($val['public_attachid'] === $attach_id)
628
		{
629
			$attach_real_id = $attachID;
630
			break;
631
		}
632
	}
633
634
	if (empty($attach_real_id))
635
	{
636
		throw new \Exception('no_access');
637
	}
638
639
	// The common name form is "post_tmp_123_0ac9a0b1fc18604e8704084656ed5f09"
640
	$id_attach = preg_replace('~[^0-9a-zA-Z_]~', '', $attach_real_id);
641
642
	// Permissions: only temporary attachments
643
	if (substr($id_attach, 0, 8) !== 'post_tmp')
644
		throw new \Exception('no_access');
645
646
	// Permissions: only author is allowed.
647
	$pieces = explode('_', substr($id_attach, 9));
648
649
	if (!isset($pieces[0]) || $pieces[0] != $user_info['id'])
650
		throw new \Exception('no_access');
651
652
	if (is_array($modSettings['attachmentUploadDir']))
653
		$dirs = $modSettings['attachmentUploadDir'];
654
	else
655
		$dirs = unserialize($modSettings['attachmentUploadDir']);
656
657
	$attach_dir = $dirs[$modSettings['currentAttachmentUploadDir']];
658
659
	if (file_exists($attach_dir . '/' . $attach_real_id) && isset($_SESSION['temp_attachments'][$attach_real_id]))
660
	{
661
		return $_SESSION['temp_attachments'][$attach_real_id];
662
	}
663
664
	throw new \Exception('no_access');
665
}
666
667
/**
668
 * Checks if an uploaded file produced any appropriate error code
669
 *
670
 * What it does:
671
 *
672
 * - Checks for error codes in the error segment of the file array that is
673
 * created by PHP during the file upload.
674
 *
675
 * @package Attachments
676
 * @param int $attachID
677
 */
678
function attachmentUploadChecks($attachID)
679
{
680
	global $modSettings, $txt;
681
682
	$errors = array();
683
684
	// Did PHP create any errors during the upload processing of this file?
685
	if (!empty($_FILES['attachment']['error'][$attachID]))
686
	{
687
		// The file exceeds the max_filesize directive in php.ini
688
		if ($_FILES['attachment']['error'][$attachID] == 1)
689
			$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
690
		// The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.
691
		elseif ($_FILES['attachment']['error'][$attachID] == 2)
692
			$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
693
		// Missing or a full a temp directory on the server
694
		elseif ($_FILES['attachment']['error'][$attachID] == 6)
695
			Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_6'], 'critical');
696
		// One of many errors such as (3)partially uploaded, (4)empty file,
697
		else
698
			Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$attachID]]);
699
700
		// If we did not set an user error (3,4,6,7,8) to show then give them a generic one as there is
701
		// no need to provide back specifics of a server error, those are logged
702
		if (empty($errors))
703
			$errors[] = 'attach_php_error';
704
	}
705
706
	return $errors;
707
}
708
709
/**
710
 * Performs various checks on an uploaded file.
711
 *
712
 * What it does:
713
 *
714
 * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
715
 *
716
 * @package Attachments
717
 *
718
 * @param int $attachID id of the attachment to check
719
 *
720
 * @return bool
721
 * @throws Elk_Exception attach_check_nag
722
 */
723
function attachmentChecks($attachID)
724
{
725
	global $modSettings, $context, $attachmentOptions;
726
727
	$db = database();
728
729
	// No data or missing data .... Not necessarily needed, but in case a mod author missed something.
730
	if (empty($_SESSION['temp_attachments'][$attachID]))
731
		$error = '$_SESSION[\'temp_attachments\'][$attachID]';
732
	elseif (empty($attachID))
733
		$error = '$attachID';
734
	elseif (empty($context['attachments']))
735
		$error = '$context[\'attachments\']';
736
	elseif (empty($context['attach_dir']))
737
		$error = '$context[\'attach_dir\']';
738
739
	// Let's get their attention.
740
	if (!empty($error))
741
		throw new Elk_Exception('attach_check_nag', 'debug', array($error));
742
743
	// Just in case this slipped by the first checks, we stop it here and now
744
	if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
745
	{
746
		$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
747
		return false;
748
	}
749
750
	// First, the dreaded security check. Sorry folks, but this should't be avoided
751
	$size = elk_getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
752
	$valid_mime = getValidMimeImageType($size[2]);
753
754
	if ($valid_mime !== '')
755
	{
756
		require_once(SUBSDIR . '/Graphics.subs.php');
757
		if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
758
		{
759
			// It's bad. Last chance, maybe we can re-encode it?
760
			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
761
			{
762
				// Nothing to do: not allowed or not successful re-encoding it.
763
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
764
				return false;
765
			}
766
767
			// Success! However, successes usually come for a price:
768
			// we might get a new format for our image...
769
			$old_format = $size[2];
770
			$size = elk_getimagesize($attachmentOptions['tmp_name']);
771
772
			if (!(empty($size)) && ($size[2] !== $old_format))
773
			{
774
				$valid_mime = getValidMimeImageType($size[2]);
775
				if ($valid_mime !== '')
776
				{
777
					$_SESSION['temp_attachments'][$attachID]['type'] = $valid_mime;
778
				}
779
			}
780
		}
781
	}
782
783
	// Is there room for this in the directory?
784
	if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
785
	{
786
		// Check the folder size and count. If it hasn't been done already.
787
		if (empty($context['dir_size']) || empty($context['dir_files']))
788
		{
789
			$request = $db->query('', '
790
				SELECT COUNT(*), SUM(size)
791
				FROM {db_prefix}attachments
792
				WHERE id_folder = {int:folder_id}
793
					AND attachment_type != {int:type}',
794
				array(
795
					'folder_id' => $modSettings['currentAttachmentUploadDir'],
796
					'type' => 1,
797
				)
798
			);
799
			list ($context['dir_files'], $context['dir_size']) = $db->fetch_row($request);
800
			$db->free_result($request);
801
		}
802
		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
803
		$context['dir_files']++;
804
805
		// Are we about to run out of room? Let's notify the admin then.
806
		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...
807
			|| (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
808
		{
809
			require_once(SUBSDIR . '/Admin.subs.php');
810
			emailAdmins('admin_attachments_full');
811
			updateSettings(array('attachment_full_notified' => 1));
812
		}
813
814
		// No room left.... What to do now???
815
		if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] + 2 > $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...
816
			|| (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
817
		{
818
			// If we are managing the directories space automatically, lets get to it
819
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
820
			{
821
				// Move it to the new folder if we can.
822
				if (automanage_attachments_by_space())
823
				{
824
					rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
825
					$_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
826
					$_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
827
					$context['dir_size'] = 0;
828
					$context['dir_files'] = 0;
829
				}
830
				// Or, let the user know that its not going to happen.
831
				else
832
				{
833
					if (isset($context['dir_creation_error']))
834
						$_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
835
					else
836
						$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
837
				}
838
			}
839
			else
840
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
841
		}
842
	}
843
844
	// Is the file too big?
845
	if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
846
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
847
848
	// Check the total upload size for this post...
849
	$context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
850
	if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
851
		$_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)));
852
853
	// Have we reached the maximum number of files we are allowed?
854
	$context['attachments']['quantity']++;
855
856
	// Set a max limit if none exists
857
	if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
858
		$modSettings['attachmentNumPerPostLimit'] = 50;
859
860
	if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
861
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
862
863
	// File extension check
864
	if (!empty($modSettings['attachmentCheckExtensions']))
865
	{
866
		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
867
		foreach ($allowed as $k => $dummy)
868
			$allowed[$k] = trim($dummy);
869
870
		if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
871
		{
872
			$allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
873
			$_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
874
		}
875
	}
876
877
	// Undo the math if there's an error
878
	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
879
	{
880
		if (isset($context['dir_size']))
881
			$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
882
		if (isset($context['dir_files']))
883
			$context['dir_files']--;
884
885
		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
886
		$context['attachments']['quantity']--;
887
888
		return false;
889
	}
890
891
	return true;
892
}
893
894
/**
895
 * Create an attachment, with the given array of parameters.
896
 *
897
 * What it does:
898
 *
899
 * - Adds any additional or missing parameters to $attachmentOptions.
900
 * - Renames the temporary file.
901
 * - Creates a thumbnail if the file is an image and the option enabled.
902
 *
903
 * @package Attachments
904
 * @param mixed[] $attachmentOptions associative array of options
905
 */
906
function createAttachment(&$attachmentOptions)
907
{
908
	global $modSettings, $context;
909
910
	$db = database();
911
912
	require_once(SUBSDIR . '/Graphics.subs.php');
913
914
	// If this is an image we need to set a few additional parameters.
915
	$size = elk_getimagesize($attachmentOptions['tmp_name']);
916
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
917
	$attachmentOptions['width'] = max(0, $attachmentOptions['width']);
918
	$attachmentOptions['height'] = max(0, $attachmentOptions['height']);
919
920
	// If it's an image get the mime type right.
921
	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
922
	{
923
		// Got a proper mime type?
924
		if (!empty($size['mime']))
925
		{
926
			$attachmentOptions['mime_type'] = $size['mime'];
927
		}
928
		// Otherwise a valid one?
929
		else
930
		{
931
			$attachmentOptions['mime_type'] = getValidMimeImageType($size[2]);
932
		}
933
	}
934
935
	// It is possible we might have a MIME type that isn't actually an image but still have a size.
936
	// For example, Shockwave files will be able to return size but be 'application/shockwave' or similar.
937
	if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0)
938
	{
939
		$attachmentOptions['width'] = 0;
940
		$attachmentOptions['height'] = 0;
941
	}
942
943
	// Get the hash if no hash has been given yet.
944
	if (empty($attachmentOptions['file_hash']))
945
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], 0, null, true);
946
947
	// Assuming no-one set the extension let's take a look at it.
948
	if (empty($attachmentOptions['fileext']))
949
	{
950
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
951
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
952
			$attachmentOptions['fileext'] = '';
953
	}
954
955
	$db->insert('',
956
		'{db_prefix}attachments',
957
		array(
958
			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
959
			'size' => 'int', 'width' => 'int', 'height' => 'int',
960
			'mime_type' => 'string-20', 'approved' => 'int',
961
		),
962
		array(
963
			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
964
			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
965
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
966
		),
967
		array('id_attach')
968
	);
969
	$attachmentOptions['id'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
970
971
	// @todo Add an error here maybe?
972
	if (empty($attachmentOptions['id']))
973
		return false;
974
975
	// Now that we have the attach id, let's rename this and finish up.
976
	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
977
	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
978
979
	// If it's not approved then add to the approval queue.
980
	if (!$attachmentOptions['approved'])
981
		$db->insert('',
982
			'{db_prefix}approval_queue',
983
			array(
984
				'id_attach' => 'int', 'id_msg' => 'int',
985
			),
986
			array(
987
				$attachmentOptions['id'], (int) $attachmentOptions['post'],
988
			),
989
			array()
990
		);
991
992
	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
993
		return true;
994
995
	// Like thumbnails, do we?
996
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
997
	{
998
		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
999
		{
1000
			// Figure out how big we actually made it.
1001
			$size = elk_getimagesize($attachmentOptions['destination'] . '_thumb');
1002
			list ($thumb_width, $thumb_height) = $size;
1003
1004
			if (!empty($size['mime']))
1005
			{
1006
				$thumb_mime = $size['mime'];
1007
			}
1008
			else
1009
			{
1010
				$thumb_mime = getValidMimeImageType($size[2]);
1011
			}
1012
1013
			$thumb_filename = $attachmentOptions['name'] . '_thumb';
1014
			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
1015
			$thumb_file_hash = getAttachmentFilename($thumb_filename, 0, null, true);
1016
			$thumb_path = $attachmentOptions['destination'] . '_thumb';
1017
1018
			// We should check the file size and count here since thumbs are added to the existing totals.
1019
			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...
1020
			{
1021
				$context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
1022
				$context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
1023
1024
				// If the folder is full, try to create a new one and move the thumb to it.
1025
				if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
1026
				{
1027
					if (automanage_attachments_by_space())
1028
					{
1029
						rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
1030
						$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
1031
						$context['dir_size'] = 0;
1032
						$context['dir_files'] = 0;
1033
					}
1034
				}
1035
			}
1036
1037
			// If a new folder has been already created. Gotta move this thumb there then.
1038
			if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
1039
			{
1040
				rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
1041
				$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
1042
			}
1043
1044
			// To the database we go!
1045
			$db->insert('',
1046
				'{db_prefix}attachments',
1047
				array(
1048
					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
1049
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
1050
				),
1051
				array(
1052
					$modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
1053
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
1054
				),
1055
				array('id_attach')
1056
			);
1057
			$attachmentOptions['thumb'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
1058
1059
			if (!empty($attachmentOptions['thumb']))
1060
			{
1061
				$db->query('', '
1062
					UPDATE {db_prefix}attachments
1063
					SET id_thumb = {int:id_thumb}
1064
					WHERE id_attach = {int:id_attach}',
1065
					array(
1066
						'id_thumb' => $attachmentOptions['thumb'],
1067
						'id_attach' => $attachmentOptions['id'],
1068
					)
1069
				);
1070
1071
				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
1072
			}
1073
		}
1074
	}
1075
1076
	return true;
1077
}
1078
1079
/**
1080
 * Get the avatar with the specified ID.
1081
 *
1082
 * What it does:
1083
 *
1084
 * - It gets avatar data (folder, name of the file, filehash, etc)
1085
 * from the database.
1086
 * - Must return the same values and in the same order as getAttachmentFromTopic()
1087
 *
1088
 * @package Attachments
1089
 * @param int $id_attach
1090
 */
1091
function getAvatar($id_attach)
1092
{
1093
	$db = database();
1094
1095
	// Use our cache when possible
1096
	$cache = array();
1097
	if (Cache::instance()->getVar($cache, 'getAvatar_id-' . $id_attach))
1098
		$avatarData = $cache;
1099
	else
1100
	{
1101
		$request = $db->query('', '
1102
			SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member
1103
			FROM {db_prefix}attachments
1104
			WHERE id_attach = {int:id_attach}
1105
				AND id_member > {int:blank_id_member}
1106
			LIMIT 1',
1107
			array(
1108
				'id_attach' => $id_attach,
1109
				'blank_id_member' => 0,
1110
			)
1111
		);
1112
		$avatarData = array();
1113
		if ($db->num_rows($request) != 0)
1114
			$avatarData = $db->fetch_row($request);
1115
		$db->free_result($request);
1116
1117
		Cache::instance()->put('getAvatar_id-' . $id_attach, $avatarData, 900);
1118
	}
1119
1120
	return $avatarData;
1121
}
1122
1123
/**
1124
 * Get the specified attachment.
1125
 *
1126
 * What it does:
1127
 *
1128
 * - This includes a check of the topic
1129
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and query_see_board...
1130
 * - Must return the same values and in the same order as getAvatar()
1131
 *
1132
 * @package Attachments
1133
 * @param int $id_attach
1134
 * @param int $id_topic
1135
 */
1136
function getAttachmentFromTopic($id_attach, $id_topic)
1137
{
1138
	$db = database();
1139
1140
	// Make sure this attachment is on this board.
1141
	$request = $db->query('', '
1142
		SELECT a.id_folder, a.filename, a.file_hash, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, m.id_member
1143
		FROM {db_prefix}attachments AS a
1144
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1145
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1146
		WHERE a.id_attach = {int:attach}
1147
		LIMIT 1',
1148
		array(
1149
			'attach' => $id_attach,
1150
			'current_topic' => $id_topic,
1151
		)
1152
	);
1153
1154
	$attachmentData = array();
1155
	if ($db->num_rows($request) != 0)
1156
	{
1157
		$attachmentData = $db->fetch_row($request);
1158
	}
1159
	$db->free_result($request);
1160
1161
	return $attachmentData;
1162
}
1163
1164
/**
1165
 * Get the thumbnail of specified attachment.
1166
 *
1167
 * What it does:
1168
 *
1169
 * - This includes a check of the topic
1170
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and query_see_board...
1171
 * - Must return the same values and in the same order as getAvatar()
1172
 *
1173
 * @package Attachments
1174
 * @param int $id_attach
1175
 * @param int $id_topic
1176
 */
1177
function getAttachmentThumbFromTopic($id_attach, $id_topic)
1178
{
1179
	$db = database();
1180
1181
	// Make sure this attachment is on this board.
1182
	$request = $db->query('', '
1183
		SELECT th.id_folder, th.filename, th.file_hash, th.fileext, th.id_attach, th.attachment_type, th.mime_type,
1184
			a.id_folder AS attach_id_folder, a.filename AS attach_filename,
1185
			a.file_hash AS attach_file_hash, a.fileext AS attach_fileext,
1186
			a.id_attach AS attach_id_attach, a.attachment_type AS attach_attachment_type,
1187
			a.mime_type AS attach_mime_type,
1188
		 	a.approved, m.id_member
1189
		FROM {db_prefix}attachments AS a
1190
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1191
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1192
			LEFT JOIN {db_prefix}attachments AS th ON (th.id_attach = a.id_thumb)
1193
		WHERE a.id_attach = {int:attach}',
1194
		array(
1195
			'attach' => $id_attach,
1196
			'current_topic' => $id_topic,
1197
		)
1198
	);
1199
	$attachmentData = array_fill(0, 9, '');
1200
	if ($db->num_rows($request) != 0)
1201
	{
1202
		$fetch = $db->fetch_assoc($request);
1203
1204
		// If there is a hash then the thumbnail exists
1205
		if (!empty($fetch['file_hash']))
1206
		{
1207
			$attachmentData = array(
1208
				$fetch['id_folder'],
1209
				$fetch['filename'],
1210
				$fetch['file_hash'],
1211
				$fetch['fileext'],
1212
				$fetch['id_attach'],
1213
				$fetch['attachment_type'],
1214
				$fetch['mime_type'],
1215
				$fetch['approved'],
1216
				$fetch['id_member'],
1217
			);
1218
		}
1219
		// otherwise $modSettings['attachmentThumbnails'] may be (or was) off, so original file
1220
		elseif (getValidMimeImageType($fetch['attach_mime_type']) !== '')
1221
		{
1222
			$attachmentData = array(
1223
				$fetch['attach_id_folder'],
1224
				$fetch['attach_filename'],
1225
				$fetch['attach_file_hash'],
1226
				$fetch['attach_fileext'],
1227
				$fetch['attach_id_attach'],
1228
				$fetch['attach_attachment_type'],
1229
				$fetch['attach_mime_type'],
1230
				$fetch['approved'],
1231
				$fetch['id_member'],
1232
			);
1233
		}
1234
	}
1235
	$db->free_result($request);
1236
1237
	return $attachmentData;
1238
}
1239
1240
/**
1241
 * Returns if the given attachment ID is an image file or not
1242
 *
1243
 * What it does:
1244
 *
1245
 * - Given an attachment id, checks that it exists as an attachment
1246
 * - Verifies the message its associated is on a board the user can see
1247
 * - Sets 'is_image' if the attachment is an image file
1248
 * - Returns basic attachment values
1249
 *
1250
 * @package Attachments
1251
 * @param int $id_attach
1252
 *
1253
 * @returns array|boolean
1254
 */
1255
function isAttachmentImage($id_attach)
1256
{
1257
	$db = database();
1258
1259
	// Make sure this attachment is on this board.
1260
	$request = $db->query('', '
1261
		SELECT
1262
			a.filename, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, a.downloads, a.size, a.width, a.height,
1263
			m.id_topic, m.id_board
1264
		FROM {db_prefix}attachments as a
1265
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1266
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1267
		WHERE id_attach = {int:attach}
1268
			AND attachment_type = {int:type}
1269
			AND a.approved = {int:approved}
1270
		LIMIT 1',
1271
		array(
1272
			'attach' => $id_attach,
1273
			'approved' => 1,
1274
			'type' => 0,
1275
		)
1276
	);
1277
	$attachmentData = array();
1278
	if ($db->num_rows($request) != 0)
1279
	{
1280
		$attachmentData = $db->fetch_assoc($request);
1281
		$attachmentData['is_image'] = substr($attachmentData['mime_type'], 0, 5) === 'image';
1282
		$attachmentData['size'] = byte_format($attachmentData['size']);
1283
	}
1284
	$db->free_result($request);
1285
1286
	return !empty($attachmentData) ? $attachmentData : false;
1287
}
1288
1289
/**
1290
 * Increase download counter for id_attach.
1291
 *
1292
 * What it does:
1293
 *
1294
 * - Does not check if it's a thumbnail.
1295
 *
1296
 * @package Attachments
1297
 * @param int $id_attach
1298
 */
1299
function increaseDownloadCounter($id_attach)
1300
{
1301
	$db = database();
1302
1303
	$db->query('attach_download_increase', '
1304
		UPDATE LOW_PRIORITY {db_prefix}attachments
1305
		SET downloads = downloads + 1
1306
		WHERE id_attach = {int:id_attach}',
1307
		array(
1308
			'id_attach' => $id_attach,
1309
		)
1310
	);
1311
}
1312
1313
/**
1314
 * Saves a file and stores it locally for avatar use by id_member.
1315
 *
1316
 * What it does:
1317
 *
1318
 * - supports GIF, JPG, PNG, BMP and WBMP formats.
1319
 * - detects if GD2 is available.
1320
 * - uses resizeImageFile() to resize to max_width by max_height, and saves the result to a file.
1321
 * - updates the database info for the member's avatar.
1322
 * - returns whether the download and resize was successful.
1323
 *
1324
 * @uses subs/Graphics.subs.php
1325
 * @package Attachments
1326
 * @param string $temporary_path the full path to the temporary file
1327
 * @param int $memID member ID
1328
 * @param int $max_width
1329
 * @param int $max_height
1330
 * @return boolean whether the download and resize was successful.
1331
 *
1332
 */
1333
function saveAvatar($temporary_path, $memID, $max_width, $max_height)
1334
{
1335
	global $modSettings;
1336
1337
	$db = database();
1338
1339
	$ext = !empty($modSettings['avatar_download_png']) ? 'png' : 'jpeg';
1340
	$destName = 'avatar_' . $memID . '_' . time() . '.' . $ext;
1341
1342
	// Just making sure there is a non-zero member.
1343
	if (empty($memID))
1344
		return false;
1345
1346
	require_once(SUBSDIR . '/ManageAttachments.subs.php');
1347
	removeAttachments(array('id_member' => $memID));
1348
1349
	$id_folder = getAttachmentPathID();
1350
	$avatar_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, 0, null, true) : '';
1351
	$db->insert('',
1352
		'{db_prefix}attachments',
1353
		array(
1354
			'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int',
1355
			'id_folder' => 'int',
1356
		),
1357
		array(
1358
			$memID, empty($modSettings['custom_avatar_enabled']) ? 0 : 1, $destName, $avatar_hash, $ext, 1,
1359
			$id_folder,
1360
		),
1361
		array('id_attach')
1362
	);
1363
	$attachID = $db->insert_id('{db_prefix}attachments', 'id_attach');
1364
1365
	// First, the temporary file will have the .tmp extension.
1366
	$tempName = getAvatarPath() . '/' . $destName . '.tmp';
1367
1368
	// The destination filename will depend on whether custom dir for avatars has been set
1369
	$destName = getAvatarPath() . '/' . $destName;
1370
	$path = getAttachmentPath();
1371
	$destName = empty($avatar_hash) ? $destName : $path . '/' . $attachID . '_' . $avatar_hash . '.elk';
1372
1373
	// Resize it.
1374
	require_once(SUBSDIR . '/Graphics.subs.php');
1375
	if (!empty($modSettings['avatar_download_png']))
1376
		$success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height, 3);
1377
	else
1378
		$success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height);
1379
1380
	if ($success)
1381
	{
1382
		// Remove the .tmp extension from the attachment.
1383
		if (rename($tempName, $destName))
1384
		{
1385
			list ($width, $height) = elk_getimagesize($destName);
1386
			$mime_type = getValidMimeImageType($ext);
1387
1388
			// Write filesize in the database.
1389
			$db->query('', '
1390
				UPDATE {db_prefix}attachments
1391
				SET size = {int:filesize}, width = {int:width}, height = {int:height},
1392
					mime_type = {string:mime_type}
1393
				WHERE id_attach = {int:current_attachment}',
1394
				array(
1395
					'filesize' => filesize($destName),
1396
					'width' => (int) $width,
1397
					'height' => (int) $height,
1398
					'current_attachment' => $attachID,
1399
					'mime_type' => $mime_type,
1400
				)
1401
			);
1402
1403
			// Retain this globally in case the script wants it.
1404
			$modSettings['new_avatar_data'] = array(
1405
				'id' => $attachID,
1406
				'filename' => $destName,
1407
				'type' => empty($modSettings['custom_avatar_enabled']) ? 0 : 1,
1408
			);
1409
			return true;
1410
		}
1411
		else
1412
			return false;
1413
	}
1414
	else
1415
	{
1416
		$db->query('', '
1417
			DELETE FROM {db_prefix}attachments
1418
			WHERE id_attach = {int:current_attachment}',
1419
			array(
1420
				'current_attachment' => $attachID,
1421
			)
1422
		);
1423
1424
		@unlink($tempName);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1424
		/** @scrutinizer ignore-unhandled */ @unlink($tempName);

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...
1425
		return false;
1426
	}
1427
}
1428
1429
/**
1430
 * Get the size of a specified image with better error handling.
1431
 *
1432
 * What it does:
1433
 *
1434
 * - Uses getimagesize() to determine the size of a file.
1435
 * - Attempts to connect to the server first so it won't time out.
1436
 *
1437
 * @todo see if it's better in subs/Graphics.subs.php, but one step at the time.
1438
 *
1439
 * @package Attachments
1440
 * @param string $url
1441
 * @return array or false, the image size as array(width, height), or false on failure
1442
 */
1443
function url_image_size($url)
1444
{
1445
	// Make sure it is a proper URL.
1446
	$url = str_replace(' ', '%20', $url);
1447
1448
	// Can we pull this from the cache... please please?
1449
	$temp = array();
1450
	if (Cache::instance()->getVar($temp, 'url_image_size-' . md5($url), 240))
1451
		return $temp;
1452
1453
	$t = microtime(true);
1454
1455
	// Get the host to pester...
1456
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
1457
1458
	// Can't figure it out, just try the image size.
1459
	if ($url == '' || $url == 'http://' || $url == 'https://')
1460
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1461
	elseif (!isset($match[1]))
1462
		$size = elk_getimagesize($url, false);
1463
	else
1464
	{
1465
		// Try to connect to the server... give it half a second.
1466
		$temp = 0;
1467
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
1468
1469
		// Successful?  Continue...
1470
		if ($fp !== false)
1471
		{
1472
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
1473
			fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/ELK' . "\r\n" . 'Connection: close' . "\r\n\r\n");
1474
1475
			// Read in the HTTP/1.1 or whatever.
1476
			$test = substr(fgets($fp, 11), -1);
1477
			fclose($fp);
1478
1479
			// See if it returned a 404/403 or something.
1480
			if ($test < 4)
1481
			{
1482
				$size = elk_getimagesize($url, false);
1483
1484
				// This probably means allow_url_fopen is off, let's try GD.
1485
				if ($size === false && function_exists('imagecreatefromstring'))
1486
				{
1487
					include_once(SUBSDIR . '/Package.subs.php');
1488
1489
					// It's going to hate us for doing this, but another request...
1490
					$image = @imagecreatefromstring(fetch_web_data($url));
1491
					if ($image !== false)
1492
					{
1493
						$size = array(imagesx($image), imagesy($image));
1494
						imagedestroy($image);
1495
					}
1496
				}
1497
			}
1498
		}
1499
	}
1500
1501
	// If we didn't get it, we failed.
1502
	if (!isset($size))
1503
		$size = false;
1504
1505
	// If this took a long time, we may never have to do it again, but then again we might...
1506
	if (microtime(true) - $t > 0.8)
1507
		Cache::instance()->put('url_image_size-' . md5($url), $size, 240);
1508
1509
	// Didn't work.
1510
	return $size;
1511
}
1512
1513
/**
1514
 * The current attachments path:
1515
 *
1516
 * What it does:
1517
 *  - BOARDDIR . '/attachments', if nothing is set yet.
1518
 *  - if the forum is using multiple attachments directories,
1519
 *    then the current path is stored as unserialize($modSettings['attachmentUploadDir'])[$modSettings['currentAttachmentUploadDir']]
1520
 *  - otherwise, the current path is $modSettings['attachmentUploadDir'].
1521
 *
1522
 * @package Attachments
1523
 * @return string
1524
 */
1525
function getAttachmentPath()
1526
{
1527
	global $modSettings;
1528
1529
	// Make sure this thing exists and it is unserialized
1530
	if (empty($modSettings['attachmentUploadDir']))
1531
		$attachmentDir = BOARDDIR . '/attachments';
1532
	elseif (!empty($modSettings['currentAttachmentUploadDir']) && !is_array($modSettings['attachmentUploadDir']) && (@unserialize($modSettings['attachmentUploadDir']) !== false))
1533
	{
1534
		// @todo this is here to prevent the package manager to die when complete the installation of the patch (the new Util class has not yet been loaded so we need the normal one)
1535
		if (function_exists('Util::unserialize'))
1536
		{
1537
			$attachmentDir = Util::unserialize($modSettings['attachmentUploadDir']);
1538
		}
1539
		else
1540
		{
1541
			$attachmentDir = unserialize($modSettings['attachmentUploadDir']);
1542
		}
1543
	}
1544
	else
1545
		$attachmentDir = $modSettings['attachmentUploadDir'];
1546
1547
	return is_array($attachmentDir) ? $attachmentDir[$modSettings['currentAttachmentUploadDir']] : $attachmentDir;
1548
}
1549
1550
/**
1551
 * The avatars path: if custom avatar directory is set, that's it.
1552
 * Otherwise, it's attachments path.
1553
 *
1554
 * @package Attachments
1555
 * @return string
1556
 */
1557
function getAvatarPath()
1558
{
1559
	global $modSettings;
1560
1561
	return empty($modSettings['custom_avatar_enabled']) ? getAttachmentPath() : $modSettings['custom_avatar_dir'];
1562
}
1563
1564
/**
1565
 * Little utility function for the $id_folder computation for attachments.
1566
 *
1567
 * What it does:
1568
 *
1569
 * - This returns the id of the folder where the attachment or avatar will be saved.
1570
 * - If multiple attachment directories are not enabled, this will be 1 by default.
1571
 *
1572
 * @package Attachments
1573
 * @return int 1 if multiple attachment directories are not enabled,
1574
 * or the id of the current attachment directory otherwise.
1575
 */
1576
function getAttachmentPathID()
1577
{
1578
	global $modSettings;
1579
1580
	// utility function for the endless $id_folder computation for attachments.
1581
	return !empty($modSettings['currentAttachmentUploadDir']) ? $modSettings['currentAttachmentUploadDir'] : 1;
1582
}
1583
1584
/**
1585
 * Returns the ID of the folder avatars are currently saved in.
1586
 *
1587
 * @package Attachments
1588
 * @return int 1 if custom avatar directory is enabled,
1589
 * and the ID of the current attachment folder otherwise.
1590
 * NB: the latter could also be 1.
1591
 */
1592
function getAvatarPathID()
1593
{
1594
	global $modSettings;
1595
1596
	// Little utility function for the endless $id_folder computation for avatars.
1597
	if (!empty($modSettings['custom_avatar_enabled']))
1598
		return 1;
1599
	else
1600
		return getAttachmentPathID();
1601
}
1602
1603
/**
1604
 * Get all attachments associated with a set of posts.
1605
 *
1606
 * What it does:
1607
 *  - This does not check permissions.
1608
 *
1609
 * @package Attachments
1610
 * @param int[] $messages array of messages ids
1611
 * @param bool $includeUnapproved = false
1612
 * @param string|null $filter name of a callback function
1613
 * @param mixed[] $all_posters
1614
 */
1615
function getAttachments($messages, $includeUnapproved = false, $filter = null, $all_posters = array())
1616
{
1617
	global $modSettings;
1618
1619
	$db = database();
1620
1621
	$attachments = array();
1622
	$request = $db->query('', '
1623
		SELECT
1624
			a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved,
1625
			a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
1626
			COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
1627
			FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
1628
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
1629
		WHERE a.id_msg IN ({array_int:message_list})
1630
			AND a.attachment_type = {int:attachment_type}',
1631
		array(
1632
			'message_list' => $messages,
1633
			'attachment_type' => 0,
1634
		)
1635
	);
1636
	$temp = array();
1637
	while ($row = $db->fetch_assoc($request))
1638
	{
1639
		if (!$row['approved'] && !$includeUnapproved && (empty($filter) || !call_user_func($filter, $row, $all_posters)))
1640
			continue;
1641
1642
		$temp[$row['id_attach']] = $row;
1643
1644
		if (!isset($attachments[$row['id_msg']]))
1645
			$attachments[$row['id_msg']] = array();
1646
	}
1647
	$db->free_result($request);
1648
1649
	// This is better than sorting it with the query...
1650
	ksort($temp);
1651
1652
	foreach ($temp as $row)
1653
		$attachments[$row['id_msg']][] = $row;
1654
1655
	return $attachments;
1656
}
1657
1658
/**
1659
 * Get all avatars information... as long as they're in default directory still?
1660
 * Not currently used
1661
 *
1662
 * @deprecated since 1.0
1663
 *
1664
 * @return mixed[] avatars information
1665
 */
1666
function getAvatarsDefault()
1667
{
1668
	$db = database();
1669
1670
	return $db->fetchQuery('
1671
		SELECT id_attach, id_folder, id_member, filename, file_hash
1672
		FROM {db_prefix}attachments
1673
		WHERE attachment_type = {int:attachment_type}
1674
			AND id_member > {int:guest_id_member}',
1675
		array(
1676
			'attachment_type' => 0,
1677
			'guest_id_member' => 0,
1678
		)
1679
	);
1680
}
1681
1682
/**
1683
 * Recursive function to retrieve server-stored avatar files
1684
 *
1685
 * @package Attachments
1686
 * @param string $directory
1687
 * @param int $level
1688
 * @return array
1689
 */
1690
function getServerStoredAvatars($directory, $level)
1691
{
1692
	global $context, $txt, $modSettings;
1693
1694
	$result = array();
1695
1696
	// Open the directory..
1697
	$dir = dir($modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory);
1698
	$dirs = array();
1699
	$files = array();
1700
1701
	if (!$dir)
1702
		return array();
1703
1704
	while ($line = $dir->read())
1705
	{
1706
		if (in_array($line, array('.', '..', 'blank.png', 'index.php')))
1707
			continue;
1708
1709
		if (is_dir($modSettings['avatar_directory'] . '/' . $directory . (!empty($directory) ? '/' : '') . $line))
1710
			$dirs[] = $line;
1711
		else
1712
			$files[] = $line;
1713
	}
1714
	$dir->close();
1715
1716
	// Sort the results...
1717
	natcasesort($dirs);
1718
	natcasesort($files);
1719
1720
	if ($level == 0)
1721
	{
1722
		$result[] = array(
1723
			'filename' => 'blank.png',
1724
			'checked' => in_array($context['member']['avatar']['server_pic'], array('', 'blank.png')),
1725
			'name' => $txt['no_pic'],
1726
			'is_dir' => false
1727
		);
1728
	}
1729
1730
	foreach ($dirs as $line)
1731
	{
1732
		$tmp = getServerStoredAvatars($directory . (!empty($directory) ? '/' : '') . $line, $level + 1);
1733
		if (!empty($tmp))
1734
			$result[] = array(
1735
				'filename' => htmlspecialchars($line, ENT_COMPAT, 'UTF-8'),
1736
				'checked' => strpos($context['member']['avatar']['server_pic'], $line . '/') !== false,
1737
				'name' => '[' . htmlspecialchars(str_replace('_', ' ', $line), ENT_COMPAT, 'UTF-8') . ']',
1738
				'is_dir' => true,
1739
				'files' => $tmp
1740
		);
1741
		unset($tmp);
1742
	}
1743
1744
	foreach ($files as $line)
1745
	{
1746
		$filename = substr($line, 0, (strlen($line) - strlen(strrchr($line, '.'))));
1747
		$extension = substr(strrchr($line, '.'), 1);
1748
1749
		// Make sure it is an image.
1750
		if (getValidMimeImageType($extension) === '')
1751
			continue;
1752
1753
		$result[] = array(
1754
			'filename' => htmlspecialchars($line, ENT_COMPAT, 'UTF-8'),
1755
			'checked' => $line == $context['member']['avatar']['server_pic'],
1756
			'name' => htmlspecialchars(str_replace('_', ' ', $filename), ENT_COMPAT, 'UTF-8'),
1757
			'is_dir' => false
1758
		);
1759
		if ($level == 1)
1760
			$context['avatar_list'][] = $directory . '/' . $line;
1761
	}
1762
1763
	return $result;
1764
}
1765
1766
/**
1767
 * Update an attachment's thumbnail
1768
 *
1769
 * @package Attachments
1770
 * @param string $filename
1771
 * @param int $id_attach
1772
 * @param int $id_msg
1773
 * @param int $old_id_thumb = 0
1774
 * @return array The updated information
1775
 */
1776
function updateAttachmentThumbnail($filename, $id_attach, $id_msg, $old_id_thumb = 0, $real_filename = '')
1777
{
1778
	global $modSettings;
1779
1780
	$attachment = array('id_attach' => $id_attach);
1781
1782
	require_once(SUBSDIR . '/Graphics.subs.php');
1783
	if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
1784
	{
1785
		// So what folder are we putting this image in?
1786
		$id_folder_thumb = getAttachmentPathID();
1787
1788
		// Calculate the size of the created thumbnail.
1789
		$size = elk_getimagesize($filename . '_thumb');
1790
		list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
1791
		$thumb_size = filesize($filename . '_thumb');
1792
1793
		// Figure out the mime type.
1794
		if (!empty($size['mime']))
1795
		{
1796
			$thumb_mime = $size['mime'];
1797
		}
1798
		else
1799
		{
1800
			$thumb_mime = getValidMimeImageType($size[2]);
1801
		}
1802
		$thumb_ext = substr($thumb_mime, strpos($thumb_mime, '/') + 1);
1803
1804
		$thumb_filename = (!empty($real_filename) ? $real_filename : $filename) . '_thumb';
1805
		$thumb_hash = getAttachmentFilename($thumb_filename, 0, null, true);
1806
1807
		$db = database();
1808
1809
		// Add this beauty to the database.
1810
		$db->insert('',
1811
			'{db_prefix}attachments',
1812
			array('id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'size' => 'int', 'width' => 'int', 'height' => 'int', 'fileext' => 'string-8', 'mime_type' => 'string-255'),
1813
			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),
1814
			array('id_attach')
1815
		);
1816
1817
		$attachment['id_thumb'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
1818
		if (!empty($attachment['id_thumb']))
1819
		{
1820
			$db->query('', '
1821
				UPDATE {db_prefix}attachments
1822
				SET id_thumb = {int:id_thumb}
1823
				WHERE id_attach = {int:id_attach}',
1824
				array(
1825
					'id_thumb' => $attachment['id_thumb'],
1826
					'id_attach' => $attachment['id_attach'],
1827
				)
1828
			);
1829
1830
			$thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
1831
			rename($filename . '_thumb', $thumb_realname);
1832
1833
			// Do we need to remove an old thumbnail?
1834
			if (!empty($old_id_thumb))
1835
			{
1836
				require_once(SUBSDIR . '/ManageAttachments.subs.php');
1837
				removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1838
			}
1839
		}
1840
	}
1841
1842
	return $attachment;
1843
}
1844
1845
/**
1846
 * Compute and return the total size of attachments to a single message.
1847
 *
1848
 * @package Attachments
1849
 * @param int $id_msg
1850
 * @param bool $include_count = true if true, it also returns the attachments count
1851
 */
1852
function attachmentsSizeForMessage($id_msg, $include_count = true)
1853
{
1854
	$db = database();
1855
1856
	if ($include_count)
1857
	{
1858
		$request = $db->query('', '
1859
			SELECT COUNT(*), SUM(size)
1860
			FROM {db_prefix}attachments
1861
			WHERE id_msg = {int:id_msg}
1862
				AND attachment_type = {int:attachment_type}',
1863
			array(
1864
				'id_msg' => $id_msg,
1865
				'attachment_type' => 0,
1866
			)
1867
		);
1868
	}
1869
	else
1870
	{
1871
		$request = $db->query('', '
1872
			SELECT COUNT(*)
1873
			FROM {db_prefix}attachments
1874
			WHERE id_msg = {int:id_msg}
1875
				AND attachment_type = {int:attachment_type}',
1876
			array(
1877
				'id_msg' => $id_msg,
1878
				'attachment_type' => 0,
1879
			)
1880
		);
1881
	}
1882
	$size = $db->fetch_row($request);
1883
	$db->free_result($request);
1884
1885
	return $size;
1886
}
1887
1888
/**
1889
 * This loads an attachment's contextual data including, most importantly, its size if it is an image.
1890
 *
1891
 * What it does:
1892
 *
1893
 * - Pre-condition: $attachments array to have been filled with the proper attachment data, as Display() does.
1894
 * - It requires the view_attachments permission to calculate image size.
1895
 * - It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by
1896
 * the max_image_width and max_image_height settings.
1897
 *
1898
 * @todo change this pre-condition, too fragile and error-prone.
1899
 *
1900
 * @package Attachments
1901
 * @param int $id_msg message number to load attachments for
1902
 * @return array of attachments
1903
 */
1904
function loadAttachmentContext($id_msg)
1905
{
1906
	global $attachments, $modSettings, $scripturl, $topic;
1907
1908
	// Set up the attachment info - based on code by Meriadoc.
1909
	$attachmentData = array();
1910
	$have_unapproved = false;
1911
	if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable']))
1912
	{
1913
		foreach ($attachments[$id_msg] as $i => $attachment)
1914
		{
1915
			$attachmentData[$i] = array(
1916
				'id' => $attachment['id_attach'],
1917
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8')),
1918
				'downloads' => $attachment['downloads'],
1919
				'size' => byte_format($attachment['filesize']),
1920
				'byte_size' => $attachment['filesize'],
1921
				'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'],
1922
				'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8') . '</a>',
1923
				'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1924
				'is_approved' => $attachment['approved'],
1925
				'file_hash' => $attachment['file_hash'],
1926
			);
1927
1928
			// If something is unapproved we'll note it so we can sort them.
1929
			if (!$attachment['approved'])
1930
				$have_unapproved = true;
1931
1932
			if (!$attachmentData[$i]['is_image'])
1933
				continue;
1934
1935
			$attachmentData[$i]['real_width'] = $attachment['width'];
1936
			$attachmentData[$i]['width'] = $attachment['width'];
1937
			$attachmentData[$i]['real_height'] = $attachment['height'];
1938
			$attachmentData[$i]['height'] = $attachment['height'];
1939
1940
			// Let's see, do we want thumbs?
1941
			if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1942
			{
1943
				// A proper thumb doesn't exist yet? Create one! Or, it needs update.
1944
				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']))
1945
				{
1946
					$filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder'], false, $attachment['file_hash']);
1947
					$attachment = array_merge($attachment, updateAttachmentThumbnail($filename, $attachment['id_attach'], $id_msg, $attachment['id_thumb'], $attachment['filename']));
1948
				}
1949
1950
				// Only adjust dimensions on successful thumbnail creation.
1951
				if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1952
				{
1953
					$attachmentData[$i]['width'] = $attachment['thumb_width'];
1954
					$attachmentData[$i]['height'] = $attachment['thumb_height'];
1955
				}
1956
			}
1957
1958
			if (!empty($attachment['id_thumb']))
1959
			{
1960
				$attachmentData[$i]['thumbnail'] = array(
1961
					'id' => $attachment['id_thumb'],
1962
					'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_thumb'] . ';image',
1963
				);
1964
			}
1965
			$attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1966
1967
			// If thumbnails are disabled, check the maximum size of the image.
1968
			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'])))
1969
			{
1970
				if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1971
				{
1972
					$attachmentData[$i]['width'] = $modSettings['max_image_width'];
1973
					$attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1974
				}
1975
				elseif (!empty($modSettings['max_image_width']))
1976
				{
1977
					$attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1978
					$attachmentData[$i]['height'] = $modSettings['max_image_height'];
1979
				}
1980
			}
1981
			elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1982
			{
1983
				// Data attributes for use in expandThumb
1984
				$attachmentData[$i]['thumbnail']['lightbox'] = 'data-lightboxmessage="' . $id_msg . '" data-lightboximage="' . $attachment['id_attach'] . '"';
1985
1986
				// If the image is too large to show inline, make it a popup.
1987
				// @todo this needs to be removed or depreciated
1988
				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'])))
1989
					$attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);';
1990
				else
1991
					$attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1992
			}
1993
1994
			if (!$attachmentData[$i]['thumbnail']['has_thumb'])
1995
				$attachmentData[$i]['downloads']++;
1996
		}
1997
	}
1998
1999
	// Do we need to instigate a sort?
2000
	if ($have_unapproved)
2001
		usort($attachmentData, 'approved_attach_sort');
2002
2003
	return $attachmentData;
2004
}
2005
2006
/**
2007
 * A sort function for putting unapproved attachments first.
2008
 *
2009
 * @package Attachments
2010
 * @param mixed[] $a
2011
 * @param mixed[] $b
2012
 * @return int -1, 0, 1
2013
 */
2014
function approved_attach_sort($a, $b)
2015
{
2016
	if ($a['is_approved'] == $b['is_approved'])
2017
		return 0;
2018
2019
	return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
2020
}
2021
2022
/**
2023
 * Callback filter for the retrieval of attachments.
2024
 *
2025
 * What it does:
2026
 * This function returns false when:
2027
 *  - the attachment is unapproved, and
2028
 *  - the viewer is not the poster of the message where the attachment is
2029
 *
2030
 * @package Attachments
2031
 * @param mixed[] $attachment_info
2032
 * @param mixed[] $all_posters
2033
 */
2034
function filter_accessible_attachment($attachment_info, $all_posters)
2035
{
2036
	global $user_info;
2037
2038
	return !(!$attachment_info['approved'] && (!isset($all_posters[$attachment_info['id_msg']]) || $all_posters[$attachment_info['id_msg']] != $user_info['id']));
2039
}
2040
2041
/**
2042
 * Older attachments may still use this function.
2043
 *
2044
 * @package Attachments
2045
 * @param string $filename
2046
 * @param int $attachment_id
2047
 * @param string|null $dir
2048
 * @param boolean $new
2049
 */
2050
function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false)
2051
{
2052
	global $modSettings;
2053
2054
	$clean_name = $filename;
2055
2056
	// Sorry, no spaces, dots, or anything else but letters allowed.
2057
	$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
2058
2059
	$enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
2060
	$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
2061
2062
	if (empty($attachment_id) || ($new && empty($modSettings['attachmentEncryptFilenames'])))
2063
		return $clean_name;
2064
	elseif ($new)
2065
		return $enc_name;
2066
2067
	// Are we using multiple directories?
2068
	if (!empty($modSettings['currentAttachmentUploadDir']))
2069
	{
2070
		if (!is_array($modSettings['attachmentUploadDir']))
2071
			$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
2072
		$path = $modSettings['attachmentUploadDir'][$dir];
2073
	}
2074
	else
2075
		$path = $modSettings['attachmentUploadDir'];
2076
2077
	if (file_exists($path . '/' . $enc_name))
2078
		$filename = $path . '/' . $enc_name;
2079
	else
2080
		$filename = $path . '/' . $clean_name;
2081
2082
	return $filename;
2083
}
2084
2085
/**
2086
 * Binds a set of attachments to a message.
2087
 *
2088
 * @package Attachments
2089
 * @param int $id_msg
2090
 * @param int[] $attachment_ids
2091
 */
2092
function bindMessageAttachments($id_msg, $attachment_ids)
2093
{
2094
	$db = database();
2095
2096
	$db->query('', '
2097
		UPDATE {db_prefix}attachments
2098
		SET id_msg = {int:id_msg}
2099
		WHERE id_attach IN ({array_int:attachment_list})',
2100
		array(
2101
			'attachment_list' => $attachment_ids,
2102
			'id_msg' => $id_msg,
2103
		)
2104
	);
2105
}
2106
2107
/**
2108
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
2109
 *
2110
 * - If new is set returns a hash for the db
2111
 * - If no file hash is supplied, determines one and returns it
2112
 * - Returns the path to the file
2113
 *
2114
 * @todo this currently returns the hash if new, and the full filename otherwise.
2115
 * Something messy like that.
2116
 * @todo and of course everything relies on this behavior and work around it. :P.
2117
 * Converters included.
2118
 *
2119
 * @param string $filename The name of the file
2120
 * @param int|null $attachment_id The ID of the attachment
2121
 * @param string|null $dir Which directory it should be in (null to use current)
2122
 * @param bool $new If this is a new attachment, if so just returns a hash
2123
 * @param string $file_hash The file hash
2124
 */
2125
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
2126
{
2127
	global $modSettings;
2128
2129
	// Just make up a nice hash...
2130
	if ($new)
2131
		return hash('sha1', hash('md5', $filename . time()) . mt_rand());
2132
2133
	// In case of files from the old system, do a legacy call.
2134
	if (empty($file_hash))
2135
	{
2136
		return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new);
2137
	}
2138
2139
	// Are we using multiple directories?
2140
	if (!empty($modSettings['currentAttachmentUploadDir']))
2141
	{
2142
		if (!is_array($modSettings['attachmentUploadDir']))
2143
			$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
2144
		$path = isset($modSettings['attachmentUploadDir'][$dir]) ? $modSettings['attachmentUploadDir'][$dir] : $modSettings['basedirectory_for_attachments'];
2145
	}
2146
	else
2147
		$path = $modSettings['attachmentUploadDir'];
2148
2149
	return $path . '/' . $attachment_id . '_' . $file_hash . '.elk';
2150
}
2151
2152
/**
2153
 * Returns the board and the topic the attachment belongs to.
2154
 *
2155
 * @package Attachments
2156
 * @param int $id_attach
2157
 * @return int[]|boolean on fail else an array of id_board, id_topic
2158
 */
2159
function getAttachmentPosition($id_attach)
2160
{
2161
	$db = database();
2162
2163
	// Make sure this attachment is on this board.
2164
	$request = $db->query('', '
2165
		SELECT m.id_board, m.id_topic
2166
		FROM {db_prefix}attachments AS a
2167
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
2168
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
2169
		WHERE a.id_attach = {int:attach}
2170
			AND {query_see_board}
2171
		LIMIT 1',
2172
		array(
2173
			'attach' => $id_attach,
2174
		)
2175
	);
2176
2177
	$attachmentData = $db->fetch_assoc($request);
2178
	$db->free_result($request);
2179
2180
	if (empty($attachmentData))
2181
	{
2182
		return false;
2183
	}
2184
	else
2185
	{
2186
		return $attachmentData;
2187
	}
2188
}
2189
2190
/**
2191
 * Simple wrapper for getimagesize
2192
 *
2193
 * @param string $file
2194
 * @param string|boolean $error return array or false on error
2195
 *
2196
 * @return array|boolean
2197
 */
2198
function elk_getimagesize($file, $error = 'array')
2199
{
2200
	$sizes = @getimagesize($file);
2201
2202
	// Can't get it, what shall we return
2203
	if (empty($sizes))
2204
	{
2205
		if ($error === 'array')
2206
		{
2207
			$sizes = array(-1, -1, -1);
2208
		}
2209
		else
2210
		{
2211
			$sizes = false;
2212
		}
2213
	}
2214
2215
	return $sizes;
2216
}
2217
2218
/**
2219
 * Checks if we have a known and support mime-type for which we have a thumbnail image
2220
 *
2221
 * @param string $file_ext
2222
 * @param bool $url
2223
 *
2224
 * @return bool|string
2225
 */
2226
function returnMimeThumb($file_ext, $url = false)
2227
{
2228
	global $settings;
2229
2230
	// These are not meant to be exhaustive, just some of the most common attached on a forum
2231
	static $generics = array(
2232
		'arc' => array('tgz', 'zip', 'rar', '7z', 'gz'),
2233
		'doc' =>array('doc', 'docx', 'wpd', 'odt'),
2234
		'sound' => array('wav', 'mp3', 'pcm', 'aiff', 'wma', 'm4a'),
2235
		'video' => array('mp4', 'mgp', 'mpeg', 'mp4', 'wmv', 'flv', 'aiv', 'mov', 'swf'),
2236
		'txt' => array('rtf', 'txt', 'log'),
2237
		'presentation' => array('ppt', 'pps', 'odp'),
2238
		'spreadsheet' => array('xls', 'xlr', 'ods'),
2239
		'web' => array('html', 'htm')
2240
	);
2241
	foreach ($generics as $generic_extension => $generic_types)
2242
	{
2243
		if (in_array($file_ext, $generic_types))
2244
		{
2245
			$file_ext = $generic_extension;
2246
			break;
2247
		}
2248
	}
2249
2250
	static $distinct = array('arc', 'doc', 'sound', 'video', 'txt', 'presentation', 'spreadsheet', 'web',
2251
		'c', 'cpp', 'css', 'csv', 'java', 'js', 'pdf', 'php', 'sql', 'xml');
2252
2253
	if (empty($settings))
2254
	{
2255
		loadEssentialThemeData();
2256
	}
2257
2258
	// Return the mine thumbnail if it exists or just the default
2259
	if (!in_array($file_ext, $distinct) || !file_exists($settings['theme_dir'] . '/images/mime_images/' . $file_ext . '.png'))
2260
	{
2261
		$file_ext = 'default';
2262
	}
2263
2264
	$location = $url ? $settings['theme_url'] : $settings['theme_dir'];
2265
	$filename = $location . '/images/mime_images/' . $file_ext . '.png';
2266
2267
	return $filename;
2268
}
2269
2270
/**
2271
 * Finds in $_SESSION['temp_attachments'] an attachment id from its public id
2272
 *
2273
 * @param string $public_attachid
2274
 *
2275
 * @return string
2276
 */
2277
function getAttachmentIdFromPublic($public_attachid)
2278
{
2279
	if (empty($_SESSION['temp_attachments']))
2280
	{
2281
		return $public_attachid;
2282
	}
2283
2284
	foreach ($_SESSION['temp_attachments'] as $key => $val)
2285
	{
2286
		if (isset($val['public_attachid']) && $val['public_attachid'] === $public_attachid)
2287
		{
2288
			return $key;
2289
		}
2290
	}
2291
	return $public_attachid;
2292
}
2293
2294
/**
2295
 * From either a mime type, an extension or an IMAGETYPE_* constant
2296
 * returns a valid image mime type
2297
 *
2298
 * @param string $mime
2299
 *
2300
 * @return string
2301
 */
2302
function getValidMimeImageType($mime)
2303
{
2304
	// These are the only valid image types.
2305
	static $validImageTypes = array(
2306
		-1 => 'jpg',
2307
		// Starting from here are the IMAGETYPE_* constants
2308
		1 => 'gif',
2309
		2 => 'jpeg',
2310
		3 => 'png',
2311
		5 => 'psd',
2312
		6 => 'bmp',
2313
		7 => 'tiff',
2314
		8 => 'tiff',
2315
		9 => 'jpeg',
2316
		14 => 'iff'
2317
	);
2318
2319
	if ((int) $mime > 0)
2320
	{
2321
		$ext = isset($validImageTypes[$mime]) ? $validImageTypes[$mime] : '';
2322
	}
2323
	elseif (strpos($mime, '/'))
2324
	{
2325
		$ext = substr($mime, strpos($mime, '/') + 1);
2326
	}
2327
	else
2328
	{
2329
		$ext = $mime;
2330
	}
2331
	$ext = strtolower($ext);
2332
2333
	foreach ($validImageTypes as $valid_ext)
2334
	{
2335
		if ($valid_ext === $ext)
2336
		{
2337
			return 'image/' . $ext;
2338
		}
2339
	}
2340
2341
	return '';
2342
}
2343
2344
/**
2345
 * This function returns the mimeType of a file using the best means available
2346
 *
2347
 * @param string $filename
2348
 * @return bool|mixed|string
2349
 */
2350
function get_finfo_mime($filename)
2351
{
2352
	$mimeType = false;
2353
2354
	// Check only existing readable files
2355
	if (!file_exists($filename) || !is_readable($filename))
2356
	{
2357
		return $mimeType;
2358
	}
2359
2360
	// Try finfo, this is the preferred way
2361
	if (function_exists('finfo_open'))
2362
	{
2363
		$finfo = finfo_open(FILEINFO_MIME);
2364
		$mimeType = finfo_file($finfo, $filename);
2365
		finfo_close($finfo);
2366
	}
2367
	// No finfo? What? lets try the old mime_content_type
2368
	elseif (function_exists('mime_content_type'))
2369
	{
2370
		$mimeType = mime_content_type($filename);
2371
	}
2372
	// Try using an exec call
2373
	elseif (function_exists('exec'))
2374
	{
2375
		$mimeType = @exec("/usr/bin/file -i -b $filename");
2376
	}
2377
2378
	// Still nothing? We should at least be able to get images correct
2379
	if (empty($mimeType))
2380
	{
2381
		$imageData = elk_getimagesize($filename, 'none');
2382
		if (!empty($imageData['mime']))
2383
		{
2384
			$mimeType = $imageData['mime'];
2385
		}
2386
	}
2387
2388
	// Account for long responses like text/plain; charset=us-ascii
2389
	if (!empty($mimeType) && strpos($mimeType, ';'))
2390
	{
2391
		list($mimeType,) = explode(';', $mimeType);
2392
	}
2393
2394
	return $mimeType;
2395
}
2396