PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
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
← All changes | classes/Optimization/Process/AbstractProcess.php +118 -31 2.2.8 → trunk View file →
@@ -244,8 +244,12 @@
244 244 /**
245 245 * Optimize a media files.
246 246 *
247 247 * @since 1.9
248 + * @since 2.3.2 Added the $args parameter (e.g. 'bulk', 'priority'). See self::optimize_sizes()
249 + * for the full list. 'bulk'/'priority' are extracted before the item is queued,
250 + * any other key ends up in the queued item's 'data', forwarded verbatim to the
251 + * 'imagify_before_*'/'imagify_after_*' hooks.
248 252 *
249 253 * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
250 254 * @param array $args An array of optionnal arguments.
251 255 *
@@ -290,8 +294,12 @@
290 294 /**
291 295 * Re-optimize a media files with a different level.
292 296 *
293 297 * @since 1.9
298 + * @since 2.3.2 Added the $args parameter (e.g. 'bulk', 'priority'). See self::optimize_sizes()
299 + * for the full list. 'bulk'/'priority' are extracted before the item is queued,
300 + * any other key ends up in the queued item's 'data', forwarded verbatim to the
301 + * 'imagify_before_*'/'imagify_after_*' hooks.
294 302 *
295 303 * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
296 304 * @param array $args An array of optionnal arguments.
297 305 *
@@ -346,8 +354,10 @@
346 354 * @param array $args {
347 355 * An array of optionnal arguments.
348 356 *
349 357 * @type string $hook_suffix Suffix used to trigger hooks before and after optimization.
358 + * @type bool $priority Whether this optimization should jump ahead of the queue (e.g. new uploads, manual clicks). Default false. Extracted before queueing: not part of the queued item's 'data'.
359 + * @type bool $bulk Whether this optimization was triggered by a bulk run (see Bulk::force_optimize()). Default false. Kept in the queued item's 'data', so it is forwarded verbatim to the 'imagify_before_*'/'imagify_after_*' hook callbacks.
350 360 * }
351 361 *
352 362 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
353 363 */
@@ -448,22 +458,32 @@
448 458 if ( $new_args && is_array( $new_args ) ) {
449 459 $args = array_merge( $new_args, $args );
450 460 }
451 461
462 + // 'priority' is a queue-mechanics flag: it must be a sibling key of 'id'/'sizes'/etc.,
463 + // not nested inside 'data' (which is opaque payload forwarded verbatim to the
464 + // 'imagify_before_*'/'imagify_after_*' hook callbacks). Extract it before building 'data'.
465 + $is_priority = ! empty( $args['priority'] );
466 + unset( $args['priority'] );
467 +
452 468 /**
453 469 * Push the item to the queue, save the queue in the DB, empty the queue.
454 470 * A "batch" is then created in the DB with this unique item, it is then free to loop through its steps (files) without another item interfering (each media optimization has its own dedicated batch/queue).
455 471 */
456 - MediaOptimization::get_instance()->push_to_queue(
457 - [
458 - 'id' => $media->get_id(),
459 - 'sizes' => $sizes,
460 - 'optimization_level' => $optimization_level,
461 - 'process_class' => get_class( $this ),
462 - 'data' => $args,
463 - ]
464 - )->save();
472 + $item = [
473 + 'id' => $media->get_id(),
474 + 'sizes' => $sizes,
475 + 'optimization_level' => $optimization_level,
476 + 'process_class' => get_class( $this ),
477 + 'data' => $args,
478 + ];
465 479
480 + if ( $is_priority ) {
481 + $item['priority'] = true;
482 + }
483 +
484 + MediaOptimization::get_instance()->push_to_queue( $item )->save();
485 +
466 486 return true;
467 487 }
468 488
469 489 /**
@@ -504,28 +524,38 @@
504 524 )
505 525 );
506 526 }
507 527
508 - if ( $this->get_data()->get_size_data( $size, 'success' ) ) { // Bail out.
509 - // This size is already optimized with Imagify, and must not be optimized again.
510 - if ( $next_gen ) {
511 - return new WP_Error(
512 - 'size_is_successfully_optimized',
513 - sprintf(
514 - /* translators: %s is a size name. */
515 - __( 'The Next-Gen format for the size %s already exists.', 'imagify' ),
516 - '<code>' . esc_html( $thumb_size ) . '</code>'
517 - )
518 - );
519 - } else {
520 - return new WP_Error(
521 - 'size_is_successfully_optimized',
522 - sprintf(
523 - /* translators: %s is a size name. */
524 - __( 'The size %s is already optimized by Imagify.', 'imagify' ),
525 - '<code>' . esc_html( $thumb_size ) . '</code>'
526 - )
527 - );
528 + if ( $this->get_data()->get_size_data( $size, 'success' ) ) {
529 + /*
530 + * The data says this size is already optimized. For a next-gen size, this can be
531 + * stale: an API response carrying a `message` (see File::optimize()) could have
532 + * been recorded as `success` without any next-gen file ever being written. In that
533 + * case, don't bail out: fall through and let the file be (re)generated.
534 + */
535 + $next_gen_file_is_missing = $next_gen && $this->is_next_gen_file_missing( $size, $sizes[ $thumb_size ]['path'] );
536 +
537 + if ( ! $next_gen_file_is_missing ) { // Bail out.
538 + // This size is already optimized with Imagify, and must not be optimized again.
539 + if ( $next_gen ) {
540 + return new WP_Error(
541 + 'size_is_successfully_optimized',
542 + sprintf(
543 + /* translators: %s is a size name. */
544 + __( 'The Next-Gen format for the size %s already exists.', 'imagify' ),
545 + '<code>' . esc_html( $thumb_size ) . '</code>'
546 + )
547 + );
548 + } else {
549 + return new WP_Error(
550 + 'size_is_successfully_optimized',
551 + sprintf(
552 + /* translators: %s is a size name. */
553 + __( 'The size %s is already optimized by Imagify.', 'imagify' ),
554 + '<code>' . esc_html( $thumb_size ) . '</code>'
555 + )
556 + );
557 + }
528 558 }
529 559 }
530 560
531 561 /**
@@ -1083,8 +1113,17 @@
1083 1113 if ( ! $copied ) {
1084 1114 return false;
1085 1115 }
1086 1116
1117 + /**
1118 + * The backup file may be the original, un-rotated JPEG that WordPress auto-rotated on
1119 + * upload (WordPress resets the orientation on the rotated file, but keeps the original
1120 + * orientation in the backup). Correct the orientation of this disposable temporary copy
1121 + * so that a Next-Gen version (or a thumbnail) generated from it isn't mis-oriented.
1122 + * The backup file itself is never touched.
1123 + */
1124 + $tmp_file->maybe_correct_exif_orientation();
1125 +
1087 1126 if ( 'full' === $size ) {
1088 1127 /**
1089 1128 * We create a copy of the backup to be able to create a next-gen version from it.
1090 1129 * That means the optimization process will resize the file if needed, so there is nothing more to do here.
@@ -1562,8 +1601,24 @@
1562 1601 return false;
1563 1602 }
1564 1603
1565 1604 /**
1605 + * Tell if the next-gen file for a size is missing from disk.
1606 + *
1607 + * @since 2.3.2
1608 + *
1609 + * @param string $size The (next-gen) size name, e.g. "thumbnail@imagify-avif".
1610 + * @param string $original_path Path to the non-next-gen version of that size.
1611 + * @return bool
1612 + */
1613 + protected function is_next_gen_file_missing( string $size, string $original_path ): bool {
1614 + $format = strpos( $size, static::AVIF_SUFFIX ) ? 'avif' : 'webp';
1615 + $next_gen_path = imagify_path_to_nextgen( $original_path, $format );
1616 +
1617 + return ! $next_gen_path || ! $this->filesystem->exists( $next_gen_path );
1618 + }
1619 +
1620 + /**
1566 1621 * Get suffix from format.
1567 1622 *
1568 1623 * @param string $format Format extension of next-gen image.
1569 1624 * @return string
@@ -1969,15 +2024,47 @@
1969 2024 * @param object $media_data The DataInterface instance of the media.
1970 2025 */
1971 2026 $data = (array) apply_filters( "imagify{$_unauthorized}_file_optimization_data", $data, $response, $size, $level, $this->get_data() );
1972 2027
1973 - if ( property_exists( $response, 'message' ) ) {
1974 - $size = str_replace( $this->format, '', $size );
2028 + if ( property_exists( $response, 'message' ) && $this->is_next_gen_size( $size ) ) {
2029 + /**
2030 + * The API answered with a 200 and a message instead of a converted file: it declined the
2031 + * conversion, because the next-gen version would be heavier than the original or the file is
2032 + * already compressed. No next-gen file is written to disk in that case (see
2033 + * Imagify\Optimization\File::optimize()), so recording a success here would make the plugin
2034 + * report a next-gen version that does not exist.
2035 + *
2036 + * Store a terminal entry instead, flagged as a permanent refusal so the bulk queries can skip
2037 + * the media without re-sending it to the API on every run. Transient failures go through the
2038 + * WP_Error branch above and are not flagged, so they keep being retried.
2039 + */
2040 + $data['success'] = false;
2041 + $data['error'] = $data['message'];
2042 + $data['permanent_error'] = true;
1975 2043 }
1976 2044 // Store.
1977 2045 $this->get_data()->update_size_optimization_data( $size, $data );
1978 2046
1979 2047 return $data;
2048 + }
2049 +
2050 + /**
2051 + * Tell if a size name refers to a next-gen (AVIF or WebP) version.
2052 + *
2053 + * @param string $size The size name.
2054 + *
2055 + * @return bool
2056 + */
2057 + private function is_next_gen_size( $size ) {
2058 + $size = (string) $size;
2059 +
2060 + foreach ( [ static::AVIF_SUFFIX, static::WEBP_SUFFIX ] as $suffix ) {
2061 + if ( '' !== $suffix && substr( $size, - strlen( $suffix ) ) === $suffix ) {
2062 + return true;
2063 + }
2064 + }
2065 +
2066 + return false;
1980 2067 }
1981 2068
1982 2069 /**
1983 2070 * Get a plugin’s option.