Completed
Pull Request — master (#3237)
by Emanuele
14:50
created

Attachments.subs.php ➔ get_finfo_mime()   B

Complexity

Conditions 10
Paths 25

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
nc 25
nop 1
dl 0
loc 46
rs 7.3115
c 0
b 0
f 0
ccs 0
cts 29
cp 0
crap 110

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.5
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 View Code Duplication
		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 View Code Duplication
	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 View Code Duplication
		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 View Code Duplication
		case 4:
99
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
100
			break;
101 View Code Duplication
		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
Bug introduced by
The variable $outputCreation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
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);
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 View Code Duplication
	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 View Code Duplication
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
402
		{
403
			if (!empty($dummy))
404
			{
405
				$ignore_temp = false;
406
				break;
407
			}
408
		}
409
410
		// Need to make space for the new files. So, bye bye.
411
		if (!$ignore_temp)
412
		{
413 View Code Duplication
			foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
414
			{
415
				if (strpos($attachID, 'post_tmp_' . $user_info['id'] . '_') !== false)
416
					@unlink($attachment['tmp_name']);
417
			}
418
419
			$attach_errors->activate()->addError('temp_attachments_flushed');
420
			$_SESSION['temp_attachments'] = array();
421
		}
422
	}
423
424
	if (!isset($_FILES['attachment']['name']))
425
		$_FILES['attachment']['tmp_name'] = array();
426
427
	if (!isset($_SESSION['temp_attachments']))
428
		$_SESSION['temp_attachments'] = array();
429
430
	// Remember where we are at. If it's anywhere at all.
431
	if (!$ignore_temp)
432
		$_SESSION['temp_attachments']['post'] = array(
433
			'msg' => !empty($id_msg) ? $id_msg : 0,
434
			'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
435
			'topic' => !empty($topic) ? $topic : 0,
436
			'board' => !empty($board) ? $board : 0,
437
		);
438
439
	// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
440
	foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
441
	{
442
		if ($_FILES['attachment']['name'][$n] == '')
443
			continue;
444
445
		// If we have an initial error, lets just display it.
446
		if (!empty($initial_error) && $added_initial_error === false)
447
		{
448
			$added_initial_error = true;
449
			$_SESSION['temp_attachments']['initial_error'] = $initial_error;
450
451
			// This is a generic error
452
			$attach_errors->activate();
453
			$attach_errors->addError('attach_no_upload');
454
			// @todo This is likely the result of some refactoring, verify when $attachment is not set and why
455
			if (isset($attachment))
456
			{
457
				$attach_errors->addError(is_array($attachment) ? array($attachment[0], $attachment[1]) : $attachment);
458
			}
459
460
			// And delete the files 'cos they ain't going nowhere.
461
			foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
462
			{
463 View Code Duplication
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
464
					unlink($_FILES['attachment']['tmp_name'][$n]);
465
			}
466
467
			$_FILES['attachment']['tmp_name'] = array();
468
		}
469
470
		// First, let's first check for PHP upload errors.
471
		$errors = attachmentUploadChecks($n);
472
473
		// Set the names and destination for this file
474
		$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
475
		$destName = $context['attach_dir'] . '/' . $attachID;
476
477
		// If we are error free, Try to move and rename the file before doing more checks on it.
478
		if (empty($errors))
479
		{
480
			$_SESSION['temp_attachments'][$attachID] = array(
481
				'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'),
482
				'tmp_name' => $destName,
483
				'attachid' => $attachID,
484
				'public_attachid' => 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand()),
485
				'size' => $_FILES['attachment']['size'][$n],
486
				'type' => $_FILES['attachment']['type'][$n],
487
				'id_folder' => $modSettings['currentAttachmentUploadDir'],
488
				'errors' => array(),
489
			);
490
491
			// Move the file to the attachments folder with a temp name for now.
492
			if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
493
				@chmod($destName, 0644);
494
			else
495
			{
496
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
497 View Code Duplication
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
498
					unlink($_FILES['attachment']['tmp_name'][$n]);
499
			}
500
		}
501
		// Upload error(s) were detected, flag the error, remove the file
502
		else
503
		{
504
			$_SESSION['temp_attachments'][$attachID] = array(
505
				'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n]), ENT_COMPAT, 'UTF-8'),
506
				'tmp_name' => $destName,
507
				'errors' => $errors,
508
			);
509
510 View Code Duplication
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
511
				unlink($_FILES['attachment']['tmp_name'][$n]);
512
		}
513
514
		// If there were no errors to this point, we apply some additional checks
515
		if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
516
			attachmentChecks($attachID);
517
518
		// Want to correct for phonetographer photos?
519
		if (!empty($modSettings['attachment_autorotate']) && empty($_SESSION['temp_attachments'][$attachID]['errors']) && substr($_SESSION['temp_attachments'][$attachID]['type'], 0, 5) === 'image')
520
		{
521
			autoRotateImage($_SESSION['temp_attachments'][$attachID]['tmp_name']);
522
		}
523
524
		// Sort out the errors for display and delete any associated files.
525
		if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
526
		{
527
			$attach_errors->addAttach($attachID, $_SESSION['temp_attachments'][$attachID]['name']);
528
			$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');
529
530
			foreach ($_SESSION['temp_attachments'][$attachID]['errors'] as $error)
531
			{
532
				if (!is_array($error))
533
				{
534
					$attach_errors->addError($error);
535
					if (in_array($error, $log_these))
536
					{
537
						Errors::instance()->log_error($_SESSION['temp_attachments'][$attachID]['name'] . ': ' . $txt[$error], 'critical');
538
539
						// For critical errors, we don't want the file or session data to persist
540
						if (file_exists($_SESSION['temp_attachments'][$attachID]['tmp_name']))
541
						{
542
							unlink($_SESSION['temp_attachments'][$attachID]['tmp_name']);
543
						}
544
						unset($_SESSION['temp_attachments'][$attachID]);
545
					}
546
				}
547
				else
548
					$attach_errors->addError(array($error[0], $error[1]));
549
			}
550
		}
551
	}
552
553
	// Mod authors, finally a hook to hang an alternate attachment upload system upon
554
	// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
555
	// Populate $_SESSION['temp_attachments'][$attachID] with the following:
556
	//   name => The file name
557
	//   tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
558
	//   size => File size (required).
559
	//   type => MIME type (optional if not available on upload).
560
	//   id_folder => $modSettings['currentAttachmentUploadDir']
561
	//   errors => An array of errors (use the index of the $txt variable for that error).
562
	// Template changes can be done using "integrate_upload_template".
563
	call_integration_hook('integrate_attachment_upload');
564
}
565
566
/**
567
 * Deletes a temporary attachment from the $_SESSION (and the filesystem)
568
 *
569
 * @package Attachments
570
 * @param string $attach_id the temporary name generated when a file is uploaded
571
 *               and used in $_SESSION to help identify the attachment itself
572
 */
573
function removeTempAttachById($attach_id)
574
{
575
	foreach ($_SESSION['temp_attachments'] as $attachID => $attach)
576
	{
577
		if ($attachID === $attach_id)
578
		{
579
			// This file does exist, so lets terminate it!
580
			if (file_exists($attach['tmp_name']))
581
			{
582
				@unlink($attach['tmp_name']);
583
				unset($_SESSION['temp_attachments'][$attachID]);
584
585
				return true;
586
			}
587
			// Nope can't delete it if we can't find it
588
			else
589
				return 'attachment_not_found';
590
		}
591
	}
592
593
	return 'attachment_not_found';
594
}
595
596
/**
597
 * Finds and return a temporary attachment by its id
598
 *
599
 * @package Attachments
600
 * @param string $attach_id the temporary name generated when a file is uploaded
601
 *  and used in $_SESSION to help identify the attachment itself
602
 *
603
 * @return mixed
604
 * @throws Exception
605
 */
