Passed
Push — development ( b93807...dda237 )
by Emanuele
01:10 queued 23s
created

Attachment::_send_headers()   C

Complexity

Conditions 13
Paths 12

Size

Total Lines 73
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 33
nc 12
nop 8
dl 0
loc 73
ccs 0
cts 31
cp 0
crap 182
rs 6.6166
c 1
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * Attachment display.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Action;
21
use ElkArte\Errors\AttachmentErrorContext;
22
use ElkArte\Exceptions\Exception;
23
use ElkArte\Graphics\TextImage;
24
use ElkArte\Graphics\Image;
25
use ElkArte\AttachmentsDirectory;
26
use ElkArte\Http\Headers;
27
use ElkArte\TemporaryAttachmentsList;
28
use ElkArte\Themes\ThemeLoader;
29
use ElkArte\User;
30
31
/**
32
 * Everything to do with attachment handling / processing
33
 *
34
 * What it does:
35
 *
36
 * - Handles the downloading of an attachment or avatar
37
 * - Handles the uploading of attachments via Ajax
38
 * - Increments the download count where applicable
39
 *
40
 * @package Attachments
41
 */
42
class Attachment extends AbstractController
43
{
44 2
	/**
45
	 * {@inheritdoc }
46 2
	 */
47
	public function needTheme($action = '')
48
	{
49 2
		global $modSettings, $maintenance;
50
51
		// If guests are not allowed to browse and the use is a guest... kick him!
52
		if (empty($modSettings['allow_guestAccess']) && $this->user->is_guest)
53
		{
54
			return true;
55 2
		}
56
57 2
		// If not in maintenance or allowed to use the forum in maintenance
58
		if (empty($maintenance) || allowedTo('admin_forum'))
59 2
		{
60
			$sa = $this->_req->get('sa');
61
62
			return $sa === 'ulattach' || $sa === 'rmattach';
63
		}
64
		// else... politely kick them out
65
		else
66
		{
67
			return true;
68
		}
69
	}
70
71
	/**
72
	 * {@inheritdoc }
73
	 */
74
	public function trackStats($action = '')
75
	{
76
		return false;
77
	}
78
79
	/**
80
	 * The default action is to download an attachment.
81
	 * This allows ?action=attachment to be forwarded to action_dlattach()
82
	 */
83
	public function action_index()
84
	{
85
		// add an subaction array to act accordingly
86
		$subActions = array(
87
			'dlattach' => array($this, 'action_dlattach'),
88
			'tmpattach' => array($this, 'action_tmpattach'),
89
			'ulattach' => array($this, 'action_ulattach'),
90
			'rmattach' => array($this, 'action_rmattach'),
91
		);
92
93
		// Setup the action handler
94
		$action = new Action('attachments');
95
		$subAction = $action->initialize($subActions, 'dlattach');
96
97
		// Call the action
98
		$action->dispatch($subAction);
99
	}
100
101
	/**
102
	 * Function to upload attachments via ajax calls
103
	 *
104
	 * - Currently called by drag drop attachment functionality
105 2
	 * - Pass the form data with session vars
106
	 * - Responds back with errors or file data
107 2
	 */
108
	public function action_ulattach()
109 2
	{
110 2
		global $context, $modSettings, $txt;
111 2
112
		$resp_data = array();
113
		ThemeLoader::loadLanguageFile('Errors');
114 2
		$context['attachments']['can']['post'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment') || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments')));
115 2
116 2
		// Set up the template details
117 2
		$template_layers = theme()->getLayers();
118
		$template_layers->removeAll();
119
		theme()->getTemplates()->load('Json');
120 2
		$context['sub_template'] = 'send_json';
121
122
		// Make sure the session is still valid
123
		if (checkSession('request', '', false) != '')
124
		{
125
			$context['json_data'] = array('result' => false, 'data' => $txt['session_timeout_file_upload']);
126
127
			return false;
128 2
		}
129
130 2
		// We should have files, otherwise why are we here?
131
		if (isset($_FILES['attachment']))
