Completed
Push — master ( f36c89...5d71f2 )
by Jonathan
06:08 queued 02:46
created

Server::setCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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