606
function getTempAttachById($attach_id)
607
{
608
	global $modSettings, $user_info;
609
610
	$attach_real_id = null;
611
612
	if (empty($_SESSION['temp_attachments']))
613
	{
614
		throw new \Exception('no_access');
615
	}
616
617
	foreach ($_SESSION['temp_attachments'] as $attachID => $val)
618
	{
619
		if ($attachID === 'post')
620
		{
621
			continue;
622
		}
623
624
		if ($val['public_attachid'] === $attach_id)
625
		{
626
			$attach_real_id = $attachID;
627
			break;
628
		}
629
	}
630
631
	if (empty($attach_real_id))
632
	{
633
		throw new \Exception('no_access');
634
	}
635
636
	// The common name form is "post_tmp_123_0ac9a0b1fc18604e8704084656ed5f09"
637
	$id_attach = preg_replace('~[^0-9a-zA-Z_]~', '', $attach_real_id);
638
639
	// Permissions: only temporary attachments
640
	if (substr($id_attach, 0, 8) !== 'post_tmp')
641
		throw new \Exception('no_access');
642
643
	// Permissions: only author is allowed.
644
	$pieces = explode('_', substr($id_attach, 9));
645
646
	if (!isset($pieces[0]) || $pieces[0] != $user_info['id'])
647
		throw new \Exception('no_access');
648
649
	if (is_array($modSettings['attachmentUploadDir']))
650
		$dirs = $modSettings['attachmentUploadDir'];
651
	else
652
		$dirs = unserialize($modSettings['attachmentUploadDir']);
653
654
	$attach_dir = $dirs[$modSettings['currentAttachmentUploadDir']];
655
656
	if (file_exists($attach_dir . '/' . $attach_real_id) && isset($_SESSION['temp_attachments'][$attach_real_id]))
657
	{
658
		return $_SESSION['temp_attachments'][$attach_real_id];
659
	}
660
661
	throw new \Exception('no_access');
662
}
663
664
/**
665
 * Checks if an uploaded file produced any appropriate error code
666
 *
667
 * What it does:
668
 *
669
 * - Checks for error codes in the error segment of the file array that is
670
 * created by PHP during the file upload.
671
 *
672
 * @package Attachments
673
 * @param int $attachID
674
 */
675
function attachmentUploadChecks($attachID)
676
{
677
	global $modSettings, $txt;
678
679
	$errors = array();
680
681
	// Did PHP create any errors during the upload processing of this file?
682
	if (!empty($_FILES['attachment']['error'][$attachID]))
683
	{
684
		// The file exceeds the max_filesize directive in php.ini
685
		if ($_FILES['attachment']['error'][$attachID] == 1)
686
			$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
687
		// The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.
688
		elseif ($_FILES['attachment']['error'][$attachID] == 2)
689
			$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
690
		// Missing or a full a temp directory on the server
691 View Code Duplication
		elseif ($_FILES['attachment']['error'][$attachID] == 6)
692
			Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_6'], 'critical');
693
		// One of many errors such as (3)partially uploaded, (4)empty file,
694 View Code Duplication
		else
695
			Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$attachID]]);
696
697
		// If we did not set an user error (3,4,6,7,8) to show then give them a generic one as there is
698
		// no need to provide back specifics of a server error, those are logged
699
		if (empty($errors))
700
			$errors[] = 'attach_php_error';
701
	}
702
703
	return $errors;
704
}
705
706
/**
707
 * Performs various checks on an uploaded file.
708
 *
709
 * What it does:
710
 *
711
 * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
712
 *
713
 * @package Attachments
714
 *
715
 * @param int $attachID id of the attachment to check
716
 *
717
 * @return bool
718
 * @throws Elk_Exception attach_check_nag
719
 */
720
function attachmentChecks($attachID)
721
{
722
	global $modSettings, $context, $attachmentOptions;
723
724
	$db = database();
725
726
	// No data or missing data .... Not necessarily needed, but in case a mod author missed something.
727
	if (empty($_SESSION['temp_attachments'][$attachID]))
728
		$error = '$_SESSION[\'temp_attachments\'][$attachID]';
729
	elseif (empty($attachID))
730
		$error = '$attachID';
731
	elseif (empty($context['attachments']))
732
		$error = '$context[\'attachments\']';
733
	elseif (empty($context['attach_dir']))
734
		$error = '$context[\'attach_dir\']';
735
736
	// Let's get their attention.
737
	if (!empty($error))
738
		throw new Elk_Exception('attach_check_nag', 'debug', array($error));
739
740
	// Just in case this slipped by the first checks, we stop it here and now
741
	if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
742
	{
743
		$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
744
		return false;
745
	}
746
747
	// First, the dreaded security check. Sorry folks, but this should't be avoided
748
	$size = elk_getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
749
	$valid_mime = getValidMimeImageType($size[2]);
750
751
	if ($valid_mime !== '')
752
	{
753
		require_once(SUBSDIR . '/Graphics.subs.php');
754
		if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
755
		{
756
			// It's bad. Last chance, maybe we can re-encode it?
757
			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
758
			{
759
				// Nothing to do: not allowed or not successful re-encoding it.
760
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
761
				return false;
762
			}
763
764
			// Success! However, successes usually come for a price:
765
			// we might get a new format for our image...
766
			$old_format = $size[2];
767
			$size = elk_getimagesize($attachmentOptions['tmp_name']);
768
769
			if (!(empty($size)) && ($size[2] !== $old_format))
770
			{
771
				$valid_mime = getValidMimeImageType($size[2]);
772
				if ($valid_mime !== '')
773
				{
774
					$_SESSION['temp_attachments'][$attachID]['type'] = $valid_mime;
775
				}
776
			}
777
		}
778
	}
779
780
	// Is there room for this in the directory?
781
	if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
782
	{
783
		// Check the folder size and count. If it hasn't been done already.
784
		if (empty($context['dir_size']) || empty($context['dir_files']))
785
		{
786
			$request = $db->query('', '
787
				SELECT COUNT(*), SUM(size)
788
				FROM {db_prefix}attachments
789
				WHERE id_folder = {int:folder_id}
790
					AND attachment_type != {int:type}',
791
				array(
792
					'folder_id' => $modSettings['currentAttachmentUploadDir'],
793
					'type' => 1,
794
				)
795
			);
796
			list ($context['dir_files'], $context['dir_size']) = $db->fetch_row($request);
797
			$db->free_result($request);
798
		}
799
		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
800
		$context['dir_files']++;
801
802
		// Are we about to run out of room? Let's notify the admin then.
803
		if (empty($modSettings['attachment_full_notified']) && !empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
804
			|| (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
805
		{
806
			require_once(SUBSDIR . '/Admin.subs.php');
807
			emailAdmins('admin_attachments_full');
808
			updateSettings(array('attachment_full_notified' => 1));
809
		}
810
811
		// No room left.... What to do now???
812
		if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit']
813
			|| (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
814
		{
815
			// If we are managing the directories space automatically, lets get to it
816
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
817
			{
818
				// Move it to the new folder if we can.
819
				if (automanage_attachments_by_space())
820
				{
821
					rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
822
					$_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
823
					$_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
824
					$context['dir_size'] = 0;
825
					$context['dir_files'] = 0;
826
				}
827
				// Or, let the user know that its not going to happen.
828
				else
829
				{
830
					if (isset($context['dir_creation_error']))
831
						$_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
832
					else
833
						$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
834
				}
835
			}
836
			else
837
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
838
		}
839
	}
840
841
	// Is the file too big?
842 View Code Duplication
	if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
843
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
844
845
	// Check the total upload size for this post...
846
	$context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
847
	if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
848
		$_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)));
849
850
	// Have we reached the maximum number of files we are allowed?
851
	$context['attachments']['quantity']++;
852
853
	// Set a max limit if none exists
854
	if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
855
		$modSettings['attachmentNumPerPostLimit'] = 50;
856
857 View Code Duplication
	if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
858
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
859
860
	// File extension check
861
	if (!empty($modSettings['attachmentCheckExtensions']))
862
	{
863
		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
864
		foreach ($allowed as $k => $dummy)
865
			$allowed[$k] = trim($dummy);
866
867
		if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
868
		{
869
			$allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
870
			$_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
871
		}
872
	}
873
874
	// Undo the math if there's an error
875
	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
876
	{
877
		if (isset($context['dir_size']))
878
			$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
879
		if (isset($context['dir_files']))
880
			$context['dir_files']--;
881
882
		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
883
		$context['attachments']['quantity']--;
884
885
		return false;
886
	}
887
888
	return true;
889
}
890
891
/**
892
 * Create an attachment, with the given array of parameters.
893
 *
894
 * What it does:
895
 *
896
 * - Adds any additional or missing parameters to $attachmentOptions.
897
 * - Renames the temporary file.
898
 * - Creates a thumbnail if the file is an image and the option enabled.
899
 *
900
 * @package Attachments
901
 * @param mixed[] $attachmentOptions associative array of options
902
 */