132 2
		{
133 2
			ThemeLoader::loadLanguageFile('Post');
134
135 2
			$attach_errors = AttachmentErrorContext::context();
136
			$attach_errors->activate();
137 2
138
			if ($context['attachments']['can']['post'] && empty($this->_req->post->from_qr))
139 2
			{
140 2
				require_once(SUBSDIR . '/Attachments.subs.php');
141
142
				$process = $this->_req->getPost('msg', 'intval', 0);
143
				processAttachments($process);
144 2
			}
145
146 2
			// Any mistakes?
147
			if ($attach_errors->hasErrors())
148
			{
149 2
				$errors = $attach_errors->prepareErrors();
150
151 2
				// Bad news for you, the attachments did not process, lets tell them why
152
				foreach ($errors as $key => $error)
153
				{
154 2
					$resp_data[] = $error;
155
				}
156
157
				$context['json_data'] = array('result' => false, 'data' => $resp_data);
158
			}
159
			// No errors, lets get the details of what we have for our response back to the upload dialog
160
			else
161
			{
162
				$tmp_attachments = new TemporaryAttachmentsList();
163
				foreach ($tmp_attachments->toArray() as $attachID => $val)
164
				{
165
					// We need to grab the name anyhow
166
					if (!empty($val['tmp_name']))
167
					{
168
						$resp_data = array(
169
							'name' => $val['name'],
170
							'attachid' => $val['public_attachid'],
171
							'size' => $val['size']
172
						);
173 2
					}
174
				}
175
176
				$context['json_data'] = array('result' => true, 'data' => $resp_data);
177
			}
178
		}
179 2
		// Could not find the files you claimed to have sent
180
		else
181 2
		{
182
			$context['json_data'] = array('result' => false, 'data' => $txt['no_files_uploaded']);
183
		}
184
	}
185
186
	/**
187
	 * Function to remove attachments which were added via ajax calls
188
	 *
189
	 * What it does:
190
	 *
191
	 * - Currently called by drag drop attachment functionality
192
	 * - Requires file name and file path
193
	 * - Responds back with success or error
194
	 */
195
	public function action_rmattach()
196
	{
197
		global $context, $txt;
198
199
		// Prepare the template so we can respond with json
200
		$template_layers = theme()->getLayers();
201
		$template_layers->removeAll();
202
		theme()->getTemplates()->load('Json');
203
		$context['sub_template'] = 'send_json';
204
205
		// Make sure the session is valid
206
		if (checkSession('request', '', false) !== '')
207
		{
208
			ThemeLoader::loadLanguageFile('Errors');
209
			$context['json_data'] = array('result' => false, 'data' => $txt['session_timeout']);
210
211
			return false;
212
		}
213
214
		// We need a filename and path or we are not going any further
215
		if (isset($this->_req->post->attachid))
216
		{
217
			$result = false;
218
			$tmp_attachments = new TemporaryAttachmentsList();
219
			if ($tmp_attachments->hasAttachments())
220
			{
221
				require_once(SUBSDIR . '/Attachments.subs.php');
222
223
				$attachId = $tmp_attachments->getIdFromPublic($this->_req->post->attachid);
224
225
				try
226
				{
227
					$tmp_attachments->removeById($attachId);
228
					$context['json_data'] = array('result' => true);
229
					$result = true;
230
				}
231
				catch (\Exception $e)
232
				{
233
					$result = $e->getMessage();
234
				}
235
			}
236
237
			if ($result !== true)
238
			{
239
				require_once(SUBSDIR . '/ManageAttachments.subs.php');
240
				$result_tmp = removeAttachments(array('id_attach' => $this->_req->getPost('attachid', 'intval')), '', true);
241
				if (!empty($result_tmp))
242
				{
243
					$context['json_data'] = array('result' => true);
244
					$result = true;
245
				}
246
				else
247
				{
248
					$result = $result_tmp;
249
				}
250
			}
251
252
			if ($result !== true)
253
			{
254
				ThemeLoader::loadLanguageFile('Errors');
255
				$context['json_data'] = array('result' => false, 'data' => $txt[!empty($result) ? $result : 'attachment_not_found']);
256
			}
257
		}
258
		else
259
		{
260
			ThemeLoader::loadLanguageFile('Errors');
261
			$context['json_data'] = array('result' => false, 'data' => $txt['attachment_not_found']);
262
		}
263
	}
