From 533300e809cc14ca2a5d5e25d5e2a403971f929c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Viguier?= Date: Wed, 13 May 2020 23:01:22 +0200 Subject: [PATCH 1/5] Fixes #491: Auto-optimization on image upload doesn't optimize thumbnails (#494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reworked the auto-optimization process That’s something I didn't want to do but there is no other choice but to use `wp_generate_attachment_metadata()`’s hook now. The tunnels are now: - For WP < 5.3: `add_attachment` => `wp_update_attachment_metadata` => `added_post_meta`. - For WP >= 5.3: `wp_generate_attachment_metadata` => `wp_update_attachment_metadata` => `imagify_after_auto_optimization_init`. --- .../class-imagify-auto-optimization.php | 392 +++++++++--------- ...s-imagify-auto-optimization-deprecated.php | 55 +++ 2 files changed, 253 insertions(+), 194 deletions(-) create mode 100644 inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php diff --git a/inc/classes/class-imagify-auto-optimization.php b/inc/classes/class-imagify-auto-optimization.php index 992ad3d22..0321ca2ba 100644 --- a/inc/classes/class-imagify-auto-optimization.php +++ b/inc/classes/class-imagify-auto-optimization.php @@ -4,91 +4,89 @@ /** * Class that handles the auto-optimization process. * This occurs when a new image is uploaded, and when an optimized image is worked with (resized, etc). - * The process will work only if wp_update_attachment_metadata() is used. + * The process will work only if wp_generate_attachment_metadata() and wp_update_attachment_metadata() are used. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 */ -class Imagify_Auto_Optimization { +class Imagify_Auto_Optimization extends Imagify_Auto_Optimization_Deprecated { use \Imagify\Traits\InstanceGetterTrait; /** - * An array containing the IDs (as keys) of attachments just being uploaded. - * - * @var array - * @since 1.8.4 - * @access protected - * @author Grégory Viguier + * An array containing all the "steps" an attachment is going through. + * This is used to decide the behavior of the automatic optimization. + * + * @var array { + * An array of arrays with attachment ID as keys. + * Each array can contain the following: + * + * @type $upload int Set to 1 if the attachment is a new upload. + * @type $generate int Set to 1 when going though wp_generate_attachment_metadata(). + * @type $update int Set to 1 when going though wp_update_attachment_metadata(). + * } + * @since 1.8.4 + * @since 1.9.10 Private. + * @since 1.9.10 Items are arrays instead of 1s. */ - protected $uploads = []; + private $attachments = []; /** - * An array containing the IDs (as keys) of attachments that must be optimized automatically. - * The values tell if the attachment is a new upload. + * Tell if we’re using WP 5.3+. * - * @var array - * @since 1.8.4 - * @access protected - * @author Grégory Viguier + * @var bool + * @since 1.9.10 */ - protected $attachments = []; + private $is_wp_53; /** * The ID of the attachment that failed to be uploaded. * - * @var int - * @since 1.9.8 - * @access protected - * @author Grégory Viguier + * @var int + * @since 1.9.8 */ protected $upload_failure_id = 0; /** * Used to prevent an auto-optimization locally. * - * @var array - * @since 1.8.4 - * @access private - * @author Grégory Viguier + * @var array + * @since 1.8.4 */ private static $prevented = []; /** * Used to prevent an auto-optimization internally. * - * @var array - * @since 1.9.8 - * @access private - * @author Grégory Viguier + * @var array + * @since 1.9.8 */ private static $prevented_internally = []; /** * Init. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier + * @since 1.8.4 */ public function init() { global $wp_version; - $prio = IMAGIFY_INT_MAX - 30; - // Automatic optimization tunel. - add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $prio ); - add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $prio, 2 ); - add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio, 4 ); - add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio, 4 ); - add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $prio, 3 ); + $priority = IMAGIFY_INT_MAX - 30; + $this->is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0; - if ( version_compare( $wp_version, '5.3-alpha1' ) >= 0 ) { + // Automatic optimization tunel. + if ( $this->is_wp_53 ) { // WP 5.3+. - add_filter( 'big_image_size_threshold', [ $this, 'prevent_auto_optimization_when_generating_thumbnails' ], $prio, 4 ); - add_filter( 'wp_generate_attachment_metadata', [ $this, 'allow_auto_optimization_when_generating_thumbnails' ], $prio, 3 ); - add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $prio, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 3 ); + add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 ); + add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 ); // Upload failure recovering. add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1). + } else { + add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); + add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 ); + add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 ); + add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 ); } + add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 ); // Prevent to re-optimize when updating the image width and height (when resizing the full image). add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 ); @@ -98,28 +96,26 @@ public function init() { /** * Remove the hooks. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier + * @since 1.8.4 */ public function remove_hooks() { - $prio = IMAGIFY_INT_MAX - 30; + $priority = IMAGIFY_INT_MAX - 30; // Automatic optimization tunel. - remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $prio ); - remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $prio ); - remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio ); - remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio ); - remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $prio ); - - if ( version_compare( $wp_version, '5.3-alpha1' ) >= 0 ) { + if ( $this->is_wp_53 ) { // WP 5.3+. - remove_filter( 'big_image_size_threshold', [ $this, 'prevent_auto_optimization_when_generating_thumbnails' ], $prio ); - remove_filter( 'wp_generate_attachment_metadata', [ $this, 'allow_auto_optimization_when_generating_thumbnails' ], $prio ); - remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $prio ); - // Upload failure handling. - remove_action( 'wp_ajax_media_create_image_subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); + remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority ); + remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority ); + remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority ); + // Upload failure recovering. + remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); + } else { + remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); + remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority ); + remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority ); + remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority ); } + remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority ); // Prevent to re-optimize when updating the image width and height (when resizing the full image). remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 ); @@ -132,32 +128,58 @@ public function remove_hooks() { /** ----------------------------------------------------------------------------------------- */ /** - * Store the ID of attachments that just have been uploaded. - * We use those IDs to tell the difference later in `wp_update_attachment_metadata()`. + * In WP < WP 5.3, store the "upload step" when an attachment has just been uploaded. * - * @since 1.8.4 - * @access public - * @see $this->store_ids_to_optimize() - * @author Grégory Viguier + * @since 1.8.4 + * @since 1.9.10 Used only in WP < 5.3. Also add the 'generate' step. + * @see $this->store_ids_to_optimize() * * @param int $attachment_id Current attachment ID. */ public function store_upload_ids( $attachment_id ) { if ( ! self::is_optimization_prevented( $attachment_id ) && imagify_is_attachment_mime_type_supported( $attachment_id ) ) { - $this->uploads[ $attachment_id ] = 1; + $this->set_step( $attachment_id, 'upload' ); + $this->set_step( $attachment_id, 'generate' ); // Required in $this->store_ids_to_optimize(). + } + } + + /** + * In WP 5.3+, store the "generate step" when wp_generate_attachment_metadata() is used. + * Can also store the "upload step". + * + * @since 1.9.10 + * + * @param array $metadata An array of attachment meta data. + * @param int $attachment_id Current attachment ID. + * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated. + * @return array + */ + public function maybe_store_generate_step( $metadata, $attachment_id, $context = null ) { + if ( self::is_optimization_prevented( $attachment_id ) ) { + return $metadata; + } + + if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) { + $this->unset_steps( $attachment_id ); + return $metadata; + } + + if ( 'create' === $context ) { + $this->set_step( $attachment_id, 'upload' ); } + + $this->set_step( $attachment_id, 'generate' ); + + return $metadata; } /** - * After the attachment meta data has been generated, launch an async optimization. + * After the attachment meta data has been generated (partially, since WP 5.3), init the auto-optimization. * Two cases are possible to trigger the optimization: * - It's a new upload and auto-optimization is enabled. * - It's not a new upload (it is regenerated) and the attachment is already optimized. * - * @since 1.8.4 - * @access public - * @see $this->store_upload_ids() - * @author Grégory Viguier + * @since 1.8.4 * * @param array $metadata An array of attachment meta data. * @param int $attachment_id Current attachment ID. @@ -170,13 +192,17 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { return $metadata; } - $is_new_upload = ! empty( $this->uploads[ $attachment_id ] ); - unset( $this->uploads[ $attachment_id ] ); + if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) { + $this->unset_steps( $attachment_id ); + return $metadata; + } - if ( ! $metadata || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) { + if ( ! $this->has_step( $attachment_id, 'generate' ) ) { return $metadata; } + $is_new_upload = $this->has_step( $attachment_id, 'upload' ); + if ( $is_new_upload ) { // It's a new upload. if ( ! isset( $auto_optimize ) ) { @@ -187,8 +213,7 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { /** * Fires when a new attachment is uploaded but auto-optimization is disabled. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id Attachment ID. * @param array $metadata An array of attachment meta data. @@ -201,8 +226,7 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { /** * Allow to prevent automatic optimization for a specific attachment. * - * @since 1.6.12 - * @author Grégory Viguier + * @since 1.6.12 * * @param bool $optimize True to optimize, false otherwise. * @param int $attachment_id Attachment ID. @@ -232,8 +256,7 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { /** * Fires when an attachment is updated but not optimized yet. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id Attachment ID. * @param array $metadata An array of attachment meta data. @@ -246,8 +269,7 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { /** * Allow to prevent automatic reoptimization for a specific attachment. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param bool $optimize True to optimize, false otherwise. * @param int $attachment_id Attachment ID. @@ -265,13 +287,12 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { } // Ready for the next step. - $this->attachments[ $attachment_id ] = $is_new_upload; + $this->set_step( $attachment_id, 'update' ); /** * Triggered after a media auto-optimization init. * - * @since 1.9.8 - * @author Grégory Viguier + * @since 1.9.8 * * @param int $attachment_id The media ID. * @param bool $is_new_upload True if it's a new upload. False otherwize. @@ -284,10 +305,8 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { /** * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated. * - * @since 1.9 - * @since 1.9 Previously named do_auto_optimization(). - * @access public - * @author Grégory Viguier + * @since 1.9 + * @since 1.9 Previously named do_auto_optimization(). * * @param int $meta_id ID of the metadata entry. * @param int $attachment_id Current attachment ID. @@ -299,39 +318,35 @@ public function do_auto_optimization_after_meta_update( $meta_id, $attachment_id return; } - if ( ! isset( $this->attachments[ $attachment_id ] ) ) { + if ( self::is_optimization_prevented( $attachment_id ) ) { return; } - if ( self::is_optimization_prevented( $attachment_id ) ) { + if ( ! $this->has_step( $attachment_id, 'update' ) ) { return; } - $is_new_upload = $this->attachments[ $attachment_id ]; - unset( $this->attachments[ $attachment_id ] ); - - $this->do_auto_optimization( $attachment_id, $is_new_upload ); + $this->do_auto_optimization( $attachment_id, $this->has_step( $attachment_id, 'upload' ) ); } /** * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated. * - * @since 1.8.4 - * @since 1.9.8 Changed signature. - * @access public - * @author Grégory Viguier + * @since 1.8.4 + * @since 1.9.8 Changed signature. * * @param int $attachment_id The media ID. * @param bool $is_new_upload True if it's a new upload. False otherwize. */ public function do_auto_optimization( $attachment_id, $is_new_upload ) { + $this->unset_steps( $attachment_id ); + $process = imagify_get_optimization_process( $attachment_id, 'wp' ); /** * Fires before an attachment auto-optimization is triggered. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id The attachment ID. * @param bool $is_new_upload True if it's a new upload. False otherwize. @@ -341,8 +356,7 @@ public function do_auto_optimization( $attachment_id, $is_new_upload ) { /** * Triggered before a media is auto-optimized. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id The media ID. * @param bool $is_new_upload True if it's a new upload. False otherwize. @@ -391,8 +405,7 @@ public function do_auto_optimization( $attachment_id, $is_new_upload ) { /** * Triggered after a media auto-optimization is launched. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id The media ID. * @param bool $is_new_upload True if it's a new upload. False otherwize. @@ -403,68 +416,18 @@ public function do_auto_optimization( $attachment_id, $is_new_upload ) { /** * Remove the attachment ID from the $attachments property if the post meta '_wp_attachment_metadata' is deleted. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier + * @since 1.8.4 * * @param int $meta_ids An array of deleted metadata entry IDs. * @param int $attachment_id Current attachment ID. * @param string $meta_key Meta key. */ public function unset_optimization( $meta_ids, $attachment_id, $meta_key ) { - if ( '_wp_attachment_metadata' !== $meta_key || ! isset( $this->attachments[ $attachment_id ] ) ) { + if ( '_wp_attachment_metadata' !== $meta_key ) { return; } - unset( $this->attachments[ $attachment_id ] ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** WP 5.3+ HOOKS =========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * With WP 5.3+, prevent auto-optimization inside wp_generate_attachment_metadata() because it triggers a wp_update_attachment_metadata() for each thumbnail size. - * - * @since 1.9.8 - * @access public - * @see wp_generate_attachment_metadata() - * @see wp_create_image_subsizes() - * @author Grégory Viguier - * - * @param int $threshold The threshold value in pixels. Default 2560. - * @param array $imagesize Indexed array of the image width and height (in that order). - * @param string $file Full path to the uploaded image file. - * @param int $attachment_id Attachment post ID. - * @return int The threshold value in pixels. - */ - public function prevent_auto_optimization_when_generating_thumbnails( $threshold, $imagesize, $file, $attachment_id ) { - static::prevent_optimization_internally( $attachment_id ); - return $threshold; - } - - /** - * With WP 5.3+, allow auto-optimization back after wp_generate_attachment_metadata(). - * - * @since 1.9.8 - * @access public - * @see $this->prevent_auto_optimization_when_generating_thumbnails() - * @author Grégory Viguier - * - * @param array $metadata An array of attachment meta data. - * @param int $attachment_id Current attachment ID. - * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated. - * @return array An array of attachment meta data. - */ - public function allow_auto_optimization_when_generating_thumbnails( $metadata, $attachment_id, $context = null ) { - if ( ! empty( $context ) && 'create' !== $context ) { - return $metadata; - } - - // Fired from wp_generate_attachment_metadata(): $context is empty (WP < 5.3) or equal to 'create' (>P >= 5.3). - static::allow_optimization_internally( $attachment_id ); - return $metadata; + $this->unset_steps( $attachment_id ); } @@ -475,11 +438,9 @@ public function allow_auto_optimization_when_generating_thumbnails( $metadata, $ /** * With WP 5.3+, prevent auto-optimization when WP tries to create thumbnails after an upload error, because it triggers wp_update_attachment_metadata() for each thumbnail size. * - * @since 1.9.8 - * @access public - * @see wp_ajax_media_create_image_subsizes() - * @see wp_update_image_subsizes() - * @author Grégory Viguier + * @since 1.9.8 + * @see wp_ajax_media_create_image_subsizes() + * @see wp_update_image_subsizes() */ public function prevent_auto_optimization_when_recovering_from_upload_failure() { if ( ! check_ajax_referer( 'media-form', false, false ) ) { @@ -496,7 +457,7 @@ public function prevent_auto_optimization_when_recovering_from_upload_failure() $attachment_id = ! empty( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0; - if ( ! $attachment_id ) { + if ( empty( $attachment_id ) ) { return; } @@ -506,8 +467,6 @@ public function prevent_auto_optimization_when_recovering_from_upload_failure() $this->upload_failure_id = $attachment_id; - static::prevent_optimization_internally( $attachment_id ); - // Auto-optimization will be done on shutdown. ob_start( [ $this, 'maybe_do_auto_optimization_after_recovering_from_upload_failure' ] ); } @@ -515,10 +474,8 @@ public function prevent_auto_optimization_when_recovering_from_upload_failure() /** * Maybe launch auto-optimization after recovering from an upload failure, when all thumbnails are created. * - * @since 1.9.8 - * @access public - * @see wp_ajax_media_create_image_subsizes() - * @author Grégory Viguier + * @since 1.9.8 + * @see wp_ajax_media_create_image_subsizes() * * @param string $content Buffer’s content. * @return string Buffer’s content. @@ -528,7 +485,7 @@ public function maybe_do_auto_optimization_after_recovering_from_upload_failure( return $content; } - if ( ! $this->upload_failure_id ) { + if ( empty( $this->upload_failure_id ) ) { // Uh? return $content; } @@ -546,12 +503,9 @@ public function maybe_do_auto_optimization_after_recovering_from_upload_failure( $attachment_id = $this->upload_failure_id; $metadata = wp_get_attachment_metadata( $attachment_id ); - $this->upload_failure_id = 0; - $this->uploads[ $attachment_id ] = 1; // New upload. - - static::allow_optimization_internally( $attachment_id ); - // Launch the process. + $this->upload_failure_id = 0; + $this->set_step( $attachment_id, 'generate' ); $this->store_ids_to_optimize( $metadata, $attachment_id ); return $content; @@ -562,6 +516,66 @@ public function maybe_do_auto_optimization_after_recovering_from_upload_failure( /** TOOLS =================================================================================== */ /** ----------------------------------------------------------------------------------------- */ + /** + * Set a "step" for an attachment. + * + * @since 1.9.10 + * @see $this->attachments + * + * @param int $attachment_id Current attachment ID. + * @param string $step The step to add. + */ + public function set_step( $attachment_id, $step ) { + if ( empty( $this->attachments[ $attachment_id ] ) ) { + $this->attachments[ $attachment_id ] = []; + } + + $this->attachments[ $attachment_id ][ $step ] = 1; + } + + /** + * Unset a "step" for an attachment. + * + * @since 1.9.10 + * @see $this->attachments + * + * @param int $attachment_id Current attachment ID. + * @param string $step The step to add. + */ + public function unset_step( $attachment_id, $step ) { + unset( $this->attachments[ $attachment_id ][ $step ] ); + + if ( empty( $this->attachments[ $attachment_id ] ) ) { + $this->unset_steps( $attachment_id ); + } + } + + /** + * Unset all "steps" for an attachment. + * + * @since 1.9.10 + * @see $this->attachments + * + * @param int $attachment_id Current attachment ID. + */ + public function unset_steps( $attachment_id ) { + unset( $this->attachments[ $attachment_id ] ); + } + + /** + * Tell if a "step" for an attachment exists. + * + * @since 1.9.10 + * @see $this->attachments + * + * @param int $attachment_id Current attachment ID. + * @param string $step The step to add. + * @return bool + */ + public function has_step( $attachment_id, $step ) { + return ! empty( $this->attachments[ $attachment_id ][ $step ] ); + } + /** * Prevent an auto-optimization locally. * How to use it: @@ -569,10 +583,8 @@ public function maybe_do_auto_optimization_after_recovering_from_upload_failure( * wp_update_attachment_metadata( $attachment_id ); * Imagify_Auto_Optimization::allow_optimization( $attachment_id ); * - * @since 1.8.4 - * @since 1.9.8 Prevents/Allows can stack. - * @access public - * @author Grégory Viguier + * @since 1.8.4 + * @since 1.9.8 Prevents/Allows can stack. * * @param int $attachment_id Current attachment ID. */ @@ -591,10 +603,8 @@ public static function prevent_optimization( $attachment_id ) { * wp_update_attachment_metadata( $attachment_id ); * Imagify_Auto_Optimization::allow_optimization( $attachment_id ); * - * @since 1.8.4 - * @since 1.9.8 Prevents/Allows can stack. - * @access public - * @author Grégory Viguier + * @since 1.8.4 + * @since 1.9.8 Prevents/Allows can stack. * * @param int $attachment_id Current attachment ID. */ @@ -612,9 +622,7 @@ public static function allow_optimization( $attachment_id ) { /** * Tell if an auto-optimization is prevented locally. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier + * @since 1.8.4 * * @param int $attachment_id Current attachment ID. * @return bool @@ -626,9 +634,7 @@ public static function is_optimization_prevented( $attachment_id ) { /** * Prevent an auto-optimization internally. * - * @since 1.9.8 - * @access protected - * @author Grégory Viguier + * @since 1.9.8 * * @param int $attachment_id Current attachment ID. */ @@ -639,9 +645,7 @@ protected static function prevent_optimization_internally( $attachment_id ) { /** * Allow an auto-optimization internally. * - * @since 1.9.8 - * @access protected - * @author Grégory Viguier + * @since 1.9.8 * * @param int $attachment_id Current attachment ID. */ diff --git a/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php b/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php new file mode 100644 index 000000000..b47d279cf --- /dev/null +++ b/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php @@ -0,0 +1,55 @@ +prevent_auto_optimization_when_generating_thumbnails() + * + * @param array $metadata An array of attachment meta data. + * @param int $attachment_id Current attachment ID. + * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated. + * @return array An array of attachment meta data. + */ + public function allow_auto_optimization_when_generating_thumbnails( $metadata, $attachment_id, $context = null ) { + _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9.10' ); + + if ( ! empty( $context ) && 'create' !== $context ) { + return $metadata; + } + + // Fired from wp_generate_attachment_metadata(): $context is empty (WP < 5.3) or equal to 'create' (>P >= 5.3). + static::allow_optimization_internally( $attachment_id ); + return $metadata; + } +} From c833c74c9870b2e54497a41c7c0a5a67a70eefc1 Mon Sep 17 00:00:00 2001 From: Git I Hate You Date: Thu, 14 May 2020 00:05:24 +0200 Subject: [PATCH 2/5] Hotfix: import trait It was probably lost in a merge conflict. --- inc/classes/class-imagify-auto-optimization.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/classes/class-imagify-auto-optimization.php b/inc/classes/class-imagify-auto-optimization.php index 0321ca2ba..220e8ce76 100644 --- a/inc/classes/class-imagify-auto-optimization.php +++ b/inc/classes/class-imagify-auto-optimization.php @@ -1,4 +1,7 @@ Date: Tue, 26 May 2020 14:16:09 +0200 Subject: [PATCH 3/5] Fixes #493: compatibility with plugin "Enable Media Replace" broken with WP 5.3 (PR #497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use the hook 'wp_handle_replace' to initiate the class Also removed some `if` statements and tests that are not needed anymore, AMR has already done them. * Import more classes * Deprecate method store_old_backup_path() - The old webp file paths are stored in `init()` now: like explained in the DocBlock, this prevents some orphan files from being kept indefinitely when the user chooses not to rename the files. - The method `store_old_backup_path()` is not needed anymore: the old backup path is already correct and the old webp file paths are stored in `init()`. * Removed @access and @author tags, imported trait * Removed useless "if" statement * Improve comments * Fixed auto-optimization for existing media The new argument `$context` passed to the filter `wp_generate_attachment_metadata` cannot be trusted. This argument’s value was meant to be `'create'`for new uploads and `'update'` for the other cases. In reality, it can be `'create'` when the media is not a new upload: this is the value used in the function `wp_generate_attachment_metadata()`, but this function is used by developers to re-create the thumbnails, this doesn't mean it is a new upload! * Made the class Imagify_Auto_Optimization_Deprecated abstract --- .../enable-media-replace/classes/Main.php | 162 ++++++------------ .../enable-media-replace.php | 2 +- .../class-imagify-auto-optimization.php | 40 ++--- ...s-imagify-auto-optimization-deprecated.php | 2 +- ...magify-enable-media-replace-deprecated.php | 98 +++++++---- 5 files changed, 137 insertions(+), 167 deletions(-) diff --git a/inc/3rd-party/enable-media-replace/classes/Main.php b/inc/3rd-party/enable-media-replace/classes/Main.php index 78cdceee5..ce14ea479 100755 --- a/inc/3rd-party/enable-media-replace/classes/Main.php +++ b/inc/3rd-party/enable-media-replace/classes/Main.php @@ -1,89 +1,71 @@ media_id = (int) filter_input( INPUT_POST, 'ID' ); - $this->media_id = max( 0, $this->media_id ); - - if ( ! $this->media_id || empty( $_FILES['userfile']['tmp_name'] ) ) { - $this->media_id = 0; - return $unfiltered; - } - - $tmp_name = wp_unslash( $_FILES['userfile']['tmp_name'] ); + public function init( $args = [] ) { + if ( is_array( $args ) && ! empty( $args['post_id'] ) ) { + $this->media_id = $args['post_id']; + } else { + // Backward compatibility. + $this->media_id = (int) filter_input( INPUT_POST, 'ID' ); + $this->media_id = max( 0, $this->media_id ); - if ( ! is_uploaded_file( $tmp_name ) ) { - $this->media_id = 0; - return $unfiltered; + if ( ! $this->media_id ) { + return; + } } // Store the old backup file path. @@ -91,23 +73,31 @@ public function init( $unfiltered = true ) { if ( ! $this->process ) { $this->media_id = 0; - return $unfiltered; + return; } $this->old_backup_path = $this->process->get_media()->get_backup_path(); if ( ! $this->old_backup_path ) { $this->media_id = 0; - return $unfiltered; + return; } - // Store the old backup file path. - add_filter( 'emr_unique_filename', [ $this, 'store_old_backup_path' ], 10, 3 ); - // Delete the old backup file. + /** + * Keep track of existing webp files. + * + * Whether the user chooses to rename the files or not, we will need to delete the current webp files before creating new ones: + * - Rename the files: the old ones must be removed, they are useless now. + * - Do not rename the files: the thumbnails may still get new names because of the suffix containing the image dimensions, which may differ (for example when thumbnails are scaled, not cropped). + * In this last case, the thumbnails with the old dimensions are removed from the drive and from the WP’s post meta, so there is no need of keeping orphan webp files that would stay on the drive for ever, even after the attachment is deleted from WP. + */ + foreach ( $this->process->get_media()->get_media_files() as $media_file ) { + $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] ); + } + + // Delete the old backup file and old webp files. add_action( 'imagify_before_auto_optimization', [ $this, 'delete_backup' ] ); add_action( 'imagify_not_optimized_attachment_updated', [ $this, 'delete_backup' ] ); - - return $unfiltered; } @@ -116,59 +106,10 @@ public function init( $unfiltered = true ) { /** ----------------------------------------------------------------------------------------- */ /** - * When the user chooses to change the file name, store the old backup file path. This path will be used later to delete the file. - * - * @since 1.6.9 - * @access public - * @author Grégory Viguier - * - * @param string $new_filename The new file name. - * @param string $current_path The current file path. - * @param int $post_id The attachment ID. - * @return string The same file name. - */ - public function store_old_backup_path( $new_filename, $current_path, $post_id ) { - if ( ! $this->media_id || $post_id !== $this->media_id ) { - return $new_filename; - } - - $this->get_process(); - - if ( ! $this->process ) { - $this->media_id = 0; - return $new_filename; - } - - $media = $this->process->get_media(); - $backup_path = $media->get_backup_path(); - - if ( $backup_path ) { - $this->old_backup_path = $backup_path; - - // Keep track of existing webp files. - $media_files = $media->get_media_files(); - - if ( $media_files ) { - foreach ( $media_files as $media_file ) { - $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] ); - } - } - } else { - $this->media_id = 0; - $this->old_backup_path = false; - $this->old_webp_paths = []; - } - - return $new_filename; - } - - /** - * Delete previous backup file. This is done after the images have been already replaced by Enable Media Replace. - * It will prevent having a backup file not corresponding to the current images. + * Delete previous backup file and webp files. + * This is done after the images have been already replaced by Enable Media Replace. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier + * @since 1.8.4 * * @param int $media_id The attachment ID. */ @@ -177,15 +118,16 @@ public function delete_backup( $media_id ) { return; } - $filesystem = \Imagify_Filesystem::get_instance(); + $filesystem = Imagify_Filesystem::get_instance(); if ( $filesystem->exists( $this->old_backup_path ) ) { + // Delete old backup file. $filesystem->delete( $this->old_backup_path ); $this->old_backup_path = false; } - if ( $this->old_webp_paths ) { - // If the files have been renamed, delete old webp files. + if ( ! empty( $this->old_webp_paths ) ) { + // Delete old webp files. $this->old_webp_paths = array_filter( $this->old_webp_paths, [ $filesystem, 'exists' ] ); array_map( [ $filesystem, 'delete' ], $this->old_webp_paths ); $this->old_webp_paths = []; @@ -200,9 +142,7 @@ public function delete_backup( $media_id ) { /** * Get the optimization process corresponding to the current media. * - * @since 1.9 - * @author Grégory Viguier - * @access protected + * @since 1.9 * * @return ProcessInterface|bool False if invalid. */ diff --git a/inc/3rd-party/enable-media-replace/enable-media-replace.php b/inc/3rd-party/enable-media-replace/enable-media-replace.php index 2e0f39272..671992d9c 100755 --- a/inc/3rd-party/enable-media-replace/enable-media-replace.php +++ b/inc/3rd-party/enable-media-replace/enable-media-replace.php @@ -5,6 +5,6 @@ class_alias( '\\Imagify\\ThirdParty\\EnableMediaReplace\\Main', '\\Imagify_Enable_Media_Replace' ); - add_filter( 'emr_unfiltered_get_attached_file', [ \Imagify\ThirdParty\EnableMediaReplace\Main::get_instance(), 'init' ] ); + add_filter( 'wp_handle_replace', [ \Imagify\ThirdParty\EnableMediaReplace\Main::get_instance(), 'init' ] ); endif; diff --git a/inc/classes/class-imagify-auto-optimization.php b/inc/classes/class-imagify-auto-optimization.php index 220e8ce76..0d4cf1a34 100644 --- a/inc/classes/class-imagify-auto-optimization.php +++ b/inc/classes/class-imagify-auto-optimization.php @@ -76,19 +76,20 @@ public function init() { $this->is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0; // Automatic optimization tunel. + add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 2 ); + add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 ); + if ( $this->is_wp_53 ) { // WP 5.3+. - add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 3 ); - add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 ); add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 ); // Upload failure recovering. - add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1). + add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1). } else { - add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); - add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 ); add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 ); add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 ); } + add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 ); // Prevent to re-optimize when updating the image width and height (when resizing the full image). @@ -105,19 +106,20 @@ public function remove_hooks() { $priority = IMAGIFY_INT_MAX - 30; // Automatic optimization tunel. + remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); + remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority ); + remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority ); + if ( $this->is_wp_53 ) { // WP 5.3+. - remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority ); - remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority ); remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority ); // Upload failure recovering. - remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); + remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); } else { - remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority ); - remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority ); remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority ); remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority ); } + remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority ); // Prevent to re-optimize when updating the image width and height (when resizing the full image). @@ -131,10 +133,9 @@ public function remove_hooks() { /** ----------------------------------------------------------------------------------------- */ /** - * In WP < WP 5.3, store the "upload step" when an attachment has just been uploaded. + * Store the "upload step" when an attachment has just been uploaded. * * @since 1.8.4 - * @since 1.9.10 Used only in WP < 5.3. Also add the 'generate' step. * @see $this->store_ids_to_optimize() * * @param int $attachment_id Current attachment ID. @@ -142,22 +143,19 @@ public function remove_hooks() { public function store_upload_ids( $attachment_id ) { if ( ! self::is_optimization_prevented( $attachment_id ) && imagify_is_attachment_mime_type_supported( $attachment_id ) ) { $this->set_step( $attachment_id, 'upload' ); - $this->set_step( $attachment_id, 'generate' ); // Required in $this->store_ids_to_optimize(). } } /** - * In WP 5.3+, store the "generate step" when wp_generate_attachment_metadata() is used. - * Can also store the "upload step". + * Store the "generate step" when wp_generate_attachment_metadata() is used. * * @since 1.9.10 * - * @param array $metadata An array of attachment meta data. - * @param int $attachment_id Current attachment ID. - * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated. + * @param array $metadata An array of attachment meta data. + * @param int $attachment_id Current attachment ID. * @return array */ - public function maybe_store_generate_step( $metadata, $attachment_id, $context = null ) { + public function maybe_store_generate_step( $metadata, $attachment_id ) { if ( self::is_optimization_prevented( $attachment_id ) ) { return $metadata; } @@ -167,10 +165,6 @@ public function maybe_store_generate_step( $metadata, $attachment_id, $context = return $metadata; } - if ( 'create' === $context ) { - $this->set_step( $attachment_id, 'upload' ); - } - $this->set_step( $attachment_id, 'generate' ); return $metadata; diff --git a/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php b/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php index b47d279cf..cb9260d07 100644 --- a/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php +++ b/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php @@ -6,7 +6,7 @@ * * @since 1.9.10 */ -class Imagify_Auto_Optimization_Deprecated { +abstract class Imagify_Auto_Optimization_Deprecated { /** * With WP 5.3+, prevent auto-optimization inside wp_generate_attachment_metadata() because it triggers a wp_update_attachment_metadata() for each thumbnail size. diff --git a/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php b/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php index f1ef4875f..9c20b42db 100644 --- a/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php +++ b/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php @@ -4,8 +4,7 @@ /** * Compat class for Enable Media Replace plugin. * - * @since 1.8.4 - * @author Grégory Viguier + * @since 1.8.4 * @deprecated */ class Imagify_Enable_Media_Replace_Deprecated { @@ -13,11 +12,9 @@ class Imagify_Enable_Media_Replace_Deprecated { /** * The attachment ID. * - * @var int - * @since 1.6.9 - * @since 1.9 Deprecated - * @access protected - * @author Grégory Viguier + * @var int + * @since 1.6.9 + * @since 1.9 Deprecated * @deprecated */ protected $attachment_id; @@ -25,11 +22,9 @@ class Imagify_Enable_Media_Replace_Deprecated { /** * The attachment. * - * @var Imagify_Attachment - * @since 1.6.9 - * @since 1.9 Deprecated - * @access protected - * @author Grégory Viguier + * @var Imagify_Attachment + * @since 1.6.9 + * @since 1.9 Deprecated * @deprecated */ protected $attachment; @@ -38,11 +33,9 @@ class Imagify_Enable_Media_Replace_Deprecated { * Tell if the attachment has data. * No data means not processed by Imagify, or restored. * - * @var bool - * @since 1.8.4 - * @since 1.9 Deprecated - * @access protected - * @author Grégory Viguier + * @var bool + * @since 1.8.4 + * @since 1.9 Deprecated * @deprecated */ protected $attachment_has_data; @@ -50,11 +43,9 @@ class Imagify_Enable_Media_Replace_Deprecated { /** * Filesystem object. * - * @var object Imagify_Filesystem - * @since 1.7.1 - * @since 1.8.4 Deprecated - * @author Grégory Viguier - * @access protected + * @var object Imagify_Filesystem + * @since 1.7.1 + * @since 1.8.4 Deprecated * @deprecated */ protected $filesystem; @@ -63,11 +54,9 @@ class Imagify_Enable_Media_Replace_Deprecated { * Optimize the attachment files if the old ones were also optimized. * Delete the old backup file. * - * @since 1.6.9 - * @since 1.8.4 Deprecated - * @author Grégory Viguier - * @see $this->store_old_backup_path() - * @access protected + * @since 1.6.9 + * @since 1.8.4 Deprecated. + * @see $this->store_old_backup_path() * @deprecated * * @param string $return_url The URL the user will be redirected to. @@ -120,13 +109,60 @@ public function optimize( $return_url ) { return $return_url; } + /** + * When the user chooses to change the file name, store the old backup file path. This path will be used later to delete the file. + * + * @since 1.6.9 + * @since 1.9.10 Deprecated. + * @deprecated + * + * @param string $new_filename The new file name. + * @param string $current_path The current file path. + * @param int $post_id The attachment ID. + * @return string The same file name. + */ + public function store_old_backup_path( $new_filename, $current_path, $post_id ) { + _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9.10' ); + + if ( ! $this->media_id || $post_id !== $this->media_id ) { + return $new_filename; + } + + $this->get_process(); + + if ( ! $this->process ) { + $this->media_id = 0; + return $new_filename; + } + + $media = $this->process->get_media(); + $backup_path = $media->get_backup_path(); + + if ( $backup_path ) { + $this->old_backup_path = $backup_path; + + // Keep track of existing webp files. + $media_files = $media->get_media_files(); + + if ( $media_files ) { + foreach ( $media_files as $media_file ) { + $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] ); + } + } + } else { + $this->media_id = 0; + $this->old_backup_path = false; + $this->old_webp_paths = []; + } + + return $new_filename; + } + /** * Get the attachment. * - * @since 1.6.9 - * @since 1.9 Deprecated - * @author Grégory Viguier - * @access protected + * @since 1.6.9 + * @since 1.9 Deprecated. * @deprecated * * @return object A Imagify_Attachment object (or any class extending it). From be54ae85abe87cea7b0ac61589306b00d8384f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Tue, 26 May 2020 08:26:29 -0400 Subject: [PATCH 4/5] update version --- imagify.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imagify.php b/imagify.php index 08de9a122..e5d405fd4 100644 --- a/imagify.php +++ b/imagify.php @@ -3,7 +3,7 @@ * Plugin Name: Imagify * Plugin URI: https://wordpress.org/plugins/imagify/ * Description: Dramaticaly reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth using Imagify, the new most advanced image optimization tool. - * Version: 1.9.9 + * Version: 1.9.10 * Requires PHP: 5.4 * Author: WP Media * Author URI: https://wp-media.me/ @@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' ); // Imagify defines. -define( 'IMAGIFY_VERSION', '1.9.9' ); +define( 'IMAGIFY_VERSION', '1.9.10' ); define( 'IMAGIFY_SLUG', 'imagify' ); define( 'IMAGIFY_FILE', __FILE__ ); define( 'IMAGIFY_PATH', realpath( plugin_dir_path( IMAGIFY_FILE ) ) . '/' ); From 37fe3ed1e2eda20c1614a6c388076b239021275c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Tue, 26 May 2020 08:26:41 -0400 Subject: [PATCH 5/5] update tested up to, stable tag and changelog --- readme.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 7f6140269..a81388fc2 100755 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: wp_media, GregLone Tags: optimize images, images, optimize, performance, webp Requires at least: 4.0.0 -Tested up to: 5.3.2 -Stable tag: 1.9.9 +Tested up to: 5.4 +Stable tag: 1.9.10 Optimize images in one click: reduce image file sizes, convert WebP, keep your images beautiful… and boost your loading time and your SEO! @@ -153,6 +153,10 @@ When the plugin is disabled, your existing images remain optimized. Backups of t 4. Other Media Page == Changelog == += 1.9.10 - 2020/05/26 = +* Fix: Correctly optimize thumbnails during auto-optimization of image upload +* Fix: Fix broken compatibility with Enable Media Replace plugin after WordPress 5.3 + = 1.9.9 - 2020/02/13 = * Fix: do not warn that all the quota has been consumed when it is not the case. * Fix: fix a "chunky upload" error that some users experienced.