903
function createAttachment(&$attachmentOptions)
904
{
905
	global $modSettings, $context;
906
907
	$db = database();
908
909
	require_once(SUBSDIR . '/Graphics.subs.php');
910
911
	// If this is an image we need to set a few additional parameters.
912
	$size = elk_getimagesize($attachmentOptions['tmp_name']);
913
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
914
915
	// If it's an image get the mime type right.
916
	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
917
	{
918
		// Got a proper mime type?
919
		if (!empty($size['mime']))
920
		{
921
			$attachmentOptions['mime_type'] = $size['mime'];
922
		}
923
		// Otherwise a valid one?
924
		else
925
		{
926
			$attachmentOptions['mime_type'] = getValidMimeImageType($size[2]);
927
		}
928
	}
929
930
	// It is possible we might have a MIME type that isn't actually an image but still have a size.
931
	// For example, Shockwave files will be able to return size but be 'application/shockwave' or similar.
932
	if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0)
933
	{
934
		$attachmentOptions['width'] = 0;
935
		$attachmentOptions['height'] = 0;
936
	}
937
938
	// Get the hash if no hash has been given yet.
939
	if (empty($attachmentOptions['file_hash']))
940
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], 0, null, true);
941
942
	// Assuming no-one set the extension let's take a look at it.
943
	if (empty($attachmentOptions['fileext']))
944
	{
945
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
946
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
947
			$attachmentOptions['fileext'] = '';
948
	}
949
950
	$db->insert('',
951
		'{db_prefix}attachments',
952
		array(
953
			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
954
			'size' => 'int', 'width' => 'int', 'height' => 'int',
955
			'mime_type' => 'string-20', 'approved' => 'int',
956
		),
957
		array(
958
			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
959
			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
960
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
961
		),
962
		array('id_attach')
963
	);
