Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Sources/Subs-Attachments.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file handles the uploading and creation of attachments
5
 * as well as the auto management of the attachment directories.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2017 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * Check if the current directory is still valid or not.
22
 * If not creates the new directory
23
 *
24
 * @return void|bool False if any error occurred
25
 */
26
function automanage_attachments_check_directory()
27
{
28
	global $smcFunc, $boarddir, $modSettings, $context;
29
30
	// Not pretty, but since we don't want folders created for every post. It'll do unless a better solution can be found.
31
	if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'admin')
32
		$doit = true;
33
	elseif (empty($modSettings['automanage_attachments']))
34
		return;
35
	elseif (!isset($_FILES))
36
		return;
37
	elseif (isset($_FILES['attachment']))
38 View Code Duplication
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
39
			if (!empty($dummy))
40
			{
41
				$doit = true;
42
				break;
43
			}
44
45
	if (!isset($doit))
46
		return;
47
48
	$year = date('Y');
49
	$month = date('m');
50
51
	$rand = md5(mt_rand());
52
	$rand1 = $rand[1];
53
	$rand = $rand[0];
54
55
	if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
56
	{
57
			if (!is_array($modSettings['attachment_basedirectories']))
58
				$modSettings['attachment_basedirectories'] = $smcFunc['json_decode']($modSettings['attachment_basedirectories'], true);
59
			$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
60
	}
61
	else
62
		$base_dir = 0;
63
64
	if ($modSettings['automanage_attachments'] == 1)
65
	{
66
		if (!isset($modSettings['last_attachments_directory']))
67
			$modSettings['last_attachments_directory'] = array();
68
		if (!is_array($modSettings['last_attachments_directory']))
69
			$modSettings['last_attachments_directory'] = $smcFunc['json_decode']($modSettings['last_attachments_directory'], true);
70
		if (!isset($modSettings['last_attachments_directory'][$base_dir]))
71
			$modSettings['last_attachments_directory'][$base_dir] = 0;
72
	}
73
74
	$basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
75
	//Just to be sure: I don't want directory separators at the end
76
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
77
	$basedirectory = rtrim($basedirectory, $sep);
78
79
	switch ($modSettings['automanage_attachments'])
80
	{
81
		case 1:
82
			$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
83
			break;
84
		case 2:
85
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
86
			break;
87
		case 3:
88
			$updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
89
			break;
90 View Code Duplication
		case 4:
91
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
92
			break;
93 View Code Duplication
		case 5:
94
			$updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
95
			break;
96
		default :
97
			$updir = '';
98
	}
99
100
	if (!is_array($modSettings['attachmentUploadDir']))
101
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
102
	if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
103
		$outputCreation = automanage_attachments_create_directory($updir);
104
	elseif (in_array($updir, $modSettings['attachmentUploadDir']))
105
		$outputCreation = true;
106
107
	if ($outputCreation)
0 ignored issues
show
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...
108
	{
109
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
110
		$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
111
112
		updateSettings(array(
113
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
114
		));
115
	}
116
117
	return $outputCreation;
118
}
119
120
/**
121
 * Creates a directory
122
 *
123
 * @param string $updir The directory to be created
124
 *
125
 * @return bool False on errors
126
 */
127
function automanage_attachments_create_directory($updir)
128
{
129
	global $smcFunc, $modSettings, $context, $boarddir;
130
131
	$tree = get_directory_tree_elements($updir);
132
	$count = count($tree);
133
134
	$directory = attachments_init_dir($tree, $count);
0 ignored issues
show
It seems like $tree defined by get_directory_tree_elements($updir) on line 131 can also be of type false; however, attachments_init_dir() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
135
	if ($directory === false)
136
	{
137
		// Maybe it's just the folder name
138
		$tree = get_directory_tree_elements($boarddir . DIRECTORY_SEPARATOR . $updir);
139
		$count = count($tree);
140
141
		$directory = attachments_init_dir($tree, $count);
0 ignored issues
show
It seems like $tree defined by get_directory_tree_eleme...ORY_SEPARATOR . $updir) on line 138 can also be of type false; however, attachments_init_dir() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
142
		if ($directory === false)
143
			return false;
144
	}
145
146
	$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
147
148
	while (!@is_dir($directory) || $count != -1)
149
	{
150
		if (!@is_dir($directory))
151
		{
152
			if (!@mkdir($directory, 0755))
153
			{
154
				$context['dir_creation_error'] = 'attachments_no_create';
155
				return false;
156
			}
157
		}
158
159
		$directory .= DIRECTORY_SEPARATOR . array_shift($tree);
160
		$count--;
161
	}
162
163
	// Check if the dir is writable.
164
	if (!smf_chmod($directory))
165
	{
166
		$context['dir_creation_error'] = 'attachments_no_write';
167
		return false;
168
	}
169
170
	// Everything seems fine...let's create the .htaccess
171
	if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htaccess'))
172
		secureDirectory($updir, true);
173
174
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
175
	$updir = rtrim($updir, $sep);
176
177
	// Only update if it's a new directory
178
	if (!in_array($updir, $modSettings['attachmentUploadDir']))
179
	{
180
		$modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) + 1;
181
		$modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
182
183
		updateSettings(array(
184
			'attachmentUploadDir' => $smcFunc['json_encode']($modSettings['attachmentUploadDir']),
185
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
186
		), true);
187
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
188
	}
189
190
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
191
	return true;
192
}
193
194
/**
195
 * Called when a directory space limit is reached.
196
 * Creates a new directory and increments the directory suffix number.
197
 *
198
 * @return void|bool False on errors, true if successful, nothing if auto-management of attachments is disabled
199
 */
