PluginProbe
Depicter — Popup & Slider Builder / 1.5.2
Depicter — Popup & Slider Builder v1.5.2
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / app / src / Document / Models / Elements / Image.php

Image.php in Depicter — Popup & Slider Builder 1.5.2, at app/src/Document/Models/Elements/Image.php

479 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Depicter\Document\Models\Elements;
3
4 use Averta\Core\Utility\Data;
5 use Averta\WordPress\Utility\JSON;
6 use Depicter;
7 use Depicter\Document\CSS\Breakpoints;
8 use Depicter\Document\Models;
9 use Depicter\Front\Preview;
10 use Depicter\Html\Html;
11 use Depicter\Media\Image\FileResizedFinder;
12 use Depicter\Services\MediaBridge;
13
14 class Image extends Models\Element
15 {
16 use Models\Traits\MediaSourceTrait;
17
18 /**
19 * SVG Mime type
20 */
21 const SVG_MIME = 'image/svg+xml';
22
23 /**
24 * SVG Mime type
25 */
26 const GIF_MIME = 'image/gif';
27
28 /**
29 * @var array
30 */
31 protected $renderArgs = [];
32
33 /**
34 * Whether in preview mode or not
35 *
36 * @var bool
37 */
38 protected $isPreviewMode;
39
40 /**
41 * List of breakpoint sizes
42 *
43 * @var array
44 */
45 protected $breakpoints;
46
47 /**
48 * Stores inherited crop data options
49 *
50 * @var array
51 */
52 protected $inheritedOptions = [];
53
54
55 /**
56 * Calculates render options
57 *
58 * @return $this
59 */
60 protected function setRenderOptions(){
61 $this->breakpoints = Breakpoints::all();
62 $this->breakpoints['default'] = 1025;
63
64 $this->inheritedOptions = [
65 'resizeW' => null,
66 'resizeH' => null,
67 'cropW' => null,
68 'cropH' => null,
69 'focalX' => 0.5,
70 'focalY' => 0.5,
71 'cropData'=> null
72 ];
73
74 $this->renderArgs['assetId'] = $this->hasDataSheet() ? $this->maybeReplaceDataSheetTags( $this->options->source ) : $this->options->source;
75 if ( empty( $this->renderArgs['assetId'] ) ) {
76 return '';
77 }
78
79 $this->renderArgs['isPreview'] = Depicter::front()->preview()->isPreview();
80 $this->renderArgs['isSVG'] = $this->isSvg( $this->renderArgs['assetId'] );
81 $this->renderArgs['isGif'] = $this->isGif( $this->renderArgs['assetId'] );
82 $this->renderArgs['sizeUnit'] = $this->size->default->width->unit ?: 'px';
83 $this->renderArgs['attachmentId'] = Depicter::media()->getAttachmentId( $this->renderArgs['assetId'] );
84 $this->renderArgs['isAttachment'] = is_numeric( $this->renderArgs['attachmentId'] );
85 $this->renderArgs['altText'] = Depicter::media()->getAltText( $this->renderArgs['attachmentId'] );
86
87 $this->isPreviewMode = $this->renderArgs['isPreview'] || $this->renderArgs['isSVG'] || $this->renderArgs['isGif'] || ! $this->renderArgs['isAttachment'];
88
89 return $this;
90 }
91
92 /**
93 * Whether it's preview mode or not
94 *
95 * @return bool
96 */
97 protected function isPreviewMode(){
98 return $this->isPreviewMode;
99 }
100
101
102 protected function getUnitSize( $device = 'default' ){
103 return $this->size->{$device}->width->unit ?: 'px';
104 }
105
106 protected function hasRelativeUnitSize( $device = 'default' ){
107 $relativeUnits = ['%'];
108
109 if( isset( $this->size->{$device}->width->unit ) && in_array( $this->size->{$device}->width->unit, $relativeUnits ) ){
110 return true;
111 }
112 if( isset( $this->size->{$device}->height->unit ) && in_array( $this->size->{$device}->height->unit, $relativeUnits ) ){
113 return true;
114 }
115 return false;
116 }
117
118 /**
119 * Render the element wrapper tag
120 *
121 * @throws \JsonMapper_Exception
122 */
123 protected function renderPictureWrapper(){
124
125 $args = $this->getDefaultAttributes();
126
127 $devices = Breakpoints::names();
128 foreach( $devices as $device ) {
129 $dataAttrName = $device == 'default' ? 'data-crop' : 'data-' . $device . '-crop';
130 $hasRelativeUnitSize = $this->hasRelativeUnitSize( $device );
131
132 if ( $this->isPreviewMode || $hasRelativeUnitSize ) {
133
134 if( ! empty( $this->cropData->{$device} ) ){
135 $mediaWidth = $this->cropData->{$device}->mediaSize->width;
136 $mediaHeight = $this->cropData->{$device}->mediaSize->height;
137
138 if( $this->hasDataSheet() ){
139 $attachment = wp_get_attachment_image_src( $this->renderArgs['attachmentId'], 'full' );
140 if ( !$attachment ) {
141 continue;
142 }
143 $originalMediaWidth = $attachment[1] ?: $mediaWidth;
144 $originalMediaHeight = $attachment[2] ?: $mediaHeight;
145 [ $mediaWidth, $mediaHeight ] = $this->fitToSourceMedia( 'contain', $mediaWidth, $mediaHeight, $originalMediaWidth, $originalMediaHeight );
146 }
147
148 $args[ $dataAttrName ] = [
149 'mediaSize'=> [
150 'width' => $mediaWidth,
151 'height' => $mediaHeight
152 ]
153 ];
154 $args[ $dataAttrName ]['focalPoint'] = [
155 'x' => ! empty( $this->cropData->{$device}->focalPoint->x ) ? $this->cropData->{$device}->focalPoint->x : 0.5,
156 'y' => ! empty( $this->cropData->{$device}->focalPoint->y ) ? $this->cropData->{$device}->focalPoint->y : 0.5
157 ];
158
159 $args[ $dataAttrName ] = JSON::encode( $args[ $dataAttrName ] );
160 }
161
162 } elseif( ! $hasRelativeUnitSize && ! empty( $this->cropData->{$device} ) && $this->inheritedOptions['cropData'] !== "false" ){
163 $args[ $dataAttrName ] = "false";
164 }
165
166 if( isset( $args[ $dataAttrName ] ) ){
167 $this->inheritedOptions['cropData'] = $args[ $dataAttrName ];
168 }
169 }
170
171 $this->markup = Html::picture( $args );
172 }
173
174 /**
175 * Renders a default image tag
176 *
177 * @return mixed
178 * @throws \Exception
179 */
180 protected function renderImageTag(){
181 [ $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args ] = $this->getImageEditOptions( 'default' );
182
183 $hasRelativeUnitSize = $this->hasRelativeUnitSize( 'default' );
184
185 if ( $this->isPreviewMode ) {
186 $imageSource = Depicter::media()->getSourceUrl( $this->renderArgs['assetId'], [ $cropWidth, $cropHeight ], false, [ 'dry' => true ] );
187
188 } else {
189 $args['upscale'] = true;
190
191 if( $hasRelativeUnitSize ){
192 $cropWidth = null;
193 $cropHeight = null;
194 }
195 $imageSource = Depicter::media()->resizeSourceUrl(
196 $this->renderArgs['attachmentId'], $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args
197 );
198 }
199
200 $img = Html::img( '', [
201 'src' => \Depicter::media()::IMAGE_PLACEHOLDER_SRC,
202 'data-depicter-src' => $imageSource,
203 'alt' => $this->renderArgs['altText']
204 ] );
205
206 if ( $this->isPreviewMode && ! $this->renderArgs['isSVG'] && ! $hasRelativeUnitSize ) {
207 $img = Html::source([
208 'data-depicter-srcset' => $imageSource,
209 ]) . $img;
210 }
211
212 if( $this->renderArgs['isSVG'] ){
213 $this->addMediaUlrToDictionary( $imageSource, '', '', self::SVG_MIME );
214 }
215
216 $this->markup->nest( $img . "\n" );
217
218 if ( false !== $a = $this->getLinkTag() ) {
219 $this->markup = $a->nest( "\n". $this->markup . "\n" );
220 }
221
222 return $this->markup;
223 }
224
225 /**
226 * Renders element markup
227 *
228 * @return string|void
229 * @throws \JsonMapper_Exception
230 * @throws \Exception
231 */
232 public function render() {
233
234 $this->setRenderOptions();
235
236 if ( empty( $this->renderArgs['assetId'] ) ) {
237 return '';
238 }
239
240 $this->renderPictureWrapper();
241
242 $this->renderSourceTags();
243 $this->renderImageTag();
244
245 return $this->markup;
246 }
247
248
249 /**
250 * Retrieves attachment mime type
251 *
252 * @param string|int $assetId
253 *
254 * @return false|string
255 */
256 protected function getMimeType( $assetId ){
257 $attachmentId = Depicter::media()->getAttachmentId( $assetId );
258 return is_numeric( $attachmentId ) ? get_post_mime_type( $attachmentId ) : false;
259 }
260
261 /**
262 * Whether it's svg file or not
263 *
264 * @param string|int $assetId
265 *
266 * @return bool
267 */
268 public function isSvg( $assetId ) {
269 return $this->getMimeType( $assetId ) == self::SVG_MIME;
270 }
271
272 /**
273 * Whether it's gif file or not
274 *
275 * @param string|int $assetId
276 *
277 * @return bool
278 */
279 public function isGif( $assetId ) {
280 return $this->getMimeType( $assetId ) == self::GIF_MIME;
281 }
282
283 /**
284 * Retrieves image edit options for a breakpoint
285 *
286 * @param string $device
287 *
288 * @return array
289 */
290 protected function getImageEditOptions( $device = 'default' ){
291 $params = [];
292
293 $this->inheritedOptions['resizeW'] = $this->cropData->{$device}->mediaSize->width ?? null;
294 $this->inheritedOptions['resizeH'] = $this->cropData->{$device}->mediaSize->height ?? null;
295
296 if( $this->hasDataSheet() ){
297 $attachment = wp_get_attachment_image_src( $this->renderArgs['attachmentId'], 'full' );
298 if ( !$attachment ) {
299 return;
300 }
301 $originalMediaWidth = $attachment[1] ?: null;
302 $originalMediaHeight = $attachment[2] ?: null;
303 [ $mediaWidth, $mediaHeight ] = $this->fitToSourceMedia( 'contain', $this->inheritedOptions['resizeW'], $this->inheritedOptions['resizeH'], $originalMediaWidth, $originalMediaHeight );
304 $params[] = $mediaWidth;
305 $params[] = $mediaHeight;
306
307 } else {
308 $params[] = $this->inheritedOptions['resizeW'];
309 $params[] = $this->inheritedOptions['resizeH'];
310 }
311
312 $params[] = $this->inheritedOptions['cropW'] = !empty( $this->size->{$device}->width->value ) ?
313 $this->size->{$device}->width->value :
314 null;
315
316 $params[] = $this->inheritedOptions['cropH'] = !empty( $this->size->{$device}->height->value ) ?
317 $this->size->{$device}->height->value :
318 null;
319
320 $this->inheritedOptions['focalX'] = !empty( $this->cropData->{$device}->focalPoint->x ) ?
321 $this->cropData->{$device}->focalPoint->x :
322 $this->inheritedOptions['focalX'];
323
324 $this->inheritedOptions['focalY'] = !empty( $this->cropData->{$device}->focalPoint->y ) ?
325 $this->cropData->{$device}->focalPoint->y :
326 $this->inheritedOptions['focalY'];
327
328 $params[] = [
329 'focalX' => $this->inheritedOptions['focalX'],
330 'focalY' => $this->inheritedOptions['focalY']
331 ];
332
333 return $params;
334 }
335
336 /**
337 * Retrieves source urls (srcset) for a breakpoint in array
338 *
339 * @param string $device
340 *
341 * @return array
342 */
343 protected function getSourceUrls( $device = 'default' ){
344
345 [ $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args ] = $this->getImageEditOptions( $device );
346
347 if( ! $cropWidth && ! $cropHeight && ! $resizeWidth && ! $resizeHeight ){
348 return [];
349 }
350
351 $args['upscale'] = true;
352
353 if( $this->hasRelativeUnitSize( $device ) ){
354 $cropWidth = null;
355 $cropHeight = null;
356 }
357
358 $imageSources = [];
359 $imageSources[] = Depicter::media()->resizeSourceUrl(
360 $this->renderArgs['attachmentId'], $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args
361 );
362
363 if( empty( $imageSources ) ){
364 return [];
365 }
366
367 // $args['upscale'] = false;
368
369 $retinaImageSource = Depicter::media()->resizeSourceUrl(
370 $this->renderArgs['attachmentId'],
371 $resizeWidth ? $resizeWidth * 2 : $resizeWidth,
372 $resizeHeight ? $resizeHeight * 2 : $resizeHeight,
373 $cropWidth ? $cropWidth * 2 : $cropWidth,
374 $cropHeight ? $cropHeight * 2 : $cropHeight,
375 $args
376 );
377
378 if( $retinaImageSource ){
379 $imageSources[] = $retinaImageSource . ' 2x';
380 }
381
382 return $imageSources;
383 }
384
385 /**
386 * Generates and appends a source tag with media query to element markup
387 *
388 * @param array $imageSources
389 * @param string $mediaQueryCondition
390 * @param int $mediaQuerySize
391 */
392 protected function appendSourceTag( $imageSources = [], $mediaQueryCondition = 'max-width', $mediaQuerySize = null ){
393 if( ! $imageSources ){
394 return;
395 }
396
397 $this->addMediaUlrToDictionary( $imageSources, $mediaQueryCondition, $mediaQuerySize );
398
399 $attributes = [
400 'data-depicter-srcset' => trim( implode( ', ', $imageSources ), ', ' ),
401 'srcset' => \Depicter::media()::IMAGE_PLACEHOLDER_SRC,
402 ];
403 if( $mediaQueryCondition && $mediaQuerySize ){
404 $attributes['media'] = '(' . $mediaQueryCondition . ': ' . $mediaQuerySize . 'px)';
405 }
406 $sourceTag = Html::source( $attributes );
407
408 $this->markup->nest( "\n" . $sourceTag . "\n" );
409 }
410
411 /**
412 * Renders and appends necessary source tags with media queries for breakpoints
413 */
414 protected function renderSourceTags(){
415 if( $this->isPreviewMode ){
416 return;
417 }
418
419 $desktopSources = $this->getSourceUrls( 'default' );
420 $tabletSources = $this->getSourceUrls( 'tablet' );
421 $mobileSources = $this->getSourceUrls( 'mobile' );
422
423 if( ! $tabletSources && ! $mobileSources ){
424 $this->appendSourceTag( $desktopSources );
425 return;
426 }
427
428 if( $desktopSources == $tabletSources ){
429 if( $tabletSources == $mobileSources ){
430 // if all breakpoints sources are the same
431 $this->appendSourceTag( $desktopSources );
432 } else {
433 // if desktop and tablet sources are the same
434 $this->appendSourceTag( $mobileSources, 'max-width', $this->breakpoints['mobile'] );
435 $this->appendSourceTag( $desktopSources, 'min-width', $mobileSources ? (int) $this->breakpoints['mobile'] + 1 : 0 );
436 }
437 } elseif( $tabletSources == $mobileSources ){
438 // if tablet and mobile sources are the same
439 $this->appendSourceTag( $tabletSources, 'max-width', $this->breakpoints['tablet'] );
440 $this->appendSourceTag( $desktopSources, 'min-width', ( $tabletSources ? (int) $this->breakpoints['tablet'] + 1 : 0 ) );
441 } else {
442 $this->appendSourceTag( $mobileSources, 'max-width', $this->breakpoints['mobile'] );
443 $this->appendSourceTag( $tabletSources, 'max-width', $this->breakpoints['tablet'] );
444
445 $mediaQuerySize = (int) $this->breakpoints['default'];
446 if( ! $tabletSources ){
447 $mediaQuerySize = $mobileSources ? (int) $this->breakpoints['mobile'] + 1 : 0;
448 }
449 $this->appendSourceTag( $desktopSources, 'min-width', $mediaQuerySize );
450 }
451
452 }
453
454 /**
455 * Fit image to source image with and height
456 * Mostly used in data source images
457 *
458 * @param $type
459 * @param $sourceMediaWidth
460 * @param $sourceMediaHeight
461 * @param $width
462 * @param $height
463 *
464 * @return float[]|int[]
465 */
466 protected function fitToSourceMedia( $type, $sourceMediaWidth, $sourceMediaHeight, $width, $height ) {
467 $withRatio = $sourceMediaWidth / $width;
468 $heightRatio = $sourceMediaHeight / $height;
469
470 $ratio = $type === 'cover' ? max($withRatio, $heightRatio) : min($withRatio, $heightRatio);
471
472 return [
473 $width * $ratio,
474 $height * $ratio,
475 ];
476 }
477
478 }
479