964
	$attachmentOptions['id'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
965
966
	// @todo Add an error here maybe?
967
	if (empty($attachmentOptions['id']))
968
		return false;
969
970
	// Now that we have the attach id, let's rename this and finish up.
971
	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
972
	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
973
974
	// If it's not approved then add to the approval queue.
975
	if (!$attachmentOptions['approved'])
976
		$db->insert('',
977
			'{db_prefix}approval_queue',
978
			array(
979
				'id_attach' => 'int', 'id_msg' => 'int',
980
			),
981
			array(
982
				$attachmentOptions['id'], (int) $attachmentOptions['post'],
983
			),
984
			array()
985
		);
986
987
	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
988
		return true;
989
990
	// Like thumbnails, do we?
991
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
992
	{
993
		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
994
		{
995
			// Figure out how big we actually made it.
996
			$size = elk_getimagesize($attachmentOptions['destination'] . '_thumb');
997
			list ($thumb_width, $thumb_height) = $size;
998
999 View Code Duplication
			if (!empty($size['mime']))
1000
			{
1001
				$thumb_mime = $size['mime'];
1002
			}
1003
			else
1004
			{
1005
				$thumb_mime = getValidMimeImageType($size[2]);
1006
			}
1007
1008
			$thumb_filename = $attachmentOptions['name'] . '_thumb';
1009
			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
1010
			$thumb_file_hash = getAttachmentFilename($thumb_filename, 0, null, true);
1011
			$thumb_path = $attachmentOptions['destination'] . '_thumb';
1012
1013
			// We should check the file size and count here since thumbs are added to the existing totals.
1014
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
1015
			{
1016
				$context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
1017
				$context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
1018
1019
				// If the folder is full, try to create a new one and move the thumb to it.
1020
				if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
1021
				{
1022
					if (automanage_attachments_by_space())
1023
					{
1024
						rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
1025
						$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
1026
						$context['dir_size'] = 0;
1027
						$context['dir_files'] = 0;
1028
					}
1029
				}
1030
			}
1031
1032
			// If a new folder has been already created. Gotta move this thumb there then.
1033
			if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
1034
			{
1035
				rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
1036
				$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
1037
			}
1038
1039
			// To the database we go!
1040
			$db->insert('',
1041
				'{db_prefix}attachments',
1042
				array(
1043
					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
1044
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
1045
				),
1046
				array(
1047
					$modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
1048
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
1049
				),
1050
				array('id_attach')
1051
			);
1052
			$attachmentOptions['thumb'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
1053
1054
			if (!empty($attachmentOptions['thumb']))
1055
			{
1056
				$db->query('', '
1057
					UPDATE {db_prefix}attachments
1058
					SET id_thumb = {int:id_thumb}
1059
					WHERE id_attach = {int:id_attach}',
1060
					array(
1061
						'id_thumb' => $attachmentOptions['thumb'],
1062
						'id_attach' => $attachmentOptions['id'],
1063
					)
1064
				);
1065
1066
				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
1067
			}
1068
		}
1069
	}
1070
1071
	return true;
1072
}
1073
1074
/**
1075
 * Get the avatar with the specified ID.
1076
 *
1077
 * What it does:
1078
 *
1079
 * - It gets avatar data (folder, name of the file, filehash, etc)
1080
 * from the database.
1081
 * - Must return the same values and in the same order as getAttachmentFromTopic()
1082
 *
1083
 * @package Attachments
1084
 * @param int $id_attach
1085
 */
1086
function getAvatar($id_attach)
1087
{
1088
	$db = database();
1089
1090
	// Use our cache when possible
1091
	$cache = array();
1092
	if (Cache::instance()->getVar($cache, 'getAvatar_id-' . $id_attach))
1093
		$avatarData = $cache;
1094
	else
1095
	{
1096
		$request = $db->query('', '
1097
			SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member
1098
			FROM {db_prefix}attachments
1099
			WHERE id_attach = {int:id_attach}
1100
				AND id_member > {int:blank_id_member}
1101
			LIMIT 1',
1102
			array(
1103
				'id_attach' => $id_attach,
1104
				'blank_id_member' => 0,
1105
			)
1106
		);
1107
		$avatarData = array();
1108
		if ($db->num_rows($request) != 0)
1109
			$avatarData = $db->fetch_row($request);
1110
		$db->free_result($request);
1111
1112
		Cache::instance()->put('getAvatar_id-' . $id_attach, $avatarData, 900);
1113
	}
1114
1115
	return $avatarData;
1116
}
1117
1118
/**
1119
 * Get the specified attachment.
1120
 *
1121
 * What it does:
1122
 *
1123
 * - This includes a check of the topic
1124
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and query_see_board...
1125
 * - Must return the same values and in the same order as getAvatar()
1126
 *
1127
 * @package Attachments
1128
 * @param int $id_attach
1129
 * @param int $id_topic
1130
 */
1131
function getAttachmentFromTopic($id_attach, $id_topic)
1132
{
1133
	$db = database();
1134
1135
	// Make sure this attachment is on this board.
1136
	$request = $db->query('', '
1137
		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
1138
		FROM {db_prefix}attachments AS a
1139
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1140
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1141
		WHERE a.id_attach = {int:attach}
1142
		LIMIT 1',
1143
		array(
1144
			'attach' => $id_attach,
1145
			'current_topic' => $id_topic,
1146
		)
1147
	);
1148
1149
	$attachmentData = array();
1150
	if ($db->num_rows($request) != 0)
1151
	{
1152
		$attachmentData = $db->fetch_row($request);
1153
	}
1154
	$db->free_result($request);
1155
1156
	return $attachmentData;
1157
}
1158
1159
/**
1160
 * Get the thumbnail of specified attachment.
1161
 *
1162
 * What it does:
1163
 *
1164
 * - This includes a check of the topic
1165
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and query_see_board...
1166
 * - Must return the same values and in the same order as getAvatar()
1167
 *
1168
 * @package Attachments
1169
 * @param int $id_attach
1170
 * @param int $id_topic
1171
 */
1172
function getAttachmentThumbFromTopic($id_attach, $id_topic)
1173
{
1174
	$db = database();
1175
1176
	// Make sure this attachment is on this board.
1177
	$request = $db->query('', '
1178
		SELECT th.id_folder, th.filename, th.file_hash, th.fileext, th.id_attach, th.attachment_type, th.mime_type,
1179
			a.id_folder AS attach_id_folder, a.filename AS attach_filename,
1180
			a.file_hash AS attach_file_hash, a.fileext AS attach_fileext,
1181
			a.id_attach AS attach_id_attach, a.attachment_type AS attach_attachment_type,
1182
			a.mime_type AS attach_mime_type,
1183
		 	a.approved, m.id_member
1184
		FROM {db_prefix}attachments AS a
1185
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
1186
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1187
			LEFT JOIN {db_prefix}attachments AS th ON (th.id_attach = a.id_thumb)
1188
		WHERE a.id_attach = {int:attach}',
1189
		array(
1190
			'attach' => $id_attach,
1191
			'current_topic' => $id_topic,
1192
		)
1193
	);
1194
	$attachmentData = array_fill(0, 9, '');
1195
	if ($db->num_rows($request) != 0)
1196
	{
1197
		$fetch = $db->fetch_assoc($request);
1198
1199
		// If there is a hash then the thumbnail exists
1200
		if (!empty($fetch['file_hash']))
1201
		{
1202
			$attachmentData = array(
1203
				$fetch['id_folder'],
1204
				$fetch['filename'],
1205
				$fetch['file_hash'],
1206
				$fetch['fileext'],
1207
				$fetch['id_attach'],
1208
				$fetch['attachment_type'],
1209
				$fetch['mime_type'],
1210
				$fetch['approved'],
1211
				$fetch['id_member'],
1212
			);
1213
		}
1214
		// otherwise $modSettings['attachmentThumbnails'] may be (or was) off, so original file
1215
		elseif (getValidMimeImageType($fetch['attach_mime_type']) !== '')
1216
		{
1217
			$attachmentData = array(
1218
				$fetch['attach_id_folder'],
1219
				$fetch['attach_filename'],
1220
				$fetch['attach_file_hash'],
1221
				$fetch['attach_fileext'],
1222
				$fetch['attach_id_attach'],
1223
				$fetch['attach_attachment_type'],
1224
				$fetch['attach_mime_type'],
1225
				$fetch['approved'],
1226
				$fetch['id_member'],
1227
			);
1228
		}
1229
	}
1230
	$db->free_result($request);
1231
1232
	return $attachmentData;
1233
}
1234
1235
/**
1236
 * Returns if the given attachment ID is an image file or not
1237
 *
1238
 * What it does:
1239
 *
1240
 * - Given an attachment id, checks that it exists as an attachment
1241
 * - Verifies the message its associated is on a board the user can see
1242
 * - Sets 'is_image' if the attachment is an image file
1243
 * - Returns basic attachment values
1244
 *
1245
 * @package Attachments
1246
 * @param int $id_attach
1247
 *
1248
 * @returns array|boolean
1249
 */
1250
function isAttachmentImage($id_attach)
1251
{
1252
	$db = database();
1253
1254
	// Make sure this attachment is on this board.
1255
	$request = $db->query('', '
1256
		SELECT
1257
			a.filename, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, a.downloads, a.size, a.width, a.height,
1258
			m.id_topic, m.id_board
1259
		FROM {db_prefix}attachments as a
1260
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1261
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1262
		WHERE id_attach = {int:attach}
1263
			AND attachment_type = {int:type}
1264
			AND a.approved = {int:approved}
1265
		LIMIT 1',
1266
		array(
1267
			'attach' => $id_attach,
1268
			'approved' => 1,
1269
			'type' => 0,
1270
		)
1271
	);
1272
	$attachmentData = array();
1273
	if ($db->num_rows($request) != 0)
1274
	{
1275
		$attachmentData = $db->fetch_assoc($request);
1276
		$attachmentData['is_image'] = substr($attachmentData['mime_type'], 0, 5) === 'image';
1277
		$attachmentData['size'] = byte_format($attachmentData['size']);
1278
	}
1279
	$db->free_result($request);
1280
1281
	return !empty($attachmentData) ? $attachmentData : false;
1282
}
1283
1284
/**
1285
 * Increase download counter for id_attach.
1286
 *
1287
 * What it does:
1288
 *
1289
 * - Does not check if it's a thumbnail.
1290
 *
1291
 * @package Attachments
1292
 * @param int $id_attach
1293
 */
1294
function increaseDownloadCounter($id_attach)
1295
{
1296
	$db = database();
1297
1298
	$db->query('attach_download_increase', '
1299
		UPDATE LOW_PRIORITY {db_prefix}attachments
1300
		SET downloads = downloads + 1
1301
		WHERE id_attach = {int:id_attach}',
1302
		array(
1303
			'id_attach' => $id_attach,
1304
		)
1305
	);
1306
}
1307
1308
/**
1309
 * Saves a file and stores it locally for avatar use by id_member.
1310
 *
1311
 * What it does:
1312
 *
1313
 * - supports GIF, JPG, PNG, BMP and WBMP formats.
1314
 * - detects if GD2 is available.
1315
 * - uses resizeImageFile() to resize to max_width by max_height, and saves the result to a file.
1316
 * - updates the database info for the member's avatar.
1317
 * - returns whether the download and resize was successful.
1318
 *
1319
 * @uses subs/Graphics.subs.php
1320
 * @package Attachments
1321
 * @param string $temporary_path the full path to the temporary file
1322
 * @param int $memID member ID
1323
 * @param int $max_width
1324
 * @param int $max_height
1325
 * @return boolean whether the download and resize was successful.
1326
 *
1327
 */
1328
function saveAvatar($temporary_path, $memID, $max_width, $max_height)
1329
{
1330
	global $modSettings;
1331
1332
	$db = database();
1333
1334
	$ext = !empty($modSettings['avatar_download_png']) ? 'png' : 'jpeg';
1335
	$destName = 'avatar_' . $memID . '_' . time() . '.' . $ext;
1336
1337
	// Just making sure there is a non-zero member.
1338
	if (empty($memID))
1339
		return false;
1340
1341
	require_once(SUBSDIR . '/ManageAttachments.subs.php');
1342
	removeAttachments(array('id_member' => $memID));
1343
1344
	$id_folder = getAttachmentPathID();
1345
	$avatar_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, 0, null, true) : '';
1346
	$db->insert('',
1347
		'{db_prefix}attachments',
1348
		array(
1349
			'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int',
1350
			'id_folder' => 'int',
1351
		),
1352
		array(
1353
			$memID, empty($modSettings['custom_avatar_enabled']) ? 0 : 1, $destName, $avatar_hash, $ext, 1,
1354
			$id_folder,
1355
		),
1356
		array('id_attach')
1357
	);
1358
	$attachID = $db->insert_id('{db_prefix}attachments', 'id_attach');
1359
1360
	// First, the temporary file will have the .tmp extension.
1361
	$tempName = getAvatarPath() . '/' . $destName . '.tmp';
1362
1363
	// The destination filename will depend on whether custom dir for avatars has been set
1364
	$destName = getAvatarPath() . '/' . $destName;
1365
	$path = getAttachmentPath();
1366
	$destName = empty($avatar_hash) ? $destName : $path . '/' . $attachID . '_' . $avatar_hash . '.elk';
1367
1368
	// Resize it.
1369
	require_once(SUBSDIR . '/Graphics.subs.php');
1370
	if (!empty($modSettings['avatar_download_png']))
1371
		$success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height, 3);
1372
	else
1373
		$success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height);
1374
1375
	if ($success)
1376
	{
1377
		// Remove the .tmp extension from the attachment.
1378
		if (rename($tempName, $destName))
1379
		{
1380
			list ($width, $height) = elk_getimagesize($destName);
1381
			$mime_type = getValidMimeImageType($ext);
1382
1383
			// Write filesize in the database.
1384
			$db->query('', '
1385
				UPDATE {db_prefix}attachments
1386
				SET size = {int:filesize}, width = {int:width}, height = {int:height},
1387
					mime_type = {string:mime_type}
1388
				WHERE id_attach = {int:current_attachment}',
1389
				array(
1390
					'filesize' => filesize($destName),
1391
					'width' => (int) $width,
1392
					'height' => (int) $height,
1393
					'current_attachment' => $attachID,
1394
					'mime_type' => $mime_type,
1395
				)
1396
			);
1397
1398
			// Retain this globally in case the script wants it.
1399
			$modSettings['new_avatar_data'] = array(
1400
				'id' => $attachID,
1401
				'filename' => $destName,
1402
				'type' => empty($modSettings['custom_avatar_enabled']) ? 0 : 1,
1403
			);
1404
			return true;
1405
		}
1406
		else
1407
			return false;
1408
	}
1409
	else
