Completed
Branch php7.0 (0e9be3)
by Alexander
02:51
created

Storage::changeAttributesWithoutStretch()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 4
nop 0
1
<?php
2
3
namespace Image;
4
5
use Phalcon\Mvc\User\Component;
6
7
define('IMG_ROOT_REL_PATH', 'img');
8
define('DIR_SEP', '/');
9
define('IMG_ROOT_PATH', ROOT . DIR_SEP);
10
define('IMG_STORAGE_SERVER', '');
11
define('IMG_EXTENSION', 'jpg');
12
define('NOIMAGE', '/static/images/noimage.jpg');
13
14
define('IMG_DEBUG_MODE', true);
15
16
class Storage extends Component
17
{
18
19
    private static $STRATEGIES = [
20
        'w', // Масштабируем по ширине
21
        'wh', // Масштабируем по заданной ширине и высоте. Изображение подганяется в этот прямоугольник
22
        'a', // Центрируем и обрезаем изображение по заданной высоте и ширине таким образом, чтоб оно полностью заполнило пространство
23
    ];
24
    private $id = null;
25
    private $image_hash = null;
26
    private $type = 'publication';
27
    private $strategy = 'w';
28
    private $width = 100;
29
    private $height = null;
30
    private $container = false;
31
    private $hash = false;
32
    private $attributes = [];
33
    private $exists = true;
34
    private $widthHeight = true;
35
    private $stretch = true;
36
37
    public function __construct(array $params = [], array $attributes = [])
38
    {
39
        $this->setIdFromParams($params);
40
        $this->attributes = $attributes;
41
42
        $this->type = (isset($params['type'])) ? $params['type'] : 'publication';
43
        $this->strategy = (isset($params['strategy'])) ? $params['strategy'] : 'w';
44
        $this->container = (isset($params['container'])) ? $params['container'] : false;
45
        $this->image_hash = (isset($params['image_hash'])) ? $params['image_hash'] : null;
46
        $this->hash = (isset($params['hash'])) ? $params['hash'] : false;
47
48
        $this->setDimensionsAttributes($params);
49
    }
50
51
    private function setDimensionsAttributes(array $params = [])
52
    {
53
        $this->width = (isset($params['width'])) ? $params['width'] : 100;
54
        $this->height = (isset($params['height'])) ? $params['height'] : null;
55
56
        $this->widthHeight = (isset($params['widthHeight'])) ? $params['widthHeight'] : true;
57
        $this->widthHeight = (isset($params['widthHeight']) && MOBILE_DEVICE) ? false : true;
58
59
        $this->stretch = (isset($params['stretch'])) ? $params['stretch'] : null;
60
    }
61
62
    private function setIdFromParams($params)
63
    {
64
        if (isset($params['id'])) {
65
            if (preg_match('/^\d+$/', $params['id'])) {
66
                $this->id = (int) $params['id'];
67
            } else {
68
                $this->id = $params['id'];
69
            }
70
        } else {
71
            if (IMG_DEBUG_MODE) {
72
                throw new \Exception("ID не определен");
73
            }
74
        }
75
    }
76
77
    /**
78
     * HTML-тег изображения, готовый к использованию
79
     * <img src="" alt="" />
80
     */
81
    public function imageHtml()
82
    {
83
        //Из заданных параметров и атрибутов составляем html-тэг
84
        $attributes = $this->attributesForImageHtml();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $attributes is correct as $this->attributesForImageHtml() (which targets Image\Storage::attributesForImageHtml()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
85
86
        // Получаем относительный адрес файла кешированного изображения
87
        $src = $this->cachedRelPath();
88
89
        if ($this->exists) {
90
            if ($this->hash) {
91
                $src .= '?' . microtime();
92
            }
93
        } else {
94
            $src = NOIMAGE;
95
            $attributes['width'] = $this->width;
96
            $attributes['height'] = $this->height;
97
        }
98
99
        $attr_src = 'src="' . $this->config->base_path . $src . '"';
0 ignored issues
show
Documentation introduced by
The property config does not exist on object<Image\Storage>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
100
        $result = '<img ' . $attr_src . $this->attributesResultForImageHtml($attributes) . '/>';
101
102
        if ($this->container) {
103
            $result = '<div class="img-container" style="width:' . $this->width . 'px; height:' . $this->height . 'px">' . $result . '</div>';
104
        }
105
106
        return $result;
107
    }
108
109
    private function attributesForImageHtml()
110
    {
111
        if ($this->widthHeight) {
112
            if ($this->stretch && in_array($this->strategy, ['wh', 'a'])) {
113
                $this->stretch = false;
114
            }
115
            $this->changeAttributesInAccordanceWithStretch();
116
        }
117
        $this->attributes['alt'] = (isset($this->attributes['alt'])) ?
118
            htmlspecialchars($this->attributes['alt'], ENT_QUOTES) :
119
            '';
120
    }
121
122
    private function changeAttributesInAccordanceWithStretch()
123
    {
124
        if ($this->stretch) {
125
            $this->changeAttributesForStretch();
126
        } else {
127
            $this->changeAttributesWithoutStretch();
128
        }
129
    }
130
131
    private function changeAttributesForStretch()
132
    {
133
        if ($this->width) {
134
            $this->attributes['width'] = $this->width;
135
        }
136
        if ($this->height) {
137
            $this->attributes['height'] = $this->height;
138
        }
139
    }
140
141
    private function changeAttributesWithoutStretch()
142
    {
143
        $widthHeight = $this->getImageWidthHeight();
144
        if ($widthHeight['width']) {
145
            $this->attributes['width'] = $widthHeight['width'];
146
        }
147
        if ($widthHeight['height']) {
148
            $this->attributes['height'] = $widthHeight['height'];
149
        }
150
    }
151
152
    private function attributesResultForImageHtml($attributes)
153
    {
154
        $attributesHtmlArray = [];
155
        foreach ($attributes as $el => $val) {
156
            $attributesHtmlArray[] = $el . '="' . $val . '"';
157
        }
158
        $attributesHtml = implode(' ', $attributesHtmlArray);
159
        $attributesHtmlResult = ($attributesHtml) ? ' ' . $attributesHtml : '';
160
161
        return $attributesHtmlResult;
162
    }
163
164
    /**
165
     * Относительный адрес файла кешированного изображения
166
     * /img/preview/405102/405102_1_w_100.jpg
167
     */
168
    public function cachedRelPath()
169
    {
170
        // Рассчитываем по входящим параметрам относительный путь к кешированному файлу
171
        $cachedRelPath = $this->calculateCachedRelPath();
172
        // Совмещаем относительный путь с корневым, получаем абсолютный путь
173
        $cachedAbsPath = IMG_ROOT_PATH . $cachedRelPath;
174
        // Проверяем существование такого файла. если файл не существует:
175
        if (!file_exists($cachedAbsPath)) {
176
            // Генерируем кеш-файл по заданным параметрам
177
            $this->generateCachedImage();
178
        }
179
        return IMG_STORAGE_SERVER . $cachedRelPath;
180
181
    }
182
183
    public function cachedAbsPath()
184
    {
185
        return IMG_ROOT_PATH . $this->cachedRelPath();
186
187
    }
188
189
    /**
190
     * Относительный адрес файла оригинального изображения
191
     */
192
    public function originalRelPath()
193
    {
194
        return IMG_STORAGE_SERVER . $this->calculateOriginalRelPath();
195
196
    }
197
198
    /**
199
     * Абсолютный адрес файла оригинального изображения
200
     */
201
    public function originalAbsPath()
202
    {
203
        return $this->getOriginalAbsPath();
204
205
    }
206
207
    public function save($file)
208
    {
209
        if (file_exists($file)) {
210
            return copy($file, $this->originalAbsPath());
211
        }
212
    }
213
214
    public function originalWidthHeight()
215
    {
216
        $imageSize = getimagesize($this->originalAbsPath());
217 View Code Duplication
        if (!empty($imageSize)) {
218
            return [
219
                'width'  => $imageSize[0],
220
                'height' => $imageSize[1]
221
            ];
222
        }
223
224
    }
225
226
    public function cachedFileSize()
227
    {
228
        $path = $this->cachedAbsPath();
229
        if (file_exists($path)) {
230
            return filesize($path);
231
        }
232
233
    }
234
235
    public function isExists()
236
    {
237
        if (file_exists($this->getOriginalAbsPath())) {
238
            return true;
239
        } else {
240
            return false;
241
        }
242
    }
243
244
    /**
245
     * Рассчитываем по входящим параметрам относительный путь к кешированному файлу
246
     * /img/preview/405/405102_1_w_100.jpg
247
     */
248
    private function calculateCachedRelPath()
249
    {
250
        $pathParts = [];
251
        $pathParts[] = IMG_ROOT_REL_PATH;
252
        $pathParts[] = 'cache';
253
        $pathParts[] = $this->type;
254
255 View Code Duplication
        if (is_int($this->id)) {
256
            $idPart = floor($this->id / 1000);
257
        } else {
258
            $idPart = $this->id;
259
        }
260
        $pathParts[] = $idPart;
261
262
        $fileParts = [];
263
        $fileParts[] = $this->id;
264
        if ($this->image_hash) {
265
            $fileParts[] = $this->image_hash;
266
        }
267
        if (in_array($this->strategy, self::$STRATEGIES)) {
268
            $fileParts[] = $this->strategy;
269
        } else {
270
            if (IMG_DEBUG_MODE) {
271
                throw new \Exception("Параметр 'strategy' указан неверно");
272
            }
273
            return;
274
        }
275
        $fileParts[] = $this->width;
276
        if ($this->height) {
277
            $fileParts[] = $this->height;
278
        }
279
280
        // "img/preview/405"
281
        $path = implode(DIR_SEP, $pathParts);
282
        // "405102_1_w_100"
283
        $file = implode('_', $fileParts);
284
285
        return $path . DIR_SEP . $file . '.jpg';
286
287
    }
288
289
    /**
290
     * Рассчитываем по входящим параметрам относительный путь к оригинальному файлу
291
     * /img/original/preview/405/405102_1.jpg
292
     */
293
    private function calculateOriginalRelPath()
294
    {
295
        $pathParts = [];
296
        $pathParts[] = IMG_ROOT_REL_PATH;
297
        $pathParts[] = 'original';
298
        $pathParts[] = $this->type;
299
300 View Code Duplication
        if (is_int($this->id)) {
301
            $idPart = floor($this->id / 1000);
302
        } else {
303
            $idPart = $this->id;
304
        }
305
        $pathParts[] = $idPart;
306
307
        $fileParts = [];
308
        $fileParts[] = $this->id;
309
        if ($this->image_hash) {
310
            $fileParts[] = $this->image_hash;
311
        }
312
313
        // "img/original/preview/405"
314
        $path = implode(DIR_SEP, $pathParts);
315
        // "405102_1"
316
        $file = implode('_', $fileParts);
317
318
        return $path . DIR_SEP . $file . '.jpg';
319
320
    }
321
322
    /**
323
     * генерируем кеш-файл по заданным параметрам
324
     */
325
    private function generateCachedImage()
326
    {
327
        // Абсолютный путь оригинального изображения
328
        $originalAbsPath = IMG_ROOT_PATH . $this->calculateOriginalRelPath();
329
        $this->checkOriginalExists($originalAbsPath);
330
331
        require_once __DIR__ . '/PHPThumb/ThumbLib.inc.php';
332
        $image = \PhpThumbFactory::create($originalAbsPath);
333
        // Для мобильных устройств отдаем изображение с качеством на уровне 60%
334
        if (MOBILE_DEVICE) {
335
            $options = ['jpegQuality' => 60];
336
            $image->setOptions($options);
337
        }
338
        switch ($this->strategy) {
339
            case 'w':
340
                // Масштабируем по ширине
341
                $image->resize($this->width);
342
                break;
343
            case 'wh':
344
                // Масштабируем по заданной ширине и высоте. Изображение подганяется в этот прямоугольник
345
                $image->resize($this->width, $this->height);
346
                break;
347
            case 'a':
348
                // Центрируем и обрезаем изображение по заданной высоте и ширине таким образом, чтоб оно полностью заполнило пространство
349
                $image->adaptiveResize($this->width, $this->height);
350
                break;
351
        }
352
353
        $this->saveImage($image, $originalAbsPath);
354
    }
355
356
    public function cropOriginal($left, $top, $width, $height)
357
    {
358
        $originalAbsPath = IMG_ROOT_PATH . $this->calculateOriginalRelPath(); // Абсолютный путь оригинального изображения
359
        $this->checkOriginalExists($originalAbsPath);
360
361
        require_once __DIR__ . '/PHPThumb/ThumbLib.inc.php';
362
        $image = \PhpThumbFactory::create($originalAbsPath);
363
        $image->crop($left, $top, $width, $height);
364
365
        $this->saveImage($image, $originalAbsPath);
366
    }
367
368
    /**
369
     * @param string $originalAbsPath
370
     */
371
    private function checkOriginalExists($originalAbsPath)
372
    {
373 View Code Duplication
        if (!file_exists($originalAbsPath)) {
374
            if (IMG_DEBUG_MODE) {
375
                throw new \Exception("Файл {$originalAbsPath} не существует");
376
            } else {
377
                $this->exists = false;
378
            }
379
            return;
380
        }
381
    }
382
383
    /**
384
     * @param string $originalAbsPath
385
     */
386
    private function saveImage($image, $originalAbsPath)
387
    {
388
        // Если оригинал не заблокирован, блокируем. Это необходимо для предотвращения множественной генерации кеш-файла параллельными запросами
389
        if ($this->lockOriginal($originalAbsPath)) {
390
            // Сохраняем кешированное изображение
391
            $image->save($this->getCachedAbsPath());
392
            // Снимаем блокировку
393
            $this->unlockOriginal($originalAbsPath);
394 View Code Duplication
        } else {
395
            if (IMG_DEBUG_MODE) {
396
                throw new \Exception("Файл {$originalAbsPath} заблокирован механизмом проверки _LOCK или не существует");
397
            } else {
398
                $this->exists = false;
399
            }
400
            return;
401
        }
402
    }
403
404
    /**
405
     * Удаляет оригинальные и кешированные изображения
406
     */
407
    public function remove($removeAll = true)
408
    {
409
        $this->removeCached();
410
        $this->removeOriginal($removeAll);
411
    }
412
413
    /**
414
     * Удаляет оригинальные изображения
415
     */
416
    public function removeOriginal($removeAll = true)
417
    {
418
        if (!$removeAll) {
419
            if (file_exists($this->originalAbsPath())) {
420
                unlink($this->originalAbsPath());
421
            }
422
            return;
423
        }
424
425
        $originalAbsPath = IMG_ROOT_PATH . $this->calculateOriginalRelPath();
426
        $originalAbsPathDir = implode(DIR_SEP, array_slice(explode(DIR_SEP, $originalAbsPath), 0, -1)); // Абсолютный путь директории
427
428
        if ($this->image_hash) {
429
            $search = $originalAbsPathDir . "/" . $this->id . "_*.jpg";
430
        } else {
431
            $search = $originalAbsPathDir . "/" . $this->id . ".jpg";
432
        }
433
        $files = glob($search);
434
        if (!empty($files)) {
435
            foreach ($files as $file) {
436
                if (is_file($file)) {
437
                    unlink($file);
438
                }
439
            }
440
        }
441
    }
442
443
    /**
444
     * Удаляет кешированные изображения
445
     */
446
    public function removeCached()
447
    {
448
        $cachedAbsPath = IMG_ROOT_PATH . $this->calculateCachedRelPath();
449
        $cachedAbsPathDir = implode(DIR_SEP, array_slice(explode(DIR_SEP, $cachedAbsPath), 0, -1)); // Абсолютный путь директории
450
451
        $search = $cachedAbsPathDir . "/" . $this->id . "_*.jpg";
452
        $files = glob($search);
453
        if (!empty($files)) {
454
            foreach ($files as $file) {
455
                if (is_file($file)) {
456
                    unlink($file);
457
                }
458
            }
459
        }
460
    }
461
462
    /**
463
     * Размеры кешированного изображения
464
     */
465
    public function getImageWidthHeight()
466
    {
467
        $cachedAbsPath = IMG_ROOT_PATH . $this->calculateCachedRelPath();
468
        if (file_exists($cachedAbsPath)) {
469
            $imageSize = getimagesize($cachedAbsPath);
470 View Code Duplication
            if (!empty($imageSize)) {
471
                return [
472
                    'width'  => $imageSize[0],
473
                    'height' => $imageSize[1]
474
                ];
475
            }
476
        } else {
477
            return [
478
                'width'  => null,
479
                'height' => null
480
            ];
481
        }
482
    }
483
484
    /**
485
     * Проверяем блокировку оригинала изображения. Если нет, то блокируем
486
     * @param string $originalAbsPath
487
     * @return boolean true|false
488
     */
489
    private function lockOriginal($originalAbsPath)
490
    {
491
        $lockFileName = $this->getLockFileName($originalAbsPath);
492
        if (file_exists($lockFileName)) {
493
            return false;
494
        } else {
495
            $handle = fopen($lockFileName, 'w+');
496
            if (flock($handle, LOCK_EX)) {
497
                fwrite($handle, '1');
498
                flock($handle, LOCK_UN);
499
                fclose($handle);
500
                return true;
501
            } else {
502
                if ($handle) {
503
                    fclose($handle);
504
                }
505
                return false;
506
            }
507
        }
508
    }
509
510
    /**
511
     * Снимаем блокировку оригинала изображения
512
     * @param string $originalAbsPath
513
     */
514
    private function unlockOriginal($originalAbsPath)
515
    {
516
        unlink($this->getLockFileName($originalAbsPath));
517
    }
518
519
    /**
520
     * Возвращает имя файла для блокировки оригинала изображения
521
     * @param string $originalAbsPath
522
     * @return string
523
     */
524
    private function getLockFileName($originalAbsPath)
525
    {
526
        return preg_replace('/\.' . IMG_EXTENSION . '/i', '_lock.' . IMG_EXTENSION, $originalAbsPath);
527
    }
528
529
    /**
530
     * Возвращает абсолютный путь к оригинальному изображению.
531
     * При необходимости генерируется дерево директорий для сохранения оригинальных файлов
532
     */
533 View Code Duplication
    private function getOriginalAbsPath()
534
    {
535
        $originalAbsPath = IMG_ROOT_PATH . $this->calculateOriginalRelPath();
536
        // Абсолютный путь директории
537
        $originalAbsPathDir = implode(DIR_SEP, array_slice(explode(DIR_SEP, $originalAbsPath), 0, -1));
538
539
        // Если директория отсутствует
540
        if (!is_dir($originalAbsPathDir)) {
541
            // Создаем дерево директорий
542
            mkdir($originalAbsPathDir, 0777, true);
543
        }
544
545
        return $originalAbsPath;
546
    }
547
548
    /**
549
     * Возвращает абсолютный путь к кешированному изображению.
550
     * При необходимости генерируется дерево директорий для сохранения кеш-файлов
551
     */
552 View Code Duplication
    private function getCachedAbsPath()
553
    {
554
        $cachedAbsPath = IMG_ROOT_PATH . $this->calculateCachedRelPath();
555
        // Абсолютный путь директории
556
        $cachedAbsPathDir = implode(DIR_SEP, array_slice(explode(DIR_SEP, $cachedAbsPath), 0, -1));
557
558
        // Если директория отсутствует
559
        if (!is_dir($cachedAbsPathDir)) {
560
            // Создаем дерево директорий
561
            mkdir($cachedAbsPathDir, 0777, true);
562
        }
563
        return $cachedAbsPath;
564
565
    }
566
567
}