Completed
Pull Request — master (#282)
by
unknown
13:02
created

Server::getCachePathCallable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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