1410
	{
1411
		$db->query('', '
1412
			DELETE FROM {db_prefix}attachments
1413
			WHERE id_attach = {int:current_attachment}',
1414
			array(
1415
				'current_attachment' => $attachID,
1416
			)
1417
		);
1418
1419
		@unlink($tempName);
1420
		return false;
1421
	}
1422
}
1423
1424
/**
1425
 * Get the size of a specified image with better error handling.
1426
 *
1427
 * What it does:
1428
 *
1429
 * - Uses getimagesize() to determine the size of a file.
1430
 * - Attempts to connect to the server first so it won't time out.
1431
 *
1432
 * @todo see if it's better in subs/Graphics.subs.php, but one step at the time.
1433
 *
1434
 * @package Attachments
1435
 * @param string $url
1436
 * @return array or false, the image size as array(width, height), or false on failure
1437
 */
1438
function url_image_size($url)
1439
{
1440
	// Make sure it is a proper URL.
1441
	$url = str_replace(' ', '%20', $url);
1442
1443
	// Can we pull this from the cache... please please?
1444
	$temp = array();
1445
	if (Cache::instance()->getVar($temp, 'url_image_size-' . md5($url), 240))
1446
		return $temp;
1447
1448
	$t = microtime(true);
1449
1450
	// Get the host to pester...
1451
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
1452
1453
	// Can't figure it out, just try the image size.
1454
	if ($url == '' || $url == 'http://' || $url == 'https://')
1455
		return false;
1456
	elseif (!isset($match[1]))
1457
		$size = elk_getimagesize($url, false);
1458
	else
1459
	{
1460
		// Try to connect to the server... give it half a second.
1461
		$temp = 0;
1462
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
1463
1464
		// Successful?  Continue...
1465
		if ($fp !== false)
1466
		{
1467
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
1468
			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");
1469
1470
			// Read in the HTTP/1.1 or whatever.
1471
			$test = substr(fgets($fp, 11), -1);
1472
			fclose($fp);
1473
1474
			// See if it returned a 404/403 or something.
1475
			if ($test < 4)
1476
			{
1477
				$size = elk_getimagesize($url, false);
1478
1479
				// This probably means allow_url_fopen is off, let's try GD.
1480
				if ($size === false && function_exists('imagecreatefromstring'))
1481
				{
1482
					include_once(SUBSDIR . '/Package.subs.php');
1483
1484
					// It's going to hate us for doing this, but another request...
1485
					$image = @imagecreatefromstring(fetch_web_data($url));
1486
					if ($image !== false)
1487
					{
1488
						$size = array(imagesx($image), imagesy($image));
1489
						imagedestroy($image);
1490
					}
1491
				}
1492
			}
1493
		}
1494
	}
1495
1496
	// If we didn't get it, we failed.
1497
	if (!isset($size))
1498
		$size = false;
1499
1500
	// If this took a long time, we may never have to do it again, but then again we might...
1501
	if (microtime(true) - $t > 0.8)
1502
		Cache::instance()->put('url_image_size-' . md5($url), $size, 240);
1503
1504
	// Didn't work.
1505
	return $size;
1506
}
1507
1508
/**
1509
 * The current attachments path:
1510
 *
1511
 * What it does:
1512
 *  - BOARDDIR . '/attachments', if nothing is set yet.
1513
 *  - if the forum is using multiple attachments directories,
1514
 *    then the current path is stored as unserialize($modSettings['attachmentUploadDir'])[$modSettings['currentAttachmentUploadDir']]
1515
 *  - otherwise, the current path is $modSettings['attachmentUploadDir'].
1516
 *
1517
 * @package Attachments
1518
 * @return string
1519
 */
1520
function getAttachmentPath()
1521
{
1522
	global $modSettings;
1523
1524
	// Make sure this thing exists and it is unserialized
1525
	if (empty($modSettings['attachmentUploadDir']))
1526
		$attachmentDir = BOARDDIR . '/attachments';
1527
	elseif (!empty($modSettings['currentAttachmentUploadDir']) && !is_array($modSettings['attachmentUploadDir']) && (@unserialize($modSettings['attachmentUploadDir']) !== false))
1528
	{
1529
		// @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)
1530
		if (function_exists('Util::unserialize'))
1531
		{
1532
			$attachmentDir = Util::unserialize($modSettings['attachmentUploadDir']);
1533
		}
1534
		else
1535
		{
1536
			$attachmentDir = unserialize($modSettings['attachmentUploadDir']);
1537
		}
1538
	}
1539
	else
1540
		$attachmentDir = $modSettings['attachmentUploadDir'];
1541
1542
	return is_array($attachmentDir) ? $attachmentDir[$modSettings['currentAttachmentUploadDir']] : $attachmentDir;
1543
}
1544
1545
/**
1546
 * The avatars path: if custom avatar directory is set, that's it.
1547
 * Otherwise, it's attachments path.
1548
 *
1549
 * @package Attachments
1550
 * @return string
1551
 */
1552
function getAvatarPath()
1553
{
1554
	global $modSettings;
1555
1556
	return empty($modSettings['custom_avatar_enabled']) ? getAttachmentPath() : $modSettings['custom_avatar_dir'];
1557
}
1558
1559
/**
1560
 * Little utility function for the $id_folder computation for attachments.
1561
 *
1562
 * What it does:
1563
 *
1564
 * - This returns the id of the folder where the attachment or avatar will be saved.
1565
 * - If multiple attachment directories are not enabled, this will be 1 by default.
1566
 *
1567
 * @package Attachments
1568
 * @return int 1 if multiple attachment directories are not enabled,
1569
 * or the id of the current attachment directory otherwise.
1570
 */
1571
function getAttachmentPathID()
1572
{
1573
	global $modSettings;
1574
1575
	// utility function for the endless $id_folder computation for attachments.
1576
	return !empty($modSettings['currentAttachmentUploadDir']) ? $modSettings['currentAttachmentUploadDir'] : 1;
1577
}
1578
1579
/**
1580
 * Returns the ID of the folder avatars are currently saved in.
1581
 *
1582
 * @package Attachments
1583
 * @return int 1 if custom avatar directory is enabled,
1584
 * and the ID of the current attachment folder otherwise.
1585
 * NB: the latter could also be 1.
1586
 */
1587
function getAvatarPathID()
1588
{
1589
	global $modSettings;
1590
1591
	// Little utility function for the endless $id_folder computation for avatars.
1592
	if (!empty($modSettings['custom_avatar_enabled']))
1593
		return 1;
1594
	else
1595
		return getAttachmentPathID();
1596
}
1597
1598
/**
1599
 * Get all attachments associated with a set of posts.
1600
 *
1601
 * What it does:
1602
 *  - This does not check permissions.
1603
 *
1604
 * @package Attachments
1605
 * @param int[] $messages array of messages ids
1606
 * @param bool $includeUnapproved = false
1607
 * @param string|null $filter name of a callback function
1608
 * @param mixed[] $all_posters
1609
 */
1610
function getAttachments($messages, $includeUnapproved = false, $filter = null, $all_posters = array())
1611
{
1612
	global $modSettings;
1613
1614
	$db = database();
1615
1616
	$attachments = array();
1617
	$request = $db->query('', '
1618
		SELECT
1619
			a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved,
1620
			a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
1621
			COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
1622
			FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
1623
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
1624
		WHERE a.id_msg IN ({array_int:message_list})
1625
			AND a.attachment_type = {int:attachment_type}',
1626
		array(
1627
			'message_list' => $messages,
1628
			'attachment_type' => 0,
1629
		)
1630
	);
1631
	$temp = array();
1632
	while ($row = $db->fetch_assoc($request))
1633
	{
1634
		if (!$row['approved'] && !$includeUnapproved && (empty($filter) || !call_user_func($filter, $row, $all_posters)))
1635
			continue;
1636
1637
		$temp[$row['id_attach']] = $row;
1638
1639
		if (!isset($attachments[$row['id_msg']]))
1640
			$attachments[$row['id_msg']] = array();
1641
	}
1642
	$db->free_result($request);
1643
1644
	// This is better than sorting it with the query...
1645
	ksort($temp);
1646
1647
	foreach ($temp as $row)
1648
		$attachments[$row['id_msg']][] = $row;
1649
1650
	return $attachments;
1651
}
1652
1653
/**
1654
 * Get all avatars information... as long as they're in default directory still?
1655
 * Not currently used
1656
 *
1657
 * @deprecated since 1.0
1658
 *
1659
 * @return mixed[] avatars information
1660
 */
