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

Attachment::prepare_headers()   B

Complexity

Conditions 11
Paths 9

Size

Total Lines 76
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 41
nc 9
nop 7
dl 0
loc 76
ccs 0
cts 31
cp 0
crap 132
rs 7.3166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * 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