Passed
Push — 2.4.0 ( ...442824 )
by steve
20:14
created

FileController::getName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 8
ccs 0
cts 8
cp 0
crap 12
rs 10
1
<?php
2
/**
3
 * @link http://www.newicon.net/neon
4
 * @copyright Copyright (c) 28/11/2016 Newicon Ltd
5
 * @license http://www.newicon.net/neon/license/
6
 */
7
8
namespace neon\firefly\controllers;
9
10
use Carbon\Carbon;
11
use neon\core\helpers\Arr;
12
use neon\core\helpers\Hash;
13
use neon\core\helpers\Str;
14
use neon\firefly\services\plupload\Receiver;
15
use neon\core\web\Response;
16
use yii\helpers\FileHelper;
17
use yii\web\HttpException;
18
19
class FileController extends \yii\web\Controller
20
{
21
	public function actionDestroyDeleted()
22
	{
23
		return neon()->firefly->mediaManager->destroyDeleted();
24
	}
25
26
	/**
27
	 * Show a file manager managed file
28
	 *
29
	 * /firefly/file/get?id=[fileuuid]
30
	 * /firefly/[fileuuid]
31
	 * @return \neon\core\web\Response
32
	 */
33
	public function actionGet()
34
	{
35
		$request = neon()->request;
36
		$firefly = neon()->firefly;
37
		$id = $request->get('id');
38
		if (!$firefly->fileManager->exists($id)) {
39
			// show placeholder image - should be handled by image manager - controllers should not know
40
			return neon()->response->sendFile(dirname(__DIR__) . '/assets/placeholder.jpg', 'No File', ['inline' => true]);
41
		}
42
43
		// as the url is unique for a file we can be aggressive about caching
44
		// see if we already have this in cache and the browser already has it too
45
		$this->handleNotModified();
46
47
		// set expiry information on the file
48
		$meta = $firefly->fileManager->getMeta($id);
49
		$this->sendCacheHeaders($meta['mime_type'], $meta['size'], $meta['updated_at'], md5($request->absoluteUrl), $meta['name']);
50
		$download = $request->get('download', $request->get('dl', false));
51
		try {
52
			// this can throw an error if the path is unknown
53
			return $firefly->fileManager->sendFile($id, (bool) $download);
54
		} catch (\Exception $e) {
55
			// if path unknown respond with a blank placeholder
56
			return neon()->response->sendFile(dirname(__DIR__) . '/assets/placeholder.jpg', 'No File', ['inline' => true]);
57
		}
58
	}
59
60
	/**
61
	 * Alternative action url for getting images for more consistent interface
62
	 *
63
	 * @return \neon\core\web\Response
64
	 * @throws HttpException
65
	 */
66
	public function actionImage()
67
	{
68
		return $this->actionImg();
69
	}
70
71
	/**
72
	 * Show a file manager managed image
73
	 * /mydomain.com/firefly/file/img?id=[fileuuid]
74
	 * @throws HttpException if id request parameter is not defined
75
	 * @return Response
76
	 */
77
	public function actionImg()
78
	{
79
		$id = neon()->request->getRequired('id');
80
81
		// create a unique request key - add v2 to force use of new cached version
82
		$requestKey = neon()->request->absoluteUrl . 'v2';
83
84
		// see if we already have this in cache and the browser already has it too
85
		// We want to remove the need to process images when ever possible
86
		// Send not modified header if already cached
87
		$this->handleNotModified($requestKey);
88
89
		$image = $this->handleImageProcessing($requestKey, $id);
90
91
		// This caches this whole request.... by generating the image file
92
		// note we only have the id when originally asking for the image via `getImage()`
93
		// Only images should ever be made public - these have been resaved as images by this point
94
		// $url = $this->generatePublicFile($id, $image['image']);
95
		// return $this->redirect($url);
96
		neon()->response->format = Response::FORMAT_RAW;
97
		$this->sendCacheHeaders($image['mime_type'], $image['size'], $image['updated_at'], $image['eTag'], basename($id));
98
		neon()->response->content = $image['image'];
99
		return neon()->response;
100
	}
101
102
103
	public function handleImageProcessing($requestKey, $idOrUrl)
104
	{
105
		// retrieve (or generate) from cache and return with appropriate caching headers
106
		return neon()->firefly->getCache()->getOrSet($requestKey, function() use ($idOrUrl, $requestKey) {
107
			$processedImage = neon()->firefly->imageManager->process($idOrUrl, neon()->request->get());
108
			return [
109
				// recalculate the mime type in case was spoofed
110
				'size' => strlen($processedImage),
111
				'mime_type' => finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $processedImage),
112
				'updated_at' => date("Y-m-d H:i:s"),
113
				'image' => $processedImage,
114
				'eTag' => md5($requestKey)
115
			];
116
		});
117
	}