1661
function getAvatarsDefault()
1662
{
1663
	$db = database();
1664
1665
	return $db->fetchQuery('
1666
		SELECT id_attach, id_folder, id_member, filename, file_hash
1667
		FROM {db_prefix}attachments
1668
		WHERE attachment_type = {int:attachment_type}
1669
			AND id_member > {int:guest_id_member}',
1670
		array(
1671
			'attachment_type' => 0,
1672
			'guest_id_member' => 0,
1673
		)
1674
	);
1675
}
1676
1677
/**
1678
 * Recursive function to retrieve server-stored avatar files
1679
 *
1680
 * @package Attachments
1681
 * @param string $directory
1682
 * @param int $level
1683
 * @return array
1684
 */
1685
function getServerStoredAvatars($directory, $level)
1686
{
1687
	global $context, $txt, $modSettings;
1688
1689
	$result = array();
1690
1691
	// Open the directory..
1692
	$dir = dir($modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory);
1693
	$dirs = array();
1694
	$files = array();
1695
1696
	if (!$dir)
1697
		return array();
1698
1699
	while ($line = $dir->read())
1700
	{
1701
		if (in_array($line, array('.', '..', 'blank.png', 'index.php')))
1702
			continue;
1703
1704
		if (is_dir($modSettings['avatar_directory'] . '/' . $directory . (!empty($directory) ? '/' : '') . $line))
1705
			$dirs[] = $line;
1706
		else
1707
			$files[] = $line;
1708
	}
1709
	$dir->close();
1710
1711
	// Sort the results...
1712
	natcasesort($dirs);
1713
	natcasesort($files);
1714
1715
	if ($level == 0)
1716
	{
1717
		$result[] = array(
1718
			'filename' => 'blank.png',
1719
			'checked' => in_array($context['member']['avatar']['server_pic'], array('', 'blank.png')),
1720
			'name' => $txt['no_pic'],
1721
			'is_dir' => false
1722
		);
1723
	}
1724
1725
	foreach ($dirs as $line)
1726
	{
1727
		$tmp = getServerStoredAvatars($directory . (!empty($directory) ? '/' : '') . $line, $level + 1);
1728
		if (!empty($tmp))
1729
			$result[] = array(
1730
				'filename' => htmlspecialchars($line, ENT_COMPAT, 'UTF-8'),
1731
				'checked' => strpos($context['member']['avatar']['server_pic'], $line . '/') !== false,
1732
				'name' => '[' . htmlspecialchars(str_replace('_', ' ', $line), ENT_COMPAT, 'UTF-8') . ']',
1733
				'is_dir' => true,
1734
				'files' => $tmp
1735
		);
1736
		unset($tmp);
1737
	}
1738
1739
	foreach ($files as $line)
1740
	{
1741
		$filename = substr($line, 0, (strlen($line) - strlen(strrchr($line, '.'))));
1742
		$extension = substr(strrchr($line, '.'), 1);
1743
1744
		// Make sure it is an image.
1745
		if (getValidMimeImageType($extension) === '')
1746
			continue;
1747
1748
		$result[] = array(
1749
			'filename' => htmlspecialchars($line, ENT_COMPAT, 'UTF-8'),
1750
			'checked' => $line == $context['member']['avatar']['server_pic'],
1751
			'name' => htmlspecialchars(str_replace('_', ' ', $filename), ENT_COMPAT, 'UTF-8'),
1752
			'is_dir' => false
1753
		);
1754
		if ($level == 1)
1755
			$context['avatar_list'][] = $directory . '/' . $line;
1756
	}
1757
1758
	return $result;
1759
}
1760
1761
/**
1762
 * Update an attachment's thumbnail
1763
 *
1764
 * @package Attachments
1765
 * @param string $filename
1766
 * @param int $id_attach
1767
 * @param int $id_msg
1768
 * @param int $old_id_thumb = 0
1769
 * @return array The updated information
1770
 */
1771
function updateAttachmentThumbnail($filename, $id_attach, $id_msg, $old_id_thumb = 0, $real_filename = '')
1772
{
1773
	global $modSettings;
1774
1775
	$attachment = array('id_attach' => $id_attach);
1776
1777
	require_once(SUBSDIR . '/Graphics.subs.php');
1778
	if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
1779
	{
1780
		// So what folder are we putting this image in?
1781
		$id_folder_thumb = getAttachmentPathID();
1782
1783
		// Calculate the size of the created thumbnail.
1784
		$size = elk_getimagesize($filename . '_thumb');
1785
		list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
1786
		$thumb_size = filesize($filename . '_thumb');
1787
1788
		// Figure out the mime type.
1789 View Code Duplication
		if (!empty($size['mime']))
1790
		{
1791
			$thumb_mime = $size['mime'];
1792
		}
1793
		else
1794
		{
1795
			$thumb_mime = getValidMimeImageType($size[2]);
1796
		}
1797
		$thumb_ext = substr($thumb_mime, strpos($thumb_mime, '/') + 1);
1798
1799
		$thumb_filename = (!empty($real_filename) ? $real_filename : $filename) . '_thumb';
1800
		$thumb_hash = getAttachmentFilename($thumb_filename, 0, null, true);
1801
1802
		$db = database();
1803
1804
		// Add this beauty to the database.
1805
		$db->insert('',
1806
			'{db_prefix}attachments',
1807
			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'),
1808
			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),
1809
			array('id_attach')
1810
		);
1811
1812
		$attachment['id_thumb'] = $db->insert_id('{db_prefix}attachments', 'id_attach');
1813
		if (!empty($attachment['id_thumb']))
1814
		{
1815
			$db->query('', '
1816
				UPDATE {db_prefix}attachments
1817
				SET id_thumb = {int:id_thumb}
1818
				WHERE id_attach = {int:id_attach}',
1819
				array(
1820
					'id_thumb' => $attachment['id_thumb'],
1821
					'id_attach' => $attachment['id_attach'],
1822
				)
1823
			);
1824
1825
			$thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
1826
			rename($filename . '_thumb', $thumb_realname);
1827
1828
			// Do we need to remove an old thumbnail?
1829
			if (!empty($old_id_thumb))
1830
			{
1831
				require_once(SUBSDIR . '/ManageAttachments.subs.php');
1832
				removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1833
			}
1834
		}
1835
	}
1836
1837
	return $attachment;
1838
}
1839
1840
/**
1841
 * Compute and return the total size of attachments to a single message.
1842
 *
1843
 * @package Attachments
1844
 * @param int $id_msg
1845
 * @param bool $include_count = true if true, it also returns the attachments count
1846
 */
1847 View Code Duplication
function attachmentsSizeForMessage($id_msg, $include_count = true)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
1848
{
1849
	$db = database();
1850
1851
	if ($include_count)
1852
	{
1853
		$request = $db->query('', '
1854
			SELECT COUNT(*), SUM(size)
1855
			FROM {db_prefix}attachments
1856
			WHERE id_msg = {int:id_msg}
1857
				AND attachment_type = {int:attachment_type}',
1858
			array(
1859
				'id_msg' => $id_msg,
1860
				'attachment_type' => 0,
1861
			)
1862
		);
1863
	}
1864
	else
1865
	{
1866
		$request = $db->query('', '
1867
			SELECT COUNT(*)
1868
			FROM {db_prefix}attachments
1869
			WHERE id_msg = {int:id_msg}
1870
				AND attachment_type = {int:attachment_type}',
1871
			array(
1872
				'id_msg' => $id_msg,
1873
				'attachment_type' => 0,
1874
			)
1875
		);
1876
	}
1877
	$size = $db->fetch_row($request);
1878
	$db->free_result($request);
1879
1880
	return $size;
1881
}
1882
1883
/**
1884
 * This loads an attachment's contextual data including, most importantly, its size if it is an image.
1885
 *
1886
 * What it does:
1887
 *
1888
 * - Pre-condition: $attachments array to have been filled with the proper attachment data, as Display() does.
1889
 * - It requires the view_attachments permission to calculate image size.
1890
 * - It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by
1891
 * the max_image_width and max_image_height settings.
1892
 *
1893
 * @todo change this pre-condition, too fragile and error-prone.
1894
 *
1895
 * @package Attachments
1896
 * @param int $id_msg message number to load attachments for
1897
 * @return array of attachments
1898
 */