264
265
	/**
266
	 * Downloads an attachment or avatar, and increments the download count.
267
	 *
268
	 * What it does:
269
	 *
270
	 * - It requires the view_attachments permission. (not for avatars!)
271
	 * - It disables the session parser, and clears any previous output.
272
	 * - It is accessed via the query string ?action=dlattach.
273
	 * - Views to attachments and avatars do not increase hits and are not logged
274
	 *   in the "Who's Online" log.
275
	 *
276
	 * @throws \ElkArte\Exceptions\Exception
277
	 */
278
	public function action_dlattach()
279
	{
280
		global $modSettings, $context, $topic, $board, $settings;
281
282
		// Some defaults that we need.
283
		$context['no_last_modified'] = true;
284
		$filename = null;
285
286
		// Make sure some attachment was requested!
287
		if (!isset($this->_req->query->attach) && !isset($this->_req->query->id))
288
		{
289
			// Give them the old can't find it image
290
			$this->action_no_attach();
291
		}
292
293
		// We need to do some work on attachments and avatars.
294
		require_once(SUBSDIR . '/Attachments.subs.php');
295
296
		// Temporary attachment, special case...
297
		if (isset($this->_req->query->attach) && strpos($this->_req->query->attach, 'post_tmp_' . $this->user->id . '_') !== false)
298
		{
299
			// Return via tmpattach, back presumably to the post form
300
			$this->action_tmpattach();
301
		}
302
303
		$id_attach = $this->_req->getQuery('attach', 'intval', $this->_req->getQuery('id', 'intval', 0));
304
		if ($this->_req->getQuery('type') === 'avatar')
305
		{
306
			$attachment = getAvatar($id_attach);
307
308
			$is_avatar = true;
309
			$this->_req->query->image = true;
310
		}
311
		// This is just a regular attachment...
312
		else
313
		{
314
			if (empty($topic) && !empty($id_attach))
315
			{
316
				$id_board = 0;
317
				$id_topic = 0;
318
				$attachPos = getAttachmentPosition($id_attach);
319
				if ($attachPos !== false)
320
				{
321
					list($id_board, $id_topic) = array_values($attachPos);
0 ignored issues
show
Bug introduced by
It seems like $attachPos can also be of type true; however, parameter $array of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

321
					list($id_board, $id_topic) = array_values(/** @scrutinizer ignore-type */ $attachPos);
Loading history...
322
				}
323
			}
324
			else
325
			{
326
				$id_board = $board;
327
				$id_topic = $topic;
328
			}
329
330
			isAllowedTo('view_attachments', $id_board);
331
332
			if ($this->_req->getQuery('thumb') === null)
333
			{
334
				$attachment = getAttachmentFromTopic($id_attach, $id_topic);
335
			}
336
			else
337
			{
338
				$this->_req->query->image = true;
339
				$attachment = getAttachmentThumbFromTopic($id_attach, $id_topic);
340
341
				// 1 is the file name, no file name, no thumbnail, no image.
342
				if (empty($attachment[1]))
343
				{
344
					$full_attach = getAttachmentFromTopic($id_attach, $id_topic);
345
					$attachment[1] = !empty($full_attach[1]) ? $full_attach[1] : '';
346
					$attachment[4] = 0;
347
					$attachment[5] = 0;
348
					$attachment[7] = $full_attach[7];
349
					$attachment[8] = $full_attach[8];
350
351
					// return mime type ala mimetype extension
352
					$check = returnMimeThumb(!empty($full_attach[3]) ? $full_attach[3] : 'default');
353
354
					if ($check !== false)
0 ignored issues
show
introduced by
The condition $check !== false is always true.
Loading history...
355
					{
356
						$attachment[3] = 'png';
357
						$attachment[6] = 'image/png';
358
						$filename = $check;
359
					}
360
					else
361
					{
362
						$attachmentsDir = new AttachmentsDirectory($modSettings, database());
363
						$filename = $attachmentsDir->getCurrent() . '/' . $attachment[1];
364
					}
365
366
					if (substr(get_finfo_mime($filename), 0, 5) !== 'image')
0 ignored issues
show
Bug introduced by
It seems like get_finfo_mime($filename) can also be of type boolean; however, parameter $string of substr() 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

366
					if (substr(/** @scrutinizer ignore-type */ get_finfo_mime($filename), 0, 5) !== 'image')
Loading history...
367
					{
368
						$attachment[3] = 'png';
369
						$attachment[6] = 'image/png';
370
						$filename = $settings['theme_dir'] . '/images/mime_images/default.png';
371
					}
372
				}
373
			}
374
		}
375
376
		if (empty($attachment))
377
		{
378
			// Exit via no_attach
379
			$this->action_no_attach();
380
		}
381
382
		list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $attachment;
383
384
		// If it isn't yet approved, do they have permission to view it?
385
		if (!$is_approved && ($id_member == 0 || $this->user->id !== $id_member) && ($attachment_type == 0 || $attachment_type == 3))
386
		{
387
			isAllowedTo('approve_posts', $id_board ?? $board);
388
		}
389
390
		// Update the download counter (unless it's a thumbnail or an avatar).
391
		if (!empty($id_attach) && empty($is_avatar) || $attachment_type != 3)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($id_attach) && ...| $attachment_type != 3, Probably Intended Meaning: ! empty($id_attach) && (... $attachment_type != 3)
Loading history...
392
		{
393
			increaseDownloadCounter($id_attach);
394
		}
395
396
		if ($filename === null)
397
		{
398
			$filename = getAttachmentFilename($real_filename, $id_attach, $id_folder, false, $file_hash);
399
		}
400
401
		$eTag = '"' . substr($id_attach . $real_filename . @filemtime($filename), 0, 64) . '"';
0 ignored issues
show
Bug introduced by
Are you sure @filemtime($filename) of type false|integer can be used in concatenation? ( Ignorable by Annotation )

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

401
		$eTag = '"' . substr($id_attach . $real_filename . /** @scrutinizer ignore-type */ @filemtime($filename), 0, 64) . '"';
Loading history...
402
		$disposition = !isset($this->_req->query->image) ? 'attachment' : 'inline';
403
		$do_cache = !(!isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== '');
404
405
		// Make sure the mime type warrants an inline display.
406
		if (isset($this->_req->query->image) && !empty($mime_type) && strpos($mime_type, 'image/') !== 0)
407
		{
408
			unset($this->_req->query->image);
409
			$mime_type = '';
410
		}
411
		// Does this have a mime type?
412
		elseif (empty($mime_type) || !isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== '')
413
		{
414
			$mime_type = '';
415
			if (isset($this->_req->query->image))
416
			{
417
				unset($this->_req->query->image);
418
			}
419
		}
420
421
		$this->prepare_headers($filename, $eTag, $mime_type, $disposition, $real_filename, $do_cache);
422
		$this->send_file($filename, $mime_type);
423
424
		obExit(false);
425
	}
