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

456 lines 12.8 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 $this->renderSourceTags();
244 $this->renderImageTag();
245
246 return $this->markup;
247 }
248
249
250 /**
251 * Retrieves attachment mime type
252 *
253 * @param string|int $assetId
254 *
255 * @return false|string
256 */
257 protected function getMimeType( $assetId ){
258 $attachmentId = Depicter::media()->getAttachmentId( $assetId );
259 return is_numeric( $attachmentId ) ? get_post_mime_type( $attachmentId ) : false;
260 }
261
262 /**
263 * Whether it's svg file or not
264 *
265 * @param string|int $assetId
266 *
267 * @return bool
268 */
269 public function isSvg( $assetId ) {
270 return $this->getMimeType( $assetId ) == self::SVG_MIME;
271 }
272
273 /**
274 * Whether it's gif file or not
275 *
276 * @param string|int $assetId
277 *
278 * @return bool
279 */
280 public function isGif( $assetId ) {
281 return $this->getMimeType( $assetId ) == self::GIF_MIME;
282 }
283
284 /**
285 * Retrieves image edit options for a breakpoint
286 *
287 * @param string $device
288 *
289 * @return array
290 */
291 protected function getImageEditOptions( $device = 'default' ){
292 $params = [];
293
294 $this->inheritedOptions['resizeW'] = $this->cropData->{$device}->mediaSize->width ?? null;
295 $this->inheritedOptions['resizeH'] = $this->cropData->{$device}->mediaSize->height ?? null;
296
297 if( $this->hasDataSheet() ){
298 $attachment = wp_get_attachment_image_src( $this->renderArgs['attachmentId'], 'full' );
299 if ( !$attachment ) {
300 return;
301 }
302 $originalMediaWidth = $attachment[1] ?: null;
303 $originalMediaHeight = $attachment[2] ?: null;
304 [ $mediaWidth, $mediaHeight ] = Media::fitInBox( 'contain', $this->inheritedOptions['resizeW'], $this->inheritedOptions['resizeH'], $originalMediaWidth, $originalMediaHeight );
305 $params[] = $mediaWidth;
306 $params[] = $mediaHeight;
307
308 } else {
309 $params[] = $this->inheritedOptions['resizeW'];
310 $params[] = $this->inheritedOptions['resizeH'];
311 }
312
313 $params[] = $this->inheritedOptions['cropW'] = !empty( $this->size->{$device}->width->value ) ?
314 $this->size->{$device}->width->value :
315 null;
316
317 $params[] = $this->inheritedOptions['cropH'] = !empty( $this->size->{$device}->height->value ) ?
318 $this->size->{$device}->height->value :
319 null;
320
321 $this->inheritedOptions['focalX'] = !empty( $this->cropData->{$device}->focalPoint->x ) ?
322 $this->cropData->{$device}->focalPoint->x :
323 $this->inheritedOptions['focalX'];
324
325 $this->inheritedOptions['focalY'] = !empty( $this->cropData->{$device}->focalPoint->y ) ?
326 $this->cropData->{$device}->focalPoint->y :
327 $this->inheritedOptions['focalY'];
328
329 $params[] = [
330 'focalX' => $this->inheritedOptions['focalX'],
331 'focalY' => $this->inheritedOptions['focalY']
332 ];
333
334 return $params;
335 }
336
337 /**
338 * Retrieves source urls (srcset) for a breakpoint in array
339 *
340 * @param string $device
341 *
342 * @return array
343 */
344 protected function getSourceUrls( $device = 'default' ){
345
346 [ $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args ] = $this->getImageEditOptions( $device );
347
348 if( ! $cropWidth && ! $cropHeight && ! $resizeWidth && ! $resizeHeight ){
349 return [];
350 }
351
352 $args['upscale'] = true;
353
354 if( $this->hasRelativeUnitSize( $device ) ){
355 $cropWidth = null;
356 $cropHeight = null;
357 }
358
359 $imageSources = [];
360 $imageSources[] = Depicter::media()->resizeSourceUrl(
361 $this->renderArgs['attachmentId'], $resizeWidth, $resizeHeight, $cropWidth, $cropHeight, $args
362 );
363
364 if( empty( $imageSources ) ){
365 return [];
366 }
367
368 // $args['upscale'] = false;
369
370 $retinaImageSource = Depicter::media()->resizeSourceUrl(
371 $this->renderArgs['attachmentId'],
372 $resizeWidth ? $resizeWidth * 2 : $resizeWidth,
373 $resizeHeight ? $resizeHeight * 2 : $resizeHeight,
374 $cropWidth ? $cropWidth * 2 : $cropWidth,
375 $cropHeight ? $cropHeight * 2 : $cropHeight,
376 $args
377 );
378
379 if( $retinaImageSource ){
380 $imageSources[] = $retinaImageSource . ' 2x';
381 }
382
383 return $imageSources;
384 }
385
386 /**
387 * Generates and appends a source tag with media query to element markup
388 *
389 * @param array $imageSources
390 * @param string $mediaQueryCondition
391 * @param int $mediaQuerySize
392 */
393 protected function appendSourceTag( $imageSources = [], $mediaQueryCondition = 'max-width', $mediaQuerySize = null ){
394 if( ! $imageSources ){
395 return;
396 }
397
398 $this->addMediaUlrToDictionary( $imageSources, $mediaQueryCondition, $mediaQuerySize );
399
400 $attributes = [
401 'data-depicter-srcset' => trim( implode( ', ', $imageSources ), ', ' ),
402 'srcset' => \Depicter::media()::IMAGE_PLACEHOLDER_SRC,
403 ];
404 if( $mediaQueryCondition && $mediaQuerySize ){
405 $attributes['media'] = '(' . $mediaQueryCondition . ': ' . $mediaQuerySize . 'px)';
406 }
407 $sourceTag = Html::source( $attributes );
408
409 $this->markup->nest( "\n" . $sourceTag . "\n" );
410 }
411
412 /**
413 * Renders and appends necessary source tags with media queries for breakpoints
414 */
415 protected function renderSourceTags(){
416 if( $this->isPreviewMode ){
417 return;
418 }
419
420 $desktopSources = $this->getSourceUrls( 'default' );
421 $tabletSources = $this->getSourceUrls( 'tablet' );
422 $mobileSources = $this->getSourceUrls( 'mobile' );
423
424 if( ! $tabletSources && ! $mobileSources ){
425 $this->appendSourceTag( $desktopSources );
426 return;
427 }
428
429 if( $desktopSources == $tabletSources ){
430 if( $tabletSources == $mobileSources ){
431 // if all breakpoints sources are the same
432 $this->appendSourceTag( $desktopSources );
433 } else {
434 // if desktop and tablet sources are the same
435 $this->appendSourceTag( $mobileSources, 'max-width', $this->breakpoints['mobile'] );
436 $this->appendSourceTag( $desktopSources, 'min-width', $mobileSources ? (int) $this->breakpoints['mobile'] + 1 : 0 );
437 }
438 } elseif( $tabletSources == $mobileSources ){
439 // if tablet and mobile sources are the same
440 $this->appendSourceTag( $tabletSources, 'max-width', $this->breakpoints['tablet'] );
441 $this->appendSourceTag( $desktopSources, 'min-width', ( $tabletSources ? (int) $this->breakpoints['tablet'] + 1 : 0 ) );
442 } else {
443 $this->appendSourceTag( $mobileSources, 'max-width', $this->breakpoints['mobile'] );
444 $this->appendSourceTag( $tabletSources, 'max-width', $this->breakpoints['tablet'] );
445
446 $mediaQuerySize = (int) $this->breakpoints['default'];
447 if( ! $tabletSources ){
448 $mediaQuerySize = $mobileSources ? (int) $this->breakpoints['mobile'] + 1 : 0;
449 }
450 $this->appendSourceTag( $desktopSources, 'min-width', $mediaQuerySize );
451 }
452
453 }
454
455 }
456