Completed
Pull Request — development (#3620)
by Emanuele
07:38 queued 07:38
created

loadAttachmentContext()   D

Complexity

Conditions 34
Paths 4

Size

Total Lines 128
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 896.5408

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 58
dl 0
loc 128
rs 4.1666
c 2
b 0
f 0
cc 34
nc 4
nop 1
ccs 4
cts 43
cp 0.093
crap 896.5408

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttachmentPosition() 0 27 2
A elk_getimagesize() 0 11 3

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file handles the uploading and creation of attachments
5
 * as well as the auto management of the attachment directories.
6
 * Note to enhance documentation later:
7
 * attachment_type = 3 is a thumbnail, etc.
8
 *
9
 * @package   ElkArte Forum
10
 * @copyright ElkArte Forum contributors
11
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
12
 *
13
 * This file contains code covered by:
14
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
15
 *
16
 * @version 2.0 dev
17
 *
18
 */
19
20
use ElkArte\Cache\Cache;
21
use ElkArte\Errors\AttachmentErrorContext;
22
use ElkArte\Graphics\Image;
23
use ElkArte\Http\FsockFetchWebdata;
24
use ElkArte\TemporaryAttachment;
25
use ElkArte\Themes\ThemeLoader;
26
use ElkArte\TokenHash;
27
use ElkArte\User;
28
use ElkArte\AttachmentsDirectory;
29
use ElkArte\TemporaryAttachmentsList;
30
use ElkArte\FileFunctions;
31
32
/**
33
 * Handles the actual saving of attachments to a directory.
34
 *
35
 * What it does:
36
 *
37
 * - Loops through $_FILES['attachment'] array and saves each file to the current attachments' folder.
38
 * - Validates the save location actually exists.
39
 *
40
 * @param int $id_msg 0 or id of the message with attachments, if any.
41
 *                    If 0, this is an upload in progress for a new post.
42
 * @return bool
43
 * @package Attachments
44
 */
45
function processAttachments($id_msg = 0)
46
{
47
	global $context, $modSettings, $txt, $topic, $board;
48 2
49
	$attach_errors = AttachmentErrorContext::context();
50 2
	$file_functions = FileFunctions::instance();
51 2
	$tmp_attachments = new TemporaryAttachmentsList();
52
	$attachmentDirectory = new AttachmentsDirectory($modSettings, database());
53
54 2
	// Validate we need to do processing, nothing new, nothing previously sent
55
	if ($tmp_attachments->getPostParam('files') === null
56
		&& !$tmp_attachments->hasAttachments()
57 2
		&& !$attachmentDirectory->hasFileTmpAttachments())
58
	{
59 2
		return false;
60
	}
61 2
62
	// Make sure we're uploading to the right place.
63 2
	$attachmentDirectory->automanageCheckDirectory(isset($_REQUEST['action']) && $_REQUEST['action'] === 'admin');
64 2
	$attach_current_dir = $attachmentDirectory->getCurrent();
65
	if (!$file_functions->isDir($attach_current_dir))
66
	{
67
		$tmp_attachments->setSystemError('attach_folder_warning');
68
		\ElkArte\Errors\Errors::instance()->log_error(sprintf($txt['attach_folder_admin_warning'], $attach_current_dir), 'critical');
69
	}
70
71
	if ($tmp_attachments->hasSystemError() === false && !isset($context['attachments']['quantity']))
72
	{
73 2
		$context['attachments']['quantity'] = 0;
74
		$context['attachments']['total_size'] = 0;
75
76
		// If this isn't a new post, check the current attachments.
77
		if ($id_msg !== 0)
78
		{
79
			list ($context['attachments']['quantity'], $context['attachments']['total_size']) = attachmentsSizeForMessage($id_msg);
80
		}
81
	}
82
83
	// There are files in session (temporary attachments list), likely already processed
84
	$ignore_temp = false;
85
	if ($tmp_attachments->getPostParam('files') !== null && $tmp_attachments->hasAttachments())
86
	{
87
		// Let's try to keep them. But...
88 2
		$ignore_temp = true;
89 2
90
		// If new files are being added. We can't ignore those
91
		if (!empty($_FILES['attachment']['tmp_name']))
92
		{
93
			// If the array is not empty
94
			if (count(array_filter($_FILES['attachment']['tmp_name'])) !== 0)
95
			{
96
				$ignore_temp = false;
97
			}
98
		}
99
100
		// Need to make space for the new files. So, bye bye.
101
		if (!$ignore_temp)
102
		{
103
			$tmp_attachments->removeAll(User::$info->id);
104
			$tmp_attachments->unset();
105
106
			$attach_errors->activate()->addError('temp_attachments_flushed');
107
		}
108
	}
109
110
	if (!isset($_FILES['attachment']['name']))
111
	{
112
		$_FILES['attachment']['tmp_name'] = [];
113
	}
114 2
115
	// Remember where we are at. If it's anywhere at all.
116 2
	if (!$ignore_temp)
117
	{
118
		$tmp_attachments->setPostParam([
119
			'msg' => $id_msg,
120 2
			'last_msg' => (int) ($_REQUEST['last_msg'] ?? 0),
121
			'topic' => (int) ($topic ?? 0),
122 2
			'board' => (int) ($board ?? 0),
123 2
		]);
124 2
	}
125 2
126 2
	// If we have an initial error, lets just display it.
127
	if ($tmp_attachments->hasSystemError())
128
	{
129
		// This is a generic error
130
		$attach_errors->activate();
131 2
		$attach_errors->addError('attach_no_upload');
132
133
		// And delete the files 'cos they ain't going nowhere.
134 2
		foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
135 2
		{
136
			if (is_writable($_FILES['attachment']['tmp_name'][$n]))
137
			{
138 2
				unlink($_FILES['attachment']['tmp_name'][$n]);
139
			}
140
		}
141
142
		$_FILES['attachment']['tmp_name'] = array();
143
	}
144
145
	// Loop through $_FILES['attachment'] array and move each file to the current attachments' folder.
146 2
	foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
147
	{
148
		if ($_FILES['attachment']['name'][$n] === '')
149
		{
150 2
			continue;
151
		}
152
153
		// First, let's check for PHP upload errors.
154
		$errors = attachmentUploadChecks($n);
155
156
		$tokenizer = new TokenHash();
157
		$temp_file = new TemporaryAttachment([
158
			'name' => basename($_FILES['attachment']['name'][$n]),
159
			'tmp_name' => $_FILES['attachment']['tmp_name'][$n],
160
			'attachid' => $tmp_attachments->getTplName(User::$info->id, $tokenizer->generate_hash(16)),
161
			'public_attachid' => $tmp_attachments->getTplName(User::$info->id, $tokenizer->generate_hash(16)),
162
			'user_id' => User::$info->id,
163
			'size' => $_FILES['attachment']['size'][$n],
164
			'type' => $_FILES['attachment']['type'][$n],
165
			'id_folder' => $attachmentDirectory->currentDirectoryId(),
166
			'mime' => getMimeType($_FILES['attachment']['tmp_name'][$n]),
167
		]);
168
169
		// If we are error free, Try to move and rename the file before doing more checks on it.
170
		if (empty($errors))
171
		{
172
			$temp_file->moveUploaded($attach_current_dir);
173
		}
174
		// Upload error(s) were detected, flag the error, remove the file
175
		else
176
		{
177
			$temp_file->setErrors($errors);
178
			$temp_file->remove(false);
179
		}
180
181
		// The file made it to the server, so do more checks injection, size, extension
182
		$temp_file->doChecks($attachmentDirectory);
183
184
		// Sort out the errors for display and delete any associated files.
185
		if ($temp_file->hasErrors())
186
		{
187
			$attach_errors->addAttach($temp_file['attachid'], $temp_file->getName());
0 ignored issues
show
Bug introduced by
It seems like $temp_file['attachid'] can also be of type array; however, parameter $id of ElkArte\Errors\AttachmentErrorContext::addAttach() 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

187
			$attach_errors->addAttach(/** @scrutinizer ignore-type */ $temp_file['attachid'], $temp_file->getName());
Loading history...
188
			$log_these = ['attachments_no_create', 'attachments_no_write', 'attach_timeout',
189
						  'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file', 'bad_attachment'];
190
191
			foreach ($temp_file->getErrors() as $error)
192
			{
193
				$error = array_filter($error);
194
				$attach_errors->addError(isset($error[1]) ? $error : $error[0]);
195
				if (in_array($error[0], $log_these))
196
				{
197
					\ElkArte\Errors\Errors::instance()->log_error($temp_file->getName() . ': ' . $txt[$error[0]], 'critical');
198
199
					// For critical errors, we don't want the file or session data to persist
200
					$temp_file->remove(false);
201
				}
202
			}
203
		}
204
205
		// Want to correct for phone rotated photos, hell yeah ya do!
206
		if (!empty($modSettings['attachment_autorotate']))
207
		{
208
			$temp_file->autoRotate();
209
		}
210
211
		$tmp_attachments->addAttachment($temp_file);
212
	}
213
214
	// Mod authors, finally a hook to hang an alternate attachment upload system upon
215
	// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . User::$info->id . '_' . md5(mt_rand())
216
	// Populate TemporaryAttachmentsList[$attachID] with the following:
217
	//   name => The file name
218
	//   tmp_name => Path to the temp file (AttachmentsDirectory->getCurrent() . '/' . $attachID).
219
	//   size => File size (required).
220
	//   type => MIME type (optional if not available on upload).
221
	//   id_folder => AttachmentsDirectory->currentDirectoryId
222
	//   errors => An array of errors (use the index of the $txt variable for that error).
223
	// Template changes can be done using "integrate_upload_template".
224
	call_integration_hook('integrate_attachment_upload');
225
226
	return $ignore_temp;
227
}
228
229
/**
230 2
 * Checks if an uploaded file produced any appropriate error code
231
 *
232 2
 * What it does:
233
 *
234
 * - Checks for error codes in the error segment of the file array that is
235
 * created by PHP during the file upload.
236
 *
237
 * @param int $attachID
238
 *
239
 * @return array
240
 * @package Attachments
241
 */
242
function attachmentUploadChecks($attachID)
243
{
244
	global $modSettings, $txt;
245
246
	$errors = array();
247
248
	// Did PHP create any errors during the upload processing of this file?
249
	if (!empty($_FILES['attachment']['error'][$attachID]))
250
	{
251
		switch ($_FILES['attachment']['error'][$attachID])
252
		{
253
			case 1:
254
			case 2:
255
				// 1 The file exceeds the max_filesize directive in php.ini
256
				// 2 The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.
257
				$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
258
				break;
259
			case 3:
260
			case 4:
261
			case 8:
262
				// 3 partially uploaded
263
				// 4 no file uploaded
264
				// 8 upload blocked by extension
265
				\ElkArte\Errors\Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$attachID]]);
266
				$errors[] = 'attach_php_error';
267
				break;
268
			case 6:
269
			case 7:
270
				// 6 Missing or a full a temp directory on the server
271
				// 7 Failed to write file
272
				\ElkArte\Errors\Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$attachID]], 'critical');
