Completed
Push — master ( 2d8f65...4181f5 )
by Stéphane
06:54
created

Manager::localUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php
2
3
/**
4
 * Image Manager
5
 */
6
namespace Onigoetz\Imagecache;
7
8
/**
9
 * Image manager
10
 *
11
 * Prepares the images for the cache
12
 *
13
 *
14
 * @author Stéphane Goetz
15
 */
16
class Manager
17
{
18
    /**
19
     * @var array Contains configurations and presets
20
     */
21
    protected $options;
22
23
    /**
24
     * @var MethodCaller
25
     */
26
    protected $methodCaller;
27
28
    /**
29
     * @var string
30
     */
31
    protected $retinaRegex = '/(.*)@2x\\.(jpe?g|png|webp|gif)/';
32
33 153
    public function __construct($options)
34
    {
35 153
        $this->options = $options + ['path_web' => 'images', 'path_cache' => 'cache'];
36 153
    }
37
38
    /**
39
     * @return MethodCaller
40
     */
41 54
    public function getMethodCaller()
42
    {
43 54
        if (!$this->methodCaller) {
44 51
            $this->methodCaller = new MethodCaller();
45 34
        }
46
47 54
        return $this->methodCaller;
48
    }
49
50
    /**
51
     * @param MethodCaller $methodCaller
52
     */
53 3
    public function setMethodCaller(MethodCaller $methodCaller)
54
    {
55 3
        $this->methodCaller = $methodCaller;
56 3
    }
57
58 12
    public function localUrl($preset, $file) {
59 12
        return "{$this->options['path_cache']}/$preset/$file";
60
    }
61
62 3
    public function url($preset, $file)
63
    {
64 3
        return "{$this->options['path_web']}/{$this->options['path_cache']}/$preset/$file";
65
    }
66
67 3
    public function imageUrl($file)
68
    {
69 3
        return "{$this->options['path_web']}/$file";
70
    }
71
72 36
    public function isRetina($file)
73
    {
74 36
        return !!preg_match($this->retinaRegex, $file);
75
    }
76
77 39
    public function getOriginalFilename($file) {
78 39
        $matched = preg_match($this->retinaRegex, $file, $matches);
79
80 39
        return ($matched)? $matches[1] . '.' . $matches[2] : $file;
81
    }
82
83 36
    protected function getPresetActions($preset_key, $file)
84
    {
85
        // Is it a valid preset
86 36
        if (!array_key_exists($preset_key, $this->options['presets'])) {
87 9
            throw new Exceptions\InvalidPresetException('invalid preset');
88
        }
89
90 27
        $preset = $this->options['presets'][$preset_key];
91
92 27
        if (!$this->isRetina($file)) {
93 21
            return $preset;
94
        }
95
96
        // Handle retina images
97
98 6
        $preset_key = "$preset_key@2x";
99
100 6
        if (array_key_exists($preset_key, $this->options['presets'])) {
101 3
            return $this->options['presets'][$preset_key];
102
        }
103
104 3
        foreach ($preset as &$action) {
105 3
            $action = $this->generateRetinaAction($action);
106 2
        }
107
108 3
        return $preset;
109
    }
110
111 27
    protected function generateRetinaAction($action)
112
    {
113 27
        foreach (['width', 'height', 'xoffset', 'yoffset'] as $option) {
114 27
            if (array_key_exists($option, $action) && is_numeric($action[$option])) {
115 25
                $action[$option] *= 2;
116 16
            }
117 18
        }
118
119 27
        return $action;
120
    }
121
122
    /**
123
     * Take a preset and a file and return a transformed image
124
     *
125
     * @param $preset_key string
126
     * @param $file string
127
     * @throws Exceptions\InvalidPresetException
128
     * @throws Exceptions\NotFoundException
129
     * @throws \RuntimeException
130
     * @return string
131
     */
132 42
    public function handleRequest($preset_key, $file)
133
    {
134
        //do it at the beginning for early validation
135 42
        $preset = $this->getPresetActions($preset_key, $file);
136
137 36
        $source_file =  $this->getOriginalFilename($file);
138
139 36
        $original_file = $this->options['path_local'] . '/' . $source_file;
140 36
        if (!is_file($original_file)) {
141 9
            throw new Exceptions\NotFoundException('File not found');
142
        }
143
144 27
        $final_file = $this->localUrl($preset_key, $file);
145
146 27
        $this->verifyDirectoryExistence($this->options['path_local'], dirname($final_file));
147
148 27
        $final_file = $this->options['path_local'] . '/' . $final_file;
149
150 27
        if (file_exists($final_file)) {
151 6
            return $final_file;
152
        }
153
154 24
        $image = $this->loadImage($original_file);
155
156 15
        return $this->buildImage($preset, $image, $final_file)->source;
157
    }
158
159
    /**
160
     * Create the folder containing the cached images if it doesn't exist
161
     *
162
     * @param $base
163
     * @param $cacheDir
164
     */
165 15
    protected function verifyDirectoryExistence($base, $cacheDir)
166
    {
167 15
        if (is_dir("$base/$cacheDir")) {
168 3
            return;
169
        }
170
171 15
        $folder_path = explode('/', $cacheDir);
172 15
        foreach ($folder_path as $element) {
173 15
            $base .= '/' . $element;
174 15
            if (!is_dir($base)) {
175 15
                mkdir($base, 0755, true);
176 15
                chmod($base, 0755);
177 10
            }
178 10
        }
179 15
    }
180
181 15
    protected function loadImage($src)
182
    {
183 15
        return new Image($src);
184
    }
185
186
    /**
187
     * Create a new image based on an image preset.
188
     *
189
     * @param array $actions An image preset array.
190
     * @param Image $image Path of the source file.
191
     * @param string $dst Path of the destination file.
192
     * @throws \RuntimeException
193
     * @return Image
194
     */
195 78
    protected function buildImage($actions, Image $image, $dst)
196
    {
197 78
        foreach ($actions as $action) {
198
            // Make sure the width and height are computed first so they can be used
199
            // in relative x/yoffsets like 'center' or 'bottom'.
200 78
            if (isset($action['width'])) {
201 60
                $action['width'] = $this->percent($action['width'], $image->getWidth());
202 40
            }
203
204 78
            if (isset($action['height'])) {
205 51
                $action['height'] = $this->percent($action['height'], $image->getHeight());
206 34
            }
207
208 78
            if (isset($action['xoffset'])) {
209 15
                $action['xoffset'] = $this->keywords($action['xoffset'], $image->getWidth(), $action['width']);
210 10
            }
211
212 78
            if (isset($action['yoffset'])) {
213 15
                $action['yoffset'] = $this->keywords($action['yoffset'], $image->getHeight(), $action['height']);
214 10
            }
215
216 78
            $this->getMethodCaller()->call($image, $action['action'], $action);
217 48
        }
218
219 72
        return $image->save($dst);
220
    }
221
222
    /**
223
     * Accept a percentage and return it in pixels.
224
     *
225
     * @param  string $value
226
     * @param  int $current_pixels
227
     * @return mixed
228
     */
229 51
    public function percent($value, $current_pixels)
230
    {
231 51
        if (strpos($value, '%') !== false) {
232 9
            $value = str_replace('%', '', $value) * 0.01 * $current_pixels;
233 6
        }
234
235 51
        return $value;
236
    }
237
238
    /**
239
     * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
240
     *
241
     * @param $value
242
     * @param $current_pixels
243
     * @param $new_pixels
244
     * @return float|int
245
     */
246 27
    public function keywords($value, $current_pixels, $new_pixels)
247
    {
248
        switch ($value) {
249 27
            case 'top':
250 26
            case 'left':
251 6
                $value = 0;
252 6
                break;
253 21
            case 'bottom':
254 20
            case 'right':
255 6
                $value = $current_pixels - $new_pixels;
256 6
                break;
257 15
            case 'center':
258 3
                $value = $current_pixels / 2 - $new_pixels / 2;
259 3
                break;
260
        }
261
262 27
        return $value;
263
    }
264
}
265