PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 3.10.0
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v3.10.0
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
optimole-wp / inc / dam.php

dam.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 3.10.0, at inc/dam.php

937 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Dam class.
4 *
5 * Author: Andrei Baicus <andrei@themeisle.com>
6 * Created on: 04/07/2023
7 *
8 * @package \Optimole\Inc
9 * @author Optimole <friends@optimole.com>
10 */
11
12 /**
13 * Class Optml_Dam
14 */
15 class Optml_Dam {
16 use Optml_Normalizer;
17
18 /**
19 * Hold the settings object.
20 *
21 * @var Optml_Settings Settings object.
22 */
23 private $settings;
24
25 /**
26 * The dam endpoint.
27 *
28 * @var string
29 */
30 private $dam_endpoint = 'https://dashboard.optimole.com/dam';
31
32 const OM_DAM_IMPORTED_FLAG = 'om-dam-imported';
33 const URL_DAM_FLAG = '/dam:1';
34 const IS_EDIT_FLAG = 'om-dam-edit';
35
36 /**
37 * Optml_Dam constructor.
38 */
39 public function __construct() {
40 $this->settings = Optml_Main::instance()->admin->settings;
41
42 if ( ! $this->settings->is_connected() ) {
43 return;
44 }
45
46 if ( $this->settings->get( 'cloud_images' ) === 'enabled' ) {
47 add_action( 'admin_menu', [ $this, 'add_menu' ] );
48 add_action( 'print_media_templates', [ $this, 'print_media_template' ] );
49 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] );
50 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_page_scripts' ] );
51
52 // Needed for this to work with elementor.
53 add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] );
54 }
55
56 if ( defined( 'OPTML_DAM_ENDPOINT' ) && constant( 'OPTML_DAM_ENDPOINT' ) ) {
57 $this->dam_endpoint = constant( 'OPTML_DAM_ENDPOINT' );
58 }
59
60 add_filter( 'wp_get_attachment_image_src', [ $this, 'alter_attachment_image_src' ], 10, 4 );
61 add_filter( 'wp_get_attachment_metadata', [ $this, 'alter_attachment_metadata' ], 10, 2 );
62 add_filter( 'image_downsize', [ $this, 'catch_downsize' ], 10, 3 );
63 add_filter( 'wp_prepare_attachment_for_js', [$this, 'alter_attachment_for_js'], 10, 3 );
64 add_filter( 'wp_image_src_get_dimensions', [$this, 'alter_img_tag_w_h'], 10, 4 );
65 add_filter( 'get_attached_file', [$this, 'alter_attached_file_response'], 10, 2 );
66
67 add_filter(
68 'elementor/image_size/get_attachment_image_html',
69 [
70 $this,
71 'alter_elementor_image_size',
72 ],
73 10,
74 4
75 );
76 }
77
78 /**
79 * Catch image downsize for the DAM imported images.
80 *
81 * @param array $image {
82 * Array of image data.
83 *
84 * @type string $0 Image source URL.
85 * @type int $1 Image width in pixels.
86 * @type int $2 Image height in pixels.
87 * @type bool $3 Whether the image is a resized image.
88 *
89 * @param int $id attachment id.
90 * @param string|int[] $size image size.
91 *
92 * @return array $image.
93 */
94 public function catch_downsize( $image, $id, $size ) {
95 return $this->alter_attachment_image_src( $image, $id, $size, false );
96 }
97
98 /**
99 * Insert attachments.
100 *
101 * @param array $images Images array.
102 *
103 * @return array
104 */
105 public function insert_attachments( $images ) {
106 $ids = [];
107
108 $existing = $this->check_existing_attachments( $images );
109
110 foreach ( $images as $image ) {
111 if ( ! isset( $image['isEdit'] ) && array_key_exists( $image['meta']['resourceS3'], $existing ) ) {
112 $ids[] = $existing[ $image['meta']['resourceS3'] ];
113
114 continue;
115 }
116
117 $id = $this->insert_attachment( $image );
118
119 if ( $id === 0 ) {
120 continue;
121 }
122 $ids[] = $id;
123 }
124
125 return $ids;
126 }
127
128 /**
129 * Insert single attachment
130 *
131 * @param array $image Image data.
132 *
133 * @return int
134 */
135 private function insert_attachment( $image ) {
136 $filename = basename( $image['url'] );
137 $name = pathinfo( $filename, PATHINFO_FILENAME );
138
139 $args = [
140 'post_title' => $name,
141 'post_type' => 'attachment',
142 'post_mime_type' => $image['meta']['mimeType'],
143 'guid' => $image['url'],
144 ];
145
146 $id = wp_insert_attachment( $args );
147
148 if ( $id === 0 ) {
149 return $id;
150 }
151
152 update_post_meta( $id, self::OM_DAM_IMPORTED_FLAG, $image['meta']['resourceS3'] );
153
154 if ( isset( $image['isEdit'] ) ) {
155 update_post_meta( $id, self::IS_EDIT_FLAG, true );
156 }
157
158 $metadata = [];
159
160 $metadata['file'] = '/id:' . $image['meta']['resourceS3'] . '/' . get_home_url() . '/' . $filename;
161 $metadata['mime-type'] = $image['meta']['mimeType'];
162
163 if ( isset( $image['meta']['filesize'] ) ) {
164 $metadata['filesize'] = $image['meta']['fileSize'];
165 }
166
167 if ( isset( $image['meta']['originalWidth'] ) && isset( $image['meta']['originalHeight'] ) ) {
168 $metadata['width'] = $image['meta']['originalWidth'];
169 $metadata['height'] = $image['meta']['originalHeight'];
170 }
171
172 wp_update_attachment_metadata( $id, $metadata );
173
174 return $id;
175 }
176
177 /**
178 * Catch image downsize for the DAM imported images.
179 *
180 * @param array|false $image {
181 * Array of image data.
182 *
183 * @type string $0 Image source URL.
184 * @type int $1 Image width in pixels.
185 * @type int $2 Image height in pixels.
186 * @type bool $3 Whether the image is a resized image.
187 * }
188 *
189 * @param int $attachment_id attachment id.
190 * @param string|int[] $size image size.
191 * @param bool $icon Whether the image should be treated as an icon.
192 *
193 * @return array $image.
194 */
195 public function alter_attachment_image_src( $image, $attachment_id, $size, $icon ) {
196 // Skip if not DAM image.
197 if ( ! $this->is_dam_imported_image( $attachment_id ) ) {
198 return $image;
199 }
200
201 $image_url = wp_get_attachment_url( $attachment_id );
202 $incoming_size = Optml_Media_Offload::parse_dimension_from_optimized_url( $image_url );
203 $width = $incoming_size[0];
204 $height = $incoming_size[1];
205
206 // Skip resize in single attachment view on backend.
207 if ( $this->is_attachment_edit_page( $attachment_id ) ) {
208 return [
209 $image_url,
210 $width,
211 $height,
212 false,
213 ];
214 }
215
216 // Use the original size if the requested size is full.
217 if ( $size === 'full' ) {
218 $metadata = wp_get_attachment_metadata( $attachment_id );
219
220 $image_url = $this->replace_dam_url_args(
221 [
222 'width' => $metadata['width'],
223 'height' => $metadata['height'],
224 'crop' => false,
225 ],
226 $image_url
227 );
228
229 return [
230 $image_url,
231 $metadata['width'],
232 $metadata['height'],
233 false,
234 ];
235 }
236
237 $crop = false;
238
239 // Size can be int [] containing width and height.
240 if ( is_array( $size ) ) {
241 $width = $size[0];
242 $height = $size[1];
243 $crop = true;
244 } else {
245 $sizes = $this->get_all_image_sizes();
246
247 if ( ! isset( $sizes[ $size ] ) ) {
248 return [
249 $image_url,
250 $width,
251 $height,
252 false,
253 ];
254 }
255
256 $width = $sizes[ $size ]['width'];
257 $height = $sizes[ $size ]['height'];
258 $crop = (bool) $sizes[ $size ]['crop'];
259 }
260
261 $image_url = $this->replace_dam_url_args(
262 [
263 'width' => $width,
264 'height' => $height,
265 'crop' => $crop,
266 ],
267 $image_url
268 );
269
270 return [
271 $image_url,
272 $width,
273 $height,
274 $crop,
275 ];
276 }
277
278 /**
279 * Get all registered image sizes.
280 *
281 * @return array
282 */
283 private function get_all_image_sizes() {
284 $additional_sizes = wp_get_additional_image_sizes();
285 $intermediate = get_intermediate_image_sizes();
286 $all = [];
287
288 foreach ( $intermediate as $size ) {
289 if ( isset( $additional_sizes[ $size ] ) ) {
290 $all[ $size ] = [
291 'width' => $additional_sizes[ $size ]['width'],
292 'height' => $additional_sizes[ $size ]['height'],
293 'crop' => isset( $additional_sizes[ $size ]['crop'] ) ? $additional_sizes[ $size ]['crop'] : false,
294 ];
295 } else {
296 $all[ $size ] = [
297 'width' => (int) get_option( $size . '_size_w' ),
298 'height' => (int) get_option( $size . '_size_h' ),
299 'crop' => (bool) get_option( $size . '_crop' ),
300 ];
301 }
302
303 if ( ! empty( $additional_sizes[ $size ]['crop'] ) ) {
304 $all[ $size ]['crop'] = $additional_sizes[ $size ]['crop'];
305 } else {
306 $all[ $size ]['crop'] = (bool) get_option( $size . '_crop' );
307 }
308 }
309
310 return $all;
311 }
312
313 /**
314 * Check if we're in the attachment edit page.
315 *
316 * /wp-admin/post.php?post=<id>&action=edit
317 *
318 * Send whatever comes from the DAM.
319 *
320 * @param int $attachment_id attachment id.
321 *
322 * @return bool
323 */
324 private function is_attachment_edit_page( $attachment_id ) {
325 if ( ! is_admin() ) {
326 return false;
327 }
328
329 $screen = get_current_screen();
330
331 if ( ! isset( $screen->base ) ) {
332 return false;
333 }
334
335 if ( $screen->base !== 'post' ) {
336 return false;
337 }
338
339 if ( $screen->post_type !== 'attachment' ) {
340 return false;
341 }
342
343 if ( $screen->id !== 'attachment' ) {
344 return false;
345 }
346
347 if ( ! isset( $_GET['post'] ) ) {
348 return false;
349 }
350
351 if ( (int) sanitize_text_field( $_GET['post'] ) !== $attachment_id ) {
352 return false;
353 }
354
355 return true;
356 }
357
358 /**
359 * Alter the attachment metadata.
360 *
361 * @param array $metadata attachment metadata.
362 * @param int $id attachment ID.
363 *
364 * @return array
365 */
366 public function alter_attachment_metadata( $metadata, $id ) {
367 if ( ! $this->is_dam_imported_image( $id ) ) {
368 return $metadata;
369 }
370
371 $sizes = $this->get_all_image_sizes();
372
373 $post = get_post( $id );
374
375 $sizes_meta = [];
376
377 // SVG files don't have a width/height so we add a dummy one. These are vector images so it doesn't matter.
378 $is_svg = ( $post->post_mime_type === Optml_Config::$image_extensions['svg'] );
379
380 if ( $is_svg ) {
381 $metadata['width'] = 150;
382 $metadata['height'] = 150;
383 }
384
385 if ( ! isset( $metadata['height'] ) || ! isset( $metadata['width'] ) ) {
386 return $metadata;
387 }
388
389 foreach ( $sizes as $size => $args ) {
390
391 // check if the image is portrait or landscape using attachment metadata.
392 $is_portrait = $metadata['height'] > $metadata['width'];
393
394 // proportionally set the width/height based on this if image is uncropped.
395 if ( ! (bool) $args['crop'] ) {
396 if ( $is_portrait ) {
397 $args['width'] = (int) ( $args['height'] * round( $metadata['width'] / $metadata['height'] ) );
398 } else {
399 $args['height'] = (int) ( $args['width'] * round( $metadata['height'] / $metadata['width'] ) );
400 }
401 }
402
403 $sizes_meta[ $size ] = [
404 'file' => $metadata['file'],
405 'width' => $args['width'],
406 'height' => $args['height'],
407 'mime-type' => $post->post_mime_type,
408 ];
409 }
410
411 $metadata['sizes'] = $sizes_meta;
412
413 return $metadata;
414 }
415
416 /**
417 * Check if the images are already imported.
418 *
419 * @param array $images List of images to check.
420 *
421 * @return array List of images that are already imported.
422 */
423 private function check_existing_attachments( $images ) {
424 $already_imported = $this->get_dam_imported_attachments( $images );
425
426 // All DAM imports are already in the DB.
427 if ( count( $already_imported ) === count( $images ) ) {
428 return $already_imported;
429 }
430
431 // Get the remaining images.
432 $remaining = array_filter(
433 $images,
434 function ( $image ) use ( $already_imported ) {
435 return ! array_key_exists( $image['meta']['resourceS3'], $already_imported );
436 }
437 );
438
439 // Offloaded images.
440 if ( $this->settings->get( 'offload_media' ) === 'enabled' ) {
441 $offloaded = $this->get_offloaded_attachments( $remaining );
442
443 $already_imported = array_merge( $already_imported, $offloaded );
444 }
445
446 return $already_imported;
447 }
448
449 /**
450 * Check if the images are already imported.
451 *
452 * @param array $images List of images to check.
453 *
454 * @return array
455 */
456 private function get_dam_imported_attachments( $images ) {
457 global $wpdb;
458
459 $s3_ids = [];
460
461 foreach ( $images as $image ) {
462 $s3_ids[] = esc_sql( strval( $image['meta']['resourceS3'] ) );
463 }
464
465 $meta_values_str = "'" . join( "', '", $s3_ids ) . "'";
466
467 // Select all the posts that have the flag and are in the list of images.
468 $found_attachments = $wpdb->get_results(
469 $wpdb->prepare(
470 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- This query cannot use interpolation.
471 "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value IN ( {$meta_values_str} )",
472 self::OM_DAM_IMPORTED_FLAG
473 )
474 );
475
476 if ( empty( $found_attachments ) ) {
477 return [];
478 }
479
480 $map = [];
481
482 // Remap this in a key/value array.
483 // Also ensures that if there are multiple attachments with the same S3 ID, we only get the one.
484 // Shouldn't happen, but just in case.
485 foreach ( $found_attachments as $attachment ) {
486 // Skip edits.
487 if ( ! empty( get_post_meta( $attachment->post_id, self::IS_EDIT_FLAG, true ) ) ) {
488 continue;
489 }
490
491 $map[ $attachment->meta_value ] = (int) $attachment->post_id;
492 }
493
494 return $map;
495 }
496
497 /**
498 * Get the offloaded attachments.
499 *
500 * [ S3 ID => Attachment Post ID ]
501 *
502 * @param array $images List of images to check.
503 *
504 * @return array
505 */
506 private function get_offloaded_attachments( $images ) {
507 global $wpdb;
508
509 $map = [];
510
511 foreach ( $images as $image ) {
512 $like = '%id:' . $image['meta']['resourceS3'] . '%';
513
514 $found_attachments = $wpdb->get_results(
515 $wpdb->prepare(
516 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value LIKE %s",
517 '_wp_attachment_metadata',
518 $like
519 )
520 );
521
522 if ( empty( $found_attachments ) ) {
523 return [];
524 }
525
526 $map[ $image['meta']['resourceS3'] ] = (int) $found_attachments[0]->post_id;
527 }
528
529 return $map;
530 }
531
532 /**
533 * Checks that the attachment is a DAM image.
534 *
535 * @param int $post_id The attachment ID.
536 *
537 * @return bool
538 */
539 private function is_dam_imported_image( $post_id ) {
540 $meta = get_post_meta( $post_id, self::OM_DAM_IMPORTED_FLAG, true );
541
542 if ( empty( $meta ) ) {
543 return false;
544 }
545
546 return true;
547 }
548
549 /**
550 * Adds menu item for DAM.
551 *
552 * @return void
553 */
554 public function add_menu() {
555 if ( defined( 'OPTIOMLE_HIDE_ADMIN_AREA' ) && OPTIOMLE_HIDE_ADMIN_AREA ) {
556 return;
557 }
558
559 add_submenu_page(
560 'optimole',
561 __( 'Cloud Library', 'optimole-wp' ),
562 __( 'Cloud Library', 'optimole-wp' ),
563 'manage_options',
564 'optimole-dam',
565 [ $this, 'render_dashboard_page' ]
566 );
567 }
568
569 /**
570 * Add media template to be used in the media library.
571 *
572 * @return void
573 */
574 public function print_media_template() {
575 ?>
576 <script type="text/html" id="tmpl-optimole-dam">
577 <?php $this->render_dashboard_page(); ?>
578 </script>
579 <?php
580 }
581
582 /**
583 * Render the dashboard page.
584 *
585 * @return void
586 */
587 public function render_dashboard_page() {
588 ?>
589 <iframe id="om-dam" style="display: none;" src="<?php echo( $this->build_iframe_url() ); ?>"></iframe>
590 <div class="om-dam-loader">
591 <img src="<?php echo esc_url( OPTML_URL . 'assets/img/logo.png' ); ?>" alt="Optimole Logo"
592 class="om-dam-logo">
593 <p><?php echo esc_html__( 'Loading', 'optimole-wp' ); ?>...</p>
594 </div>
595 <?php
596 }
597
598 /**
599 * Build the iFrame URL.
600 *
601 * @return string
602 */
603 public function build_iframe_url() {
604 $api_key = $this->settings->get( 'api_key' );
605 $connected_sites = $this->settings->get( 'cloud_sites' );
606
607 if ( empty( $api_key ) ) {
608 return '';
609 }
610
611 if ( isset( $connected_sites['all'] ) && $connected_sites['all'] === 'true' ) {
612 $connected_sites = [];
613 } else {
614 foreach ( $connected_sites as $site => $status ) {
615 if ( $status !== 'true' ) {
616 unset( $connected_sites[ $site ] );
617 }
618 }
619 }
620
621 $data = [
622 'site' => get_site_url(),
623 'token' => $api_key,
624 'sites' => array_keys( $connected_sites ),
625 ];
626
627 $data = json_encode( $data );
628 $data = rtrim( base64_encode( $data ), '=' );
629
630 return add_query_arg(
631 [
632 'data' => $data,
633 ],
634 $this->dam_endpoint
635 );
636 }
637
638 /**
639 * Enqueue script for generating cloud media tab.
640 *
641 * @return void
642 */
643 public function enqueue_media_scripts() {
644 $asset_file = include OPTML_PATH . 'assets/build/media/media-modal.asset.php';
645
646 wp_register_script(
647 OPTML_NAMESPACE . '-media-modal',
648 OPTML_URL . 'assets/build/media/media-modal.js',
649 $asset_file['dependencies'],
650 $asset_file['version'],
651 true
652 );
653
654 wp_localize_script( OPTML_NAMESPACE . '-media-modal', 'optmlMediaModal', $this->get_localized_vars() );
655 wp_enqueue_script( OPTML_NAMESPACE . '-media-modal' );
656 wp_enqueue_style( OPTML_NAMESPACE . '-media-modal', OPTML_URL . 'assets/build/media/media-modal.css' );
657 }
658
659 /**
660 * Enqueue script for generating admin page.
661 *
662 * @return void
663 */
664 public function enqueue_admin_page_scripts() {
665 $screen = get_current_screen();
666
667 if ( $screen->id !== 'optimole_page_optimole-dam' ) {
668 return;
669 }
670
671 $asset_file = include OPTML_PATH . 'assets/build/media/admin-page.asset.php';
672
673 wp_register_script(
674 OPTML_NAMESPACE . '-admin-page',
675 OPTML_URL . 'assets/build/media/admin-page.js',
676 $asset_file['dependencies'],
677 $asset_file['version'],
678 true
679 );
680
681 wp_localize_script(
682 OPTML_NAMESPACE . '-admin-page',
683 'optmlAdminPage',
684 [
685 'siteUrl' => get_site_url(),
686 ]
687 );
688
689 wp_enqueue_script( OPTML_NAMESPACE . '-admin-page' );
690
691 wp_enqueue_style( OPTML_NAMESPACE . '-admin-page', OPTML_URL . 'assets/build/media/admin-page.css' );
692 }
693
694 /**
695 * Get localized variables for the media modal.
696 *
697 * @return array
698 */
699 private function get_localized_vars() {
700 $routes = array_keys( Optml_Rest::$rest_routes['dam_routes'] );
701
702 foreach ( $routes as $route ) {
703 $routes[ $route ] = OPTML_NAMESPACE . '/v1/' . $route;
704 }
705
706 return [
707 'nonce' => wp_create_nonce( 'wp_rest' ),
708 'routes' => $routes,
709 ];
710 }
711
712 /**
713 * Alter the image size for the image widget.
714 *
715 * @param string $html the attachment image HTML string.
716 * @param array $settings Control settings.
717 * @param string $image_size_key Optional. Settings key for image size.
718 * Default is `image`.
719 * @param string $image_key Optional. Settings key for image. Default
720 * is null. If not defined uses image size key
721 * as the image key.
722 *
723 * @return string
724 */
725 public function alter_elementor_image_size( $html, $settings, $image_size_key, $image_key ) {
726 if ( ! isset( $settings['image'] ) ) {
727 return $html;
728 }
729
730 $image = $settings['image'];
731
732 if ( ! isset( $image['id'] ) ) {
733 return $html;
734 }
735
736 if ( ! $this->is_dam_imported_image( $image['id'] ) ) {
737 return $html;
738 }
739
740 if ( ! isset( $settings['image_size'] ) ) {
741 return $html;
742 }
743
744 if ( $settings['image_size'] === 'custom' ) {
745 if ( ! isset( $settings['image_custom_dimension'] ) ) {
746 return $html;
747 }
748
749 $custom_dimensions = $settings['image_custom_dimension'];
750
751 if ( ! isset( $custom_dimensions['width'] ) || ! isset( $custom_dimensions['height'] ) ) {
752 return $html;
753 }
754
755 return $this->replace_dam_url_args( $custom_dimensions, $html );
756 }
757
758 $all_sizes = $this->get_all_image_sizes();
759
760 if ( ! isset( $all_sizes[ $settings['image_size'] ] ) ) {
761 return $html;
762 }
763
764 return $this->replace_dam_url_args( $all_sizes[ $settings['image_size'] ], $html );
765 }
766
767 /**
768 * Needed as some blocks might use the image sizes.
769 *
770 * @param array $response Array of prepared attachment data. @see wp_prepare_attachment_for_js().
771 * @param WP_Post $attachment Attachment object.
772 * @param array|false $meta Array of attachment meta data, or false if there is none.
773 *
774 * @return array
775 */
776 public function alter_attachment_for_js( $response, $attachment, $meta ) {
777 if ( ! $this->is_dam_imported_image( $attachment->ID ) ) {
778 return $response;
779 }
780
781 $sizes = $this->get_all_image_sizes();
782
783 foreach ( $sizes as $size => $args ) {
784 if ( isset( $response['sizes'][ $size ] ) ) {
785 continue;
786 }
787
788 $args = [
789 'height' => $args['height'],
790 'width' => $args['width'],
791 'crop' => true,
792 ];
793
794 $response['sizes'][ $size ] = array_merge(
795 $args,
796 [
797 'url' => $this->replace_dam_url_args( $args, $response['url'] ),
798 'orientation' => ( $args['height'] > $args['width'] ) ? 'portrait' : 'landscape',
799 ]
800 );
801 }
802
803 $url_args = [
804 'height' => $response['height'],
805 'width' => $response['width'],
806 'crop' => false,
807 ];
808
809 $response['url'] = $this->replace_dam_url_args( $url_args, $response['url'] );
810
811 return $response;
812 }
813
814 /**
815 * We have to short-circuit the logic that adds width and height to the img tag.
816 * It compares the URL basename, and the `file` param for each image.
817 * This happens for any image that gets its size set non-explicitly
818 * e.g. an image block with its size set from the sidebar to `thumbnail`).
819 *
820 * Optimole has a single basename for all image resizes in its URL.
821 *
822 * @param array|false $dimensions Array with first element being the width
823 * and second element being the height, or
824 * false if dimensions could not be determined.
825 * @param string $image_src The image URL (will be Optimole URL).
826 * @param array $image_meta The image metadata.
827 * @param int $attachment_id The image attachment ID. Default 0.
828 */
829 public function alter_img_tag_w_h( $dimensions, $image_src, $image_meta, $attachment_id ) {
830 if ( ! $this->is_dam_imported_image( $attachment_id ) ) {
831 return $dimensions;
832 }
833
834 // Get the dimensions from the optimized URL.
835 $incoming_size = Optml_Media_Offload::parse_dimension_from_optimized_url( $image_src );
836 $width = $incoming_size[0];
837 $height = $incoming_size[1];
838
839 $sizes = $this->get_all_image_sizes();
840
841 // If this is an image size. Return its dimensions.
842 foreach ( $sizes as $size => $args ) {
843 if ( (int) $args['width'] !== (int) $width ) {
844 continue;
845 }
846
847 if ( (int) $args['height'] !== (int) $height ) {
848 continue;
849 }
850
851 return [
852 $args['width'],
853 $args['height'],
854 ];
855 }
856
857 // Fall-through with the original dimensions.
858 return $dimensions;
859 }
860
861 /**
862 * Replace the image size params in DAM URLs inside a string.
863 *
864 * @param array $args The arguments to replace.
865 * - width: The width of the image.
866 * - height: The height of the image.
867 * - crop: Whether to crop the image.
868 * @param string $subject The string to replace the arguments in.
869 *
870 * @return string
871 */
872 public function replace_dam_url_args( $args, $subject ) {
873 $args = wp_parse_args( $args, [ 'width' => 'auto', 'height' => 'auto', 'crop' => false, 'dam' => true] );
874
875 $width = $args['width'];
876 $height = $args['height'];
877 $crop = (bool) $args['crop'];
878
879 $gravity = Optml_Resize::GRAVITY_CENTER;
880
881 if ( $this->settings->get( 'resize_smart' ) === 'enabled' ) {
882 $gravity = Optml_Resize::GRAVITY_SMART;
883 }
884
885 if ( $width === 0 ) {
886 $width = 'auto';
887 }
888
889 if ( $height === 0 ) {
890 $height = 'auto';
891 }
892
893 // Use the proper replacement for the image size.
894 $replacement = '/w:' . $width . '/h:' . $height;
895
896 if ( $crop ) {
897 $replacement .= '/g:' . $gravity . '/rt:fill';
898 }
899
900 $replacement .= '/q:';
901
902 if ( $args['dam'] ) {
903 $replacement = self::URL_DAM_FLAG . $replacement;
904 }
905
906 return preg_replace( '/\/w:(.*)\/h:(.*)\/q:/', $replacement, $subject );
907 }
908
909 /**
910 * Elementor checks if the file exists before requesting a specific image size.
911 *
912 * Needed because otherwise there won't be any width/height on the `img` tags, breaking lazyload.
913 *
914 * Also needed because some
915 *
916 * @param string $file The file path.
917 * @param int $id The attachment ID.
918 *
919 * @return bool|string
920 */
921 public function alter_attached_file_response( $file, $id ) {
922 if ( ! $this->is_dam_imported_image( $id ) ) {
923 return $file;
924 }
925
926 $metadata = wp_get_attachment_metadata( $id );
927
928 if ( isset( $metadata['file'] ) ) {
929 $uploads = wp_get_upload_dir();
930
931 return $uploads['basedir'] . '/' . $metadata['file'];
932 }
933
934 return true;
935 }
936 }
937