200
function automanage_attachments_by_space()
201
{
202
	global $smcFunc, $modSettings, $boarddir;
203
204
	if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
205
		return;
206
207
	$basedirectory = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : $boarddir;
208
	// Just to be sure: I don't want directory separators at the end
209
	$sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
210
	$basedirectory = rtrim($basedirectory, $sep);
211
212
	// Get the current base directory
213
	if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
214
	{
215
		$base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
216
		$base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
217
	}
218
	else
219
		$base_dir = 0;
220
221
	// Get the last attachment directory for that base directory
222
	if (empty($modSettings['last_attachments_directory'][$base_dir]))
223
		$modSettings['last_attachments_directory'][$base_dir] = 0;
224
	// And increment it.
225
	$modSettings['last_attachments_directory'][$base_dir]++;
226
227
	$updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
228
	if (automanage_attachments_create_directory($updir))
229
	{
230
		$modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
231
		updateSettings(array(
232
			'last_attachments_directory' => $smcFunc['json_encode']($modSettings['last_attachments_directory']),
233
			'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
234
		));
235
		$modSettings['last_attachments_directory'] = $smcFunc['json_decode']($modSettings['last_attachments_directory'], true);
236
237
		return true;
238
	}
239
	else
240
		return false;
241
}
242
243
/**
244
 * Split a path into a list of all directories and subdirectories
245
 *
246
 * @param string $directory A path
247
 *
248
 * @return array|bool An array of all the directories and subdirectories or false on failure
249
 */
250
function get_directory_tree_elements($directory)
251
{
252
	/*
253
		In Windows server both \ and / can be used as directory separators in paths
254
		In Linux (and presumably *nix) servers \ can be part of the name
255
		So for this reasons:
256
			* in Windows we need to explode for both \ and /
257
			* while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
258
	*/
259
	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
260
		$tree = preg_split('#[\\\/]#', $directory);
261
	else
262
	{
263
		if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR)
264
			return false;
265
266
		$tree = explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR));
267
	}
268
	return $tree;
269
}
270
271
/**
272
 * Return the first part of a path (i.e. c:\ or / + the first directory), used by automanage_attachments_create_directory
273
 *
274
 * @param array $tree An array
275
 * @param int $count The number of elements in $tree
276
 *
277
 * @return string|bool The first part of the path or false on error
278
 */
279
function attachments_init_dir(&$tree, &$count)
280
{
281
	$directory = '';
282
	// If on Windows servers the first part of the path is the drive (e.g. "C:")
283
	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
284
	{
285
		 //Better be sure that the first part of the path is actually a drive letter...
286
		 //...even if, I should check this in the admin page...isn't it?
287
		 //...NHAAA Let's leave space for users' complains! :P
288
		if (preg_match('/^[a-z]:$/i', $tree[0]))
289
			$directory = array_shift($tree);
290
		else
291
			return false;
292
293
		$count--;
294
	}
295
	return $directory;
296
}
297
298
/**
299
 * Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments']
300
 */
301
function processAttachments()
302
{
303
	global $context, $modSettings, $smcFunc, $txt, $user_info;
304
305
	// Make sure we're uploading to the right place.
306
	if (!empty($modSettings['automanage_attachments']))
307
		automanage_attachments_check_directory();
308
309
	if (!is_array($modSettings['attachmentUploadDir']))
310
		$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
311
312
	$context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
313
314
	// Is the attachments folder actualy there?
315
	if (!empty($context['dir_creation_error']))
316
		$initial_error = $context['dir_creation_error'];
317
	elseif (!is_dir($context['attach_dir']))
318
	{
319
		$initial_error = 'attach_folder_warning';
320
		log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
321
	}
322
323
	if (!isset($initial_error) && !isset($context['attachments']))
324
	{
325
		// If this isn't a new post, check the current attachments.
326
		if (isset($_REQUEST['msg']))
327
		{
328
			$request = $smcFunc['db_query']('', '
329
				SELECT COUNT(*), SUM(size)
330
				FROM {db_prefix}attachments
331
				WHERE id_msg = {int:id_msg}
332
					AND attachment_type = {int:attachment_type}',
333
				array(
334
					'id_msg' => (int) $_REQUEST['msg'],
335
					'attachment_type' => 0,
336
				)
337
			);
338
			list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
339
			$smcFunc['db_free_result']($request);
340
		}
341
		else
342
			$context['attachments'] = array(
343
				'quantity' => 0,
344
				'total_size' => 0,
345
			);
346
	}
347
348
	// Hmm. There are still files in session.
349
	$ignore_temp = false;
350
	if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1)
351
	{
352
		// Let's try to keep them. But...
353
		$ignore_temp = true;
354
		// If new files are being added. We can't ignore those
355 View Code Duplication
		foreach ($_FILES['attachment']['tmp_name'] as $dummy)
356
			if (!empty($dummy))
357
			{
358
				$ignore_temp = false;
359
				break;
360
			}
361
362
		// Need to make space for the new files. So, bye bye.
363
		if (!$ignore_temp)
364
		{
365 View Code Duplication
			foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
366
				if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
367
					unlink($attachment['tmp_name']);
368
369
			$context['we_are_history'] = $txt['error_temp_attachments_flushed'];
370
			$_SESSION['temp_attachments'] = array();
371
		}
372
	}
373
374
	if (!isset($_FILES['attachment']['name']))
375
		$_FILES['attachment']['tmp_name'] = array();
376
377
	if (!isset($_SESSION['temp_attachments']))
378
		$_SESSION['temp_attachments'] = array();
379
380
	// Remember where we are at. If it's anywhere at all.
381
	if (!$ignore_temp)
382
		$_SESSION['temp_attachments']['post'] = array(
383
			'msg' => !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0,
384
			'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
385
			'topic' => !empty($topic) ? $topic : 0,
0 ignored issues
show
The variable $topic seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
386
			'board' => !empty($board) ? $board : 0,
0 ignored issues
show
The variable $board seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
387
		);