1899
function loadAttachmentContext($id_msg)
1900
{
1901
	global $attachments, $modSettings, $scripturl, $topic;
1902
1903
	// Set up the attachment info - based on code by Meriadoc.
1904
	$attachmentData = array();
1905
	$have_unapproved = false;
1906
	if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable']))
1907
	{
1908
		foreach ($attachments[$id_msg] as $i => $attachment)
1909
		{
1910
			$attachmentData[$i] = array(
1911
				'id' => $attachment['id_attach'],
1912
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8')),
1913
				'downloads' => $attachment['downloads'],
1914
				'size' => byte_format($attachment['filesize']),
1915
				'byte_size' => $attachment['filesize'],
1916
				'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'],
1917
				'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_attach'] . '">' . htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8') . '</a>',
1918
				'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1919
				'is_approved' => $attachment['approved'],
1920
				'file_hash' => $attachment['file_hash'],
1921
			);
1922
1923
			// If something is unapproved we'll note it so we can sort them.
1924
			if (!$attachment['approved'])
1925
				$have_unapproved = true;
1926
1927
			if (!$attachmentData[$i]['is_image'])
1928
				continue;
1929
1930
			$attachmentData[$i]['real_width'] = $attachment['width'];
1931
			$attachmentData[$i]['width'] = $attachment['width'];
1932
			$attachmentData[$i]['real_height'] = $attachment['height'];
1933
			$attachmentData[$i]['height'] = $attachment['height'];
1934
1935
			// Let's see, do we want thumbs?
1936
			if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1937
			{
1938
				// A proper thumb doesn't exist yet? Create one! Or, it needs update.
1939
				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']))
1940
				{
1941
					$filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder'], false, $attachment['file_hash']);
1942
					$attachment = array_merge($attachment, updateAttachmentThumbnail($filename, $attachment['id_attach'], $id_msg, $attachment['id_thumb'], $attachment['filename']));
1943
				}
1944
1945
				// Only adjust dimensions on successful thumbnail creation.
1946
				if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1947
				{
1948
					$attachmentData[$i]['width'] = $attachment['thumb_width'];
1949
					$attachmentData[$i]['height'] = $attachment['thumb_height'];
1950
				}
1951
			}
1952
1953
			if (!empty($attachment['id_thumb']))
1954
			{
1955
				$attachmentData[$i]['thumbnail'] = array(
1956
					'id' => $attachment['id_thumb'],
1957
					'href' => $scripturl . '?action=dlattach;topic=' . $topic . '.0;attach=' . $attachment['id_thumb'] . ';image',
1958
				);
1959
			}
1960
			$attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1961
1962
			// If thumbnails are disabled, check the maximum size of the image.
1963
			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'])))
1964
			{
1965
				if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1966
				{
1967
					$attachmentData[$i]['width'] = $modSettings['max_image_width'];
1968
					$attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1969
				}
1970 View Code Duplication
				elseif (!empty($modSettings['max_image_width']))
1971
				{
1972
					$attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1973
					$attachmentData[$i]['height'] = $modSettings['max_image_height'];
1974
				}
1975
			}
1976
			elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1977
			{
1978
				// Data attributes for use in expandThumb
1979
				$attachmentData[$i]['thumbnail']['lightbox'] = 'data-lightboxmessage="' . $id_msg . '" data-lightboximage="' . $attachment['id_attach'] . '"';
1980
1981
				// If the image is too large to show inline, make it a popup.
1982
				// @todo this needs to be removed or depreciated
1983
				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'])))
1984
					$attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);';
1985
				else
1986
					$attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1987
			}
1988
1989
			if (!$attachmentData[$i]['thumbnail']['has_thumb'])
1990
				$attachmentData[$i]['downloads']++;
1991
		}
1992
	}
1993
1994
	// Do we need to instigate a sort?
1995
	if ($have_unapproved)
1996
		usort($attachmentData, 'approved_attach_sort');
1997
1998
	return $attachmentData;
1999
}
2000
2001
/**
2002
 * A sort function for putting unapproved attachments first.
2003
 *
2004
 * @package Attachments
2005
 * @param mixed[] $a
2006
 * @param mixed[] $b
2007
 * @return int -1, 0, 1
2008
 */
2009
function approved_attach_sort($a, $b)
2010
{
2011
	if ($a['is_approved'] == $b['is_approved'])
2012
		return 0;
2013
2014
	return $a['is_approved'] > $b['is_approved'] ? -1 : 1;
2015
}
2016
2017
/**
2018
 * Callback filter for the retrieval of attachments.
2019
 *
2020
 * What it does:
2021
 * This function returns false when:
2022
 *  - the attachment is unapproved, and
2023
 *  - the viewer is not the poster of the message where the attachment is
2024
 *
2025
 * @package Attachments
2026
 * @param mixed[] $attachment_info
2027
 * @param mixed[] $all_posters
2028
 */
2029
function filter_accessible_attachment($attachment_info, $all_posters)
2030
{
2031
	global $user_info;
2032
2033
	return !(!$attachment_info['approved'] && (!isset($all_posters[$attachment_info['id_msg']]) || $all_posters[$attachment_info['id_msg']] != $user_info['id']));
2034
}
2035
2036
/**
2037
 * Older attachments may still use this function.
2038
 *
2039
 * @package Attachments
2040
 * @param string $filename
2041
 * @param int $attachment_id
2042
 * @param string|null $dir
2043
 * @param boolean $new
2044
 */
2045
function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false)
2046
{
2047
	global $modSettings;
2048
2049
	$clean_name = $filename;
2050
2051
	// Sorry, no spaces, dots, or anything else but letters allowed.
2052
	$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
2053
2054
	$enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
2055
	$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
2056
2057
	if (empty($attachment_id) || ($new && empty($modSettings['attachmentEncryptFilenames'])))
2058
		return $clean_name;
2059
	elseif ($new)
2060
		return $enc_name;
2061
2062
	// Are we using multiple directories?
2063 View Code Duplication
	if (!empty($modSettings['currentAttachmentUploadDir']))
2064
	{
2065
		if (!is_array($modSettings['attachmentUploadDir']))
2066
			$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
2067
		$path = $modSettings['attachmentUploadDir'][$dir];
2068
	}
2069
	else
2070
		$path = $modSettings['attachmentUploadDir'];
2071
2072
	if (file_exists($path . '/' . $enc_name))
2073
		$filename = $path . '/' . $enc_name;
2074
	else
2075
		$filename = $path . '/' . $clean_name;
2076
2077
	return $filename;
2078
}
2079
2080
/**
2081
 * Binds a set of attachments to a message.
2082
 *
2083
 * @package Attachments
2084
 * @param int $id_msg
2085
 * @param int[] $attachment_ids
2086
 */
2087
function bindMessageAttachments($id_msg, $attachment_ids)
2088
{
2089
	$db = database();
2090
2091
	$db->query('', '
2092
		UPDATE {db_prefix}attachments
2093
		SET id_msg = {int:id_msg}
2094
		WHERE id_attach IN ({array_int:attachment_list})',
2095
		array(
2096
			'attachment_list' => $attachment_ids,
2097
			'id_msg' => $id_msg,
2098
		)
2099
	);
2100
}
2101
2102
/**
2103
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
2104
 *
2105
 * - If new is set returns a hash for the db
2106
 * - If no file hash is supplied, determines one and returns it
2107
 * - Returns the path to the file
2108
 *
2109
 * @todo this currently returns the hash if new, and the full filename otherwise.
2110
 * Something messy like that.
2111
 * @todo and of course everything relies on this behavior and work around it. :P.
2112
 * Converters included.
2113
 *
2114
 * @param string $filename The name of the file
2115
 * @param int|null $attachment_id The ID of the attachment
2116
 * @param string|null $dir Which directory it should be in (null to use current)
2117
 * @param bool $new If this is a new attachment, if so just returns a hash
2118
 * @param string $file_hash The file hash
2119
 */
2120
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
2121
{
2122
	global $modSettings;
2123
2124
	// Just make up a nice hash...
2125
	if ($new)
2126
		return hash('sha1', hash('md5', $filename . time()) . mt_rand());
2127
2128
	// In case of files from the old system, do a legacy call.
2129
	if (empty($file_hash))
2130
	{
2131
		return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new);
2132
	}
2133
2134
	// Are we using multiple directories?
2135
	if (!empty($modSettings['currentAttachmentUploadDir']))
