PluginProbe
Depicter — Popup & Slider Builder / 1.3.3
Depicter — Popup & Slider Builder v1.3.3
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.3.3, at app/src/Document/Models/Elements/Image.php

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