PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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.php

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

658 lines 17.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 * @since 1.3.6
10 */
11 if ( ! class_exists( 'Imagify' ) ) {
12 return;
13 }
14
15 class Imagify {
16 /**
17 * The singleton instance.
18 *
19 * @since 1.6.6
20 * @var Imagify|null
21 */
22 protected static $instance = null;
23
24 /**
25 * The class constructor.
26 *
27 * @since 1.6.6
28 */
29 protected function __construct() {
30 $this->init();
31 }
32
33 /**
34 * Launch the hooks.
35 *
36 * @since 1.3.6
37 */
38 public function init() {
39 /**
40 * WebP images to display with a <picture> tag.
41 */
42 add_filter( 'imagify_webp_picture_process_image', [ $this, 'picture_tag_webp_image' ] );
43
44 /**
45 * Register CDN.
46 */
47 add_filter( 'imagify_cdn', [ $this, 'register_cdn' ], 8, 3 );
48
49 /**
50 * Optimization process.
51 */
52
53 // Return Local URL even if the file is on the cloud
54 add_action( 'imagify_before_optimize', [ $this, 'prepare_local_url' ], 8, 4 );
55
56 // Copy file from CDN to server before optimization if not exist on server
57 add_filter( 'imagify_before_optimize_size', [ $this, 'maybe_copy_file_from_cdn_before_optimization' ], 8, 6 );
58
59 // Add webp files to the CDN after optimization if webp is exist and remove them from server if enabled
60 add_filter( 'wpmcs_pre_update_item_additional_files_to_remove_from_server', [ $this, 'send_additinal_files_to_cdn' ], 8, 5 );
61
62 // Send media to CDN after optimization
63 add_action( 'imagify_after_optimize', [ $this, 'maybe_send_media_to_cdn_after_optimization' ], 8, 2 );
64
65 // Revert Local URL file after optimization
66 add_action( 'imagify_after_optimize', [ $this, 'revert_local_url' ], 8, 2 );
67
68
69 /**
70 * Restoration process.
71 */
72 add_action( 'imagify_after_restore_media', [ $this, 'maybe_send_media_to_cdn_after_restore' ], 8, 4 );
73
74 /**
75 * WebP support.
76 */
77 add_filter( 'wpmcs_get_item', [ $this, 'add_webp_images_to_attachment' ], 8, 3 );
78 add_filter( 'mime_types', [ $this, 'add_webp_support' ] );
79 }
80
81 /**
82 * This function is called during the optimization process and is responsible for preparing the local URL.
83 *
84 * @param array $items The array of items.
85 * @param \WP_Error|bool $wp_error The WP_Error object if an error occurs, otherwise false.
86 * @param string $process The current process.
87 * @param array $item The item being processed.
88 *
89 * @return array The array of items.
90 */
91 public function prepare_local_url( $items, $wp_error, $process, $item ) {
92 add_filter('wpmcs_get_attached_file', [ $this, 'return_local_url'], 10, 4);
93 return $items;
94 }
95
96 /**
97 * Return the local file path for optimization.
98 *
99 * @param string $url The URL of the file.
100 * @param string $file The local file path.
101 * @param int $attachment_id The attachment ID.
102 * @param array $item The item being processed.
103 *
104 * @return string The local file path.
105 */
106 public function return_local_url( $url, $file, $attachment_id, $item ) {
107 return $file;
108 }
109
110
111 /**
112 * Revert the local URL back to the provider URL after optimization.
113 *
114 * This function is called after the optimization process and is responsible for reverting the local URL back to the provider URL.
115 *
116 * @param string $process The current process.
117 * @param array $item The item being processed.
118 *
119 * @return array The array of items.
120 */
121 public function revert_local_url( $process, $item ) {
122 remove_filter( 'wpmcs_get_attached_file', [ $this, 'return_local_url'], 10, 4 );
123 }
124
125
126 /**
127 * Maybe copy the file from CDN before optimization.
128 *
129 * If the file is on the CDN but not on the server, this function will copy the file from the CDN to the server.
130 *
131 * @param array $response The response array.
132 * @param string $process The current process.
133 * @param string $file The file to copy.
134 * @param string $thumb_size The thumbnail size.
135 * @param int $optimization_level The optimization level.
136 * @param bool $webp Whether to use WebP images.
137 *
138 * @return array The response array.
139 */
140 public function maybe_copy_file_from_cdn_before_optimization( $response, $process, $file, $thumb_size, $optimization_level, $webp ) {
141 if ( is_wp_error( $response ) || 'wp' !== $process->get_media()->get_context() ) {
142 return $response;
143 }
144
145 $attachment_id = $process->get_media()->get_id();
146
147 if ( ! Item::instance()->is_available_from_provider( $attachment_id ) ) {
148 return $response;
149 }
150
151 Item::instance()->moveToServerBySourcePath(
152 $attachment_id,
153 $file->get_path()
154 );
155
156 return $response;
157 }
158
159
160
161 /**
162 * After performing a media optimization:
163 * - Save some data,
164 * - Upload the files to the CDN,
165 * - Maybe delete them from the server.
166 *
167 * @since 1.9
168 * @access public
169 *
170 * @param ProcessInterface $process The optimization process.
171 * @param array $item The item being processed.
172 */
173 public function maybe_send_media_to_cdn_after_optimization( $process, $item ) {
174 if ( 'wp' !== $process->get_media()->get_context() ) {
175 return;
176 }
177
178 $attachment_id = $process->get_media()->get_id();
179
180 if ( ! Item::instance()->is_available_from_provider( $attachment_id ) ) {
181 return;
182 }
183
184 $this->send_to_cdn( $attachment_id );
185 }
186
187
188 /**
189 * Send additinal files to CDN
190 *
191 * @since 1.9
192 * @access public
193 *
194 * @param array $files_to_remove
195 * @param int $source_id
196 * @param array $new_item
197 * @param array $old_item
198 * @param string $source_type
199 */
200 public function send_additinal_files_to_cdn( $files_to_remove, $source_id, $new_item, $old_item = [], $source_type = 'media_library' ) {
201 $upload_dir = wp_get_upload_dir();
202 $extras = isset( $new_item['extra'] ) ? Utils::maybe_unserialize( $new_item['extra'] ) : [];
203
204 if ( empty( $extras['sizes'] ) || ! is_array( $extras['sizes'] ) ) {
205 return;
206 }
207
208 $prefix = isset($extras['prefix']) ? $extras['prefix'] : '';
209 $files_to_upload = [];
210
211 // Add all sizes.
212 if( isset( $extras['sizes'] ) && !empty( $extras['sizes'] ) && is_array( $extras['sizes'] ) ) {
213 foreach ( $extras['sizes'] as $size_name => $size ) {
214 if( !isset( $size['source_path'] ) || empty( $size['source_path'] ) ) {
215 continue;
216 }
217
218 $webp_size_source_path = $this->path_to_webp( $size['source_path'] );
219 $webp_size_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_size_source_path, '/' );
220 if ( file_exists( $webp_size_path ) ) {
221 $files_to_upload[$webp_size_source_path] = $webp_size_path;
222 }
223 }
224 }
225
226 if ( ! empty( $new_item['original_source_path'] ) ) {
227 if ( isset( $new_item['original_source_path'] ) && !empty( $new_item['original_source_path'] ) ) {
228 $webp_original_source_path = $this->path_to_webp( $new_item['original_source_path'] );
229 $webp_original_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_original_source_path, '/' );
230 if ( file_exists( $webp_original_path ) ) {
231 $files_to_upload[$webp_original_source_path] = $webp_original_path;
232 }
233 }
234 }
235
236 if ( ! empty( $new_item['source_path'] ) ) {
237 if ( isset( $new_item['source_path'] ) && !empty( $new_item['source_path'] ) ) {
238 $webp_full_source_path = $this->path_to_webp( $new_item['source_path'] );
239 $webp_full_path = trailingslashit( $upload_dir['basedir'] ) . ltrim( $webp_full_source_path, '/' );
240 if ( file_exists( $webp_full_path ) ) {
241 $files_to_upload[$webp_full_source_path] = $webp_full_path;
242 }
243 }
244 }
245
246 if ( $files_to_upload ) {
247 foreach ( $files_to_upload as $relative_source_path => $path ) {
248 $uploaded = Service::instance()->uploadSingle( $path, $relative_source_path, $prefix );
249 if ( $uploaded['success'] ) {
250 $files_to_remove[] = $path;
251 }
252 }
253 }
254
255 return $files_to_remove;
256 }
257
258
259
260 /**
261 * Add the WebP files to the list of files that the CDN must handle.
262 * @since 1.3.6
263 */
264 public function add_webp_images_to_attachment( $item, $attachment_id, $source_type = 'media_library' ) {
265 if ( ! $item ) {
266 return $item;
267 }
268
269 $process = imagify_get_optimization_process( $attachment_id, 'wp' );
270
271 if ( ! $process->is_valid() ) {
272 return $item;
273 }
274
275 // Make sure it's an image.
276 if ( ! $this->is_attachment_image( $attachment_id ) ) {
277 return $item;
278 }
279
280 // Use the optimization data (the files may not be on the server).
281 $data = $process->get_data()->get_optimization_data();
282
283 if ( empty( $data['sizes'] ) ) {
284 return $item;
285 }
286
287 $extras = isset( $item['extra'] ) ? Utils::maybe_unserialize( $item['extra'] ) : [];
288
289 $sizes = isset( $extras['sizes'] ) && ! empty( $extras['sizes'] ) ? $extras['sizes'] : [];
290
291 $new_sizes = [];
292
293 foreach ( $sizes as $size_name => $size ) {
294 if ( $process->is_size_next_gen( $size_name ) ) {
295 continue;
296 }
297
298 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
299
300 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
301 // This size has no WebP version.
302 continue;
303 }
304
305 if ( ! $this->is_webp($size['source_path']) ) {
306 $new_sizes[ $webp_size_name ] = [
307 'source_path' => $this->path_to_webp( $size['source_path'] ),
308 'url' => $this->path_to_webp( $size['url'] ),
309 'key' => $this->path_to_webp( $size['key'] ),
310 'width' => $size['width'],
311 'height' => $size['height'],
312 ];
313 }
314 }
315
316 if( $item['source_path'] ) {
317 if( !$process->is_size_next_gen( 'full' ) ) {
318 $webp_size_name = 'full' . $process::WEBP_SUFFIX;
319
320 if ( ! empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
321 if ( ! $this->is_webp($item['source_path']) ) {
322 $new_sizes[ $webp_size_name ] = [
323 'source_path' => $this->path_to_webp( $item['source_path'] ),
324 'url' => $this->path_to_webp( $item['url'] ),
325 'key' => $this->path_to_webp( $item['key'] ),
326 'width' => $extras['width'],
327 'height' => $extras['height'],
328 ];
329 }
330 }
331 }
332 }
333
334 if ( $new_sizes ) {
335 $extras['sizes'] = array_merge( $extras['sizes'], $new_sizes );
336 $item['extra'] = Utils::maybe_serialize( $extras );
337 }
338
339
340 return $item;
341 }
342
343
344 /**
345 * After restoring a media:
346 * - Save some data,
347 * - Upload the files to the CDN,
348 * - Maybe delete WebP files from the CDN.
349 */
350 public function maybe_send_media_to_cdn_after_restore( $process, $response, $files, $data ) {
351 if ( 'wp' !== $process->get_media()->get_context() ) {
352 return;
353 }
354 $attachment_id = $process->get_media()->get_id();
355
356 if ( ! Item::instance()->is_available_from_provider( $attachment_id ) ) {
357 return;
358 }
359
360 if ( is_wp_error( $response ) ) {
361 $error_code = $response->get_error_code();
362
363 if ( 'copy_failed' === $error_code ) {
364 // No files have been restored.
365 return;
366 }
367
368 // No thumbnails left?
369 }
370
371 $extras = Item::instance()->get_extras( $attachment_id );
372 $sizes = isset( $extras['sizes'] ) && is_array( $extras['sizes'] ) ? $extras['sizes'] : [];
373
374 // Upload the files to the CDN.
375 $this->send_to_cdn( $attachment_id );
376
377 // Remove WebP files from CDN.
378 $webp_files = [];
379
380 if ( $files ) {
381 // Get the paths to the WebP files.
382 foreach ( $files as $size_name => $file ) {
383 $webp_size_name = $size_name . $process::WEBP_SUFFIX;
384
385 if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
386 // This size has no WebP version.
387 continue;
388 }
389
390 if ( 0 === strpos( $file['mime-type'], 'image/' ) ) {
391 if ( ! $this->is_webp( $file['path'] ) ) {
392 if($sizes && isset( $sizes[ $webp_size_name ] ) ) {
393 $webp_files[] = $sizes[ $webp_size_name ]['key'];
394 }
395 }
396 }
397 }
398 }
399
400 if ( $webp_files ) {
401 Item::instance()->delete_cloud_files_by_keys( $webp_files );
402 }
403 }
404
405
406 /**
407 * Send media to cloud after Imagify optimization or restore.
408 *
409 *
410 * @param int $attachment_id
411 * @param bool $is_new_upload
412 * @return bool|\WP_Error
413 */
414 public function send_to_cdn( $attachment_id ) {
415
416 $attachment_id = (int) $attachment_id;
417
418 if ( ! $attachment_id ) {
419 return new \WP_Error(
420 'invalid_attachment',
421 __( 'Invalid attachment ID.', 'media-cloud-sync' )
422 );
423 }
424
425 // Tell Media Cloud Sync to reupload all media files.
426 // Media may be optimized and Imagify may have created new optimized files.
427 add_filter( 'wpmcs_do_reupload_media', '__return_true', 10, 3 );
428 do_action( 'wpmcs_do_update_attachment_metadata', false, $attachment_id );
429 remove_filter( 'wpmcs_do_reupload_media', '__return_true', 10 );
430
431 return true;
432 }
433
434
435
436 /**
437 * WebP images to display with a <picture> tag.
438 *
439 * @since 1.3.6
440 *
441 * @param array $data An array of data for this image.
442 * @return array
443 */
444 public function picture_tag_webp_image( $data ) {
445 global $wpdb;
446
447 if ( ! empty( $data['src']['webp_path'] ) ) {
448 // The file is local.
449 return $data;
450 }
451
452 if(!$this->is_provider_url( $data['src']['url'] )) {
453 // Not on Provider.
454 return $data;
455 }
456
457 $full_url = Utils::remove_size_from_filename( $data['src']['url'] );
458 $path = Utils::get_attachment_source_path( $full_url, 'key' );
459
460 $item = Item::instance()->get_items_by_paths( $path, true, true, 'key' );
461
462 if ( ! $item ) {
463 // Not in the database.
464 return $data;
465 }
466
467 $post_id = (int)$item['source_id'];
468 $imagify_data = get_post_meta( $post_id, '_imagify_data', true );
469
470 if ( ! $imagify_data ) {
471 // Not optimized.
472 return $data;
473 }
474
475 if( !function_exists( 'imagify_get_optimization_process_class_name' ) ) {
476 // Imagify is not fully loaded.
477 return $data;
478 }
479
480 $webp_size_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
481 $webp_size_name = 'full' . $webp_size_suffix;
482
483 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
484 // We have a WebP image.
485 $data['src']['webp_exists'] = true;
486 }
487
488 if ( empty( $data['srcset'] ) ) {
489 return $data;
490 }
491
492 $meta_data = get_post_meta( $post_id, '_wp_attachment_metadata', true );
493
494 if ( empty( $meta_data['sizes'] ) ) {
495 return $data;
496 }
497
498 // Ease the search for corresponding file name.
499 $size_files = [];
500
501 foreach ( $meta_data['sizes'] as $size_name => $size_data ) {
502 $size_files[ $size_data['file'] ] = $size_name;
503 }
504
505 // Look for a corresponding size name.
506 foreach ( $data['srcset'] as $i => $srcset_data ) {
507 if ( empty( $srcset_data['webp_url'] ) ) {
508 // Not a supported image format.
509 continue;
510 }
511 if ( ! empty( $srcset_data['webp_path'] ) ) {
512 // The file is local.
513 continue;
514 }
515
516 $match = $this->is_provider_url( $srcset_data['url'] );
517
518 if ( ! $match ) {
519 continue;
520 }
521
522 // Try with no subdirs.
523 $filename = basename( $srcset_data['url'] );
524
525 if ( isset( $size_files[ $filename ] ) ) {
526 $size_name = $size_files[ $filename ];
527 } else {
528 continue;
529 }
530
531 $webp_size_name = $size_name . $webp_size_suffix;
532
533 if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
534 // We have a WebP image.
535 $data['srcset'][ $i ]['webp_exists'] = true;
536 }
537 }
538
539 return $data;
540 }
541
542
543
544 /**
545 * Add WebP format to the list of allowed mime types.
546 *
547 * @since 1.9
548 * @access public
549 * @see get_allowed_mime_types()
550 *
551 * @param array $mime_types A list of mime types.
552 * @return array
553 */
554 public function add_webp_support( $mime_types ) {
555 $mime_types['webp'] = 'image/webp';
556 return $mime_types;
557 }
558
559
560 /**
561 * The CDN to use for this media.
562 *
563 * @since 1.3.6
564 * @access public
565 */
566 public function register_cdn( $cdn, $media_id, $context ) {
567 if ( 'wp' !== $context->get_name() ) {
568 return $cdn;
569 }
570 if ( Item::instance()->is_available_from_provider( $media_id ) ) {
571 // Any truthy value tells Imagify a CDN exists
572 return true;
573 }
574
575 return $cdn;
576 }
577
578
579 /**
580 * Tell if the file is a WebP image.
581 * Rejects "path/to/.webp" files.
582 *
583 * @return bool
584 */
585 public function is_webp($path) {
586 return preg_match( '@(?!^|/|\\\)\.webp$@i', $path );
587 }
588
589 /**
590 * Get the path to a WebP version of an image.
591 *
592 * @param string $path The path to the original image.
593 * @return string The path to the WebP version of the image.
594 */
595 public function path_to_webp( $path ) {
596 return $path . '.webp';
597 }
598
599 /**
600 * Checks if the given attachment ID is an image.
601 *
602 * @param int $attachment_id The attachment ID to check.
603 * @return bool True if the attachment is an image, false otherwise.
604 */
605 private function is_attachment_image($attachment_id) {
606 $post = get_post($attachment_id);
607 if (!$post) return false;
608
609 // Check the MIME type prefix directly from the database
610 return str_starts_with($post->post_mime_type, 'image/');
611 }
612
613
614 /**
615 * Tell if an URL is a Provider one.
616 */
617 public function is_provider_url( $url ) {
618 /**
619 * Allow short-circuiting via filter.
620 *
621 * @param null|array|bool $is
622 * @param string $url
623 */
624 $is = apply_filters( 'wpmcs_imagify_is_provider_url', null, $url );
625
626 if ( null !== $is ) {
627 return $is;
628 }
629
630 return CDN::is_cdn_url( $url ) || Service::instance()->is_provider_url( $url );
631 }
632
633
634 /**
635 * Is installed?
636 *
637 * @return bool
638 */
639 public static function is_installed(): bool {
640 if ( defined( 'IMAGIFY_VERSION' ) ) {
641 return true;
642 }
643
644 return false;
645 }
646
647 /**
648 * Singleton instance
649 */
650 public static function instance() {
651 if (is_null(self::$instance)) {
652 self::$instance = new self();
653 }
654
655 return self::$instance;
656 }
657 }
658