Completed
Pull Request — master (#137)
by
unknown
02:59
created

Server::outputImage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 12
Bugs 2 Features 6
Metric Value
c 12
b 2
f 6
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.4285
cc 2
eloc 11
nc 2
nop 2
crap 2
1
<?php
2
3
namespace League\Glide;
4
5
use InvalidArgumentException;
6
use League\Flysystem\FileExistsException;
7
use League\Flysystem\FilesystemInterface;
8
use League\Glide\Api\ApiInterface;
9
use League\Glide\Filesystem\FileNotFoundException;
10
use League\Glide\Filesystem\FilesystemException;
11
use League\Glide\Responses\ResponseFactoryInterface;
12
13
class Server
14
{
15
    /**
16
     * Source file system.
17
     * @var FilesystemInterface
18
     */
19
    protected $source;
20
21
    /**
22
     * Source path prefix.
23
     * @var string
24
     */
25
    protected $sourcePathPrefix;
26
27
    /**
28
     * Cache file system.
29
     * @var FilesystemInterface
30
     */
31
    protected $cache;
32
33
    /**
34
     * Cache path prefix.
35
     * @var string
36
     */
37
    protected $cachePathPrefix;
38
39
    /**
40
     * Whether to group cache in folders.
41
     * @var bool
42
     */
43
    protected $groupCacheInFolders = true;
44
45
    /**
46
     * Image manipulation API.
47
     * @var ApiInterface
48
     */
49
    protected $api;
50
51
    /**
52
     * Response factory.
53
     * @var ResponseFactoryInterface|null
54
     */
55
    protected $responseFactory;
56
57
    /**
58
     * Base URL.
59
     * @var string
60
     */
61
    protected $baseUrl;
62
63
    /**
64
     * Default image manipulations.
65
     * @var array
66
     */
67
    protected $defaults = [];
68
69
    /**
70
     * Preset image manipulations.
71
     * @var array
72
     */
73
    protected $presets = [];
74
75
    /**
76
     * Create Server instance.
77
     * @param FilesystemInterface $source Source file system.
78
     * @param FilesystemInterface $cache  Cache file system.
79
     * @param ApiInterface        $api    Image manipulation API.
80
     */
81 144
    public function __construct(FilesystemInterface $source, FilesystemInterface $cache, ApiInterface $api)
82
    {
83 144
        $this->setSource($source);
84 144
        $this->setCache($cache);
85 144
        $this->setApi($api);
86 144
    }
87
88
    /**
89
     * Set source file system.
90
     * @param FilesystemInterface $source Source file system.
91
     */
92 144
    public function setSource(FilesystemInterface $source)
93
    {
94 144
        $this->source = $source;
95 144
    }
96
97
    /**
98
     * Get source file system.
99
     * @return FilesystemInterface Source file system.
100
     */
101 6
    public function getSource()
102
    {
103 6
        return $this->source;
104
    }
105
106
    /**
107
     * Set source path prefix.
108
     * @param string $sourcePathPrefix Source path prefix.
109
     */
110 15
    public function setSourcePathPrefix($sourcePathPrefix)
111
    {
112 15
        $this->sourcePathPrefix = trim($sourcePathPrefix, '/');
113 15
    }
114
115
    /**
116
     * Get source path prefix.
117
     * @return string Source path prefix.
118
     */
119 6
    public function getSourcePathPrefix()
120
    {
121 6
        return $this->sourcePathPrefix;
122
    }
123
124
    /**
125
     * Get source path.
126
     * @param  string                $path Image path.
127
     * @return string                The source path.
128
     * @throws FileNotFoundException
129
     */
130 66
    public function getSourcePath($path)
131
    {
132 66
        $path = trim($path, '/');
133
134 66
        if (substr($path, 0, strlen($this->baseUrl)) === $this->baseUrl) {
135 3
            $path = trim(substr($path, strlen($this->baseUrl)), '/');
136 3
        }
137
138 66
        if ($path === '') {
139 3
            throw new FileNotFoundException('Image path missing.');
140
        }
141
142 63
        if ($this->sourcePathPrefix) {
143 6
            $path = $this->sourcePathPrefix.'/'.$path;
144 6
        }
145
146 63
        return rawurldecode($path);
147
    }
148
149
    /**
150
     * Check if a source file exists.
151
     * @param  string $path Image path.
152
     * @return bool   Whether the source file exists.
153
     */
154 18
    public function sourceFileExists($path)
155
    {
156 18
        return $this->source->has($this->getSourcePath($path));
157
    }
158
159
    /**
160
     * Set base URL.
161
     * @param string $baseUrl Base URL.
162
     */
163 12
    public function setBaseUrl($baseUrl)
164
    {
165 12
        $this->baseUrl = trim($baseUrl, '/');
166 12
    }
167
168
    /**
169
     * Get base URL.
170
     * @return string Base URL.
171
     */
172 6
    public function getBaseUrl()
173
    {
174 6
        return $this->baseUrl;
175
    }
176
177
    /**
178
     * Set cache file system.
179
     * @param FilesystemInterface $cache Cache file system.
180
     */
181 144
    public function setCache(FilesystemInterface $cache)
182
    {
183 144
        $this->cache = $cache;
184 144
    }
185
186
    /**
187
     * Get cache file system.
188
     * @return FilesystemInterface Cache file system.
189
     */
190 6
    public function getCache()
191
    {
192 6
        return $this->cache;
193
    }
194
195
    /**
196
     * Set cache path prefix.
197
     * @param string $cachePathPrefix Cache path prefix.
198
     */
199 12
    public function setCachePathPrefix($cachePathPrefix)
200
    {
201 12
        $this->cachePathPrefix = trim($cachePathPrefix, '/');
202 12
    }
203
204
    /**
205
     * Get cache path prefix.
206
     * @return string Cache path prefix.
207
     */
208 6
    public function getCachePathPrefix()
209
    {
210 6
        return $this->cachePathPrefix;
211
    }
212
213
    /**
214
     * Set the group cache in folders setting.
215
     * @param bool $groupCacheInFolders Whether to group cache in folders.
216
     */
217 15
    public function setGroupCacheInFolders($groupCacheInFolders)
218
    {
219 15
        $this->groupCacheInFolders = $groupCacheInFolders;
220 15
    }
221
222
    /**
223
     * Get the group cache in folders setting.
224
     * @return bool Whether to group cache in folders.
225
     */
226 6
    public function getGroupCacheInFolders()
227
    {
228 6
        return $this->groupCacheInFolders;
229
    }
230
231
    /**
232
     * Get cache path.
233
     * @param  string $path   Image path.
234
     * @param  array  $params Image manipulation params.
235
     * @return string Cache path.
236
     */
237 48
    public function getCachePath($path, array $params = [])
238
    {
239 48
        $sourcePath = $this->getSourcePath($path);
240
241 48
        if ($this->sourcePathPrefix) {
242 3
            $sourcePath = substr($sourcePath, strlen($this->sourcePathPrefix) + 1);
243 3
        }
244
245 48
        $params = $this->getAllParams($params);
246 48
        unset($params['s'], $params['p']);
247 48
        ksort($params);
248
249 48
        $md5 = md5($sourcePath.'?'.http_build_query($params));
250
251 48
        $path = $this->groupCacheInFolders ? $sourcePath.'/'.$md5 : $md5;
252
253 48
        if ($this->cachePathPrefix) {
254 3
            $path = $this->cachePathPrefix.'/'.$path;
255 3
        }
256
257 48
        return $path;
258
    }
259
260
    /**
261
     * Check if a cache file exists.
262
     * @param  string $path   Image path.
263
     * @param  array  $params Image manipulation params.
264
     * @return bool   Whether the cache file exists.
265
     */
266 33
    public function cacheFileExists($path, array $params)
267
    {
268 33
        return $this->cache->has(
269 33
            $this->getCachePath($path, $params)
270 33
        );
271
    }
272
273
    /**
274
     * Delete cached manipulations for an image.
275
     * @param  string $path Image path.
276
     * @return bool   Whether the delete succeeded.
277
     */
278 6
    public function deleteCache($path)
279
    {
280 6
        if (!$this->groupCacheInFolders) {
281 3
            throw new InvalidArgumentException(
282
                'Deleting cached image manipulations is not possible when grouping cache into folders is disabled.'
283 3
            );
284
        }
285
286 3
        return $this->cache->deleteDir(
287 3
            dirname($this->getCachePath($path))
288 3
        );
289
    }
290
291
    /**
292
     * Set image manipulation API.
293
     * @param ApiInterface $api Image manipulation API.
294
     */
295 144
    public function setApi(ApiInterface $api)
296
    {
297 144
        $this->api = $api;
298 144
    }
299
300
    /**
301
     * Get image manipulation API.
302
     * @return ApiInterface Image manipulation API.
303
     */
304 6
    public function getApi()
305
    {
306 6
        return $this->api;
307
    }
308
309
    /**
310
     * Set default image manipulations.
311
     * @param array $defaults Default image manipulations.
312
     */
313 15
    public function setDefaults(array $defaults)
314
    {
315 15
        $this->defaults = $defaults;
316 15
    }
317
318
    /**
319
     * Get default image manipulations.
320
     * @return array Default image manipulations.
321
     */
322 6
    public function getDefaults()
323
    {
324 6
        return $this->defaults;
325
    }
326
327
    /**
328
     * Set preset image manipulations.
329
     * @param array $presets Preset image manipulations.
330
     */
331 15
    public function setPresets(array $presets)
332
    {
333 15
        $this->presets = $presets;
334 15
    }
335
336
    /**
337
     * Get preset image manipulations.
338
     * @return array Preset image manipulations.
339
     */
340 6
    public function getPresets()
341
    {
342 6
        return $this->presets;
343
    }
344
345
    /**
346
     * Get all image manipulations params, including defaults and presets.
347
     * @param  array $params Image manipulation params.
348
     * @return array All image manipulation params.
349
     */
350 51
    public function getAllParams(array $params)
351
    {
352 51
        $all = $this->defaults;
353
354 51
        if (isset($params['p'])) {
355 3
            foreach (explode(',', $params['p']) as $preset) {
356 3
                if (isset($this->presets[$preset])) {
357 3
                    $all = array_merge($all, $this->presets[$preset]);
358 3
                }
359 3
            }
360 3
        }
361
362 51
        return array_merge($all, $params);
363
    }
364
365
    /**
366
     * Set response factory.
367
     * @param ResponseFactoryInterface|null $responseFactory Response factory.
368
     */
369 15
    public function setResponseFactory(ResponseFactoryInterface $responseFactory = null)
370
    {
371 15
        $this->responseFactory = $responseFactory;
372 15
    }
373
374
    /**
375
     * Get response factory.
376
     * @return ResponseFactoryInterface Response factory.
377
     */
378 6
    public function getResponseFactory()
379
    {
380 6
        return $this->responseFactory;
381
    }
382
383
    /**
384
     * Generate and return image response.
385
     * @param  string                   $path   Image path.
386
     * @param  array                    $params Image manipulation params.
387
     * @return mixed                    Image response.
388
     * @throws InvalidArgumentException
389
     */
390 6
    public function getImageResponse($path, array $params)
391
    {
392 6
        if (is_null($this->responseFactory)) {
393 3
            throw new InvalidArgumentException(
394
                'Unable to get image response, no response factory defined.'
395 3
            );
396
        }
397
398 3
        $path = $this->makeImage($path, $params);
399
400 3
        return $this->responseFactory->create($this->cache, $path);
401
    }
402
403
    /**
404
     * Generate and return Base64 encoded image.
405
     * @param  string              $path   Image path.
406
     * @param  array               $params Image manipulation params.
407
     * @return string              Base64 encoded image.
408
     * @throws FilesystemException
409
     */
410 6
    public function getImageAsBase64($path, array $params)
411
    {
412 6
        $path = $this->makeImage($path, $params);
413
414 6
        $source = $this->cache->read($path);
415
416 6
        if ($source === false) {
417 3
            throw new FilesystemException(
418 3
                'Could not read the image `'.$path.'`.'
419 3
            );
420
        }
421
422 3
        return 'data:'.$this->cache->getMimetype($path).';base64,'.base64_encode($source);
423
    }
424
425
    /**
426
     * Generate and output image.
427
     * @param  string                   $path   Image path.
428
     * @param  array                    $params Image manipulation params.
429
     * @throws InvalidArgumentException
430
     */
431 3
    public function outputImage($path, array $params)
432
    {
433 3
        $path = $this->makeImage($path, $params);
434
435 3
        header('Content-Type:'.$this->cache->getMimetype($path));
436 3
        header('Content-Length:'.$this->cache->getSize($path));
437 3
        header('Cache-Control:'.'max-age=31536000, public');
438 3
        header('Expires:'.date_create('+1 years')->format('D, d M Y H:i:s').' GMT');
439
440 3
        $stream = $this->cache->readStream($path);
441
442 3
        if (ftell($stream) !== 0) {
443 3
            rewind($stream);
444 3
        }
445 3
        fpassthru($stream);
446 3
        fclose($stream);
447 3
    }
448
449
    /**
450
     * Generate manipulated image.
451
     * @param  string                $path   Image path.
452
     * @param  array                 $params Image manipulation params.
453
     * @return string                Cache path.
454
     * @throws FileNotFoundException
455
     * @throws FilesystemException
456
     */
457 30
    public function makeImage($path, array $params)
458
    {
459 30
        $sourcePath = $this->getSourcePath($path);
460 30
        $cachedPath = $this->getCachePath($path, $params);
461
462 30
        if ($this->cacheFileExists($path, $params) === true) {
463 15
            return $cachedPath;
464
        }
465
466 15
        if ($this->sourceFileExists($path) === false) {
467 3
            throw new FileNotFoundException(
468 3
                'Could not find the image `'.$sourcePath.'`.'
469 3
            );
470
        }
471
472 12
        $source = $this->source->read(
473
            $sourcePath
474 12
        );
475
476 12
        if ($source === false) {
477 3
            throw new FilesystemException(
478 3
                'Could not read the image `'.$sourcePath.'`.'
479 3
            );
480
        }
481
482
        // We need to write the image to the local disk before
483
        // doing any manipulations. This is because EXIF data
484
        // can only be read from an actual file.
485 9
        $tmp = tempnam(sys_get_temp_dir(), 'Glide');
486
487 9
        if (file_put_contents($tmp, $source) === false) {
488
            throw new FilesystemException(
489
                'Unable to write temp file for `'.$sourcePath.'`.'
490
            );
491
        }
492
493
        try {
494 9
            $write = $this->cache->write(
495 9
                $cachedPath,
496 9
                $this->api->run($tmp, $this->getAllParams($params))
497 9
            );
498
499 6
            if ($write === false) {
500 3
                throw new FilesystemException(
501 3
                    'Could not write the image `'.$cachedPath.'`.'
502 3
                );
503
            }
504 9
        } catch (FileExistsException $exception) {
505
            // This edge case occurs when the target already exists
506
            // because it's currently be written to disk in another
507
            // request. It's best to just fail silently.
508
        }
509
510 6
        unlink($tmp);
511
512 6
        return $cachedPath;
513
    }
514
}
515