388
389
	// If we have an initial error, lets just display it.
390
	if (!empty($initial_error))
391
	{
392
		$_SESSION['temp_attachments']['initial_error'] = $initial_error;
393
394
		// And delete the files 'cos they ain't going nowhere.
395 View Code Duplication
		foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
396
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
397
				unlink($_FILES['attachment']['tmp_name'][$n]);
398
399
		$_FILES['attachment']['tmp_name'] = array();
400
	}
401
402
	// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
403 View Code Duplication
	foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
404
	{
405
		if ($_FILES['attachment']['name'][$n] == '')
406
			continue;
407
408
		// First, let's first check for PHP upload errors.
409
		$errors = array();
410
		if (!empty($_FILES['attachment']['error'][$n]))
411
		{
412
			if ($_FILES['attachment']['error'][$n] == 2)
413
				$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
414
			elseif ($_FILES['attachment']['error'][$n] == 6)
415
				log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
416
			else
417
				log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
418
			if (empty($errors))
419
				$errors[] = 'attach_php_error';
420
		}
421
422
		// Try to move and rename the file before doing any more checks on it.
423
		$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
424
		$destName = $context['attach_dir'] . '/' . $attachID;
425
		if (empty($errors))
426
		{
427
			// The reported MIME type of the attachment might not be reliable.
428
			// Fortunately, PHP 5.3+ lets us easily verify the real MIME type.
429
			if (function_exists('mime_content_type'))
430
				$_FILES['attachment']['type'][$n] = mime_content_type($_FILES['attachment']['tmp_name'][$n]);
431
432
			$_SESSION['temp_attachments'][$attachID] = array(
433
				'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
434
				'tmp_name' => $destName,
435
				'size' => $_FILES['attachment']['size'][$n],
436
				'type' => $_FILES['attachment']['type'][$n],
437
				'id_folder' => $modSettings['currentAttachmentUploadDir'],
438
				'errors' => array(),
439
			);
440
441
			// Move the file to the attachments folder with a temp name for now.
442
			if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
443
				smf_chmod($destName, 0644);
444
			else
445
			{
446
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
447
				if (file_exists($_FILES['attachment']['tmp_name'][$n]))
448
					unlink($_FILES['attachment']['tmp_name'][$n]);
449
			}
450
		}
451
		else
452
		{
453
			$_SESSION['temp_attachments'][$attachID] = array(
454
				'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
455
				'tmp_name' => $destName,
456
				'errors' => $errors,
457
			);
458
459
			if (file_exists($_FILES['attachment']['tmp_name'][$n]))
460
				unlink($_FILES['attachment']['tmp_name'][$n]);
461
		}
462
		// If there's no errors to this point. We still do need to apply some additional checks before we are finished.
463
		if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
464
			attachmentChecks($attachID);
465
	}
466
	// Mod authors, finally a hook to hang an alternate attachment upload system upon
467
	// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
468
	// Populate $_SESSION['temp_attachments'][$attachID] with the following:
469
	//   name => The file name
470
	//   tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
471
	//   size => File size (required).
472
	//   type => MIME type (optional if not available on upload).
473
	//   id_folder => $modSettings['currentAttachmentUploadDir']
474
	//   errors => An array of errors (use the index of the $txt variable for that error).
475
	// Template changes can be done using "integrate_upload_template".
476
	call_integration_hook('integrate_attachment_upload', array());
477
}
478
479
/**
480
 * Performs various checks on an uploaded file.
481
 * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
482
 *
483
 * @param int $attachID The ID of the attachment
484
 * @return bool Whether the attachment is OK
485
 */
