PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / integrations / imagify / imagify.php

imagify.php in Media Cloud Sync trunk, at includes/integrations/imagify/imagify.php

842 lines 21.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined( 'ABSPATH' ) || exit;
5
6 /**
7 * Imagify Compatibility.
8 *
9 * Integration class is `Dudlewebs\WPMCS\Imagify` — not the Imagify plugin's global `\Imagify` class.
10 * Loaded by the autoloader; activation is gated via `is_installed()` in Integration::init().
11 *
12 * @since 1.3.6
13 */
14 class Imagify {
15 /**
16 * The singleton instance.
17 *
18 * @since 1.6.6
19 * @var Imagify|null
20 */
21 protected static $instance = null;
22
23 /**
24 * The class constructor.
25 *
26 * @since 1.6.6
27 */
28 protected function __construct() {
29 $this->init();
30 }
31
32 /**
33 * Launch the hooks.
34 *
35 * @since 1.3.6
36 */
37 public function init() {
38 /**
39 * WebP images to display with a <picture> tag.
40 */
41 add_filter( 'imagify_webp_picture_process_image', [ $this, 'picture_tag_webp_image' ] );
42
43 /**
44 * Register CDN.
45 */
46 add_filter( 'imagify_cdn', [ $this, 'register_cdn' ], 8, 3 );
47
48 /**
49 * Optimization process.
50 */
51 add_filter( 'imagify_before_optimize_size', [ $this, 'maybe_copy_file_from_cdn_before_optimization' ], 8, 6 );
52
53 // Add webp files to the CDN after optimization if webp is exist and remove them from server if enabled
54 add_filter( 'wpmcs_pre_update_item_additional_files_to_remove_from_server', [ $this, 'send_additinal_files_to_cdn' ], 8, 5 );
55
56 // Send media to CDN after optimization
57 add_action( 'imagify_after_optimize', [ $this, 'maybe_send_media_to_cdn_after_optimization' ], 8, 2 );
58
59
60 /**
61 * Restoration process.
62 */
63 add_action( 'imagify_after_restore_media', [ $this, 'maybe_send_media_to_cdn_after_restore' ], 8, 4 );
64
65 /**
66 * WebP support.
67 */
68 add_filter( 'wpmcs_get_item', [ $this, 'add_webp_images_to_attachment' ], 8, 3 );
69 add_filter( 'mime_types', [ $this, 'add_webp_support' ] );
70 }
71
72 /**
73 * Copy file from cloud storage before optimization when it is missing locally.
74 *
75 * Mirrors Imagify's official AS3CF integration hook handler.
76 *
77 * @param null|\WP_Error $response The response.
78 * @param object $process The optimization process instance.
79 * @param object $file The Imagify file instance.
80 * @param string $thumb_size The media size.
81 * @param int $optimization_level The optimization level.
82 * @param bool $webp Whether the image will be converted to WebP.
83 *
84 * @return null|\WP_Error
85 */
86 public function maybe_copy_file_from_cdn_before_optimization( $response, $process, $file, $thumb_size, $optimization_level, $webp ) {
87 if ( is_wp_error( $response ) || 'wp' !== $process->get_media()->get_context() ) {
88 return $response;
89 }
90
91 if ( \Imagify_Filesystem::get_instance()->exists( $file->get_path() ) ) {
92 return $response;
93 }
94
95 return $this->copy_files_from_cdn( $process->get_media()->get_id(), [ $file->get_path() ] );
96 }
97
98
99
100 /**
101 * After performing a media optimization:
102 * - Save some data,
103 * - Upload the files to the CDN,
104 * - Maybe delete them from the server.
105 *
106 * @since 1.9
107 * @access public
108 *
109 * @param ProcessInterface $process The optimization process.
110 * @param array $item The item being processed.
111 */
112 public function maybe_send_media_to_cdn_after_optimization( $process, $item ) {
113 if ( 'wp' !== $process->get_media()->get_context() ) {
114 return;
115 }
116
117 $this->send_attachment_to_cdn(
118 $process->get_media()->get_id(),
119 ! empty( $item['data']['is_new_upload'] )
120 );
121 }
122
123
124 /**
125 * Send additinal files to CDN
126 *
127 * @since 1.9
128 * @access public
129 *
130 * @param array $files_to_remove
131 * @param int $source_id
132 * @param array $new_item
133 * @param array $old_item
134 * @param string $source_type
135 */
136 public function send_additinal_files_to_cdn( $files_to_remove, $source_id, $new_item, $old_item = [], $source_type = 'media_library' ) {
137 $upload_dir = wp_get_upload_dir();
138 $extras = isset( $new_item['extra'] ) ? Utils::maybe_unserialize( $new_item['extra'] ) : [];
139
140 if ( empty( $extras['sizes'] ) || ! is_array( $extras['sizes'] ) ) {
141 return $files_to_remove;
142 }
143
144 $prefix = isset($extras['prefix']) ? $extras['prefix'] : '';
145 $files_to_upload = [];
146
147 // Add all sizes.
148 if( isset( $extras['sizes'] ) && !empty( $extras['sizes'] ) && is_array( $extras['sizes'] ) ) {
149 foreach ( $extras['sizes'] as $size_name => $size ) {
150 if( !isset( $size['source_path'] ) || empty( $size['source_path'] ) ) {
151 continue;
152 }
153
154 $webp_size_source_path = $this->path_to_webp( $size['source_path'] );
155 $webp_size_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_size_source_path, '/' );
156 if ( file_exists( $webp_size_path ) ) {
157 $files_to_upload[$webp_size_source_path] = $webp_size_path;
158 }
159 }
160 }
161
162 if ( ! empty( $new_item['original_source_path'] ) ) {
163 if ( isset( $new_item['original_source_path'] ) && !empty( $new_item['original_source_path'] ) ) {
164 $webp_original_source_path = $this->path_to_webp( $new_item['original_source_path'] );
165 $webp_original_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_original_source_path, '/' );
166 if ( file_exists( $webp_original_path ) ) {
167 $files_to_upload[$webp_original_source_path] = $webp_original_path;
168 }
169 }
170 }
171
172 if ( ! empty( $new_item['source_path'] ) ) {
173 if ( isset( $new_item['source_path'] ) && !empty( $new_item['source_path'] ) ) {
174 $webp_full_source_path = $this->path_to_webp( $new_item['source_path'] );
175 $webp_full_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_full_source_path, '/' );
176 if ( file_exists( $webp_full_path ) ) {
177 $files_to_upload[$webp_full_source_path] = $webp_full_path;
178 }
179 }
180 }
181
182 if ( $files_to_upload ) {
183 foreach ( $files_to_upload as $relative_source_path => $path ) {
184 $uploaded = Service::instance()->uploadSingle( $path, $relative_source_path, $prefix );
185 if ( $uploaded['success'] ) {
186 $files_to_remove[] = $path;
187 }
188 }
189 }
190
191 return $files_to_remove;
192 }
193
194
195
196 /**
197 * Add the WebP files to the list of files that the CDN must handle.
198 * @since 1.3.6
199 */
200 public function add_webp_images_to_attachment( $item, $attachment_id, $source_type = 'media_library' ) {
201 if ( ! $item ) {
202 return $item;
203 }
204
205 $process = imagify_get_optimization_process( $attachment_id, 'wp' );
206
207 if ( ! $process->is_valid() ) {
208 return $item;
209 }
210
211 // Make sure it's an image.
212 if ( ! $this->is_attachment_image( $attachment_id ) ) {
213 return $item;
214 }
215
216 // Use the optimization data (the files may not be on the server).
217 $data = $process->get_data()->get_optimization_data();
218
219 if ( empty( $data['sizes'] ) ) {
220 return $item;
221 }
222
223 $extras = isset( $item['extra'] ) ? Utils::maybe_unserialize( $item['extra'] ) : [];
224
225 $sizes = isset( $extras['sizes'] ) && ! empty( $extras['sizes'] ) ? $extras['sizes'] : [];
226
227 $new_sizes = [];
228
229 foreach ( $sizes as $size_name => $size ) {
230 if ( $process->is_size_next_gen( $size_name ) ) {
231 continue;
232 }
233
234 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
235
236 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
237 // This size has no WebP version.
238 continue;
239 }
240
241 if ( ! $this->is_webp($size['source_path']) ) {
242 $new_sizes[ $webp_size_name ] = [
243 'source_path' => $this->path_to_webp( $size['source_path'] ),
244 'url' => $this->path_to_webp( $size['url'] ),
245 'key' => $this->path_to_webp( $size['key'] ),
246 'width' => $size['width'],
247 'height' => $size['height'],
248 ];
249 }
250 }
251
252 if( $item['source_path'] ) {
253 if( !$process->is_size_next_gen( 'full' ) ) {
254 $webp_size_name = 'full' . $process::WEBP_SUFFIX;
255
256 if ( ! empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
257 if ( ! $this->is_webp($item['source_path']) ) {
258 $new_sizes[ $webp_size_name ] = [
259 'source_path' => $this->path_to_webp( $item['source_path'] ),
260 'url' => $this->path_to_webp( $item['url'] ),
261 'key' => $this->path_to_webp( $item['key'] ),
262 'width' => $extras['width'],
263 'height' => $extras['height'],
264 ];
265 }
266 }
267 }
268 }
269
270 if ( $new_sizes ) {
271 $extras['sizes'] = array_merge( $sizes, $new_sizes );
272 $item['extra'] = Utils::maybe_serialize( $extras );
273 }
274
275
276 return $item;
277 }
278
279
280 /**
281 * After restoring a media:
282 * - Save some data,
283 * - Upload the files to the CDN,
284 * - Maybe delete WebP files from the CDN.
285 */
286 public function maybe_send_media_to_cdn_after_restore( $process, $response, $files, $data ) {
287 if ( 'wp' !== $process->get_media()->get_context() ) {
288 return;
289 }
290
291 $attachment_id = $process->get_media()->get_id();
292
293 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
294 return;
295 }
296
297 if ( is_wp_error( $response ) && 'copy_failed' === $response->get_error_code() ) {
298 return;
299 }
300
301 $this->send_attachment_to_cdn( $attachment_id );
302
303 $extras = Item::instance()->get_extras( $attachment_id );
304 $sizes = isset( $extras['sizes'] ) && is_array( $extras['sizes'] ) ? $extras['sizes'] : [];
305 $webp_files = [];
306
307 if ( $files ) {
308 foreach ( $files as $size_name => $file ) {
309 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
310
311 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
312 continue;
313 }
314
315 if ( 0 === strpos( $file['mime-type'], 'image/' ) && ! $this->is_webp( $file['path'] ) ) {
316 if ( $sizes && isset( $sizes[ $webp_size_name ] ) ) {
317 $webp_files[] = $sizes[ $webp_size_name ]['key'];
318 }
319 }
320 }
321 }
322
323 if ( $webp_files ) {
324 Item::instance()->delete_cloud_files_by_keys( $webp_files );
325 }
326 }
327
328
329 /**
330 * Send media to cloud after Imagify optimization or restore.
331 *
332 * @param int $attachment_id
333 * @param bool $is_new_upload
334 * @return bool|\WP_Error
335 */
336 public function send_to_cdn( $attachment_id ) {
337
338 $attachment_id = (int) $attachment_id;
339
340 if ( ! $attachment_id ) {
341 return new \WP_Error(
342 'invalid_attachment',
343 __( 'Invalid attachment ID.', 'media-cloud-sync' )
344 );
345 }
346
347 // Tell Media Cloud Sync to reupload all media files.
348 // Media may be optimized and Imagify may have created new optimized files.
349 add_filter( 'wpmcs_do_reupload_media', '__return_true', 10, 3 );
350 do_action( 'wpmcs_do_update_attachment_metadata', false, $attachment_id );
351 remove_filter( 'wpmcs_do_reupload_media', '__return_true', 10, 3 );
352
353 return true;
354 }
355
356 /**
357 * Tell if an attachment is stored in cloud storage.
358 *
359 * @param int $attachment_id Attachment ID.
360 * @return bool
361 * @since 1.3.12
362 */
363 public function media_is_on_provider( $attachment_id ) {
364 return Item::instance()->is_available_from_provider( (int) $attachment_id );
365 }
366
367 /**
368 * Copy missing files from cloud storage to the server.
369 *
370 * @param int $attachment_id Attachment ID.
371 * @param array $file_paths Local file paths.
372 * @return bool|\WP_Error
373 * @since 1.3.12
374 */
375 public function copy_files_from_cdn( $attachment_id, $file_paths ) {
376 $attachment_id = (int) $attachment_id;
377
378 if ( ! Utils::is_service_enabled() ) {
379 return new \WP_Error(
380 'not_ready',
381 __( 'Cloud storage is not set up.', 'media-cloud-sync' )
382 );
383 }
384
385 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
386 return new \WP_Error(
387 'not_on_cdn',
388 __( 'This media could not be found in cloud storage.', 'media-cloud-sync' )
389 );
390 }
391
392 $filesystem = \Imagify_Filesystem::get_instance();
393 $errors = [];
394
395 foreach ( (array) $file_paths as $file_path ) {
396 if ( $filesystem->exists( $file_path ) ) {
397 continue;
398 }
399
400 Item::instance()->moveToServerBySourcePath( $attachment_id, $file_path );
401
402 if ( ! $filesystem->exists( $file_path ) ) {
403 $errors[] = $file_path;
404 }
405 }
406
407 if ( ! $errors ) {
408 return true;
409 }
410
411 $error_count = count( $errors );
412 $error_paths = array_map( [ $filesystem, 'make_path_relative' ], $errors );
413 $error_paths = wp_sprintf_l( '%l', $error_paths );
414
415 return new \WP_Error(
416 'not_retrieved',
417 sprintf(
418 /* translators: %s is a list of file paths. */
419 _n(
420 'The following file could not be retrieved from cloud storage: %s.',
421 'The following files could not be retrieved from cloud storage: %s.',
422 $error_count,
423 'media-cloud-sync'
424 ),
425 $error_paths
426 ),
427 $errors
428 );
429 }
430
431 /**
432 * Upload optimized files back to cloud storage when allowed.
433 *
434 * @param int $attachment_id Attachment ID.
435 * @param bool $is_new_upload Whether this is a new upload.
436 * @return bool|\WP_Error
437 * @since 1.3.12
438 */
439 public function send_attachment_to_cdn( $attachment_id, $is_new_upload = false ) {
440 $attachment_id = (int) $attachment_id;
441
442 if ( ! Utils::is_service_enabled() || ! $this->media_is_on_provider( $attachment_id ) ) {
443 return false;
444 }
445
446 return $this->send_to_cdn( $attachment_id );
447 }
448
449 /**
450 * Remove files from cloud storage.
451 *
452 * @param int $attachment_id Attachment ID.
453 * @param array $file_paths Local file paths or file names.
454 * @return bool|\WP_Error
455 * @since 1.3.12
456 */
457 public function remove_files_from_cdn( $attachment_id, $file_paths ) {
458 $attachment_id = (int) $attachment_id;
459
460 if ( ! Utils::is_service_enabled() ) {
461 return new \WP_Error(
462 'not_ready',
463 __( 'Cloud storage is not set up.', 'media-cloud-sync' )
464 );
465 }
466
467 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
468 return new \WP_Error(
469 'not_on_cdn',
470 __( 'This media could not be found in cloud storage.', 'media-cloud-sync' )
471 );
472 }
473
474 $keys = [];
475
476 foreach ( (array) $file_paths as $file_path ) {
477 $key = $this->get_key_for_attachment_path( $attachment_id, $file_path );
478
479 if ( $key ) {
480 $keys[] = $key;
481 }
482 }
483
484 if ( ! $keys ) {
485 return true;
486 }
487
488 Item::instance()->delete_cloud_files_by_keys( array_unique( $keys ) );
489
490 return true;
491 }
492
493 /**
494 * Get a provider URL for an attachment file.
495 *
496 * @param int $attachment_id Attachment ID.
497 * @param string $file_name File name or empty for full size.
498 * @return string
499 * @since 1.3.12
500 */
501 public function get_provider_file_url( $attachment_id, $file_name = '' ) {
502 $attachment_id = (int) $attachment_id;
503
504 if ( ! $file_name ) {
505 $url = Item::instance()->get_url( $attachment_id, 'full', 'media_library' );
506
507 return $url ? $url : (string) wp_get_attachment_url( $attachment_id );
508 }
509
510 $filesystem = \Imagify_Filesystem::get_instance();
511 $full_url = $this->get_provider_file_url( $attachment_id );
512
513 return $filesystem->dir_path( $full_url ) . $file_name;
514 }
515
516 /**
517 * Get a local filesystem path for an attachment file.
518 *
519 * @param int $attachment_id Attachment ID.
520 * @param string $file_name File name, empty for full size, or `original`.
521 * @return string
522 * @since 1.3.12
523 */
524 public function get_local_file_path( $attachment_id, $file_name = '' ) {
525 $attachment_id = (int) $attachment_id;
526 $item = Item::instance()->get( $attachment_id, 'media_library' );
527 $upload_dir = wp_upload_dir();
528 $filesystem = \Imagify_Filesystem::get_instance();
529
530 if ( Utils::is_empty( $item ) ) {
531 return (string) get_attached_file( $attachment_id, true );
532 }
533
534 if ( ! $file_name ) {
535 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
536 }
537
538 if ( 'original' === $file_name ) {
539 if ( function_exists( 'wp_get_original_image_path' ) ) {
540 $original_path = wp_get_original_image_path( $attachment_id );
541
542 if ( $original_path ) {
543 return $original_path;
544 }
545 }
546
547 if ( ! empty( $item['original_source_path'] ) ) {
548 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['original_source_path'], '/' );
549 }
550
551 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
552 }
553
554 $full_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
555
556 return $filesystem->dir_path( $full_path ) . $file_name;
557 }
558
559 /**
560 * Resolve a cloud object key from a local path or file name.
561 *
562 * @param int $attachment_id Attachment ID.
563 * @param string $file_path Local file path or file name.
564 * @return string
565 * @since 1.3.12
566 */
567 protected function get_key_for_attachment_path( $attachment_id, $file_path ) {
568 $attachment_id = (int) $attachment_id;
569 $item = Item::instance()->get( $attachment_id, 'media_library' );
570
571 if ( Utils::is_empty( $item ) ) {
572 return '';
573 }
574
575 $filesystem = \Imagify_Filesystem::get_instance();
576 $source_path = Utils::get_attachment_source_path( $file_path );
577
578 if ( empty( $source_path ) ) {
579 $full_path = $this->get_local_file_path( $attachment_id );
580 $source_path = Utils::get_attachment_source_path(
581 $filesystem->dir_path( $full_path ) . wp_basename( $file_path )
582 );
583 }
584
585 if ( empty( $source_path ) ) {
586 return '';
587 }
588
589 if ( ! empty( $item['source_path'] ) && $item['source_path'] === $source_path ) {
590 return $item['key'] ?? '';
591 }
592
593 if ( ! empty( $item['original_source_path'] ) && $item['original_source_path'] === $source_path ) {
594 return $item['original_key'] ?? '';
595 }
596
597 $extras = Item::instance()->get_extras( $attachment_id, false, 'media_library' ) ?: [];
598
599 if ( ! empty( $extras['sizes'] ) ) {
600 foreach ( $extras['sizes'] as $size ) {
601 if (
602 ! empty( $size['source_path'] ) &&
603 $size['source_path'] === $source_path &&
604 ! empty( $size['key'] )
605 ) {
606 return $size['key'];
607 }
608 }
609 }
610
611 return '';
612 }
613
614
615
616 /**
617 * WebP images to display with a <picture> tag.
618 *
619 * @since 1.3.6
620 *
621 * @param array $data An array of data for this image.
622 * @return array
623 */
624 public function picture_tag_webp_image( $data ) {
625 global $wpdb;
626
627 if ( ! empty( $data['src']['webp_path'] ) ) {
628 // The file is local.
629 return $data;
630 }
631
632 if(!$this->is_provider_url( $data['src']['url'] )) {
633 // Not on Provider.
634 return $data;
635 }
636
637 $full_url = Utils::remove_size_from_filename( $data['src']['url'] );
638 $path = Utils::get_attachment_source_path( $full_url, 'key' );
639
640 $item = Item::instance()->get_items_by_paths( $path, true, true, 'key' );
641
642 if ( ! $item ) {
643 // Not in the database.
644 return $data;
645 }
646
647 $post_id = (int)$item['source_id'];
648 $imagify_data = get_post_meta( $post_id, '_imagify_data', true );
649
650 if ( ! $imagify_data ) {
651 // Not optimized.
652 return $data;
653 }
654
655 if( !function_exists( 'imagify_get_optimization_process_class_name' ) ) {
656 // Imagify is not fully loaded.
657 return $data;
658 }
659
660 $webp_size_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
661 $webp_size_name = 'full' . $webp_size_suffix;
662
663 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
664 // We have a WebP image.
665 $data['src']['webp_exists'] = true;
666 }
667
668 if ( empty( $data['srcset'] ) ) {
669 return $data;
670 }
671
672 $meta_data = get_post_meta( $post_id, '_wp_attachment_metadata', true );
673
674 if ( empty( $meta_data['sizes'] ) ) {
675 return $data;
676 }
677
678 // Ease the search for corresponding file name.
679 $size_files = [];
680
681 foreach ( $meta_data['sizes'] as $size_name => $size_data ) {
682 $size_files[ $size_data['file'] ] = $size_name;
683 }
684
685 // Look for a corresponding size name.
686 foreach ( $data['srcset'] as $i => $srcset_data ) {
687 if ( empty( $srcset_data['webp_url'] ) ) {
688 // Not a supported image format.
689 continue;
690 }
691 if ( ! empty( $srcset_data['webp_path'] ) ) {
692 // The file is local.
693 continue;
694 }
695
696 $match = $this->is_provider_url( $srcset_data['url'] );
697
698 if ( ! $match ) {
699 continue;
700 }
701
702 // Try with no subdirs.
703 $filename = basename( $srcset_data['url'] );
704
705 if ( isset( $size_files[ $filename ] ) ) {
706 $size_name = $size_files[ $filename ];
707 } else {
708 continue;
709 }
710
711 $webp_size_name = $size_name . $webp_size_suffix;
712
713 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
714 // We have a WebP image.
715 $data['srcset'][ $i ]['webp_exists'] = true;
716 }
717 }
718
719 return $data;
720 }
721
722
723
724 /**
725 * Add WebP format to the list of allowed mime types.
726 *
727 * @since 1.9
728 * @access public
729 * @see get_allowed_mime_types()
730 *
731 * @param array $mime_types A list of mime types.
732 * @return array
733 */
734 public function add_webp_support( $mime_types ) {
735 $mime_types['webp'] = 'image/webp';
736 return $mime_types;
737 }
738
739
740 /**
741 * The CDN to use for this media.
742 *
743 * @since 1.3.6
744 * @access public
745 */
746 public function register_cdn( $cdn, $media_id, $context ) {
747 if ( 'wp' !== $context->get_name() ) {
748 return $cdn;
749 }
750
751 if ( $cdn instanceof \Imagify\CDN\PushCDNInterface ) {
752 return $cdn;
753 }
754
755 if ( ! interface_exists( '\Imagify\CDN\PushCDNInterface' ) || ! class_exists( ImagifyPushCDN::class ) ) {
756 return $cdn;
757 }
758
759 if ( Item::instance()->is_available_from_provider( $media_id ) ) {
760 return new ImagifyPushCDN( (int) $media_id );
761 }
762
763 return $cdn;
764 }
765
766
767 /**
768 * Tell if the file is a WebP image.
769 * Rejects "path/to/.webp" files.
770 *
771 * @param string $path The path to the file.
772 * @return bool
773 */
774 public function is_webp($path) {
775 return preg_match( '@(?!^|/|\\\)\.webp$@i', $path );
776 }
777
778 /**
779 * Get the path to a WebP version of an image.
780 *
781 * @param string $path The path to the original image.
782 * @return string The path to the WebP version of the image.
783 */
784 public function path_to_webp( $path ) {
785 return $path . '.webp';
786 }
787
788 /**
789 * Checks if the given attachment ID is an image.
790 *
791 * @param int $attachment_id The attachment ID to check.
792 * @return bool True if the attachment is an image, false otherwise.
793 */
794 private function is_attachment_image($attachment_id) {
795 $post = get_post($attachment_id);
796 if (!$post) return false;
797
798 // Check the MIME type prefix directly from the database
799 return str_starts_with($post->post_mime_type, 'image/');
800 }
801
802
803 /**
804 * Tell if an URL is a Provider one.
805 */
806 public function is_provider_url( $url ) {
807 /**
808 * Allow short-circuiting via filter.
809 *
810 * @param null|array|bool $is
811 * @param string $url
812 */
813 $is = apply_filters( 'wpmcs_imagify_is_provider_url', null, $url );
814
815 if ( null !== $is ) {
816 return $is;
817 }
818
819 return Cdn::is_cdn_url( $url ) || Service::instance()->is_provider_url( $url );
820 }
821
822
823 /**
824 * Is installed?
825 *
826 * @return bool
827 */
828 public static function is_installed(): bool {
829 return defined( 'IMAGIFY_VERSION' ) || class_exists( '\Imagify', false );
830 }
831
832 /**
833 * Singleton instance
834 */
835 public static function instance() {
836 if (is_null(self::$instance)) {
837 self::$instance = new self();
838 }
839
840 return self::$instance;
841 }
842 }