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

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