273
				$errors[] = 'attach_php_error';
274
				break;
275
			default:
276
				\ElkArte\Errors\Errors::instance()->log_error($_FILES['attachment']['name'][$attachID] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$attachID]]);
277
				$errors[] = 'attach_php_error';
278
		}
279
	}
280
281
	return $errors;
282
}
283
284
/**
285
 * Create an attachment, with the given array of parameters.
286
 *
287
 * What it does:
288
 *
289
 * - Adds any additional or missing parameters to $attachmentOptions.
290
 * - Renames the temporary file.
291
 * - Creates a thumbnail if the file is an image and the option enabled.
292
 *
293
 * @param mixed[] $attachmentOptions associative array of options
294
 *
295
 * @return bool
296
 * @package Attachments
297
 */
298
function createAttachment(&$attachmentOptions)
299
{
300
	global $modSettings;
301
302
	$db = database();
303
	$attachmentsDir = new AttachmentsDirectory($modSettings, $db);
304
305
	$image = new Image($attachmentOptions['tmp_name']);
306
307
	// If this is an image we need to set a few additional parameters.
308
	$is_image = $image->isImageLoaded();
309
	$size = $is_image ? $image->getImageDimensions() : [0, 0, 0];
310
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
311
	$attachmentOptions['width'] = max(0, $attachmentOptions['width']);
312
	$attachmentOptions['height'] = max(0, $attachmentOptions['height']);
313
314
	// If it's an image get the mime type right.
315
	if ($is_image)
316
	{
317
		$attachmentOptions['mime_type'] = getValidMimeImageType($size[2]);
318
319
		// Want to correct for phonetographer photos?
320
		if (!empty($modSettings['attachment_autorotate']))
321
		{
322
			$image->autoRotate();
323
		}
324
	}
325
326
	// Get the hash if no hash has been given yet.
327
	if (empty($attachmentOptions['file_hash']))
328
	{
329
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], 0, null, true);
330
	}
