Completed
Pull Request — master (#165)
by Jaap
02:25
created

Server::setTempDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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