486
function attachmentChecks($attachID)
487
{
488
	global $modSettings, $context, $sourcedir, $smcFunc;
489
490
	// No data or missing data .... Not necessarily needed, but in case a mod author missed something.
491
	if (empty($_SESSION['temp_attachments'][$attachID]))
492
		$error = '$_SESSION[\'temp_attachments\'][$attachID]';
493
494
	elseif (empty($attachID))
495
		$error = '$attachID';
496
497
	elseif (empty($context['attachments']))
498
		$error = '$context[\'attachments\']';
499
500
	elseif (empty($context['attach_dir']))
501
		$error = '$context[\'attach_dir\']';
502
503
	// Let's get their attention.
504
	if (!empty($error))
505
		fatal_lang_error('attach_check_nag', 'debug', array($error));
506
507
	// Just in case this slipped by the first checks, we stop it here and now
508
	if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
509
	{
510
		$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
511
		return false;
512
	}
513
514
	// First, the dreaded security check. Sorry folks, but this shouldn't be avoided.
515
	$size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
516
	if (isset($context['validImageTypes'][$size[2]]))
517
	{
518
		require_once($sourcedir . '/Subs-Graphics.php');
519
		if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
520
		{
521
			// It's bad. Last chance, maybe we can re-encode it?
522
			if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
523
			{
524
				// Nothing to do: not allowed or not successful re-encoding it.
525
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
526
				return false;
527
			}
528
			// Success! However, successes usually come for a price:
529
			// we might get a new format for our image...
530
			$old_format = $size[2];
531
			$size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
532
			if (!(empty($size)) && ($size[2] != $old_format))
533
			{
534
				if (isset($context['validImageTypes'][$size[2]]))
535
					$_SESSION['temp_attachments'][$attachID]['type'] = 'image/' . $context['validImageTypes'][$size[2]];
536
			}
537
		}
538
	}
539
540
	// Is there room for this sucker?
541
	if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
542
	{
543
		// Check the folder size and count. If it hasn't been done already.
544
		if (empty($context['dir_size']) || empty($context['dir_files']))
545
		{
546
			$request = $smcFunc['db_query']('', '
547
				SELECT COUNT(*), SUM(size)
548
				FROM {db_prefix}attachments
549
				WHERE id_folder = {int:folder_id}
550
					AND attachment_type != {int:type}',
551
				array(
552
					'folder_id' => $modSettings['currentAttachmentUploadDir'],
553
					'type' => 1,
554
				)
555
			);
556
			list ($context['dir_files'], $context['dir_size']) = $smcFunc['db_fetch_row']($request);
557
			$smcFunc['db_free_result']($request);
558
		}
559
		$context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
560
		$context['dir_files']++;
561
562
		// Are we about to run out of room? Let's notify the admin then.
563
		if (empty($modSettings['attachment_full_notified']) && !empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
564
			|| (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
565
		{
566
			require_once($sourcedir . '/Subs-Admin.php');
567
			emailAdmins('admin_attachments_full');
568
			updateSettings(array('attachment_full_notified' => 1));
569
		}
570
571
		// // No room left.... What to do now???
572
		if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] > $modSettings['attachmentDirFileLimit']
573
			|| (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
574
		{
575
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
576
			{
577
				// Move it to the new folder if we can.
578
				if (automanage_attachments_by_space())
579
				{
580
					rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
581
					$_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
582
					$_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
583
					$context['dir_size'] = 0;
584
					$context['dir_files'] = 0;
585
				}
586
				// Or, let the user know that it ain't gonna happen.
587
				else
588
				{
589
					if (isset($context['dir_creation_error']))
590
						$_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
591
					else
592
						$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
593
				}
594
			}
595
			else
596
				$_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
597
		}
598
	}
599
600
	// Is the file too big?
601
	$context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
602 View Code Duplication
	if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
603
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
604
605
	// Check the total upload size for this post...
606
	if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
607
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attach_max_total_file_size', array(comma_format($modSettings['attachmentPostLimit'], 0), comma_format($modSettings['attachmentPostLimit'] - (($context['attachments']['total_size'] - $_SESSION['temp_attachments'][$attachID]['size']) / 1024), 0)));
608
609
	// Have we reached the maximum number of files we are allowed?
610
	$context['attachments']['quantity']++;
611
612
	// Set a max limit if none exists
613
	if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
614
		$modSettings['attachmentNumPerPostLimit'] = 50;
615
616 View Code Duplication
	if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
617
		$_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
618
619
	// File extension check
620
	if (!empty($modSettings['attachmentCheckExtensions']))
621
	{
622
		$allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
623
		foreach ($allowed as $k => $dummy)
624
			$allowed[$k] = trim($dummy);
625
626
		if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
627
		{
628
			$allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
629
			$_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
630
		}
631
	}
632
633
	// Undo the math if there's an error
634
	if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
635
	{
636
		if (isset($context['dir_size']))
637
			$context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
638
		if (isset($context['dir_files']))
639
			$context['dir_files']--;
640
		$context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
641
		$context['attachments']['quantity']--;
642
		return false;
643
	}
644
645
	return true;
646
}
647
648
/**
649
 * Create an attachment, with the given array of parameters.
650
 * - Adds any additional or missing parameters to $attachmentOptions.
651
 * - Renames the temporary file.
652
 * - Creates a thumbnail if the file is an image and the option enabled.
653
 *
654
 * @param array $attachmentOptions An array of attachment options
655
 * @return bool Whether the attachment was created successfully
656
 */
657
function createAttachment(&$attachmentOptions)
658
{
659
	global $modSettings, $sourcedir, $smcFunc, $context, $txt;
660
661
	require_once($sourcedir . '/Subs-Graphics.php');
662
663
	// If this is an image we need to set a few additional parameters.
664
	$size = @getimagesize($attachmentOptions['tmp_name']);
665
	list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
666
667
	// If it's an image get the mime type right.
668
	if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
669
	{
670
		// Got a proper mime type?
671 View Code Duplication
		if (!empty($size['mime']))
672
			$attachmentOptions['mime_type'] = $size['mime'];
673
674
		// Otherwise a valid one?
675
		elseif (isset($context['validImageTypes'][$size[2]]))
676
			$attachmentOptions['mime_type'] = 'image/' . $context['validImageTypes'][$size[2]];
677
	}
678
679
	// It is possible we might have a MIME type that isn't actually an image but still have a size.
680
	// For example, Shockwave files will be able to return size but be 'application/shockwave' or similar.
681
	if (!empty($attachmentOptions['mime_type']) && strpos($attachmentOptions['mime_type'], 'image/') !== 0)
682
	{
683
		$attachmentOptions['width'] = 0;
684
		$attachmentOptions['height'] = 0;
685
	}
686
687
	// Get the hash if no hash has been given yet.
688
	if (empty($attachmentOptions['file_hash']))
689
		$attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
690
691
	// Assuming no-one set the extension let's take a look at it.
692
	if (empty($attachmentOptions['fileext']))
693
	{
694
		$attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
695
		if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
696
			$attachmentOptions['fileext'] = '';
697
	}
698
699
	// Last chance to change stuff!
700
	call_integration_hook('integrate_createAttachment', array(&$attachmentOptions));
701
702
	// Make sure the folder is valid...
703
	$tmp = is_array($modSettings['attachmentUploadDir']) ? $modSettings['attachmentUploadDir'] : $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
704
	$folders = array_keys($tmp);
705
	if (empty($attachmentOptions['id_folder']) || !in_array($attachmentOptions['id_folder'], $folders))
706
		$attachmentOptions['id_folder'] = $modSettings['currentAttachmentUploadDir'];
707
708
	$attachmentOptions['id'] = $smcFunc['db_insert']('',
709
		'{db_prefix}attachments',
710
		array(
711
			'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
712
			'size' => 'int', 'width' => 'int', 'height' => 'int',
713
			'mime_type' => 'string-20', 'approved' => 'int',
714
		),
715
		array(
716
			(int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
717
			(int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
718
			(!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
719
		),
720
		array('id_attach'),
721
		1
722
	);
723
724
	// Attachment couldn't be created.
725
	if (empty($attachmentOptions['id']))
726
	{
727
		loadLanguage('Errors');
728
		log_error($txt['attachment_not_created'], 'general');
729
		return false;
730
	}
731
732
	// Now that we have the attach id, let's rename this sucker and finish up.
733
	$attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
734
	rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
735
736
	// If it's not approved then add to the approval queue.
737
	if (!$attachmentOptions['approved'])
738
		$smcFunc['db_insert']('',
739
			'{db_prefix}approval_queue',
740
			array(
741
				'id_attach' => 'int', 'id_msg' => 'int',
742
			),
743
			array(
744
				$attachmentOptions['id'], (int) $attachmentOptions['post'],
745
			),
746
			array()
747
		);
748
749
	if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
750
		return true;
751
752
	// Like thumbnails, do we?
753
	if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
754
	{
755
		if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
0 ignored issues
show
It seems like $attachmentOptions['destination'] can also be of type false; however, createThumbnail() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
756
		{
757
			// Figure out how big we actually made it.
758
			$size = @getimagesize($attachmentOptions['destination'] . '_thumb');
759
			list ($thumb_width, $thumb_height) = $size;
760
761 View Code Duplication
			if (!empty($size['mime']))
762
				$thumb_mime = $size['mime'];
763
			elseif (isset($context['validImageTypes'][$size[2]]))
764
				$thumb_mime = 'image/' . $context['validImageTypes'][$size[2]];
765
			// Lord only knows how this happened...
766
			else
767
				$thumb_mime = '';
768
769
			$thumb_filename = $attachmentOptions['name'] . '_thumb';
770
			$thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
771
			$thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
772
			$thumb_path = $attachmentOptions['destination'] . '_thumb';
773
774
			// We should check the file size and count here since thumbs are added to the existing totals.
775
			if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
776
			{
777
				$context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
778
				$context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
779
780
				// If the folder is full, try to create a new one and move the thumb to it.
781
				if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
782
				{
783
					if (automanage_attachments_by_space())
784
					{
785
						rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
786
						$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
787
						$context['dir_size'] = 0;
788
						$context['dir_files'] = 0;
789
					}
790
				}
791
			}
792
			// If a new folder has been already created. Gotta move this thumb there then.
793
			if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
794
			{
795
				rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
796
				$thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
797
			}
798
799
			// To the database we go!
800
			$attachmentOptions['thumb'] = $smcFunc['db_insert']('',
801
				'{db_prefix}attachments',
802
				array(
803
					'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
804
					'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
805
				),
806
				array(
807
					$modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
808
					$thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
809
				),
810
				array('id_attach'),
811
				1
812
			);
813
814
			if (!empty($attachmentOptions['thumb']))
815
			{
816
				$smcFunc['db_query']('', '
817
					UPDATE {db_prefix}attachments
818
					SET id_thumb = {int:id_thumb}
819
					WHERE id_attach = {int:id_attach}',
820
					array(
821
						'id_thumb' => $attachmentOptions['thumb'],
822
						'id_attach' => $attachmentOptions['id'],
823
					)
824
				);
825
826
				rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
0 ignored issues
show
It seems like $thumb_file_hash defined by getAttachmentFilename($t...ame, false, null, true) on line 771 can also be of type false; however, getAttachmentFilename() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
827
			}
828
		}
829
	}
830
831
	return true;
832
}
833
834
/**
835
 * Assigns the given attachments to the given message ID.
836
 *
837
 * @param $attachIDs array of attachment IDs to assign.
838
 * @param $msgID integer the message ID.
839
 *
840
 * @return boolean false on error or missing params.
841
 */
842
function assignAttachments($attachIDs = array(), $msgID = 0)
843
{
844
	global $smcFunc;
845
846
	// Oh, come on!
847
	if (empty($attachIDs) || empty($msgID))
848
		return false;
849
850
	// "I see what is right and approve, but I do what is wrong."
851
	call_integration_hook('integrate_assign_attachments', array(&$attachIDs, &$msgID));
852
853
	// One last check
854
	if (empty($attachIDs))
855
		return false;
856
857
	// Perform.
858
	$smcFunc['db_query']('', '
859
		UPDATE {db_prefix}attachments
860
		SET id_msg = {int:id_msg}
861
		WHERE id_attach IN ({array_int:attach_ids})',
862
		array(
863
			'id_msg' => $msgID,
864
			'attach_ids' => $attachIDs,
865
		)
866
	);
867
868
	return true;
869
}
870
871
/**
872
 * Gets an attach ID and tries to load all its info.
873
 *
874
 * @param int $attachID the attachment ID to load info from.
875
 *
876
 * @return mixed If succesful, it will return an array of loaded data. String, most likely a $txt key if there was some error.
877
 */
878
function parseAttachBBC($attachID = 0)
879
{
880
	global $board, $modSettings, $context, $scripturl, $smcFunc;
881
882
	// Meh...
883
	if (empty($attachID))
884
		return 'attachments_no_data_loaded';
885
886
	// Make it easy.
887
	$msgID = !empty($_REQUEST['msg']) ? (int) $_REQUEST['msg'] : 0;
888
889
	// Perhaps someone else wants to do the honors? Yes, this also includes dealing with previews ;)
890
	$externalParse = call_integration_hook('integrate_pre_parseAttachBBC', array($attachID, $msgID));
891
892
	// "I am innocent of the blood of this just person: see ye to it."
893
	if (!empty($externalParse) && (is_string($externalParse) || is_array($externalParse)))
894
		return $externalParse;
895
896
	//Are attachments enable?
897
	if (empty($modSettings['attachmentEnable']))
898
		return 'attachments_not_enable';
899
900
	// Previewing much? no msg ID has been set yet.
901
	if (!empty($context['preview_message']))
902
	{
903
		$allAttachments = getAttachsByMsg(0);
904
905
		if (empty($allAttachments[0][$attachID]))
906
			return 'attachments_no_data_loaded';
907
908
		$attachLoaded = loadAttachmentContext(0, $allAttachments);
909
910
		$attachContext = $attachLoaded[$attachID];
911
912
		// Fix the url to point out to showAvatar().
913
		$attachContext['href'] = $scripturl . '?action=dlattach;attach=' . $attachID . ';type=preview';
914
915
		$attachContext['link'] = '<a href="' . $scripturl . '?action=dlattach;attach=' . $attachID . ';type=preview' . (empty($attachContext['is_image']) ? ';file' : '') . '">' . $smcFunc['htmlspecialchars']($attachContext['name']) . '</a>';
916
917
		// Fix the thumbnail too, if the image has one.
918
		if (!empty($attachContext['thumbnail']) && !empty($attachContext['thumbnail']['has_thumb']))
919
			$attachContext['thumbnail']['href'] = $scripturl . '?action=dlattach;attach=' . $attachContext['thumbnail']['id'] . ';image;type=preview';
920
921
		return $attachContext;
922
	}
923
924
	// There is always the chance someone else has already done our dirty work...
925
	// If so, all pertinent checks were already done. Hopefully...
926
	if (!empty($context['current_attachments']) && !empty($context['current_attachments'][$attachID]))
927
		return $context['current_attachments'][$attachID];
928
929
	// If we are lucky enough to be in $board's scope then check it!
930
	if (!empty($board) && !allowedTo('view_attachments', $board))
931
		return 'attachments_not_allowed_to_see';
932
933
	// Get the message info associated with this particular attach ID.
934
	$attachInfo = getAttachMsgInfo($attachID);
935
936
	// There is always the chance this attachment no longer exists or isn't associated to a message anymore...
937
	if (empty($attachInfo) || empty($attachInfo['msg']))
938
		return 'attachments_no_msg_associated';
939
940
	// Hold it! got the info now check if you can see this attachment.
941
	if (!allowedTo('view_attachments', $attachInfo['board']))
942
		return 'attachments_not_allowed_to_see';
943
944
	$allAttachments = getAttachsByMsg($attachInfo['msg']);
945
	$attachContext = $allAttachments[$attachInfo['msg']][$attachID];
946
947
	// No point in keep going further.
948
	if (!allowedTo('view_attachments', $attachContext['board']))
949
		return 'attachments_not_allowed_to_see';
950
951
	// Load this particular attach's context.
952
	if (!empty($attachContext))
953
		$attachLoaded = loadAttachmentContext($attachContext['id_msg'], $allAttachments);
954
955
	// One last check, you know, gotta be paranoid...
956
	else
957
		return 'attachments_no_data_loaded';
958
959
	// This is the last "if" I promise!
960
	if (empty($attachLoaded))
961
		return 'attachments_no_data_loaded';
962
963
	else
964
		$attachContext = $attachLoaded[$attachID];
965
966
	// You may or may not want to show this under the post.
967
	if (!empty($modSettings['dont_show_attach_under_post']) && !isset($context['show_attach_under_post'][$attachID]))
968
		$context['show_attach_under_post'][$attachID] = $attachID;
969
970
	// Last minute changes?
971
	call_integration_hook('integrate_post_parseAttachBBC', array(&$attachContext));
972
973
	// Don't do any logic with the loaded data, leave it to whoever called this function.
974
	return $attachContext;
975
}
976
977
/**
978
 * Gets raw info directly from the attachments table.
979
 *
980
 * @param array $attachIDs An array of attachments IDs.
981
 *
982
 * @return array.
983
 */
984
function getRawAttachInfo($attachIDs)
985
{
986
	global $smcFunc, $modSettings;
987
988
	if (empty($attachIDs))
989
		return array();
990
991
	$return = array();
992
993
	$request = $smcFunc['db_query']('', '
994
		SELECT a.id_attach, a.id_msg, a.id_member, a.size, a.mime_type, a.id_folder, a.filename' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
995
				COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
996
		FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
997
				LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
998
		WHERE a.id_attach IN ({array_int:attach_ids})
999
		LIMIT 1',
1000
		array(
1001
			'attach_ids' => (array) $attachIDs,
1002
		)
1003
	);
1004
1005
	if ($smcFunc['db_num_rows']($request) != 1)
1006
		return array();
1007
1008
	while ($row = $smcFunc['db_fetch_assoc']($request))
1009
		$return[$row['id_attach']] = array(
1010
			'name' => $smcFunc['htmlspecialchars']($row['filename']),
1011
			'size' => $row['size'],
1012
			'attachID' => $row['id_attach'],
1013
			'unchecked' => false,
1014
			'approved' => 1,
1015
			'mime_type' => $row['mime_type'],
1016
			'thumb' => $row['id_thumb'],
1017
		);
1018
	$smcFunc['db_free_result']($request);
1019
1020
	return $return;
1021
}
1022
1023
/**
1024
 * Gets all needed message data associated with an attach ID
1025
 *
1026
 * @param int $attachID the attachment ID to load info from.
1027
 *
1028
 * @return array.
1029
 */
1030
function getAttachMsgInfo($attachID)
1031
{
1032
	global $smcFunc;
1033
1034
	if (empty($attachID))
1035
		return array();
1036
1037
	$request = $smcFunc['db_query']('', '
1038
		SELECT a.id_msg AS msg, m.id_topic AS topic, m.id_board AS board
1039
		FROM {db_prefix}attachments AS a
1040
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1041
		WHERE id_attach = {int:id_attach}
1042
		LIMIT 1',
1043
		array(
1044
			'id_attach' => (int) $attachID,
1045
		)
1046
	);
1047
1048
	if ($smcFunc['db_num_rows']($request) != 1)
1049
		return array();
1050
1051
	$row = $smcFunc['db_fetch_assoc']($request);
1052
	$smcFunc['db_free_result']($request);
1053
1054
	return $row;
1055
}
1056
1057
/**
1058
 * Gets attachment info associated with a message ID
1059
 *
1060
 * @param int $msgID the message ID to load info from.
1061
 *
1062
 * @return array.
1063
 */
1064
function getAttachsByMsg($msgID = 0)
1065
{
1066
	global $modSettings, $smcFunc;
1067
	static $attached = array();
1068
1069
	if (!isset($attached[$msgID]))
1070
	{
1071
		$request = $smcFunc['db_query']('', '
1072
			SELECT
1073
				a.id_attach, a.id_folder, a.id_msg, a.filename, a.file_hash, COALESCE(a.size, 0) AS filesize, a.downloads, a.approved, m.id_topic AS topic, m.id_board AS board,
1074
				a.width, a.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ',
1075
				COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
1076
			FROM {db_prefix}attachments AS a' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
1077
				LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)') . '
1078
				LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1079
			WHERE a.attachment_type = {int:attachment_type}
1080
				'. (!empty($msgID) ? 'AND a.id_msg = {int:message_id}' : '') . '',
1081
			array(
1082
				'message_id' => $msgID,
1083
				'attachment_type' => 0,
1084
				'is_approved' => 1,
1085
			)
1086
		);
1087
		$temp = array();
1088
		while ($row = $smcFunc['db_fetch_assoc']($request))
1089
		{
1090 View Code Duplication
			if (!$row['approved'] && $modSettings['postmod_active'] && !allowedTo('approve_posts') && (!isset($all_posters[$row['id_msg']]) || $all_posters[$row['id_msg']] != $user_info['id']))
0 ignored issues
show
The variable $all_posters 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...
The variable $user_info does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1091
				continue;
1092
1093
			$temp[$row['id_attach']] = $row;
1094
		}
1095
		$smcFunc['db_free_result']($request);
1096
1097
		// This is better than sorting it with the query...
1098
		ksort($temp);
1099
1100
		$attached[$msgID] = $temp;
1101
	}
1102
1103
	return $attached;
1104
}
1105
1106
/**
1107
 * This loads an attachment's contextual data including, most importantly, its size if it is an image.
1108
 * It requires the view_attachments permission to calculate image size.
1109
 * It attempts to keep the "aspect ratio" of the posted image in line, even if it has to be resized by
1110
 * the max_image_width and max_image_height settings.
1111
 *
1112
 * @param int $id_msg ID of the post to load attachments for
1113
 * @param array $attachments  An array of already loaded attachments. This function no longer depends on having $topic declared, thus, you need to load the actual topic ID for each attachment.
1114
 * @return array An array of attachment info
1115
 */
1116
function loadAttachmentContext($id_msg, $attachments)
1117
{
1118
	global $modSettings, $txt, $scripturl, $sourcedir, $smcFunc;
1119
1120
	if (empty($attachments) || empty($attachments[$id_msg]))
1121
		return array();
1122
1123
	// Set up the attachment info - based on code by Meriadoc.
1124
	$attachmentData = array();
1125
	$have_unapproved = false;
1126
	if (isset($attachments[$id_msg]) && !empty($modSettings['attachmentEnable']))
1127
	{
1128
		foreach ($attachments[$id_msg] as $i => $attachment)
1129
		{
1130
			$attachmentData[$i] = array(
1131
				'id' => $attachment['id_attach'],
1132
				'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
1133
				'downloads' => $attachment['downloads'],
1134
				'size' => ($attachment['filesize'] < 1024000) ? round($attachment['filesize'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['filesize'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'],
1135
				'byte_size' => $attachment['filesize'],
1136
				'href' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'],
1137
				'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_attach'] . '">' . $smcFunc['htmlspecialchars']($attachment['filename']) . '</a>',
1138
				'is_image' => !empty($attachment['width']) && !empty($attachment['height']) && !empty($modSettings['attachmentShowImages']),
1139
				'is_approved' => $attachment['approved'],
1140
				'topic' => $attachment['topic'],
1141
				'board' => $attachment['board'],
1142
			);
1143
1144
			// If something is unapproved we'll note it so we can sort them.
1145
			if (!$attachment['approved'])
1146
				$have_unapproved = true;
1147
1148
			if (!$attachmentData[$i]['is_image'])
1149
				continue;
1150
1151
			$attachmentData[$i]['real_width'] = $attachment['width'];
1152
			$attachmentData[$i]['width'] = $attachment['width'];
1153
			$attachmentData[$i]['real_height'] = $attachment['height'];
1154
			$attachmentData[$i]['height'] = $attachment['height'];
1155
1156
			// Let's see, do we want thumbs?
1157
			if (!empty($modSettings['attachmentThumbnails']) && !empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight']) && strlen($attachment['filename']) < 249)
1158
			{
1159
				// A proper thumb doesn't exist yet? Create one!
1160
				if (empty($attachment['id_thumb']) || $attachment['thumb_width'] > $modSettings['attachmentThumbWidth'] || $attachment['thumb_height'] > $modSettings['attachmentThumbHeight'] || ($attachment['thumb_width'] < $modSettings['attachmentThumbWidth'] && $attachment['thumb_height'] < $modSettings['attachmentThumbHeight']))
1161
				{
1162
					$filename = getAttachmentFilename($attachment['filename'], $attachment['id_attach'], $attachment['id_folder']);
1163
1164
					require_once($sourcedir . '/Subs-Graphics.php');
1165
					if (createThumbnail($filename, $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
0 ignored issues
show
It seems like $filename defined by getAttachmentFilename($a...ttachment['id_folder']) on line 1162 can also be of type false; however, createThumbnail() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1166
					{
1167
						// So what folder are we putting this image in?
1168 View Code Duplication
						if (!empty($modSettings['currentAttachmentUploadDir']))
1169
						{
1170
							if (!is_array($modSettings['attachmentUploadDir']))
1171
								$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
1172
							$id_folder_thumb = $modSettings['currentAttachmentUploadDir'];
1173
						}
1174
						else
1175
						{
1176
							$id_folder_thumb = 1;
1177
						}
1178
1179
						// Calculate the size of the created thumbnail.
1180
						$size = @getimagesize($filename . '_thumb');
1181
						list ($attachment['thumb_width'], $attachment['thumb_height']) = $size;
1182
						$thumb_size = filesize($filename . '_thumb');
1183
1184
						// What about the extension?
1185
						$thumb_ext = isset($context['validImageTypes'][$size[2]]) ? $context['validImageTypes'][$size[2]] : '';
1186
1187
						// Figure out the mime type.
1188
						if (!empty($size['mime']))
1189
							$thumb_mime = $size['mime'];
1190
						else
1191
							$thumb_mime = 'image/' . $thumb_ext;
1192
1193
						$thumb_filename = $attachment['filename'] . '_thumb';
1194
						$thumb_hash = getAttachmentFilename($thumb_filename, false, null, true);
1195
1196
						// Add this beauty to the database.
1197
						$attachment['id_thumb'] = $smcFunc['db_insert']('',
1198
							'{db_prefix}attachments',
1199
							array('id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'size' => 'int', 'width' => 'int', 'height' => 'int', 'fileext' => 'string', 'mime_type' => 'string'),
1200
							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),
1201
							array('id_attach'),
1202
							1
1203
						);
1204
						$old_id_thumb = $attachment['id_thumb'];
1205
						if (!empty($attachment['id_thumb']))
1206
						{
1207
							$smcFunc['db_query']('', '
1208
								UPDATE {db_prefix}attachments
1209
								SET id_thumb = {int:id_thumb}
1210
								WHERE id_attach = {int:id_attach}',
1211
								array(
1212
									'id_thumb' => $attachment['id_thumb'],
1213
									'id_attach' => $attachment['id_attach'],
1214
								)
1215
							);
1216
1217
							$thumb_realname = getAttachmentFilename($thumb_filename, $attachment['id_thumb'], $id_folder_thumb, false, $thumb_hash);
0 ignored issues
show
It seems like $thumb_hash defined by getAttachmentFilename($t...ame, false, null, true) on line 1194 can also be of type false; however, getAttachmentFilename() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1218
							rename($filename . '_thumb', $thumb_realname);
1219
1220
							// Do we need to remove an old thumbnail?
1221
							if (!empty($old_id_thumb))
1222
							{
1223
								require_once($sourcedir . '/ManageAttachments.php');
1224
								removeAttachments(array('id_attach' => $old_id_thumb), '', false, false);
1225
							}
1226
						}
1227
					}
1228
				}
1229
1230
				// Only adjust dimensions on successful thumbnail creation.
1231
				if (!empty($attachment['thumb_width']) && !empty($attachment['thumb_height']))
1232
				{
1233
					$attachmentData[$i]['width'] = $attachment['thumb_width'];
1234
					$attachmentData[$i]['height'] = $attachment['thumb_height'];
1235
				}
1236
			}
1237
1238
			if (!empty($attachment['id_thumb']))
1239
				$attachmentData[$i]['thumbnail'] = array(
1240
					'id' => $attachment['id_thumb'],
1241
					'href' => $scripturl . '?action=dlattach;topic=' . $attachment['topic'] . '.0;attach=' . $attachment['id_thumb'] . ';image',
1242
				);
1243
			$attachmentData[$i]['thumbnail']['has_thumb'] = !empty($attachment['id_thumb']);
1244
1245
			// If thumbnails are disabled, check the maximum size of the image.
1246
			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'])))
1247
			{
1248
				if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $attachment['height'] * $modSettings['max_image_width'] / $attachment['width'] <= $modSettings['max_image_height']))
1249
				{
1250
					$attachmentData[$i]['width'] = $modSettings['max_image_width'];
1251
					$attachmentData[$i]['height'] = floor($attachment['height'] * $modSettings['max_image_width'] / $attachment['width']);
1252
				}
1253 View Code Duplication
				elseif (!empty($modSettings['max_image_width']))
1254
				{
1255
					$attachmentData[$i]['width'] = floor($attachment['width'] * $modSettings['max_image_height'] / $attachment['height']);
1256
					$attachmentData[$i]['height'] = $modSettings['max_image_height'];
1257
				}
1258
			}
1259
			elseif ($attachmentData[$i]['thumbnail']['has_thumb'])
1260
			{
1261
				// If the image is too large to show inline, make it a popup.
1262
				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'])))
1263
					$attachmentData[$i]['thumbnail']['javascript'] = 'return reqWin(\'' . $attachmentData[$i]['href'] . ';image\', ' . ($attachment['width'] + 20) . ', ' . ($attachment['height'] + 20) . ', true);';
1264
				else
1265
					$attachmentData[$i]['thumbnail']['javascript'] = 'return expandThumb(' . $attachment['id_attach'] . ');';
1266
			}
1267
1268
			if (!$attachmentData[$i]['thumbnail']['has_thumb'])
1269
				$attachmentData[$i]['downloads']++;
1270
		}
1271
	}
1272
1273
	// Do we need to instigate a sort?
1274
	if ($have_unapproved)
1275
		usort($attachmentData, 'approved_attach_sort');
1276
1277
	return $attachmentData;
1278
}
1279
1280
?>