PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / classes / Optimization / File.php

File.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.3.1, at classes/Optimization/File.php

998 lines 26.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Optimization;
3
4 use Imagify_Requirements;
5 use WP_Error;
6
7 /**
8 * A generic optimization class focused on the file itself.
9 *
10 * @since 1.9
11 * @author Grégory Viguier
12 */
13 class File {
14
15 /**
16 * Absolute path to the file.
17 *
18 * @var string
19 * @since 1.9
20 * @author Grégory Viguier
21 */
22 protected $path;
23
24 /**
25 * Tell if the file is an image.
26 *
27 * @var bool
28 * @since 1.9
29 * @see $this->is_image()
30 * @author Grégory Viguier
31 */
32 protected $is_image;
33
34 /**
35 * Store the file mime type + file extension (if the file is supported).
36 *
37 * @var array
38 * @since 1.9
39 * @see $this->get_file_type()
40 * @author Grégory Viguier
41 */
42 protected $file_type;
43
44 /**
45 * Filesystem object.
46 *
47 * @var \Imagify_Filesystem
48 * @since 1.9
49 * @author Grégory Viguier
50 */
51 protected $filesystem;
52
53 /**
54 * The editor instance used to resize the file.
55 *
56 * @var \WP_Image_Editor_Imagick|\WP_Image_Editor_GD|WP_Error.
57 * @since 1.9
58 * @author Grégory Viguier
59 */
60 protected $editor;
61
62 /**
63 * Used to cache the plugin’s options.
64 *
65 * @var array
66 * @since 1.9
67 * @author Grégory Viguier
68 */
69 protected $options = [];
70
71 /**
72 * The constructor.
73 *
74 * @since 1.9
75 * @author Grégory Viguier
76 *
77 * @param string $file_path Absolute path to the file.
78 */
79 public function __construct( $file_path ) {
80 $this->path = $file_path;
81 $this->filesystem = \Imagify_Filesystem::get_instance();
82 }
83
84 /**
85 * Tell if the file is valid.
86 *
87 * @since 1.9
88 * @author Grégory Viguier
89 *
90 * @return bool
91 */
92 public function is_valid() {
93 return (bool) $this->path;
94 }
95
96 /**
97 * Tell if the file can be processed.
98 *
99 * @since 1.9
100 * @author Grégory Viguier
101 *
102 * @return bool|WP_Error
103 */
104 public function can_be_processed() {
105 if ( ! $this->path ) {
106 return new \WP_Error( 'empty_path', __( 'File path is empty.', 'imagify' ) );
107 }
108
109 if ( ! empty( $this->filesystem->errors->errors ) ) {
110 return new \WP_Error( 'filesystem_error', __( 'Filesystem error.', 'imagify' ), $this->filesystem->errors );
111 }
112
113 if ( ! $this->filesystem->exists( $this->path ) ) {
114 return new \WP_Error(
115 'not_exists',
116 sprintf(
117 /* translators: %s is a file path. */
118 __( 'The file %s does not seem to exist.', 'imagify' ),
119 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
120 )
121 );
122 }
123
124 if ( ! $this->filesystem->is_file( $this->path ) ) {
125 return new \WP_Error(
126 'not_a_file',
127 sprintf(
128 /* translators: %s is a file path. */
129 __( 'This does not seem to be a file: %s.', 'imagify' ),
130 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
131 )
132 );
133 }
134
135 if ( ! $this->filesystem->is_writable( $this->path ) ) {
136 return new \WP_Error(
137 'not_writable',
138 sprintf(
139 /* translators: %s is a file path. */
140 __( 'The file %s does not seem to be writable.', 'imagify' ),
141 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
142 )
143 );
144 }
145
146 $parent_folder = $this->filesystem->dir_path( $this->path );
147
148 if ( ! $this->filesystem->is_writable( $parent_folder ) ) {
149 return new \WP_Error(
150 'folder_not_writable',
151 sprintf(
152 /* translators: %s is a file path. */
153 __( 'The folder %s does not seem to be writable.', 'imagify' ),
154 '<code>' . esc_html( $this->filesystem->make_path_relative( $parent_folder ) ) . '</code>'
155 )
156 );
157 }
158
159 return true;
160 }
161
162
163 /** ----------------------------------------------------------------------------------------- */
164 /** EDITION ================================================================================= */
165 /** ----------------------------------------------------------------------------------------- */
166
167 /**
168 * Resize (and rotate) an image if it is bigger than the maximum width provided.
169 *
170 * @since 1.9
171 * @author Grégory Viguier
172 * @author Remy Perona
173 *
174 * @param array $dimensions {
175 * Array of image dimensions.
176 *
177 * @type int $width The image width.
178 * @type int $height The image height.
179 * }
180 * @param int $max_width Maximum width to resize to.
181 * @return string|WP_Error Path the the resized image. A WP_Error object on failure.
182 */
183 public function resize( $dimensions = [], $max_width = 0 ) {
184 $can_be_processed = $this->can_be_processed();
185
186 if ( is_wp_error( $can_be_processed ) ) {
187 return $can_be_processed;
188 }
189
190 if ( ! $max_width ) {
191 return new \WP_Error(
192 'no_resizing_threshold',
193 __( 'No threshold provided for resizing.', 'imagify' )
194 );
195 }
196
197 if ( ! $this->is_image() ) {
198 return new \WP_Error(
199 'not_an_image',
200 sprintf(
201 /* translators: %s is a file path. */
202 __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
203 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
204 )
205 );
206 }
207
208 $editor = $this->get_editor();
209
210 if ( is_wp_error( $editor ) ) {
211 return $editor;
212 }
213
214 // Try to correct the auto-rotation if the info is available.
215 if ( $this->filesystem->can_get_exif() && 'image/jpeg' === $this->get_mime_type() ) {
216 $exif = $this->filesystem->get_image_exif( $this->path );
217 $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1;
218
219 $this->rotate_editor_by_exif_orientation( $editor, $orientation );
220 }
221
222 if ( ! $dimensions ) {
223 $dimensions = $this->get_dimensions();
224 }
225
226 // Prevent removal of the exif data when resizing (only works with Imagick).
227 add_filter( 'image_strip_meta', '__return_false', 789 );
228
229 // Resize.
230 $new_sizes = wp_constrain_dimensions( $dimensions['width'], $dimensions['height'], $max_width );
231 $resized = $editor->resize( $new_sizes[0], $new_sizes[1], false );
232
233 // Remove the filter when we're done to prevent any conflict.
234 remove_filter( 'image_strip_meta', '__return_false', 789 );
235
236 if ( is_wp_error( $resized ) ) {
237 return $resized;
238 }
239
240 $resized_image_path = $editor->generate_filename( 'imagifyresized' );
241 $resized_image_saved = $editor->save( $resized_image_path );
242
243 if ( is_wp_error( $resized_image_saved ) ) {
244 return $resized_image_saved;
245 }
246
247 return $resized_image_path;
248 }
249
250 /**
251 * Rotate/flip an image editor instance according to a JPEG EXIF "Orientation" value.
252 *
253 * @since 2.3.1
254 *
255 * @param \WP_Image_Editor_Imagick|\WP_Image_Editor_GD $editor The image editor instance.
256 * @param int $orientation The EXIF "Orientation" value.
257 * @return void
258 */
259 protected function rotate_editor_by_exif_orientation( $editor, $orientation ) {
260 switch ( $orientation ) {
261 case 2:
262 // Flip horizontally.
263 $editor->flip( true, false );
264 break;
265 case 3:
266 // Rotate 180 degrees or flip horizontally and vertically.
267 // Flipping seems faster/uses less resources.
268 $editor->flip( true, true );
269 break;
270 case 4:
271 // Flip vertically.
272 $editor->flip( false, true );
273 break;
274 case 5:
275 // Rotate 90 degrees counter-clockwise and flip vertically.
276 $result = $editor->rotate( 90 );
277
278 if ( ! is_wp_error( $result ) ) {
279 $editor->flip( false, true );
280 }
281 break;
282 case 6:
283 // Rotate 90 degrees clockwise (270 counter-clockwise).
284 $editor->rotate( 270 );
285 break;
286 case 7:
287 // Rotate 90 degrees counter-clockwise and flip horizontally.
288 $result = $editor->rotate( 90 );
289
290 if ( ! is_wp_error( $result ) ) {
291 $editor->flip( true, false );
292 }
293 break;
294 case 8:
295 // Rotate 90 degrees counter-clockwise.
296 $editor->rotate( 90 );
297 break;
298 }
299 }
300
301 /**
302 * Correct the EXIF orientation of the current file, in place, if needed.
303 *
304 * WordPress auto-rotates JPEGs with a non-default EXIF orientation on upload (the resulting,
305 * rotated file has its orientation reset to 1), but Imagify's backup keeps a copy of the
306 * original, un-rotated file. When a temporary working copy is created from that backup (for
307 * example to generate a Next-Gen version of an already-optimized "full" size), the copy must
308 * be rotated the same way WordPress would have rotated it, otherwise the resulting file (WebP,
309 * AVIF...) ends up with the wrong orientation.
310 *
311 * This method must only ever be called on a disposable working copy: it saves the rotated
312 * image over $this->path, and must never be used on the actual backup file.
313 *
314 * @since 2.3.1
315 *
316 * @return bool|WP_Error True if the file was rotated and saved. False if no rotation was
317 * needed (or EXIF data isn't available/readable). A WP_Error object on
318 * failure.
319 */
320 public function maybe_correct_exif_orientation() {
321 if ( ! $this->filesystem->can_get_exif() || 'image/jpeg' !== $this->get_mime_type() ) {
322 return false;
323 }
324
325 $exif = $this->filesystem->get_image_exif( $this->path );
326 $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1;
327
328 if ( 1 === $orientation ) {
329 // Nothing to correct: either there is no orientation data, or the file is already
330 // upright (this also prevents rotating the same file twice).
331 return false;
332 }
333
334 $editor = $this->get_editor();
335
336 if ( is_wp_error( $editor ) ) {
337 return $editor;
338 }
339
340 $this->rotate_editor_by_exif_orientation( $editor, $orientation );
341
342 $saved = $editor->save( $this->path );
343
344 if ( is_wp_error( $saved ) ) {
345 return $saved;
346 }
347
348 // The file on disk changed: reset the cached data related to it.
349 $this->file_type = null;
350 $this->editor = null;
351
352 return true;
353 }
354
355 /**
356 * Create a thumbnail.
357 * Warning: If the destination file already exists, it will be overwritten.
358 *
359 * @since 1.9
360 * @author Grégory Viguier
361 *
362 * @param array $destination {
363 * The thumbnail data.
364 *
365 * @type string $path Path to the destination file.
366 * @type int $width The image width.
367 * @type int $height The image height.
368 * @type bool $crop True to crop, false to resize.
369 * @type bool $adjust_filename True to adjust the file name like what `$editor->multi_resize()` returns, like WP default behavior (default). False to prevent it, and use the file name from $path instead.
370 * }
371 * @return bool|array|WP_Error {
372 * A WP_Error object on error. True if the file exists.
373 * An array of thumbnail data if the file has just been created:
374 *
375 * @type string $file File name.
376 * @type int $width The image width.
377 * @type int $height The image height.
378 * @type string $mime-type The mime type.
379 * }
380 */
381 public function create_thumbnail( $destination ) {
382 $can_be_processed = $this->can_be_processed();
383
384 if ( is_wp_error( $can_be_processed ) ) {
385 return $can_be_processed;
386 }
387
388 if ( ! $this->is_image() ) {
389 return new WP_Error(
390 'not_an_image',
391 sprintf(
392 /* translators: %s is a file path. */
393 __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
394 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
395 )
396 );
397 }
398
399 $editor = $this->get_editor();
400
401 if ( is_wp_error( $editor ) ) {
402 return $editor;
403 }
404
405 // Create the file.
406 $result = $editor->multi_resize( [ $destination ] );
407
408 if ( ! $result ) {
409 return new WP_Error( 'image_resize_error', __( 'The thumbnail could not be created.', 'imagify' ) );
410 }
411
412 $result = reset( $result );
413
414 $filename = $result['file'];
415 $source_thumb_path = $this->filesystem->dir_path( $this->path ) . $filename;
416
417 if ( ! isset( $destination['adjust_filename'] ) || $destination['adjust_filename'] ) {
418 // The file name can change from what we expected (1px wider, etc), let's use the resulting data to move the file to the right place.
419 $destination_thumb_path = $this->filesystem->dir_path( $destination['path'] ) . $filename;
420 } else {
421 // Respect what is set in $path.
422 $destination_thumb_path = $destination['path'];
423 $result['file'] = $this->filesystem->file_name( $destination['path'] );
424 }
425
426 if ( $source_thumb_path === $destination_thumb_path ) {
427 return $result;
428 }
429
430 $moved = $this->filesystem->move( $source_thumb_path, $destination_thumb_path, true );
431
432 if ( ! $moved ) {
433 return new WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
434 }
435
436 return $result;
437 }
438
439 /**
440 * Backup a file.
441 *
442 * @since 1.9
443 * @since 1.9.8 Added $backup_source argument.
444 * @author Grégory Viguier
445 *
446 * @param string $backup_path The backup path.
447 * @param string $backup_source Path to the file to backup. This is useful in WP 5.3+ when we want to optimize the full size: in that case we need to backup the original file.
448 * @return bool|WP_Error True on success. False if the backup option is disabled. A WP_Error object on failure.
449 */
450 public function backup( $backup_path = null, $backup_source = null ) {
451 $can_be_processed = $this->can_be_processed();
452
453 if ( is_wp_error( $can_be_processed ) ) {
454 return $can_be_processed;
455 }
456
457 // Make sure the backups directory has no errors.
458 if ( ! $backup_path ) {
459 return new \WP_Error( 'wp_upload_error', __( 'Error while retrieving the backups directory path.', 'imagify' ) );
460 }
461
462 // Create sub-directories.
463 $created = $this->filesystem->make_dir( $this->filesystem->dir_path( $backup_path ) );
464
465 if ( ! $created ) {
466 return new \WP_Error( 'backup_dir_not_writable', __( 'The backup directory is not writable.', 'imagify' ) );
467 }
468
469 $path = $backup_source && $this->filesystem->exists( $backup_source ) ? $backup_source : $this->path;
470
471 /**
472 * Allow to overwrite the backup file if it already exists.
473 *
474 * @since 1.6.9
475 * @author Grégory Viguier
476 *
477 * @param bool $overwrite Whether to overwrite the backup file.
478 * @param string $path The file path.
479 * @param string $backup_path The backup path.
480 */
481 $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $path, $backup_path );
482
483 // Copy the file.
484 $this->filesystem->copy( $path, $backup_path, $overwrite, FS_CHMOD_FILE );
485
486 // Make sure the backup copy exists.
487 if ( ! $this->filesystem->exists( $backup_path ) ) {
488 return new \WP_Error(
489 'backup_doesnt_exist',
490 __( 'The file could not be saved.', 'imagify' ),
491 [
492 'file_path' => $this->filesystem->make_path_relative( $path ),
493 'backup_path' => $this->filesystem->make_path_relative( $backup_path ),
494 ]
495 );
496 }
497
498 // Check if a '-scaled' version of the image exists.
499 $scaled_path = preg_replace( '/(\.)([^\.]+)$/', '-scaled.$2', $backup_source );
500 if ( $this->filesystem->exists( $scaled_path ) ) {
501 // Create a backup path for the scaled image.
502 $scaled_backup_path = preg_replace( '/(\.)([^\.]+)$/', '-scaled.$2', $backup_path );
503 // Copy the '-scaled' version to the backup.
504 $this->filesystem->copy( $scaled_path, $scaled_backup_path, $overwrite, FS_CHMOD_FILE );
505
506 if ( ! $this->filesystem->exists( $scaled_backup_path ) ) {
507 return new \WP_Error(
508 'backup_doesnt_exist',
509 __( 'The file could not be saved.', 'imagify' ),
510 [
511 'file_path' => $this->filesystem->make_path_relative( $scaled_path ),
512 'backup_path' => $this->filesystem->make_path_relative( $scaled_backup_path ),
513 ]
514 );
515 }
516 }
517
518 return true;
519 }
520
521 /**
522 * Optimize a file with Imagify.
523 *
524 * @since 1.9
525 * @author Grégory Viguier
526 *
527 * @param array $args {
528 * Optional. An array of arguments.
529 *
530 * @type bool $backup False to prevent backup. True to follow the user's setting. A backup can't be forced.
531 * @type string $backup_path If a backup must be done, this is the path to use. Default is the backup path used for the WP Media Library.
532 * @type int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
533 * @type string $convert Set to 'webp' to convert the image to WebP, 'avif' to convert image to AVIF.
534 * @type string $context The context.
535 * @type int $original_size The file size, sent to the API.
536 * }
537 * @return \sdtClass|\WP_Error Optimized image data. A \WP_Error object on error.
538 */
539 public function optimize( $args = [] ) {
540 $args = array_merge(
541 [
542 'backup' => true,
543 'backup_path' => null,
544 'backup_source' => null,
545 'optimization_level' => 0,
546 'convert' => '',
547 'context' => 'wp',
548 'original_size' => 0,
549 ],
550 $args
551 );
552
553 $can_be_processed = $this->can_be_processed();
554
555 if ( is_wp_error( $can_be_processed ) ) {
556 return $can_be_processed;
557 }
558
559 // Check if external HTTP requests are blocked.
560 if ( Imagify_Requirements::is_imagify_blocked() ) {
561 return new \WP_Error( 'http_block_external', __( 'External HTTP requests are blocked.', 'imagify' ) );
562 }
563
564 /**
565 * Fires before a media file optimization.
566 *
567 * @since 1.9
568 * @author Grégory Viguier
569 *
570 * @param string $path Absolute path to the media file.
571 * @param array $args Arguments passed to the method.
572 */
573 do_action( 'imagify_before_optimize_file', $this->path, $args );
574
575 /**
576 * Fires before to optimize the Image with Imagify.
577 *
578 * @since 1.0
579 * @deprecated
580 *
581 * @param string $path Absolute path to the image file.
582 * @param bool $backup True if a backup will be make.
583 */
584 do_action_deprecated( 'before_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
585
586 if ( $args['backup'] ) {
587 $backup_result = $this->backup( $args['backup_path'], $args['backup_source'] );
588
589 if ( is_wp_error( $backup_result ) ) {
590 // Stop the process if we can't backup the file.
591 return $backup_result;
592 }
593 }
594
595 // Send file for optimization and fetch the response.
596 $data = [
597 'normal' => 0 === $args['optimization_level'],
598 'aggressive' => 1 === $args['optimization_level'],
599 'ultra' => 2 === $args['optimization_level'],
600 'keep_exif' => true,
601 'original_size' => $args['original_size'],
602 'context' => $args['context'],
603 ];
604
605 if ( $args['convert'] ) {
606 $data['convert'] = $args['convert'];
607 $format = $args['convert'];
608 }
609
610 $response = upload_imagify_image(
611 [
612 'image' => $this->path,
613 'data' => wp_json_encode( $data ),
614 ]
615 );
616
617 if ( is_wp_error( $response ) ) {
618 return new \WP_Error( 'api_error', $response->get_error_message() );
619 }
620
621 if ( ! function_exists( 'download_url' ) ) {
622 require_once ABSPATH . 'wp-admin/includes/file.php';
623 }
624
625 $temp_file = download_url( $response->image );
626
627 if ( is_wp_error( $temp_file ) ) {
628 return new \WP_Error( 'temp_file_not_found', $temp_file->get_error_message() );
629 }
630
631 if ( property_exists( $response, 'message' ) ) {
632 $args['convert'] = '';
633 }
634
635 $formats = [
636 'webp',
637 'avif',
638 ];
639 if ( in_array( $args['convert'], $formats, true ) ) {
640 $destination_path = $this->get_path_to_nextgen( $args['convert'] );
641 $this->path = $destination_path;
642 $this->file_type = null;
643 $this->editor = null;
644 } else {
645 $destination_path = $this->path;
646 }
647
648 $moved = $this->filesystem->move( $temp_file, $destination_path, true );
649
650 if ( ! $moved ) {
651 return new \WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
652 }
653
654 /**
655 * Fires after to optimize the Image with Imagify.
656 *
657 * @since 1.0
658 * @deprecated
659 *
660 * @param string $path Absolute path to the image file.
661 * @param bool $backup True if a backup has been made.
662 */
663 do_action_deprecated( 'after_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
664
665 /**
666 * Fires after a media file optimization.
667 *
668 * @since 1.9
669 * @author Grégory Viguier
670 *
671 * @param string $path Absolute path to the media file.
672 * @param array $args Arguments passed to the method.
673 */
674 do_action( 'imagify_after_optimize_file', $this->path, $args );
675
676 return $response;
677 }
678
679
680 /** ----------------------------------------------------------------------------------------- */
681 /** IMAGE EDITOR (GD/IMAGEMAGICK) =========================================================== */
682 /** ----------------------------------------------------------------------------------------- */
683
684 /**
685 * Get an image editor instance (WP_Image_Editor_Imagick, WP_Image_Editor_GD).
686 *
687 * @since 1.9
688 * @author Grégory Viguier
689 *
690 * @return WP_Image_Editor_Imagick|WP_Image_Editor_GD|WP_Error
691 */
692 protected function get_editor() {
693 if ( isset( $this->editor ) ) {
694 return $this->editor;
695 }
696
697 $this->editor = wp_get_image_editor(
698 $this->path,
699 [
700 'methods' => $this->get_editor_methods(),
701 ]
702 );
703
704 if ( ! is_wp_error( $this->editor ) ) {
705 return $this->editor;
706 }
707
708 $this->editor = new \WP_Error(
709 'image_editor',
710 sprintf(
711 /* translators: %1$s is an error message, %2$s is a "More info?" link. */
712 __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. The internal error is: %1$s. %2$s', 'imagify' ),
713 $this->editor->get_error_message(),
714 '<a href="' . esc_url( imagify_get_external_url( 'documentation-imagick-gd' ) ) . '" target="_blank">' . __( 'More info?', 'imagify' ) . '</a>'
715 )
716 );
717
718 return $this->editor;
719 }
720
721 /**
722 * Get the image editor methods we will use.
723 *
724 * @since 1.9
725 * @author Grégory Viguier
726 *
727 * @return array
728 */
729 protected function get_editor_methods() {
730 static $methods;
731
732 if ( isset( $methods ) ) {
733 return $methods;
734 }
735
736 $methods = [
737 'resize',
738 'multi_resize',
739 'generate_filename',
740 'save',
741 ];
742
743 if ( $this->filesystem->can_get_exif() ) {
744 $methods[] = 'rotate';
745 }
746
747 return $methods;
748 }
749
750
751 /** ----------------------------------------------------------------------------------------- */
752 /** VARIOUS TOOLS =========================================================================== */
753 /** ----------------------------------------------------------------------------------------- */
754
755 /**
756 * Check if a file exceeds the weight limit (> 5mo).
757 *
758 * @since 1.9
759 * @author Grégory Viguier
760 *
761 * @return bool
762 */
763 public function is_exceeded() {
764 if ( ! $this->is_valid() ) {
765 return false;
766 }
767
768 $size = $this->filesystem->size( $this->path );
769
770 return $size > IMAGIFY_MAX_BYTES;
771 }
772
773 /**
774 * Tell if the current file is supported for a given context.
775 *
776 * @since 1.9
777 * @see imagify_get_mime_types()
778 * @author Grégory Viguier
779 *
780 * @param array $allowed_mime_types A list of allowed mime types.
781 * @return bool
782 */
783 public function is_supported( $allowed_mime_types ) {
784 return in_array( $this->get_mime_type(), $allowed_mime_types, true );
785 }
786
787 /**
788 * Tell if the file is an image.
789 *
790 * @since 1.9
791 * @author Grégory Viguier
792 *
793 * @return bool
794 */
795 public function is_image() {
796 if ( isset( $this->is_image ) ) {
797 return $this->is_image;
798 }
799
800 $this->is_image = strpos( $this->get_mime_type(), 'image/' ) === 0;
801
802 return $this->is_image;
803 }
804
805 /**
806 * Tell if the file is a pdf.
807 *
808 * @since 1.9
809 * @author Grégory Viguier
810 *
811 * @return bool
812 */
813 public function is_pdf() {
814 return 'application/pdf' === $this->get_mime_type();
815 }
816
817 /**
818 * Get the file mime type.
819 *
820 * @since 1.9
821 * @author Grégory Viguier
822 *
823 * @return string
824 */
825 public function get_mime_type() {
826 return $this->get_file_type()->type;
827 }
828
829 /**
830 * Get the file extension.
831 *
832 * @since 1.9
833 *
834 * @return string|false
835 */
836 public function get_extension() {
837 return $this->get_file_type()->ext;
838 }
839
840 /**
841 * Get the file path.
842 *
843 * @since 1.9
844 * @author Grégory Viguier
845 *
846 * @return string
847 */
848 public function get_path() {
849 return $this->path;
850 }
851
852 /**
853 * Replace the file extension by WebP.
854 *
855 * @since 1.9
856 * @author Grégory Viguier
857 *
858 * @return string|bool The file path on success. False if not an image or on failure.
859 */
860 public function get_path_to_webp() {
861 if ( ! $this->is_image() ) {
862 return false;
863 }
864
865 if ( $this->is_webp() ) {
866 return false;
867 }
868
869 return imagify_path_to_webp( $this->path );
870 }
871
872 /**
873 * Replace the file extension by its next-gen format extension.
874 *
875 * @since 2.2
876 *
877 * @param string $format the format we are targeting.
878 * @return string|bool The file path on success. False if not an image or on failure.
879 */
880 public function get_path_to_nextgen( string $format ) {
881 if ( ! $this->is_image() ) {
882 return false;
883 }
884
885 if ( $this->is_webp() || $this->is_avif() ) {
886 return false;
887 }
888
889 return imagify_path_to_nextgen( $this->path, $format );
890 }
891
892 /**
893 * Tell if the file is a WebP image.
894 * Rejects "path/to/.webp" files.
895 *
896 * @since 1.9
897 * @author Grégory Viguier
898 *
899 * @return bool
900 */
901 public function is_webp() {
902 return preg_match( '@(?!^|/|\\\)\.webp$@i', $this->path );
903 }
904
905 /**
906 * Tell if the file is an AVIF image.
907 * Rejects "path/to/.avif" files.
908 *
909 * @since 2.2
910 *
911 * @return bool
912 */
913 public function is_avif() {
914 return preg_match( '@(?!^|/|\\\)\.avif$@i', $this->path );
915 }
916
917 /**
918 * Get the file mime type + file extension.
919 *
920 * @since 1.9
921 * @see wp_check_filetype()
922 * @author Grégory Viguier
923 *
924 * @return object {
925 * @type string $ext The file extension.
926 * @type string $type The mime type.
927 * }
928 */
929 protected function get_file_type() {
930 if ( isset( $this->file_type ) ) {
931 return $this->file_type;
932 }
933
934 $this->file_type = (object) [
935 'ext' => '',
936 'type' => '',
937 ];
938
939 if ( ! $this->is_valid() ) {
940 return $this->file_type;
941 }
942
943 $this->file_type = (object) wp_check_filetype( $this->path );
944
945 return $this->file_type;
946 }
947
948 /**
949 * If the media is an image, get its width and height.
950 *
951 * @since 1.9
952 * @author Grégory Viguier
953 *
954 * @return array
955 */
956 public function get_dimensions() {
957 if ( ! $this->is_image() ) {
958 return [
959 'width' => 0,
960 'height' => 0,
961 ];
962 }
963
964 $values = $this->filesystem->get_image_size( $this->path );
965
966 if ( empty( $values ) ) {
967 return [
968 'width' => 0,
969 'height' => 0,
970 ];
971 }
972
973 return [
974 'width' => $values['width'],
975 'height' => $values['height'],
976 ];
977 }
978
979 /**
980 * Get a plugin’s option.
981 *
982 * @since 1.9
983 * @author Grégory Viguier
984 *
985 * @param string $option_name The option nme.
986 * @return mixed
987 */
988 protected function get_option( $option_name ) {
989 if ( isset( $this->options[ $option_name ] ) ) {
990 return $this->options[ $option_name ];
991 }
992
993 $this->options[ $option_name ] = get_imagify_option( $option_name );
994
995 return $this->options[ $option_name ];
996 }
997 }
998