426
427
	/**
428
	 * Generates a language image based on text for display, outputs image and exits
429
	 *
430
	 * @param null|string $text
431
	 * @throws \ElkArte\Exceptions\Exception
432
	 */
433
	public function action_no_attach($text = null)
434
	{
435
		global $txt;
436
437
		if ($text === null)
438
		{
439
			new ThemeLoader();
440
			ThemeLoader::loadLanguageFile('Errors');
441
			$text = $txt['attachment_not_found'];
442
		}
443
444
		try
445
		{
446
			$img = new TextImage($text);
447
			$img = $img->generate(200);
448
		}
449
		catch (\Exception $e)
450
		{
451
			throw new Exception('no_access', false);
452
		}
453
454
		$this->prepare_headers('no_image', 'no_image', 'image/png', 'inline', 'no_image.png', true, false);
455
		Headers::instance()->sendHeaders();
456
		echo $img;
457
458
		obExit(false);
459
	}
460
461
	/**
462
	 * If the mime type benefits from compression e.g. text/xyz and gzencode is
463
	 * available and the user agent accpets gzip, then return true, else false
464
	 *
465
	 * @param string $mime_type
466
	 * @return bool if we should compress the file
467
	 */
468
	public function useCompression($mime_type)
469
	{
470
		global $modSettings;
471
472
		// Not compressible, or not supported / requested by client
473
		if (!preg_match('~^(?:text/|application/(?:json|xml|rss\+xml)$)~i', $mime_type)
474
			|| strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === false)
475
		{
476
			return false;
477
		}
478
479
		// Support is available on the server
480
		if (!function_exists('gzencode') && !empty($modSettings['enableCompressedOutput']))
481
		{
482
			return false;
483
		}
484
485
		return true;
486
	}
