Completed
Pull Request — master (#134)
by
unknown
05:44
created

Server::sourceFileExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 7
Bugs 1 Features 4
Metric Value
c 7
b 1
f 4
dl 0
loc 4
ccs 2
cts 2
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
     * 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
     * Temp image
77
     * @var string
78
     */
79
    protected $tmp;
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 144
    public function __construct(FilesystemInterface $source, FilesystemInterface $cache, ApiInterface $api)
88
    {
89 144
        $this->setSource($source);
90 144
        $this->setCache($cache);
91 144
        $this->setApi($api);
92 144
    }
93
94
    /**
95
     * Delete temp image
96
     */
97 6
    public function __destruct()
98
    {
99 6
        @unlink($this->tmp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

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

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
523
524 6
        return $cachedPath;
525
    }
526
}
527