Passed
Pull Request — master (#4)
by
unknown
13:45
created

ResizedImageRepository::isSizeAllowedInConfig()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 11
rs 10
1
<?php
2
3
/*
4
 * CKFinder
5
 * ========
6
 * http://cksource.com/ckfinder
7
 * Copyright (C) 2007-2016, CKSource - Frederico Knabben. All rights reserved.
8
 *
9
 * The software, this file and its contents are subject to the CKFinder
10
 * License. Please read the license.txt file before using, installing, copying,
11
 * modifying or distribute this file or part of its contents. The contents of
12
 * this file is part of the Source Code of CKFinder.
13
 */
14
15
namespace CKSource\CKFinder\ResizedImage;
16
17
use CKSource\CKFinder\Acl\Acl;
18
use CKSource\CKFinder\Acl\Permission;
19
use CKSource\CKFinder\CKFinder;
20
use CKSource\CKFinder\Config;
21
use CKSource\CKFinder\Event\CKFinderEvent;
22
use CKSource\CKFinder\Event\ResizeImageEvent;
23
use CKSource\CKFinder\Exception\FileNotFoundException;
24
use CKSource\CKFinder\Exception\UnauthorizedException;
25
use CKSource\CKFinder\Filesystem\Path;
26
use CKSource\CKFinder\ResourceType\ResourceType;
27
28
/**
29
 * The ThumbnailRepository class.
30
 *
31
 * A class responsible for resized image management that simplifies
32
 * operations on resized versions of the image file, like batch renaming/moving
33
 * together with the original file.
34
 *
35
 * @copyright 2016 CKSource - Frederico Knabben
36
 */
37
class ResizedImageRepository
38
{
39
    /**
40
     * @var CKFinder
41
     */
42
    protected $app;
43
44
    /**
45
     * @var Config
46
     */
47
    protected $config;
48
49
    /**
50
     * @var Acl
51
     */
52
    protected $acl;
53
54
    /**
55
     * Event dispatcher.
56
     *
57
     * @var $dispatcher
0 ignored issues
show
Documentation Bug introduced by
The doc comment $dispatcher at position 0 could not be parsed: Unknown type name '$dispatcher' at position 0 in $dispatcher.
Loading history...
58
     */
59
    protected $dispatcher;
60
61
    /**
62
     * @param CKFinder $app
63
     */
64
    public function __construct(CKFinder $app)
65
    {
66
        $this->config = $app['config'];
67
        $this->acl = $app['acl'];
68
        $this->dispatcher = $app['dispatcher'];
69
        $this->app = $app;
70
    }
71
72
    /**
73
     * Returns a resized image for the provided source file.
74
     *
75
     * If an appropriate resized version already exists, it is reused.
76
     *
77
     * @param ResourceType $sourceFileResourceType
78
     * @param string       $sourceFileDir
79
     * @param string       $sourceFileName
80
     * @param int          $requestedWidth
81
     * @param int          $requestedHeight
82
     *
83
     * @return ResizedImage
84
     *
85
     * @throws \Exception
86
     */
87
    public function getResizedImage(ResourceType $sourceFileResourceType, $sourceFileDir, $sourceFileName, $requestedWidth, $requestedHeight)
88
    {
89
        $resizedImage = new ResizedImage(
90
            $this,
91
            $sourceFileResourceType,
92
            $sourceFileDir,
93
            $sourceFileName,
94
            $requestedWidth,
95
            $requestedHeight
96
        );
97
98
        if (!$this->acl->isAllowed($sourceFileResourceType->getName(), $sourceFileDir, Permission::IMAGE_RESIZE_CUSTOM) &&
99
            !$this->isSizeAllowedInConfig($requestedWidth, $requestedHeight)) {
100
            throw new UnauthorizedException('Provided size is not allowed in images.sizes configuration');
101
        }
102
103
        if (!$resizedImage->exists() && $resizedImage->requestedSizeIsValid()) {
104
            $resizedImage->create();
105
106
            $resizeImageEvent = new ResizeImageEvent($this->app, $resizedImage);
107
            $this->dispatcher->dispatch(CKFinderEvent::CREATE_RESIZED_IMAGE, $resizeImageEvent);
108
109
            if (!$resizeImageEvent->isPropagationStopped()) {
110
                $resizedImage = $resizeImageEvent->getResizedImage();
111
                $resizedImage->save();
112
            }
113
        }
114
115
        return $resizedImage;
116
    }
117
118
    /**
119
     * Returns an existing resized image.
120
     *
121
     * @param ResourceType $sourceFileResourceType
122
     * @param string       $sourceFileDir
123
     * @param string       $sourceFileName
124
     * @param string       $thumbnailFileName
125
     *
126
     * @return ResizedImage
127
     *
128
     * @throws FileNotFoundException
129
     */
130
    public function getExistingResizedImage(ResourceType $sourceFileResourceType, $sourceFileDir, $sourceFileName, $thumbnailFileName)
131
    {
132
        $size = ResizedImage::getSizeFromFilename($thumbnailFileName);
133
134
        $resizedImage = new ResizedImage(
135
            $this,
136
            $sourceFileResourceType,
137
            $sourceFileDir,
138
            $sourceFileName,
139
            $size['width'],
140
            $size['height'],
141
            true
142
        );
143
144
        if (!$resizedImage->exists()) {
145
            throw new FileNotFoundException('Resized image not found');
146
        }
147
148
        $resizedImage->load();
149
150
        return $resizedImage;
151
    }
152
153
    /**
154
     * @return CKFinder
155
     */
156
    public function getContainer()
157
    {
158
        return $this->app;
159
    }
160
161
    /**
162
     * Checks if the provided image size is allowed in the configuration.
163
     *
164
     * This is checked when `Permission::IMAGE_RESIZE_CUSTOM`
165
     * is not allowed in the source file folder.
166
     *
167
     * @param int $width
168
     * @param int $height
169
     *
170
     * @return bool `true` if the provided size is allowed in the configuration.
171
     */
172
    protected function isSizeAllowedInConfig($width, $height)
173
    {
174
        $configSizes = $this->config->get('images.sizes');
175
176
        foreach ($configSizes as $size) {
177
            if ($size['width'] === $width && $size['height'] === $height) {
178
                return true;
179
            }
180
        }
181
182
        return false;
183
    }
184
185
    /**
186
     * Returns the size name defined in the configuration, where width
187
     * or height are equal to those given in parameters.
188
     *
189
     * Resized images keep the original image aspect ratio.
190
     * When an image is resized using the size from the configuration,
191
     * at least one of the borders has the same length.
192
     *
193
     * @param int $width
194
     * @param int $height
195
     *
196
     * @return bool `true` if the size from the configuration was used.
197
     */
198
    protected function getSizeNameFromConfig($width, $height)
199
    {
200
        $configSizes = $this->config->get('images.sizes');
201
202
        foreach ($configSizes as $sizeName => $size) {
203
            if ($size['width'] === $width || $size['height'] === $height) {
204
                return $sizeName;
205
            }
206
        }
207
208
        return null;
209
    }
210
211
    /**
212
     * Deletes all resized images for a given file.
213
     *
214
     * @param ResourceType $sourceFileResourceType
215
     * @param string       $sourceFilePath
216
     * @param string       $sourceFileName
217
     *
218
     * @return bool `true` if deleted
219
     */
220
    public function deleteResizedImages(ResourceType $sourceFileResourceType, $sourceFilePath, $sourceFileName)
221
    {
222
        $resizedImagesPath = Path::combine($sourceFileResourceType->getDirectory(), $sourceFilePath, ResizedImage::DIR, $sourceFileName);
223
224
        $backend = $sourceFileResourceType->getBackend();
225
226
        if ($backend->hasDirectory($resizedImagesPath)) {
227
            return $backend->deleteDir($resizedImagesPath);
228
        }
229
230
        return false;
231
    }
232
233
    /**
234
     * Copies all resized images for a given file.
235
     *
236
     * @param ResourceType $sourceFileResourceType
237
     * @param string       $sourceFilePath
238
     * @param string       $sourceFileName
239
     * @param ResourceType $targetFileResourceType
240
     * @param string       $targetFilePath
241
     * @param string       $targetFileName
242
     */
243
    public function copyResizedImages(ResourceType $sourceFileResourceType, $sourceFilePath, $sourceFileName,
244
                                      ResourceType $targetFileResourceType, $targetFilePath, $targetFileName)
245
    {
246
        $sourceResizedImagesPath = Path::combine($sourceFileResourceType->getDirectory(), $sourceFilePath, ResizedImage::DIR, $sourceFileName);
247
        $targetResizedImagesPath = Path::combine($targetFileResourceType->getDirectory(), $targetFilePath, ResizedImage::DIR, $targetFileName);
248
249
        $sourceBackend = $sourceFileResourceType->getBackend();
250
        $targetBackend = $targetFileResourceType->getBackend();
251
252
        if ($sourceBackend->hasDirectory($sourceResizedImagesPath)) {
253
            $resizedImages = $sourceBackend->listContents($sourceResizedImagesPath);
254
255
            foreach ($resizedImages as $resizedImage) {
256
                if (!isset($resizedImage['path'])) {
257
                    continue;
258
                }
259
260
                $resizedImageStream = $sourceBackend->readStream($resizedImage['path']);
261
262
                $sourceImageSize = ResizedImage::getSizeFromFilename($resizedImage['basename']);
263
                $targetImageFilename = ResizedImage::createFilename($targetFileName, $sourceImageSize['width'], $sourceImageSize['height']);
264
265
                $targetBackend->putStream(Path::combine($targetResizedImagesPath, $targetImageFilename), $resizedImageStream);
266
            }
267
        }
268
    }
269
270
    /**
271
     * Renames all resized images created for a given file.
272
     *
273
     * @param ResourceType $sourceFileResourceType
274
     * @param string       $sourceFilePath
275
     * @param string       $originalSourceFileName
276
     * @param string       $newSourceFileName
277
     */
278
    public function renameResizedImages(ResourceType $sourceFileResourceType, $sourceFilePath, $originalSourceFileName, $newSourceFileName)
279
    {
280
        $resizedImagesDir = Path::combine($sourceFileResourceType->getDirectory(), $sourceFilePath, ResizedImage::DIR);
281
        $resizedImagesPath = Path::combine($resizedImagesDir, $originalSourceFileName);
282
        $newResizedImagesPath = Path::combine($resizedImagesDir, $newSourceFileName);
283
284
        $backend = $sourceFileResourceType->getBackend();
285
286
        if ($backend->hasDirectory($resizedImagesPath)) {
287
            if ($backend->rename($resizedImagesPath, $newResizedImagesPath)) {
288
                $resizedImages = $backend->listContents($newResizedImagesPath);
289
290
                foreach ($resizedImages as $resizedImage) {
291
                    if (!isset($resizedImage['path'])) {
292
                        continue;
293
                    }
294
295
                    $sourceImageSize = ResizedImage::getSizeFromFilename($resizedImage['basename']);
296
                    $newResizedImageFilename = ResizedImage::createFilename($newSourceFileName, $sourceImageSize['width'], $sourceImageSize['height']);
297
298
                    $backend->rename($resizedImage['path'], Path::combine($newResizedImagesPath, $newResizedImageFilename));
299
                }
300
            }
301
        }
302
    }
303
304
    /**
305
     * Returns a list of resized images generated for a given file.
306
     *
307
     * @param ResourceType $sourceFileResourceType source file resource type
308
     * @param string       $sourceFilePath         source file backend-relative path
309
     * @param string       $sourceFileName         source file name
310
     * @param array        $filterSizes            array containing names of sizes defined
311
     *                                             in the `images.sizes` configuration
312
     *
313
     * @return array
314
     */
315
    public function getResizedImagesList(ResourceType $sourceFileResourceType, $sourceFilePath, $sourceFileName, $filterSizes = array())
316
    {
317
        $resizedImagesPath = Path::combine($sourceFileResourceType->getDirectory(), $sourceFilePath, ResizedImage::DIR, $sourceFileName);
318
319
        $backend = $sourceFileResourceType->getBackend();
320
321
        $resizedImages = array();
322
323
        if (!$backend->hasDirectory($resizedImagesPath)) {
324
            return $resizedImages;
325
        }
326
327
        $resizedImagesFiles = array_filter(
328
            $backend->listContents($resizedImagesPath),
329
            function ($v) {
330
                return isset($v['type']) && $v['type'] === 'file';
331
            }
332
        );
333
334
        foreach ($resizedImagesFiles as $resizedImage) {
335
            $size = ResizedImage::getSizeFromFilename($resizedImage['basename']);
336
337
            if ($sizeName = $this->getSizeNameFromConfig($size['width'], $size['height'])) {
338
                if (empty($filterSizes) || in_array($sizeName, $filterSizes)) {
339
                    $resizedImages[$sizeName] = $this->createNodeValue($resizedImage);
340
                }
341
                continue;
342
            }
343
344
            if (empty($filterSizes)) {
345
                if (!isset($resizedImages['__custom'])) {
346
                    $resizedImages['__custom'] = array();
347
                }
348
349
                $resizedImages['__custom'][] = $this->createNodeValue($resizedImage);
350
            }
351
        }
352
353
        return $resizedImages;
354
    }
355
356
    protected function createNodeValue($resizedImage)
357
    {
358
        if (isset($resizedImage['url'])) {
359
            return array(
360
                'name' => $resizedImage['basename'],
361
                'url'  => $resizedImage['url']
362
            );
363
        }
364
365
        return $resizedImage['basename'];
366
    }
367
368
    /**
369
     * @param ResourceType $sourceFileResourceType
370
     * @param string       $sourceFilePath
371
     * @param string       $sourceFileName
372
     * @param int          $width
373
     * @param int          $height
374
     *
375
     * @return ResizedImage|null
376
     */
377
    public function getResizedImageBySize(ResourceType $sourceFileResourceType, $sourceFilePath, $sourceFileName, $width, $height)
378
    {
379
        $resizedImagesPath = Path::combine($sourceFileResourceType->getDirectory(), $sourceFilePath, ResizedImage::DIR, $sourceFileName);
380
381
        $backend = $sourceFileResourceType->getBackend();
382
383
        if (!$backend->hasDirectory($resizedImagesPath)) {
384
            return null;
385
        }
386
387
        $resizedImagesFiles = array_filter(
388
            $backend->listContents($resizedImagesPath),
389
            function ($v) {
390
                return isset($v['type']) && $v['type'] === 'file';
391
            }
392
        );
393
394
        $thresholdPixels = $this->config->get('images.threshold.pixels');
395
        $thresholdPercent = (float) $this->config->get('images.threshold.percent') / 100;
396
397
        foreach ($resizedImagesFiles as $resizedImage) {
398
            $resizedImageSize = ResizedImage::getSizeFromFilename($resizedImage['basename']);
399
            $resizedImageWidth = $resizedImageSize['width'];
400
            $resizedImageHeight = $resizedImageSize['height'];
401
            if ($resizedImageWidth >= $width && ($resizedImageWidth <= $width + $thresholdPixels || $resizedImageWidth <= $width + $width * $thresholdPercent)
402
                && $resizedImageHeight >= $height && ($resizedImageHeight <= $height + $thresholdPixels || $resizedImageHeight <= $height + $height * $thresholdPercent)) {
403
                $resizedImage = new ResizedImage(
404
                    $this,
405
                    $sourceFileResourceType,
406
                    $sourceFilePath,
407
                    $sourceFileName,
408
                    $resizedImageWidth,
409
                    $resizedImageHeight
410
                );
411
412
                if ($resizedImage->exists()) {
413
                    $resizedImage->load();
414
415
                    return $resizedImage;
416
                }
417
            }
418
        }
419
420
        return null;
421
    }
422
}
423