PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 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 All 35 releases
media-cloud-sync / includes / integrations / imagify / imagify.php

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

851 lines 22.4 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 offload-plugin 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 $is_private = !empty( $new_item['is_private'] );
184 foreach ( $files_to_upload as $relative_source_path => $path ) {
185 $uploaded = Service::instance()->uploadSingle( $path, $relative_source_path, $prefix, $is_private );
186 if ( $uploaded['success'] ) {
187 $files_to_remove[] = $path;
188 }
189 }
190 }
191
192 return $files_to_remove;
193 }
194
195
196
197 /**
198 * Add the WebP files to the list of files that the CDN must handle.
199 * @since 1.3.6
200 */
201 public function add_webp_images_to_attachment( $item, $attachment_id, $source_type = 'media_library' ) {
202 if ( ! $item ) {
203 return $item;
204 }
205
206 $process = imagify_get_optimization_process( $attachment_id, 'wp' );
207
208 if ( ! $process->is_valid() ) {
209 return $item;
210 }
211
212 // Make sure it's an image.
213 if ( ! $this->is_attachment_image( $attachment_id ) ) {
214 return $item;
215 }
216
217 // Use the optimization data (the files may not be on the server).
218 $data = $process->get_data()->get_optimization_data();
219
220 if ( empty( $data['sizes'] ) ) {
221 return $item;
222 }
223
224 $extras = isset( $item['extra'] ) ? Utils::maybe_unserialize( $item['extra'] ) : [];
225
226 $sizes = isset( $extras['sizes'] ) && ! empty( $extras['sizes'] ) ? $extras['sizes'] : [];
227
228 $new_sizes = [];
229
230 foreach ( $sizes as $size_name => $size ) {
231 if ( $process->is_size_next_gen( $size_name ) ) {
232 continue;
233 }
234
235 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
236
237 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
238 // This size has no WebP version.
239 continue;
240 }
241
242 if ( ! $this->is_webp($size['source_path']) ) {
243 $new_sizes[ $webp_size_name ] = [
244 'source_path' => $this->path_to_webp( $size['source_path'] ),
245 'url' => $this->path_to_webp( $size['url'] ),
246 'key' => $this->path_to_webp( $size['key'] ),
247 'width' => $size['width'],
248 'height' => $size['height'],
249 ];
250 }
251 }
252
253 if( $item['source_path'] ) {
254 if( !$process->is_size_next_gen( 'full' ) ) {
255 $webp_size_name = 'full' . $process::WEBP_SUFFIX;
256
257 if ( ! empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
258 if ( ! $this->is_webp($item['source_path']) ) {
259 $new_sizes[ $webp_size_name ] = [
260 'source_path' => $this->path_to_webp( $item['source_path'] ),
261 'url' => $this->path_to_webp( $item['url'] ),
262 'key' => $this->path_to_webp( $item['key'] ),
263 'width' => $extras['width'],
264 'height' => $extras['height'],
265 ];
266 }
267 }
268 }
269 }
270
271 if ( $new_sizes ) {
272 $extras['sizes'] = array_merge( $sizes, $new_sizes );
273 $item['extra'] = Utils::maybe_serialize( $extras );
274 }
275
276
277 return $item;
278 }
279
280
281 /**
282 * After restoring a media:
283 * - Save some data,
284 * - Upload the files to the CDN,
285 * - Maybe delete WebP files from the CDN.
286 */
287 public function maybe_send_media_to_cdn_after_restore( $process, $response, $files, $data ) {
288 if ( 'wp' !== $process->get_media()->get_context() ) {
289 return;
290 }
291
292 $attachment_id = $process->get_media()->get_id();
293
294 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
295 return;
296 }
297
298 if ( is_wp_error( $response ) && 'copy_failed' === $response->get_error_code() ) {
299 return;
300 }
301
302 $this->send_attachment_to_cdn( $attachment_id );
303
304 $extras = Item::instance()->get_extras( $attachment_id );
305 $sizes = isset( $extras['sizes'] ) && is_array( $extras['sizes'] ) ? $extras['sizes'] : [];
306 $webp_files = [];
307
308 if ( $files ) {
309 foreach ( $files as $size_name => $file ) {
310 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
311
312 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
313 continue;
314 }
315
316 if ( 0 === strpos( $file['mime-type'], 'image/' ) && ! $this->is_webp( $file['path'] ) ) {
317 if ( $sizes && isset( $sizes[ $webp_size_name ] ) ) {
318 $webp_files[] = $sizes[ $webp_size_name ]['key'];
319 }
320 }
321 }
322 }
323
324 if ( $webp_files ) {
325 Item::instance()->delete_cloud_files_by_keys( $webp_files );
326 }
327 }
328
329
330 /**
331 * Send media to cloud after Imagify optimization or restore.
332 *
333 * @param int $attachment_id
334 * @param bool $is_new_upload
335 * @return bool|\WP_Error
336 */
337 public function send_to_cdn( $attachment_id ) {
338
339 $attachment_id = (int) $attachment_id;
340
341 if ( ! $attachment_id ) {
342 return new \WP_Error(
343 'invalid_attachment',
344 __( 'Invalid attachment ID.', 'media-cloud-sync' )
345 );
346 }
347
348 // Tell Media Cloud Sync to reupload all media files.
349 // Media may be optimized and Imagify may have created new optimized files.
350 add_filter( 'wpmcs_do_reupload_media', '__return_true', 10, 3 );
351 do_action( 'wpmcs_do_update_attachment_metadata', false, $attachment_id );
352 remove_filter( 'wpmcs_do_reupload_media', '__return_true', 10, 3 );
353
354 return true;
355 }
356
357 /**
358 * Tell if an attachment is stored in cloud storage.
359 *
360 * @param int $attachment_id Attachment ID.
361 * @return bool
362 * @since 1.3.12
363 */
364 public function media_is_on_provider( $attachment_id ) {
365 return Item::instance()->is_available_from_provider( (int) $attachment_id );
366 }
367
368 /**
369 * Copy missing files from cloud storage to the server.
370 *
371 * @param int $attachment_id Attachment ID.
372 * @param array $file_paths Local file paths.
373 * @return bool|\WP_Error
374 * @since 1.3.12
375 */
376 public function copy_files_from_cdn( $attachment_id, $file_paths ) {
377 $attachment_id = (int) $attachment_id;
378
379 if ( ! Utils::is_service_enabled() ) {
380 return new \WP_Error(
381 'not_ready',
382 __( 'Cloud storage is not set up.', 'media-cloud-sync' )
383 );
384 }
385
386 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
387 return new \WP_Error(
388 'not_on_cdn',
389 __( 'This media could not be found in cloud storage.', 'media-cloud-sync' )
390 );
391 }
392
393 $filesystem = \Imagify_Filesystem::get_instance();
394 $errors = [];
395
396 foreach ( (array) $file_paths as $file_path ) {
397 if ( $filesystem->exists( $file_path ) ) {
398 continue;
399 }
400
401 Item::instance()->moveToServerBySourcePath( $attachment_id, $file_path );
402
403 if ( ! $filesystem->exists( $file_path ) ) {
404 $errors[] = $file_path;
405 }
406 }
407
408 if ( ! $errors ) {
409 return true;
410 }
411
412 $error_count = count( $errors );
413 $error_paths = array_map( [ $filesystem, 'make_path_relative' ], $errors );
414 $error_paths = wp_sprintf_l( '%l', $error_paths );
415
416 return new \WP_Error(
417 'not_retrieved',
418 sprintf(
419 /* translators: %s is a list of file paths. */
420 _n(
421 'The following file could not be retrieved from cloud storage: %s.',
422 'The following files could not be retrieved from cloud storage: %s.',
423 $error_count,
424 'media-cloud-sync'
425 ),
426 $error_paths
427 ),
428 $errors
429 );
430 }
431
432 /**
433 * Upload optimized files back to cloud storage when allowed.
434 *
435 * @param int $attachment_id Attachment ID.
436 * @param bool $is_new_upload Whether this is a new upload.
437 * @return bool|\WP_Error
438 * @since 1.3.12
439 */
440 public function send_attachment_to_cdn( $attachment_id, $is_new_upload = false ) {
441 $attachment_id = (int) $attachment_id;
442
443 if ( ! Utils::is_service_enabled() || ! $this->media_is_on_provider( $attachment_id ) ) {
444 return false;
445 }
446
447 return $this->send_to_cdn( $attachment_id );
448 }
449
450 /**
451 * Remove files from cloud storage.
452 *
453 * @param int $attachment_id Attachment ID.
454 * @param array $file_paths Local file paths or file names.
455 * @return bool|\WP_Error
456 * @since 1.3.12
457 */
458 public function remove_files_from_cdn( $attachment_id, $file_paths ) {
459 $attachment_id = (int) $attachment_id;
460
461 if ( ! Utils::is_service_enabled() ) {
462 return new \WP_Error(
463 'not_ready',
464 __( 'Cloud storage is not set up.', 'media-cloud-sync' )
465 );
466 }
467
468 if ( ! $this->media_is_on_provider( $attachment_id ) ) {
469 return new \WP_Error(
470 'not_on_cdn',
471 __( 'This media could not be found in cloud storage.', 'media-cloud-sync' )
472 );
473 }
474
475 $keys = [];
476
477 foreach ( (array) $file_paths as $file_path ) {
478 $key = $this->get_key_for_attachment_path( $attachment_id, $file_path );
479
480 if ( $key ) {
481 $keys[] = $key;
482 }
483 }
484
485 if ( ! $keys ) {
486 return true;
487 }
488
489 Item::instance()->delete_cloud_files_by_keys( array_unique( $keys ) );
490
491 return true;
492 }
493
494 /**
495 * Get a provider URL for an attachment file.
496 *
497 * @param int $attachment_id Attachment ID.
498 * @param string $file_name File name or empty for full size.
499 * @return string
500 * @since 1.3.12
501 */
502 public function get_provider_file_url( $attachment_id, $file_name = '' ) {
503 $attachment_id = (int) $attachment_id;
504
505 if ( ! $file_name ) {
506 $url = Item::instance()->get_url( $attachment_id, 'full', 'media_library' );
507
508 return $url ? $url : (string) wp_get_attachment_url( $attachment_id );
509 }
510
511 // Splicing $file_name onto the full URL's directory only works for a plain public
512 // URL — the full URL is a presigned request when the item is private, and its
513 // signature is tied to that exact key. Fail closed rather than hand back a URL
514 // that looks valid but isn't (garbled signature, or unsigned and unreadable).
515 if ( Item::instance()->get_field( $attachment_id, 'is_private', 'media_library' ) ) {
516 return '';
517 }
518
519 $filesystem = \Imagify_Filesystem::get_instance();
520 $full_url = $this->get_provider_file_url( $attachment_id );
521
522 return $filesystem->dir_path( $full_url ) . $file_name;
523 }
524
525 /**
526 * Get a local filesystem path for an attachment file.
527 *
528 * @param int $attachment_id Attachment ID.
529 * @param string $file_name File name, empty for full size, or `original`.
530 * @return string
531 * @since 1.3.12
532 */
533 public function get_local_file_path( $attachment_id, $file_name = '' ) {
534 $attachment_id = (int) $attachment_id;
535 $item = Item::instance()->get( $attachment_id, 'media_library' );
536 $upload_dir = wp_upload_dir();
537 $filesystem = \Imagify_Filesystem::get_instance();
538
539 if ( Utils::is_empty( $item ) ) {
540 return (string) get_attached_file( $attachment_id, true );
541 }
542
543 if ( ! $file_name ) {
544 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
545 }
546
547 if ( 'original' === $file_name ) {
548 if ( function_exists( 'wp_get_original_image_path' ) ) {
549 $original_path = wp_get_original_image_path( $attachment_id );
550
551 if ( $original_path ) {
552 return $original_path;
553 }
554 }
555
556 if ( ! empty( $item['original_source_path'] ) ) {
557 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['original_source_path'], '/' );
558 }
559
560 return trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
561 }
562
563 $full_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $item['source_path'], '/' );
564
565 return $filesystem->dir_path( $full_path ) . $file_name;
566 }
567
568 /**
569 * Resolve a cloud object key from a local path or file name.
570 *
571 * @param int $attachment_id Attachment ID.
572 * @param string $file_path Local file path or file name.
573 * @return string
574 * @since 1.3.12
575 */
576 protected function get_key_for_attachment_path( $attachment_id, $file_path ) {
577 $attachment_id = (int) $attachment_id;
578 $item = Item::instance()->get( $attachment_id, 'media_library' );
579
580 if ( Utils::is_empty( $item ) ) {
581 return '';
582 }
583
584 $filesystem = \Imagify_Filesystem::get_instance();
585 $source_path = Utils::get_attachment_source_path( $file_path );
586
587 if ( empty( $source_path ) ) {
588 $full_path = $this->get_local_file_path( $attachment_id );
589 $source_path = Utils::get_attachment_source_path(
590 $filesystem->dir_path( $full_path ) . wp_basename( $file_path )
591 );
592 }
593
594 if ( empty( $source_path ) ) {
595 return '';
596 }
597
598 if ( ! empty( $item['source_path'] ) && $item['source_path'] === $source_path ) {
599 return $item['key'] ?? '';
600 }
601
602 if ( ! empty( $item['original_source_path'] ) && $item['original_source_path'] === $source_path ) {
603 return $item['original_key'] ?? '';
604 }
605
606 $extras = Item::instance()->get_extras( $attachment_id, false, 'media_library' ) ?: [];
607
608 if ( ! empty( $extras['sizes'] ) ) {
609 foreach ( $extras['sizes'] as $size ) {
610 if (
611 ! empty( $size['source_path'] ) &&
612 $size['source_path'] === $source_path &&
613 ! empty( $size['key'] )
614 ) {
615 return $size['key'];
616 }
617 }
618 }
619
620 return '';
621 }
622
623
624
625 /**
626 * WebP images to display with a <picture> tag.
627 *
628 * @since 1.3.6
629 *
630 * @param array $data An array of data for this image.
631 * @return array
632 */
633 public function picture_tag_webp_image( $data ) {
634 global $wpdb;
635
636 if ( ! empty( $data['src']['webp_path'] ) ) {
637 // The file is local.
638 return $data;
639 }
640
641 if(!$this->is_provider_url( $data['src']['url'] )) {
642 // Not on Provider.
643 return $data;
644 }
645
646 $full_url = Utils::remove_size_from_filename( $data['src']['url'] );
647 $path = Utils::get_attachment_source_path( $full_url, 'key' );
648
649 $item = Item::instance()->get_items_by_paths( $path, true, true, 'key' );
650
651 if ( ! $item ) {
652 // Not in the database.
653 return $data;
654 }
655
656 $post_id = (int)$item['source_id'];
657 $imagify_data = get_post_meta( $post_id, '_imagify_data', true );
658
659 if ( ! $imagify_data ) {
660 // Not optimized.
661 return $data;
662 }
663
664 if( !function_exists( 'imagify_get_optimization_process_class_name' ) ) {
665 // Imagify is not fully loaded.
666 return $data;
667 }
668
669 $webp_size_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
670 $webp_size_name = 'full' . $webp_size_suffix;
671
672 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
673 // We have a WebP image.
674 $data['src']['webp_exists'] = true;
675 }
676
677 if ( empty( $data['srcset'] ) ) {
678 return $data;
679 }
680
681 $meta_data = get_post_meta( $post_id, '_wp_attachment_metadata', true );
682
683 if ( empty( $meta_data['sizes'] ) ) {
684 return $data;
685 }
686
687 // Ease the search for corresponding file name.
688 $size_files = [];
689
690 foreach ( $meta_data['sizes'] as $size_name => $size_data ) {
691 $size_files[ $size_data['file'] ] = $size_name;
692 }
693
694 // Look for a corresponding size name.
695 foreach ( $data['srcset'] as $i => $srcset_data ) {
696 if ( empty( $srcset_data['webp_url'] ) ) {
697 // Not a supported image format.
698 continue;
699 }
700 if ( ! empty( $srcset_data['webp_path'] ) ) {
701 // The file is local.
702 continue;
703 }
704
705 $match = $this->is_provider_url( $srcset_data['url'] );
706
707 if ( ! $match ) {
708 continue;
709 }
710
711 // Try with no subdirs.
712 $filename = basename( $srcset_data['url'] );
713
714 if ( isset( $size_files[ $filename ] ) ) {
715 $size_name = $size_files[ $filename ];
716 } else {
717 continue;
718 }
719
720 $webp_size_name = $size_name . $webp_size_suffix;
721
722 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
723 // We have a WebP image.
724 $data['srcset'][ $i ]['webp_exists'] = true;
725 }
726 }
727
728 return $data;
729 }
730
731
732
733 /**
734 * Add WebP format to the list of allowed mime types.
735 *
736 * @since 1.9
737 * @access public
738 * @see get_allowed_mime_types()
739 *
740 * @param array $mime_types A list of mime types.
741 * @return array
742 */
743 public function add_webp_support( $mime_types ) {
744 $mime_types['webp'] = 'image/webp';
745 return $mime_types;
746 }
747
748
749 /**
750 * The CDN to use for this media.
751 *
752 * @since 1.3.6
753 * @access public
754 */
755 public function register_cdn( $cdn, $media_id, $context ) {
756 if ( 'wp' !== $context->get_name() ) {
757 return $cdn;
758 }
759
760 if ( $cdn instanceof \Imagify\CDN\PushCDNInterface ) {
761 return $cdn;
762 }
763
764 if ( ! interface_exists( '\Imagify\CDN\PushCDNInterface' ) || ! class_exists( ImagifyPushCDN::class ) ) {
765 return $cdn;
766 }
767
768 if ( Item::instance()->is_available_from_provider( $media_id ) ) {
769 return new ImagifyPushCDN( (int) $media_id );
770 }
771
772 return $cdn;
773 }
774
775
776 /**
777 * Tell if the file is a WebP image.
778 * Rejects "path/to/.webp" files.
779 *
780 * @param string $path The path to the file.
781 * @return bool
782 */
783 public function is_webp($path) {
784 return preg_match( '@(?!^|/|\\\)\.webp$@i', $path );
785 }
786
787 /**
788 * Get the path to a WebP version of an image.
789 *
790 * @param string $path The path to the original image.
791 * @return string The path to the WebP version of the image.
792 */
793 public function path_to_webp( $path ) {
794 return $path . '.webp';
795 }
796
797 /**
798 * Checks if the given attachment ID is an image.
799 *
800 * @param int $attachment_id The attachment ID to check.
801 * @return bool True if the attachment is an image, false otherwise.
802 */
803 private function is_attachment_image($attachment_id) {
804 $post = get_post($attachment_id);
805 if (!$post) return false;
806
807 // Check the MIME type prefix directly from the database
808 return str_starts_with($post->post_mime_type, 'image/');
809 }
810
811
812 /**
813 * Tell if an URL is a Provider one.
814 */
815 public function is_provider_url( $url ) {
816 /**
817 * Allow short-circuiting via filter.
818 *
819 * @param null|array|bool $is
820 * @param string $url
821 */
822 $is = apply_filters( 'wpmcs_imagify_is_provider_url', null, $url );
823
824 if ( null !== $is ) {
825 return $is;
826 }
827
828 return Cdn::is_cdn_url( $url ) || Service::instance()->is_provider_url( $url );
829 }
830
831
832 /**
833 * Is installed?
834 *
835 * @return bool
836 */
837 public static function is_installed(): bool {
838 return defined( 'IMAGIFY_VERSION' ) || class_exists( '\Imagify', false );
839 }
840
841 /**
842 * Singleton instance
843 */
844 public static function instance() {
845 if (is_null(self::$instance)) {
846 self::$instance = new self();
847 }
848
849 return self::$instance;
850 }
851 }