1 <?php
2
3 /*
4 * This file is part of the Icybee package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace ICanBoogie\Modules\Thumbnailer;
13
14 use Brickrouge\Element;
15
16 /**
17 * Representation of an image thumbnail.
18 *
19 * Instances of the class are used to create IMG elements. The parameters used to create the
20 * thumbnail can be specified as a serialized string, an array of options, a version name, or
21 * a version instance.
22 *
23 * @property $width int|null Width of the thumbnail, or `null` if it is not defined.
24 * @property $height int|null Height of the thumbnail, or `null`if it is not defined.
25 * @property $method string|null Resizing method, or `null` if it is not defined.
26 * @property $format string|null Image format, or `null` if it is not defined.
27 * @property $version Version|null The {@link Version} instance used to created the thumbnail,
28 * or `null` if it is not defined.
29 *
30 * @property $w int|null Alias to {@link $width}.
31 * @property $h int|null Alias to {@link $height}.
32 * @property $m string|null Alias to {@link $method}.
33 * @property $f string|null Alias to {@link $format}.
34 * @property $v Version|null Alias to {@link $version}.
35 *
36 * @property array $options Options used to create the thumbnail.
37 * @property-read array $filtered_options Filtered thumbnail options.
38 * @property-read string $url The URL of the thumbnail.
39 * @property-read string $url_base The base to build the URL of the thumbnail.
40 * @property-read array $final_size The final size (width and height) of the thumbnail.
41 */
42 class Thumbnail extends \ICanBoogie\Object
43 {
44 /**
45 * Parameters that can be used to create a path.
46 *
47 * @var array
48 */
49 static private $path_params = [
50
51 'width',
52 'height',
53 'method',
54 'format'
55
56 ];
57
58 /**
59 * The default values of the parameters that can be used to create a path.
60 *
61 * @var array
62 */
63 static private $path_params_defaults = [
64
65 'width' => null,
66 'height' => null,
67 'method' => null,
68 'format' => null
69
70 ];
71
72 /**
73 * Format the specified options as a path.
74 *
75 * Only the `width`, `height`, `method` and `format` options are used to create the path.
76 *
77 * If it is not defined, the `method` option is inferred from the `width` and `height`
78 * options. For instance, If the `width` option is defined but the `height` option is empty,
79 * the `method` option is set to `fixed-width`. Similarly, if the `height` option is
80 * defined but the `width` option is empty, the `method` option is set to `fixed-height`.
81 *
82 * The `format` option is used as the extension of the path. e.g. "200x300.png".
83 *
84 * @param array $options
85 *
86 * @return string|null A path, or `null` if both `width` and `height` are empty.
87 */
88 static public function format_options_as_path(array $options)
89 {
90 $options = Version::widen($options) + self::$path_params_defaults;
91
92 $w = $options['width'];
93 $h = $options['height'];
94
95 if (!$w && !$h)
96 {
97 return;
98 }
99
100 $m = $options['method'];
101
102 if (!$m && (!$w || !$h))
103 {
104 $m = $w ? 'fixed-width' : 'fixed-height';
105 }
106
107 $f = $options['format'];
108
109 $rc = "/{$w}x{$h}";
110
111 if ($m)
112 {
113 $rc .= "/{$m}";
114 }
115
116 if ($f)
117 {
118 $rc .= ".{$f}";
119 }
120
121 return $rc;
122 }
123
124 /**
125 * Formats the options as a query string.
126 *
127 * @param array $options The options to format. They are filtered using
128 * {@link Version::filter()}.
129 * @param bool $remove_path_params Optionally the options that can be used to format a path
130 * using the {@link format_options_as_path()} function can be filtered out.
131 *
132 * @return string A query string. Note that the option that are actually used to create the
133 * query string are shortened using the {@link Version::shorten()} method.
134 */
135 static public function format_options_as_query_string(array $options, $remove_path_params=false)
136 {
137 $options = Version::filter($options);
138
139 if ($remove_path_params)
140 {
141 $options = array_diff_key($options, self::$path_params_defaults);
142 }
143
144 $options = Version::shorten($options);
145
146 return http_build_query($options);
147 }
148
149 /**
150 * The source of the thumbnail.
151 *
152 * @var \ICanBoogie\ActiveRecord|int|string
153 */
154 public $src;
155
156 /**
157 * Options to create the thumbnail.
158 *
159 * @var array
160 */
161 public $options = [];
162
163 /**
164 * Version name of the thumbnail.
165 *
166 * @var string
167 */
168 protected $version_name;
169
170 /**
171 * Constructor.
172 *
173 * @param Icybee\Modules\Images\Image|int|string The souce of the thumbnail.
174 *
175 * @param string|array $options The options to create the thumbnail can be provided as a
176 * version name or an array of options. If a version name is provided, the `image` parameter
177 * must also be provided.
178 *
179 * @param string|array $additionnal_options Additionnal options to create the thumbnail.
180 */
181 public function __construct($src, $options=null, $additionnal_options=null)
182 {
183 if (is_string($options))
184 {
185 if (strpos($options, ':') !== false)
186 {
187 $options = Version::unserialize($options);
188 }
189 else
190 {
191 $this->version_name = $options;
192 }
193 }
194
195 if (is_array($options))
196 {
197 $this->options = Version::normalize($options);
198 }
199
200 if ($additionnal_options)
201 {
202 if (is_string($additionnal_options))
203 {
204 $additionnal_options = Version::unserialize($additionnal_options);
205 }
206
207 $this->options = $additionnal_options + $this->options;
208 }
209
210 $this->src = $src;
211 }
212
213 /**
214 * Handles version options.
215 */
216 public function __get($property)
217 {
218 if (isset(Version::$shorthands[$property]))
219 {
220 $property = Version::$shorthands[$property];
221 }
222
223 if (array_key_exists($property, Version::$defaults))
224 {
225 return $this->get_option($property);
226 }
227
228 return parent::__get($property);
229 }
230
231 /**
232 * Handles version options.
233 */
234 public function __set($property, $value)
235 {
236 if (isset(Version::$shorthands[$property]))
237 {
238 $property = Version::$shorthands[$property];
239 }
240
241 if (array_key_exists($property, Version::$defaults))
242 {
243 $this->set_option($property, $value);
244
245 return;
246 }
247
248 parent::__set($property, $value);
249 }
250
251 private function get_option($property)
252 {
253 if (!empty($this->options[$property]))
254 {
255 return $this->options[$property];
256 }
257
258 if (!$this->version)
259 {
260 return;
261 }
262
263 return $this->version->$property;
264 }
265
266 private function set_option($option, $value)
267 {
268 if ($value === null)
269 {
270 unset($this->options[$option]);
271 }
272 else
273 {
274 $this->options[$option] = $value;
275 }
276 }
277
278 private $_version;
279
280 protected function get_version()
281 {
282 global $core;
283
284 if ($this->_version)
285 {
286 return $this->_version;
287 }
288
289 if (!$this->version_name)
290 {
291 return;
292 }
293
294 return $this->_version = $core->thumbnailer_versions[$this->version_name];
295 }
296
297 /**
298 * Returns the options, filtered.
299 *
300 * @return array
301 */
302 protected function get_filtered_options()
303 {
304 return Version::filter($this->options);
305 }
306
307 /**
308 * Returns the thumbnail URL.
309 *
310 * @return string The thumbnail URL.
311 */
312 protected function get_url()
313 {
314 $version_name = $this->version_name;
315 $options = $this->filtered_options;
316
317 $url = $this->url_base;
318
319 if ($version_name)
320 {
321 $url .= '/' . $version_name;
322 }
323 else
324 {
325 $url .= self::format_options_as_path($options);
326 }
327
328 $query_string = self::format_options_as_query_string($options + [ 'src' => $this->src ], true);
329
330 if ($query_string)
331 {
332 $url .= '?'. $query_string;
333 }
334
335 return $url;
336 }
337
338 /**
339 * Returns a base to build the thumbnail's URL.
340 *
341 * @return string
342 */
343 protected function get_url_base()
344 {
345 return '/api/thumbnail';
346 }
347
348 /**
349 * Returns the final size (width and height) of the thumbnail.
350 */
351 protected function get_final_size()
352 {
353 $w = $this->w;
354 $h = $this->h;
355 $src = $this->src;
356
357 if (is_string($src))
358 {
359 list($w, $h) = \ICanBoogie\Image::compute_final_size($w, $h, $this->method, \ICanBoogie\DOCUMENT_ROOT . $src);
360 }
361
362 return [ $w , $h ];
363 }
364
365 /**
366 * Convert the thumbnail into a IMG element.
367 *
368 * The `width` and `height` attribute of the element are defined whenever possible. The `alt`
369 * attribute is also defined if the image src is an Image active record.
370 *
371 * @param array $attributes Additionnal attributes to create the {@link Element} instance.
372 *
373 * @return \Brickrouge\Element
374 */
375 public function to_element(array $attributes=[])
376 {
377 list($w, $h) = $this->final_size;
378
379 $class = 'thumbnail';
380 $version_name = $this->version_name;
381
382 if ($version_name)
383 {
384 $class .= ' thumbnail--' . \Brickrouge\normalize($version_name);
385 }
386
387 return new Element('img', $attributes + [
388
389 'src' => $this->url,
390 'alt' => '',
391 'width' => $w,
392 'height' => $h,
393 'class' => $class
394
395 ]);
396 }
397
398 /**
399 * Return a IMG element that can be inserted as is in the document.
400 */
401 public function __toString()
402 {
403 try
404 {
405 return (string) $this->to_element();
406 }
407 catch (\Exception $e)
408 {
409 echo \Brickrouge\render_exception($e);
410 }
411 }
412 }