331
332
	// Assuming no-one set the extension let's take a look at it.
333
	if (empty($attachmentOptions['fileext']))
334
	{
335
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
336
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
337
		{
338
			$attachmentOptions['fileext'] = '';
339
		}
340
	}
341
342
	$db->insert('',
343
		'{db_prefix}attachments',
344
		array(
345
			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
346
			'size' => 'int', 'width' => 'int', 'height' => 'int',
347
			'mime_type' => 'string-20', 'approved' => 'int',
348
		),
349
		array(
350
			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
351
			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
352
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
353
		),
354
		array('id_attach')
355
	);
356
	$attachmentOptions['id'] = $db->insert_id('{db_prefix}attachments');
357
358
	// @todo Add an error here maybe?
359
	if (empty($attachmentOptions['id']))
360
	{
361
		return false;
362
	}
363
364
	// Now that we have the attach id, let's rename this and finish up.
365
	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
366
	if (rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']) && $is_image)
367
	{
368
		// Let the manipulator the (loaded) file new location
369
		$image->setFileName($attachmentOptions['destination']);
370
	}
371
372
	// If it's not approved then add to the approval queue.
373
	if (!$attachmentOptions['approved'])
374
	{
375
		$db->insert('',
376
			'{db_prefix}approval_queue',
377
			array(
378
				'id_attach' => 'int', 'id_msg' => 'int',
379
			),
380
			array(
381
				$attachmentOptions['id'], (int) $attachmentOptions['post'],
382
			),
383
			array()
384
		);
385
	}
386
387
	if (empty($modSettings['attachmentThumbnails']) || !$is_image || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
388
	{
389
		return true;
390
	}
391
392
	// Like thumbnails, do we?
393
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight'])
394
		&& ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
395
	{
396
		$thumb_filename = $attachmentOptions['name'] . '_thumb';
397
		$thumb_path = $attachmentOptions['destination'] . '_thumb';
398
		$thumb_image = $image->createThumbnail($modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight'], $thumb_path);
399
		if ($thumb_image !== false)
400
		{
401
			// Figure out how big we actually made it.
402
			$size = $thumb_image->getImageDimensions();
403
			list ($thumb_width, $thumb_height) = $size;
404
405
			$thumb_mime = getValidMimeImageType($size[2]);
406
			$thumb_size = $thumb_image->getFilesize();
407
			$thumb_file_hash = getAttachmentFilename($thumb_filename, 0, null, true);
408
409
			// We should check the file size and count here since thumbs are added to the existing totals.
410
			$attachmentsDir->checkDirSize($thumb_size);
411
			$current_dir_id = $attachmentsDir->currentDirectoryId();
412
413
			// If a new folder has been already created. Gotta move this thumb there then.
414
			if ($attachmentsDir->isCurrentDirectoryId($attachmentOptions['id_folder']) === false)
415
			{
416
				$current_dir = $attachmentsDir->getCurrent();
417
				$current_dir_id = $attachmentsDir->currentDirectoryId();
418
				rename($thumb_path, $current_dir . '/' . $thumb_filename);
419
				$thumb_path = $current_dir . '/' . $thumb_filename;
420
			}
421
422
			// To the database we go!
423
			$db->insert('',
424
				'{db_prefix}attachments',
425
				array(
426
					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
427
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
428
				),
429
				array(
430
					$current_dir_id, (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
431
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
432
				),
433
				array('id_attach')
434
			);
435
			$attachmentOptions['thumb'] = $db->insert_id('{db_prefix}attachments');
436
437
			if (!empty($attachmentOptions['thumb']))
438
			{
439
				$db->query('', '
440
					UPDATE {db_prefix}attachments
441
					SET id_thumb = {int:id_thumb}
442
					WHERE id_attach = {int:id_attach}',
443
					array(
444
						'id_thumb' => $attachmentOptions['thumb'],
445
						'id_attach' => $attachmentOptions['id'],
446
					)
447
				);
448
449
				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $current_dir_id, false, $thumb_file_hash));
450
			}
451
		}
452
	}
453
454
	return true;
455
}
456
457
/**
458
 * Get the specified attachment.
459
 *
460
 * What it does:
461
 *
462
 * - This includes a check of the topic
463
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and
464
 * query_see_board...
465
 * - Must return the same array keys as getAvatar() and getAttachmentThumbFromTopic()
466
 *
467
 * @param int $id_attach
468
 * @param int $id_topic
469
 *
470
 * @return array
471
 * @package Attachments
472
 */
473
function getAttachmentFromTopic($id_attach, $id_topic)
474
{
475
	$db = database();
476
477
	// Make sure this attachment is on this board.
478
	$attachmentData = array();
479
	$request = $db->fetchQuery('
480
		SELECT 
481
			a.id_folder, a.filename, a.file_hash, a.fileext, a.id_attach, a.attachment_type, 
482
			a.mime_type, a.approved, m.id_member
483
		FROM {db_prefix}attachments AS a
484
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
485
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
486
		WHERE a.id_attach = {int:attach}
487
		LIMIT 1',
488
		array(
489
			'attach' => $id_attach,
490
			'current_topic' => $id_topic,
491
		)
492
	);
493
	if ($request->num_rows() != 0)
494
	{
495
		$attachmentData = $request->fetch_assoc();
496
	}
497
	$request->free_result();
498
499
	return $attachmentData;
500
}
501
502
/**
503
 * Get the thumbnail of specified attachment.
504
 *
505
 * What it does:
506
 *
507
 * - This includes a check of the topic
508
 * - it only returns the attachment if it's indeed attached to a message in the topic given as parameter, and
509
 * query_see_board...
510
 * - Must return the same array keys as getAvatar() & getAttachmentFromTopic
511
 *
512
 * @param int $id_attach
513
 * @param int $id_topic
514
 *
515
 * @return array
516
 * @package Attachments
517
 */
518
function getAttachmentThumbFromTopic($id_attach, $id_topic)
519
{
520
	$db = database();
521
522
	// Make sure this attachment is on this board.
523
	$request = $db->fetchQuery('
524
		SELECT 
525
			th.id_folder, th.filename, th.file_hash, th.fileext, th.id_attach, 
526
			th.attachment_type, th.mime_type,
527
			a.id_folder AS attach_id_folder, a.filename AS attach_filename,
528
			a.file_hash AS attach_file_hash, a.fileext AS attach_fileext,
529
			a.id_attach AS attach_id_attach, a.attachment_type AS attach_attachment_type,
530
			a.mime_type AS attach_mime_type,
531
		 	a.approved, m.id_member
532
		FROM {db_prefix}attachments AS a
533
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
534
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
535
			LEFT JOIN {db_prefix}attachments AS th ON (th.id_attach = a.id_thumb)
536
		WHERE a.id_attach = {int:attach}',
537
		array(
538
			'attach' => $id_attach,
539
			'current_topic' => $id_topic,
540
		)
541
	);
542
	$attachmentData = [
543
		'id_folder' => '', 'filename' => '', 'file_hash' => '', 'fileext' => '', 'id_attach' => '',
544
		'attachment_type' => '', 'mime_type' => '', 'approved' => '', 'id_member' => ''];
545
	if ($request->num_rows() != 0)
546
	{
547
		$row = $request->fetch_assoc();
548
549
		// If there is a hash then the thumbnail exists
550
		if (!empty($row['file_hash']))
551
		{
552
			$attachmentData = array(
553
				'id_folder' => $row['id_folder'],
554
				'filename' => $row['filename'],
555
				'file_hash' => $row['file_hash'],
556
				'fileext' => $row['fileext'],
557
				'id_attach' => $row['id_attach'],
558
				'attachment_type' => $row['attachment_type'],
559
				'mime_type' => $row['mime_type'],
560
				'approved' => $row['approved'],
561
				'id_member' => $row['id_member'],
562
			);
563
		}
564
		// otherwise $modSettings['attachmentThumbnails'] may be (or was) off, so original file
565
		elseif (getValidMimeImageType($row['attach_mime_type']) !== '')
566
		{
567
			$attachmentData = array(
568
				'id_folder' => $row['attach_id_folder'],
569
				'filename' => $row['attach_filename'],
570
				'file_hash' => $row['attach_file_hash'],
571
				'fileext' => $row['attach_fileext'],
572
				'id_attach' => $row['attach_id_attach'],
573
				'attachment_type' => $row['attach_attachment_type'],
574
				'mime_type' => $row['attach_mime_type'],
575
				'approved' => $row['approved'],
576
				'id_member' => $row['id_member'],
577
			);
578
		}
579
	}
580
581
	return $attachmentData;
582
}
583
584
/**
585
 * Returns if the given attachment ID is an image file or not
586
 *
587
 * What it does:
588
 *
589
 * - Given an attachment id, checks that it exists as an attachment
590
 * - Verifies the message its associated is on a board the user can see
591
 * - Sets 'is_image' if the attachment is an image file
592
 * - Returns basic attachment values
593
 *
594
 * @param int $id_attach
595
 *
596
 * @return array|bool
597
 * @package Attachments
598
 */
599
function isAttachmentImage($id_attach)
600
{
601
	$db = database();
602
603
	// Make sure this attachment is on this board.
604
	$attachmentData = array();
605
	$db->fetchQuery('
606
		SELECT
607
			a.filename, a.fileext, a.id_attach, a.attachment_type, a.mime_type, a.approved, 
608
			a.downloads, a.size, a.width, a.height, m.id_topic, m.id_board
609
		FROM {db_prefix}attachments as a
610
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
611
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
612
		WHERE id_attach = {int:attach}
613
			AND attachment_type = {int:type}
614
			AND a.approved = {int:approved}
615
		LIMIT 1',
616
		array(
617
			'attach' => $id_attach,
618
			'approved' => 1,
619
			'type' => 0,
620
		)
621
	)->fetch_callback(
622
		function ($row) use (&$attachmentData) {
623
			$attachmentData = $row;
624
			$attachmentData['is_image'] = substr($attachmentData['mime_type'], 0, 5) === 'image';
625
			$attachmentData['size'] = byte_format($attachmentData['size']);
626
		}
627
	);
628
629
	return !empty($attachmentData) ? $attachmentData : false;
630
}
631
632
/**
633
 * Increase download counter for id_attach.
634
 *
635
 * What it does:
636
 *
637
 * - Does not check if it's a thumbnail.
638
 *
639
 * @param int $id_attach
640
 * @package Attachments
641
 */
642
function increaseDownloadCounter($id_attach)
643
{
644
	$db = database();
645
646
	$db->fetchQuery('
647
		UPDATE {db_prefix}attachments
648
		SET downloads = downloads + 1
649
		WHERE id_attach = {int:id_attach}',
650
		array(
651
			'id_attach' => $id_attach,
652
		)
653
	);
654
}
655
656
/**
657
 * Saves a file and stores it locally for avatar use by id_member.
658
 *
659
 * What it does:
660
 *
661
 * - supports GIF, JPG, PNG, BMP and WBMP formats.
662
 * - uses createThumbnail() to resize to max_width by max_height, and saves the result to a file.
663
 * - updates the database info for the member's avatar.
664
 * - returns whether the download and resize was successful.
665
 *
666
 * @param string $temporary_path the full path to the temporary file
667
 * @param int $memID member ID
668
 * @param int $max_width
669
 * @param int $max_height
670
 * @return bool whether the download and resize was successful.
671
 * @package Attachments
672
 */
673
function saveAvatar($temporary_path, $memID, $max_width, $max_height)
674
{
675
	global $modSettings;
676
677
	$db = database();
678
679
	// Just making sure there is a non-zero member.
680
	if (empty($memID))
681
	{
682
		return false;
683
	}
684
685
	// Get this party started
686
	$valid_avatar_extensions = [
687
		IMAGETYPE_PNG => 'png',
688
		IMAGETYPE_JPEG => 'jpeg',
689
		IMAGETYPE_WEBP => 'webp'
690
	];
691
692
	$image = new Image($temporary_path);
693
	if (!$image->isImageLoaded())
694
	{
695
		return false;
696
	}
697
698
	$format = $image->getDefaultFormat();
699
	$ext = $valid_avatar_extensions[$format];
700
	$tokenizer = new TokenHash();
701
	$fileName = 'avatar_' . $memID . '_' . $tokenizer->generate_hash(16) . '.' . $ext;
702
703
	// Clear out any old attachment
704
	require_once(SUBSDIR . '/ManageAttachments.subs.php');
705
	removeAttachments(array('id_member' => $memID));
706
707
	$db->insert('',
708
		'{db_prefix}attachments',
709
		array(
710
			'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255',
711
			'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int', 'id_folder' => 'int',
712
		),
713
		array(
714
			$memID, 1, $fileName, '', $ext, 1, 1,
715
		),
716
		array('id_attach')
717
	);
718
	$attachID = $db->insert_id('{db_prefix}attachments');
719
720
	// The destination filename depends on the custom dir for avatars
721
	$destName = $modSettings['custom_avatar_dir'] . '/' . $fileName;
722
723
	// Resize and rotate it.
724
	if (!empty($modSettings['attachment_autorotate']))
725
	{
726
		$image->autoRotate();
727
	}
728
729
	$thumb_image = $image->createThumbnail($max_width, $max_height, $destName, $format);
730
	if ($thumb_image !== false)
731
	{
732
		list ($width, $height) = $thumb_image->getImageDimensions();
733
		$mime_type = $thumb_image->getMimeType();
734
735
		// Write filesize in the database.
736
		$db->query('', '
737
			UPDATE {db_prefix}attachments
738
			SET 
739
				size = {int:filesize}, width = {int:width}, height = {int:height}, 
740
				mime_type = {string:mime_type}
741
			WHERE id_attach = {int:current_attachment}',
742
			array(
743
				'filesize' => $thumb_image->getFilesize(),
744
				'width' => (int) $width,
745
				'height' => (int) $height,
746
				'current_attachment' => $attachID,
747
				'mime_type' => $mime_type,
748
			)
749
		);
750
751
		// Retain this globally in case the script wants it.
752
		$modSettings['new_avatar_data'] = array(
753
			'id' => $attachID,
754
			'filename' => $destName,
755
			'type' => 1,
756
		);
757
758
		return true;
759
	}
760
761
	// Having a problem with image manipulation, rotation, resize, etc
762
	$db->query('', '
763
		DELETE FROM {db_prefix}attachments
764
		WHERE id_attach = {int:current_attachment}',
765
		array(
766
			'current_attachment' => $attachID,
767
		)
768
	);
769
770
	return false;
771
}
772
773
/**
774
 * Get the size of a specified image with better error handling.
775
 *
776
 * What it does:
777
 *
778
 * - Uses getimagesizefromstring() to determine the dimensions of an image file.
779
 * - Attempts to connect to the server first, so it won't time out.
780
 * - Attempts to read a short byte range of the file, just enough to validate
781
 * the mime type.
782
 *
783
 * @param string $url
784
 * @return mixed[]|bool the image size as array(width, height), or false on failure
785
 * @package Attachments
786
 */
787
function url_image_size($url)
788
{
789
	// Can we pull this from the cache... please please?
790
	$temp = array();
791
	if (Cache::instance()->getVar($temp, 'url_image_size-' . md5($url), 3600))
792
	{
793
		return $temp;
794
	}
795
796
	$url_path = parse_url($url, PHP_URL_PATH);
797
	$extension = pathinfo($url_path, PATHINFO_EXTENSION);
798
799
	// Set a RANGE to read
800
	switch ($extension)
801
	{
802
		case 'jpg':
803
		case 'jpeg':
804
			// Size location block is variable, so we fetch a meaningful chunk
805
			$range = 32768;
806
			break;
807
		case 'png':
808
			// Size will be in the first 24 bytes
809
			$range = 1024;
810
			break;
811
		case 'gif':
812
			// Size will be in the first 10 bytes
813
			$range = 1024;
814
			break;
815
		case 'bmp':
816
			// Size will be in the first 32 bytes
817
			$range = 1024;
818
			break;
819
		default:
820
			// Read the entire file then, webp for example might have the exif at the end
821
			$range = 0;
822
	}
823
824
	$image = new FsockFetchWebdata(array('max_length' => $range));
825
	$image->get_url_data($url);
826
827
	// The server may not understand Range: so lets try to fetch the entire thing
828
	// assuming we were not simply turned away
829
	if ($image->result('code') != 200 && $image->result('code') != 403)
830
	{
831
		$image = new FsockFetchWebdata(array());
832
		$image->get_url_data($url);
833 2
	}
834 2
835
	// Here is the data, getimagesizefromstring does not care if its a complete image, it only
836
	// searches for size headers in a given data set.
837
	$data = $image->result('body');
838
	$size = getimagesizefromstring($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type string[]; however, parameter $string of getimagesizefromstring() 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

838
	$size = getimagesizefromstring(/** @scrutinizer ignore-type */ $data);
Loading history...
839 2
840 2
	// Well, ok, umm, fail!
841
	if ($data === false || $size === false)
842 1
	{
843
		return array(-1, -1, -1);
844 2
	}
845 2
846
	// Save this for 1hour, its not like the image size is going to change
847 2
	Cache::instance()->put('url_image_size-' . md5($url), $size, 3600);
848 2
849 2
	return $size;
850
}
851 2
852 2
/**
853 2
 * Function to retrieve server-stored avatar files
854
 *
855 2
 * @param string $directory
856 2
 * @return array
857
 * @package Attachments
858
 */
859
function getServerStoredAvatars($directory)
860
{
861
	global $context, $txt, $modSettings;
862
863
	$result = [];
864
	$file_functions = FileFunctions::instance();
865 2
866 2
	// You can always have no avatar
867
	$result[] = array(
868
		'filename' => 'blank.png',
869
		'checked' => in_array($context['member']['avatar']['server_pic'], array('', 'blank.png')),
870 2
		'name' => $txt['no_pic'],
871
		'is_dir' => false
872
	);
873
874
	// Not valid is easy
875
	$avatarDir = $modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory;
876
	if (!$file_functions->isDir($avatarDir))
877
	{
878 2
		return $result;
879 2
	}
880
881
	// Find all avatars under, and in, the avatar directory
882 2
	$serverAvatars = new RecursiveIteratorIterator(
883
		new RecursiveDirectoryIterator(
884
			$avatarDir,
885
			FilesystemIterator::SKIP_DOTS
886
		),
887 2
		\RecursiveIteratorIterator::SELF_FIRST,
888
		\RecursiveIteratorIterator::CATCH_GET_CHILD
889 2
	);
890
	$key = 0;
891
	foreach ($serverAvatars as $entry)
892
	{
893
		if ($entry->isDir())
894
		{
895
			// Add a new directory
896
			$result[] = array(
897
				'filename' => htmlspecialchars(basename($entry), ENT_COMPAT, 'UTF-8'),
898
				'checked' => strpos($context['member']['avatar']['server_pic'], basename($entry) . '/') !== false,
899
				'name' => '[' . htmlspecialchars(str_replace('_', ' ', basename($entry)), ENT_COMPAT, 'UTF-8') . ']',
900
				'is_dir' => true,
901
				'files' => []
902
			);
903
			$key++;
904
905
			continue;
906
		}
907
908
		// Add the files under the current directory we are iterating on
909
		if (!in_array($entry->getFilename(), array('blank.png', 'index.php', '.htaccess')))
910
		{
911
			$extension = $entry->getExtension();
912
			$filename = $entry->getBasename('.' . $extension);
913
914
			// Make sure it is an image.
915
			if (empty(getValidMimeImageType($extension)))
916
			{
917
				continue;
918
			}
919
920
			$result[$key]['files'][] = [
921
				'filename' => htmlspecialchars($entry->getFilename(), ENT_COMPAT, 'UTF-8'),
922
				'checked' => $entry->getFilename() == $context['member']['avatar']['server_pic'],
923
				'name' => htmlspecialchars(str_replace('_', ' ', $filename), ENT_COMPAT, 'UTF-8'),
924
				'is_dir' => false
925
			];
926
927
			if (dirname($entry->getPath(), 1) === $modSettings['avatar_directory'])
928
			{
929
				$context['avatar_list'][] = str_replace($modSettings['avatar_directory'] . '/', '', $entry->getPathname());
930
			}
931
		}
932
	}
933
934
	return $result;
935
}
936
937
/**
938
 * Update an attachment's thumbnail
939
 *
940
 * @param string $filename the actual name of the file
941
 * @param int $id_attach the numeric attach id
942
 * @param int $id_msg the numeric message the attachment is associated with
943
 * @param int $old_id_thumb = 0 id of thumbnail to remove, such as from our post form
944
 * @param string $real_filename the fully qualified hash name of where the file is
945
 * @return array The updated information
946
 * @package Attachments
947
 */
948
function updateAttachmentThumbnail($filename, $id_attach, $id_msg, $old_id_thumb = 0, $real_filename = '')
949
{
950
	global $modSettings;
951
952
	$attachment = array('id_attach' => $id_attach);
953 2
954
	// Load our image functions, it will determine which graphics library to use
955 2
	$image = new Image($filename);
956
957 2
	// Image is not autorotated because it was at the time of upload (hopefully)
958 2
	$thumb_filename = (!empty($real_filename) ? $real_filename : $filename) . '_thumb';
959 2
	$thumb_image = $image->createThumbnail($modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']);
960
961
	if ($thumb_image instanceof Image)
962 2
	{
963 2
		// So what folder are we putting this image in?
964 2
		$attachmentsDir = new AttachmentsDirectory($modSettings, database());
965 2
		$id_folder_thumb = $attachmentsDir->currentDirectoryId();
966
967
		// Calculate the size of the created thumbnail.
968
		$size = $thumb_image->getImageDimensions();
969 2
		list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
970 2
		$thumb_size = $thumb_image->getFilesize();
971
972 2
		// Figure out the mime type and other details
973
		$thumb_mime = getValidMimeImageType($size[2]);
974
		$thumb_ext = substr($thumb_mime, strpos($thumb_mime, '/') + 1);
975
		$thumb_hash = getAttachmentFilename($thumb_filename, 0, null, true);
976
977
		// Add this beauty to the database.
978
		$db = database();
979
		$db->insert('',
980
			'{db_prefix}attachments',
981
			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'),
982
			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),
983
			array('id_attach')
984
		);
985
986 2
		$attachment['id_thumb'] = $db->insert_id('{db_prefix}attachments');
987
		if (!empty($attachment['id_thumb']))
988
		{
989
			$db->query('', '
990 2
				UPDATE {db_prefix}attachments
991
				SET id_thumb = {int:id_thumb}
992 2
				WHERE id_attach = {int:id_attach}',
993
				array(
994
					'id_thumb' => $attachment['id_thumb'],
995
					'id_attach' => $attachment['id_attach'],
996
				)
997 2
			);
998
999
			$thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
0 ignored issues
show
Bug introduced by
It seems like $attachment['id_thumb'] can also be of type boolean; however, parameter $attachment_id of getAttachmentFilename() does only seem to accept integer|null, 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

999
			$thumb_realname = getAttachmentFilename($thumb_filename, /** @scrutinizer ignore-type */ $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
Loading history...
1000
			if (file_exists($filename . '_thumb'))
1001
			{
1002
				rename($filename . '_thumb', $thumb_realname);
1003
			}
1004
1005
			// Do we need to remove an old thumbnail?
1006
			if (!empty($old_id_thumb))
1007
			{
1008
				require_once(SUBSDIR . '/ManageAttachments.subs.php');
1009
				removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1010
			}
1011
		}
1012
	}
1013
1014
	return $attachment;
1015
}
1016
1017
/**
1018
 * Compute and return the total size of attachments to a single message.
1019
 *
1020
 * @param int $id_msg
1021
 * @param bool $include_count = true if true, it also returns the attachments count
1022
 * @package Attachments
1023
 * @return mixed
1024
 */
1025
function attachmentsSizeForMessage($id_msg, $include_count = true)
1026
{
1027
	$db = database();
1028
1029
	if ($include_count)
1030
	{
1031
		$request = $db->fetchQuery('
1032
			SELECT 
1033
				COUNT(*), SUM(size)
1034
			FROM {db_prefix}attachments
1035
			WHERE id_msg = {int:id_msg}
1036
				AND attachment_type = {int:attachment_type}',
1037
			array(
1038
				'id_msg' => $id_msg,
1039
				'attachment_type' => 0,
1040
			)
1041
		);
1042
	}
1043
	else
1044
	{
1045
		$request = $db->fetchQuery('
1046
			SELECT 
1047
				COUNT(*)
1048
			FROM {db_prefix}attachments
1049
			WHERE id_msg = {int:id_msg}
1050
				AND attachment_type = {int:attachment_type}',
1051
			array(
1052
				'id_msg' => $id_msg,
1053
				'attachment_type' => 0,
1054
			)
1055
		);
1056
	}
1057
1058
	return $request->fetch_row();
1059
}
1060
1061
/**
1062
 * Older attachments may still use this function.
1063
 *
1064
 * @param string $filename
1065
 * @param int $attachment_id
1066
 * @param string|null $dir
1067
 * @param bool $new
1068
 *
1069
 * @return null|string|string[]
1070
 * @package Attachments
1071
 */
1072
function getLegacyAttachmentFilename($filename, $attachment_id, $dir = null, $new = false)
0 ignored issues
show
Unused Code introduced by
The parameter $dir is not used and could be removed. ( Ignorable by Annotation )

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

1072
function getLegacyAttachmentFilename($filename, $attachment_id, /** @scrutinizer ignore-unused */ $dir = null, $new = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1073
{
1074
	global $modSettings;
1075
1076
	$clean_name = $filename;
1077
1078
	// Sorry, no spaces, dots, or anything else but letters allowed.
1079
	$clean_name = preg_replace(array('/\s/', '/[^\w_\.\-]/'), array('_', ''), $clean_name);
1080
1081
	$enc_name = $attachment_id . '_' . strtr($clean_name, '.', '_') . md5($clean_name);
1082
	$clean_name = preg_replace('~\.[\.]+~', '.', $clean_name);
1083
1084
	if (empty($attachment_id) || ($new && empty($modSettings['attachmentEncryptFilenames'])))
1085
	{
1086
		return $clean_name;
1087
	}
1088
	elseif ($new)
1089
	{
1090
		return $enc_name;
1091
	}
1092
1093
	$attachmentsDir = new AttachmentsDirectory($modSettings, database());
1094
	$path = $attachmentsDir->getCurrent();
1095
1096
	return file_exists($path . '/' . $enc_name) ? $path . '/' . $enc_name : $path . '/' . $clean_name;
1097
}
1098
1099
/**
1100
 * Binds a set of attachments to a message.
1101
 *
1102
 * @param int $id_msg
1103
 * @param int[] $attachment_ids
1104
 * @package Attachments
1105
 */
1106
function bindMessageAttachments($id_msg, $attachment_ids)
1107
{
1108
	$db = database();
1109
1110
	$db->query('', '
1111
		UPDATE {db_prefix}attachments
1112
		SET id_msg = {int:id_msg}
1113
		WHERE id_attach IN ({array_int:attachment_list})',
1114
		array(
1115
			'attachment_list' => $attachment_ids,
1116
			'id_msg' => $id_msg,
1117
		)
1118
	);
1119
}
1120
1121
/**
1122
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
1123
 *
1124
 * - If new is set returns a hash for the db
1125
 * - If no file hash is supplied, determines one and returns it
1126
 * - Returns the path to the file
1127
 *
1128
 * @param string $filename The name of the file
1129
 * @param int|null $attachment_id The ID of the attachment
1130
 * @param string|null $dir Which directory it should be in (null to use current)
1131
 * @param bool $new If this is a new attachment, if so just returns a hash
1132
 * @param string $file_hash The file hash
1133
 *
1134
 * @return string
1135
 * @todo this currently returns the hash if new, and the full filename otherwise.
1136
 * Something messy like that.
1137
 * @todo and of course everything relies on this behavior and work around it. :P.
1138
 * Converters included.
1139
 */
1140
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
1141
{
1142
	global $modSettings;
1143
1144
	// Just make up a nice hash...
1145
	if ($new)
1146
	{
1147
		$tokenizer = new TokenHash();
1148
1149
		return $tokenizer->generate_hash(32);
1150
	}
1151
1152
	// In case of files from the old system, do a legacy call.
1153
	if (empty($file_hash))
1154
	{
1155
		return getLegacyAttachmentFilename($filename, $attachment_id, $dir, $new);
1156
	}
1157
1158
	// If we were passed the directory id, use it
1159
	$modSettings['currentAttachmentUploadDir'] = $dir;
1160
	$attachmentsDir = new AttachmentsDirectory($modSettings, database());
1161
	$path = $attachmentsDir->getCurrent();
1162
1163
	return $path . '/' . $attachment_id . '_' . $file_hash . '.elk';
1164
}
1165
1166
/**
1167
 * Returns the board and the topic the attachment belongs to.
1168
 *
1169
 * @param int $id_attach
1170
 * @return int[]|bool on fail else an array of id_board, id_topic
1171
 * @package Attachments
1172
 */
1173
function getAttachmentPosition($id_attach)
1174
{
1175
	$db = database();
1176
1177
	// Make sure this attachment is on this board.
1178
	$request = $db->fetchQuery('
1179
		SELECT 
1180
			m.id_board, m.id_topic
1181
		FROM {db_prefix}attachments AS a
1182
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1183
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1184
		WHERE a.id_attach = {int:attach}
1185
			AND {query_see_board}
1186
		LIMIT 1',
1187
		array(
1188
			'attach' => $id_attach,
1189
		)
1190
	);
1191
1192
	$attachmentData = $request->fetch_all();
1193
1194
	if (empty($attachmentData))
1195
	{
1196
		return false;
1197
	}
1198
1199
	return $attachmentData[0];
1200
}
1201
1202
/**
1203
 * Simple wrapper for getimagesize
1204
 *
1205
 * @param string $file
1206
 * @param string|bool $error return array or false on error
1207
 *
1208
 * @return array|bool
1209
 */
1210
function elk_getimagesize($file, $error = 'array')
1211
{
1212
	$sizes = @getimagesize($file);
1213
1214
	// Can't get it, what shall we return
1215
	if (empty($sizes))
1216
	{
1217
		$sizes = $error === 'array' ? array(-1, -1, -1) : false;
1218
	}
1219
1220
	return $sizes;
1221
}
1222
1223
/**
1224
 * Checks if we have a known and support mime-type for which we have a thumbnail image
1225 2
 *
1226
 * @param string $file_ext
1227
 * @param bool $url
1228 2
 *
1229 2
 * @return bool|string
1230 2
 */
1231
function returnMimeThumb($file_ext, $url = false)
1232
{
1233
	global $settings;
1234
1235
	// These are not meant to be exhaustive, just some of the most common attached on a forum
1236
	$generics = array(
1237
		'arc' => array('tgz', 'zip', 'rar', '7z', 'gz'),
1238
		'doc' => array('doc', 'docx', 'wpd', 'odt'),
1239
		'sound' => array('wav', 'mp3', 'pcm', 'aiff', 'wma', 'm4a', 'flac'),
1240
		'video' => array('mp4', 'mgp', 'mpeg', 'mp4', 'wmv', 'mkv', 'flv', 'aiv', 'mov', 'swf'),
1241
		'txt' => array('rtf', 'txt', 'log'),
1242
		'presentation' => array('ppt', 'pps', 'odp'),
1243
		'spreadsheet' => array('xls', 'xlr', 'ods'),
1244
		'web' => array('html', 'htm')
1245
	);
1246
	foreach ($generics as $generic_extension => $generic_types)
1247
	{
1248
		if (in_array($file_ext, $generic_types))
1249
		{
1250
			$file_ext = $generic_extension;
1251
			break;
1252
		}
1253
	}
1254
1255
	static $distinct = array('arc', 'doc', 'sound', 'video', 'txt', 'presentation', 'spreadsheet', 'web',
1256
							 'c', 'cpp', 'css', 'csv', 'java', 'js', 'pdf', 'php', 'sql', 'xml');
1257
1258
	if (empty($settings))
1259
	{
1260
		ThemeLoader::loadEssentialThemeData();
1261
	}
1262
1263
	// Return the mine thumbnail if it exists or just the default
1264
	if (!in_array($file_ext, $distinct) || !file_exists($settings['theme_dir'] . '/images/mime_images/' . $file_ext . '.png'))
1265
	{
1266
		$file_ext = 'default';
1267
	}
1268
1269
	$location = $url ? $settings['theme_url'] : $settings['theme_dir'];
1270
1271
	return $location . '/images/mime_images/' . $file_ext . '.png';
1272
}
1273
1274
/**
1275
 * From either a mime type, an extension or an IMAGETYPE_* constant
1276
 * returns a valid image mime type
1277
 *
1278
 * @param string $mime
1279
 *
1280
 * @return string
1281
 */
1282
function getValidMimeImageType($mime)
1283
{
1284
	// These are the only valid image types.
1285
	$validImageTypes = array(
1286
		-1 => 'jpg',
1287
		// Starting from here are the IMAGETYPE_* constants
1288
		IMAGETYPE_GIF => 'gif',
1289
		IMAGETYPE_JPEG => 'jpeg',
1290
		IMAGETYPE_PNG => 'png',
1291
		IMAGETYPE_PSD => 'psd',
1292
		IMAGETYPE_BMP => 'bmp',
1293
		IMAGETYPE_TIFF_II => 'tiff',
1294
		IMAGETYPE_TIFF_MM => 'tiff',
1295
		IMAGETYPE_JPC => 'jpeg',
1296
		IMAGETYPE_IFF => 'iff',
1297
		IMAGETYPE_WBMP => 'bmp',
1298
		IMAGETYPE_WEBP => 'webp'
1299
	);
1300
1301
	$ext = (int) $mime > 0 && isset($validImageTypes[(int) $mime]) ? $validImageTypes[(int) $mime] : '';
1302
	if (empty($ext))
1303
	{
1304
		$ext = strtolower(trim(substr($mime, strpos($mime, '/')), '/'));
1305
	}
1306
1307
	return in_array($ext, $validImageTypes) ? 'image/' . $ext : '';
1308
}
1309
1310
/**
1311
 * This function returns the mimeType of a file using the best means available
1312
 *
1313
 * @param string $filename
1314
 * @return string
1315
 */
1316
function getMimeType($filename)
1317
{
1318
	$mimeType = '';
1319
1320
	// Check only existing readable files
1321
	if (!file_exists($filename) || !is_readable($filename))
1322
	{
1323
		return '';
1324
	}
1325
1326
	// Try finfo, this is the preferred way
1327
	if (function_exists('finfo_open'))
1328
	{
1329 2
		$finfo = finfo_open(FILEINFO_MIME);
1330
		$mimeType = finfo_file($finfo, $filename);
1331
		finfo_close($finfo);
1332
	}
1333
	// No finfo? What? lets try the old mime_content_type
1334
	elseif (function_exists('mime_content_type'))
1335
	{
1336
		$mimeType = mime_content_type($filename);
1337
	}
1338
	// Try using an exec call
1339
	elseif (function_exists('exec'))
1340
	{
1341
		$mimeType = @exec("/usr/bin/file -i -b $filename");
1342 2
	}
1343
1344
	// Still nothing? We should at least be able to get images correct
1345
	if (empty($mimeType))
1346
	{
1347
		$imageData = elk_getimagesize($filename, 'none');
1348
		if (!empty($imageData['mime']))
1349
		{
1350
			$mimeType = $imageData['mime'];
1351
		}
1352
	}
1353
1354
	// Account for long responses like text/plain; charset=us-ascii
1355
	if (!empty($mimeType) && strpos($mimeType, ';'))
1356
	{
1357
		list($mimeType,) = explode(';', $mimeType);
1358
	}
1359
1360
	return $mimeType;
1361
}
1362