2136
	{
2137
		if (!is_array($modSettings['attachmentUploadDir']))
2138
			$modSettings['attachmentUploadDir'] = Util::unserialize($modSettings['attachmentUploadDir']);
2139
		$path = isset($modSettings['attachmentUploadDir'][$dir]) ? $modSettings['attachmentUploadDir'][$dir] : $modSettings['basedirectory_for_attachments'];
2140
	}
2141
	else
2142
		$path = $modSettings['attachmentUploadDir'];
2143
2144
	return $path . '/' . $attachment_id . '_' . $file_hash . '.elk';
2145
}
2146
2147
/**
2148
 * Returns the board and the topic the attachment belongs to.
2149
 *
2150
 * @package Attachments
2151
 * @param int $id_attach
2152
 * @return int[]|boolean on fail else an array of id_board, id_topic
2153
 */
2154
function getAttachmentPosition($id_attach)
2155
{
2156
	$db = database();
2157
2158
	// Make sure this attachment is on this board.
2159
	$request = $db->query('', '
2160
		SELECT m.id_board, m.id_topic
2161
		FROM {db_prefix}attachments AS a
2162
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
2163
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
2164
		WHERE a.id_attach = {int:attach}
2165
			AND {query_see_board}
2166
		LIMIT 1',
2167
		array(
2168
			'attach' => $id_attach,
2169
		)
2170
	);
2171
2172
	$attachmentData = $db->fetch_assoc($request);
2173
	$db->free_result($request);
2174
2175
	if (empty($attachmentData))
2176
	{
2177
		return false;
2178
	}
2179
	else
2180
	{
2181
		return $attachmentData;
2182
	}
2183
}
2184
2185
/**
2186
 * Simple wrapper for getimagesize
2187
 *
2188
 * @param string $file
2189
 * @param string|boolean $error return array or false on error
2190
 *
2191
 * @return array|boolean
2192
 */
2193
function elk_getimagesize($file, $error = 'array')
2194
{
2195
	$sizes = @getimagesize($file);
2196
2197
	// Can't get it, what shall we return
2198
	if (empty($sizes))
2199
	{
2200
		if ($error === 'array')
2201
		{
2202
			$sizes = array(-1, -1, -1);
2203
		}
2204
		else
2205
		{
2206
			$sizes = false;
2207
		}
2208
	}
2209
2210
	return $sizes;
2211
}
2212
2213
/**
2214
 * Checks if we have a known and support mime-type for which we have a thumbnail image
2215
 *
2216
 * @param string $file_ext
2217
 * @param bool $url
2218
 *
2219
 * @return bool|string
2220
 */
2221
function returnMimeThumb($file_ext, $url = false)
2222
{
2223
	global $settings;
2224
2225
	// These are not meant to be exhaustive, just some of the most common attached on a forum
2226
	static $generics = array(
2227
		'arc' => array('tgz', 'zip', 'rar', '7z', 'gz'),
2228
		'doc' =>array('doc', 'docx', 'wpd', 'odt'),
2229
		'sound' => array('wav', 'mp3', 'pcm', 'aiff', 'wma', 'm4a'),
2230
		'video' => array('mp4', 'mgp', 'mpeg', 'mp4', 'wmv', 'flv', 'aiv', 'mov', 'swf'),
2231
		'txt' => array('rtf', 'txt', 'log'),
2232
		'presentation' => array('ppt', 'pps', 'odp'),
2233
		'spreadsheet' => array('xls', 'xlr', 'ods'),
2234
		'web' => array('html', 'htm')
2235
	);
2236
	foreach ($generics as $generic_extension => $generic_types)
2237
	{
2238
		if (in_array($file_ext, $generic_types))
2239
		{
2240
			$file_ext = $generic_extension;
2241
			break;
2242
		}
2243
	}
2244
2245
	static $distinct = array('arc', 'doc', 'sound', 'video', 'txt', 'presentation', 'spreadsheet', 'web',
2246
		'c', 'cpp', 'css', 'csv', 'java', 'js', 'pdf', 'php', 'sql', 'xml');
2247
2248
	if (empty($settings))
2249
	{
2250
		loadEssentialThemeData();
2251
	}
2252
2253
	// Return the mine thumbnail if it exists or just the default
2254
	if (!in_array($file_ext, $distinct) || !file_exists($settings['theme_dir'] . '/images/mime_images/' . $file_ext . '.png'))
2255
	{
2256
		$file_ext = 'default';
2257
	}
2258
2259
	$location = $url ? $settings['theme_url'] : $settings['theme_dir'];
2260
	$filename = $location . '/images/mime_images/' . $file_ext . '.png';
2261
2262
	return $filename;
2263
}
2264
2265
/**
2266
 * Finds in $_SESSION['temp_attachments'] an attachment id from its public id
2267
 *
2268
 * @param string $public_attachid
2269
 *
2270
 * @return string
2271
 */
2272
function getAttachmentIdFromPublic($public_attachid)
2273
{
2274
	if (empty($_SESSION['temp_attachments']))
2275
	{
2276
		return $public_attachid;
2277
	}
2278
2279
	foreach ($_SESSION['temp_attachments'] as $key => $val)
2280
	{
2281
		if (isset($val['public_attachid']) && $val['public_attachid'] === $public_attachid)
2282
		{
2283
			return $key;
2284
		}
2285
	}
2286
	return $public_attachid;
2287
}
2288
2289
/**
2290
 * From either a mime type, an extension or an IMAGETYPE_* constant
2291
 * returns a valid image mime type
2292
 *
2293
 * @param string $mime
2294
 *
2295
 * @return string
2296
 */
2297
function getValidMimeImageType($mime)
2298
{
2299
	// These are the only valid image types.
2300
	static $validImageTypes = array(
2301
		-1 => 'jpg',
2302
		// Starting from here are the IMAGETYPE_* constants
2303
		1 => 'gif',
2304
		2 => 'jpeg',
2305
		3 => 'png',
2306
		5 => 'psd',
2307
		6 => 'bmp',
2308
		7 => 'tiff',
2309
		8 => 'tiff',
2310
		9 => 'jpeg',
2311
		14 => 'iff'
2312
	);
2313
2314
	if ((int) $mime > 0)
2315
	{
2316
		$ext = isset($validImageTypes[$mime]) ? $validImageTypes[$mime] : '';
2317
	}
2318 View Code Duplication
	elseif (strpos($mime, '/'))
2319
	{
2320
		$ext = substr($mime, strpos($mime, '/') + 1);
2321
	}
2322
	else
2323
	{
2324
		$ext = $mime;
2325
	}
2326
	$ext = strtolower($ext);
2327
2328
	foreach ($validImageTypes as $valid_ext)
2329
	{
2330
		if ($valid_ext === $ext)
2331
		{
2332
			return 'image/' . $ext;
2333
		}
2334
	}
2335
2336
	return '';
2337
}
2338
2339
/**
2340
 * This function returns the mimeType of a file using the best means available
2341
 *
2342
 * @param string $filename
2343
 * @return bool|mixed|string
2344
 */
2345
function get_finfo_mime($filename)
2346
{
2347
	$mimeType = false;
2348
2349
	// Check only existing readable files
2350
	if (!file_exists($filename) || !is_readable($filename))
2351
	{
2352
		return $mimeType;
2353
	}
2354
2355
	// Try finfo, this is the preferred way
2356
	if (function_exists('finfo_open'))
2357
	{
2358
		$finfo = finfo_open(FILEINFO_MIME);
2359
		$mimeType = finfo_file($finfo, $filename);
2360
		finfo_close($finfo);
2361
	}
2362
	// No finfo? What? lets try the old mime_content_type
2363
	elseif (function_exists('mime_content_type'))
2364
	{
2365
		$mimeType = mime_content_type($filename);
2366
	}
2367
	// Try using an exec call
2368
	elseif (function_exists('exec'))
2369
	{
2370
		$mimeType = @exec("/usr/bin/file -i -b $filename");
2371
	}
2372
2373
	// Still nothing? We should at least be able to get images correct
2374
	if (empty($mimeType))
2375
	{
2376
		$imageData = elk_getimagesize($filename, 'none');
2377
		if (!empty($imageData['mime']))
2378
		{
2379
			$mimeType = $imageData['mime'];
2380
		}
2381
	}
2382
2383
	// Account for long responses like text/plain; charset=us-ascii
2384
	if (!empty($mimeType) && strpos($mimeType, ';'))
2385
	{
2386
		list($mimeType,) = explode(';', $mimeType);
2387
	}
2388
2389
	return $mimeType;
2390
}
2391