118
119
120
	/**
121
	 * Another potential cache strategy to generate a static asset image file in a publicly accessible folder
122
	 * @param string $id - fileManager file id
123
	 * @param mixed $imageContent - the contents of the image
124
	 * @throws \yii\base\Exception
125
	 * @return string - the public url path
126
	 */
127
	public function generatePublicFile($id, $imageContent)
128
	{
129
		$dir = substr($id,0, 3);
130
		$publicDir = neon()->getAlias("@webroot/assets/firefly/$dir");
131
		if (!file_exists($publicDir)) {
132
			FileHelper::createDirectory($publicDir);
133
			file_put_contents("$publicDir/$id", $imageContent);
134
		}
135
		return neon()->getAlias("@web/assets/firefly/$dir/$id");
136
	}
137
138
	/**
139
	 * Send appropriate cache headers
140
	 * @param string $mime - mime type of file
141
	 * @param string|int $size - size in bytes of response
142
 	 * @param string $modified - MySQL Datetime
143
	 * @param string $eTag - eTag token header to use
144
	 * @param string $name - filename string to use in content disposition header
145
	 */
146
	public function sendCacheHeaders($mime, $size, $modified, $eTag, $name)
147
	{
148
		$headers = neon()->response->headers;
149
		$headers->add('Accept-Ranges', 'bytes');
150
		$headers->add('Content-Type', $mime);
151
		$headers->add('Content-Length', $size);
152
		$headers->add('Access-Control-Allow-Origin', '*');
153
		$headers->add('Last-Modified', Carbon::createFromFormat('Y-m-d H:i:s', $modified)->toRfc1123String());
154
		$headers->add('Cache-Control', 'private, max-age=30000000, immutable, only-if-cached');
155
		$headers->add('Expires', Carbon::now()->addSeconds(30000000)->toRfc1123String());
156
		$headers->add('ETag', $eTag);
157
		$headers->add('Content-Disposition', 'inline; filename="'.$name.'"');
158
	}
159
160
	/**
161
	 * Handle a request that can return a 304 not modified header
162
	 * @param bool|string $cacheKey - default false - if cache key is specified
163
	 */
164
	public function handleNotModified($cacheKey=false)
165
	{
166
		$requestHeaders = neon()->request->headers;
167
		$browserHasCache = (isset($requestHeaders['If-Modified-Since']) || isset($requestHeaders['If-None-Match']));
168
		if ($browserHasCache && (!$cacheKey || neon()->firefly->getCache()->exists($cacheKey))) {
169
			$this->sendNotModifiedHeaders();
170
		}
171
	}
172
173
	/**
174
	 * Send appropriate headers for 304 Not Modified header
175
	 */
176
	public function sendNotModifiedHeaders()
177
	{
178
		header($_SERVER['SERVER_PROTOCOL'].' 304 Not modified');
179
		header('Cache-Control: private, max-age=30000000, immutable, only-if-cached');
180
		header('Expires: ' . Carbon::now()->addYear(10)->toRfc1123String());
0 ignored issues
show
Unused Code introduced by
The call to Carbon\Carbon::addYear() has too many arguments starting with 10. ( Ignorable by Annotation )

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

180
		header('Expires: ' . Carbon::now()->/** @scrutinizer ignore-call */ addYear(10)->toRfc1123String());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
181
		exit;
182
	}
183
184
	/**
185
	 * API endpoint for plupload
186
	 * @return array
187
	 */
188
	public function actionUpload()
189
	{
190
		$r = new Receiver(neon()->request);
191
		return $r->receiveToFirefly('file');
192
	}
193
}
194