487
488
	/**
489
	 * Takes care of sending out the most common headers.
490
	 *
491
	 * @param string $filename Full path+file name of the file in the filesystem
492
	 * @param string $eTag ETag cache validator
493
	 * @param string $mime_type The mime-type of the file
494
	 * @param string $disposition The value of the Content-Disposition header
495
	 * @param string $real_filename The original name of the file
496
	 * @param bool $do_cache If send the a max-age header or not
497
	 * @param bool $check_filename When false, any check on $filename is skipped
498
	 */
499
	public function prepare_headers($filename, $eTag, $mime_type, $disposition, $real_filename, $do_cache, $check_filename = true)
500
	{
501
		global $txt;
502
503
		$headers = Headers::instance();
504
505
		// No point in a nicer message, because this is supposed to be an attachment anyway...
506
		if ($check_filename && !file_exists($filename))
507
		{
508
			ThemeLoader::loadLanguageFile('Errors');
509
510
			$protocol = preg_match('~HTTP/1\.[01]~i', $this->_req->server->SERVER_PROTOCOL) ? $this->_req->server->SERVER_PROTOCOL : 'HTTP/1.0';
511
			$headers
512
				->removeHeader('all')
513
				->headerSpecial($protocol . ' 404 Not Found')
514
				->sendHeaders();
515
516
			// We need to die like this *before* we send any anti-caching headers as below.
517
			die('404 - ' . $txt['attachment_not_found']);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
518
		}
519
520
		// If it hasn't been modified since the last time this attachment was retrieved, there's no need to display it again.
521
		if (!empty($this->_req->server->HTTP_IF_MODIFIED_SINCE))
522
		{
523
			list ($modified_since) = explode(';', $this->_req->server->HTTP_IF_MODIFIED_SINCE);
524
			if (!$check_filename || strtotime($modified_since) >= filemtime($filename))
525
			{
526
				@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

526
				/** @scrutinizer ignore-unhandled */ @ob_end_clean();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
527
528
				// Answer the question - no, it hasn't been modified ;).
529
				$headers
530
					->removeHeader('all')
531
					->headerSpecial('HTTP/1.1 304 Not Modified')
532
					->sendHeaders();
533
				exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
534
			}
535
		}
536
537
		// Check whether the ETag was sent back, and cache based on that...
538
		if (!empty($this->_req->server->HTTP_IF_NONE_MATCH) && strpos($this->_req->server->HTTP_IF_NONE_MATCH, $eTag) !== false)
539
		{
540
			@ob_end_clean();
541
542
			$headers
543
				->removeHeader('all')
544
				->headerSpecial('HTTP/1.1 304 Not Modified')
545
				->sendHeaders();
546
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
547
		}
548
549
		// Send the attachment headers.
550
		$headers
551
			->header('Expires', gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT')
552
			->header('Last-Modified', gmdate('D, d M Y H:i:s', $check_filename ? filemtime($filename) : time() - 525600 * 60) . ' GMT')
553
			->header('Accept-Ranges', 'bytes')
554
			->header('Connection', 'close')
555
			->header('ETag', $eTag);
556
557
		// Different browsers like different standards...
558
		$headers->setAttachmentFileParams($mime_type, $real_filename, $disposition);
559
560
		// If this has an "image extension" - but isn't actually an image - then ensure it isn't cached cause of silly IE.
561
		if ($do_cache)
562
		{
563
			$headers
564
				->header('Cache-Control', 'max-age=' . (525600 * 60) . ', private');
565
		}
566
		else
567
		{
568
			$headers
569
				->header('Pragma', 'no-cache')
570
				->header('Cache-Control', 'no-cache');
571
		}
572
573
		// Try to buy some time...
574
		detectServer()->setTimeLimit(600);
575
	}
576
577
	/**
578
	 * Sends the requested file to the user.  If the file is compressible e.g.
579
	 * has a mine type of text/??? may compress the file prior to sending.
580
	 *
581
	 * @param string $filename
582
	 * @param string $mime_type
583
	 */
584
	public function send_file($filename, $mime_type)
585
	{
586
		$headers = Headers::instance();
587
		$body = file_get_contents($filename);
588
		$use_compression = $this->useCompression($mime_type);
589
590
		// If we can/should compress this file
591
		if ($use_compression && strlen($body) > 250)
592
		{
593
			$body = gzencode($body, 2);
594
			$headers
595
				->header('Content-Encoding', 'gzip')
596
				->header('Vary', 'Accept-Encoding');
597
		}
598
599
		// Someone is getting a present
600
		$headers->header('Content-Length', strlen($body));
601
		$headers->send();
602
		echo $body;
603
	}
604
605
	/**
606
	 * "Simplified", cough, version of action_dlattach to send out thumbnails while creating
607
	 * or editing a message.
608
	 */
609
	public function action_tmpattach()
610
	{
611
		global $modSettings, $topic;
612
613
		// Make sure some attachment was requested!
614
		if (!isset($this->_req->query->attach))
615
		{
616
			$this->action_no_attach();
617
		}
618
619
		// We need to do some work on attachments and avatars.
620
		require_once(SUBSDIR . '/Attachments.subs.php');
621
		$tmp_attachments = new TemporaryAttachmentsList();
622
		$attachmentsDir = new AttachmentsDirectory($modSettings, database());
623
624
		try
625
		{
626
			if (empty($topic) || (string) (int) $this->_req->query->attach !== (string) $this->_req->query->attach)
627
			{
628
				$attach_data = $tmp_attachments->getTempAttachById($this->_req->query->attach, $attachmentsDir, User::$info->id);
629
				$file_ext = pathinfo($attach_data['name'], PATHINFO_EXTENSION);
630
				$filename = $attach_data['tmp_name'];
631
				$id_attach = $attach_data['attachid'];
632
				$real_filename = $attach_data['name'];
633
				$mime_type = $attach_data['type'];
634
			}
635
			else
636
			{
637
				$id_attach = $this->_req->getQuery('attach', 'intval', -1);
638
639
				isAllowedTo('view_attachments');
640
				$attachment = getAttachmentFromTopic($id_attach, $topic);
641
				if (empty($attachment))
642
				{
643
					// Exit via no_attach
644
					$this->action_no_attach();
645
				}
646
647
				list ($id_folder, $real_filename, $file_hash, $file_ext, $id_attach, $attachment_type, $mime_type, $is_approved, $id_member) = $attachment;
648
649
				// If it isn't yet approved, do they have permission to view it?
650
				if (!$is_approved && ($id_member == 0 || $this->user->id !== $id_member) && ($attachment_type == 0 || $attachment_type == 3))
651
				{
652
					isAllowedTo('approve_posts');
653
				}
654
655
				$filename = getAttachmentFilename($real_filename, $id_attach, $id_folder, false, $file_hash);
656
			}
657
		}
658
		catch (\Exception $e)
659
		{
660
			throw new Exception($e->getMessage(), false);
661
		}
662
663
		$resize = true;
664
665
		// Return mime type ala mimetype extension
666
		if (substr(get_finfo_mime($filename), 0, 5) !== 'image')
0 ignored issues
show
Bug introduced by
It seems like get_finfo_mime($filename) can also be of type boolean; however, parameter $string of substr() 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

666
		if (substr(/** @scrutinizer ignore-type */ get_finfo_mime($filename), 0, 5) !== 'image')
Loading history...
667
		{
668
			$checkMime = returnMimeThumb($file_ext);
669
			$mime_type = 'image/png';
670
			$resize = false;
671
			$filename = $checkMime;
672
		}
673
674
		$eTag = '"' . substr($id_attach . $real_filename . filemtime($filename), 0, 64) . '"';
675
		$do_cache = !(!isset($this->_req->query->image) && getValidMimeImageType($file_ext) !== '');
676
677
		$this->prepare_headers($filename, $eTag, $mime_type, 'inline', $real_filename, $do_cache);
678
679
		if ($resize)
680
		{
681
			// Create a thumbnail image and write it directly to the screen
682
			$image = new Image($filename);
683
684
			$thumb_filename = $filename . '_thumb';
685
			$thumb_image = $image->createThumbnail(100, 100, $thumb_filename);
686
687
			Headers::instance()->header('Content-Length', $thumb_image->getFilesize())->sendHeaders();
688
689
			if (@readfile($thumb_filename) === null)
690
			{
691
				echo file_get_contents($thumb_filename);
692
			}
693
		}
694
695
		obExit(false);
696
	}
697
}
698