From 6587bb31af27f80ba618c8448a1fd342914b5de8 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Wed, 26 Jan 2022 13:06:59 -0500 Subject: [PATCH 01/21] Add code for WPR2x that was removed from 68c489b4 This is just grabbing the WPR2X code that was deleted. It's no longer wired into our stuff, so it won't do anything yet. --- .../class-imagify-wp-retina-2x-core.php | 1694 +++++++++++++++++ .../classes/class-imagify-wp-retina-2x.php | 681 +++++++ inc/3rd-party/perfect-images/wp-retina-2x.php | 8 + 3 files changed, 2383 insertions(+) create mode 100644 inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php create mode 100644 inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php create mode 100755 inc/3rd-party/perfect-images/wp-retina-2x.php diff --git a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php b/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php new file mode 100644 index 000000000..1916d69ca --- /dev/null +++ b/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php @@ -0,0 +1,1694 @@ +filesystem = Imagify_Filesystem::get_instance(); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** GENERATE RETINA IMAGES ================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Generate retina images (except full size), and optimize them if the non-retina images are. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function generate_retina_images( $attachment ) { + $tests = $this->validate( __FUNCTION__, $attachment ); + + if ( true !== $tests ) { + return $tests; + } + + // Backup the optimized full-sized image and replace it by the original backup file, so it can be used to create new retina images. + $this->backup_optimized_file( $attachment ); + + if ( ! $this->filesystem->exists( $attachment->get_original_path() ) ) { + return new WP_Error( 'file_missing', 'The main file does not exist.' ); + } + + // Create retina images. + wr2x_generate_images( wp_get_attachment_metadata( $attachment->get_id() ) ); + + // Put the optimized full-sized file back. + $this->put_optimized_file_back( $attachment ); + + /** + * If the non-retina images are optimized by Imagify (or at least the user wanted it to be optimized at some point, and now has a "already optimized" or "error" status), optimize newly created retina files. + * If the retina version of the full size exists and is not optimized yet, it will be processed as well. + */ + if ( $attachment->is_optimized() && $this->can_auto_optimize() ) { + $this->optimize_retina_images( $attachment ); + } + + return true; + } + + /** + * Delete previous retina images and recreate them (except full size), and optimize them if they previously were. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function regenerate_retina_images( $attachment ) { + $tests = $this->validate( __FUNCTION__, $attachment ); + + if ( true !== $tests ) { + return $tests; + } + + // Delete the retina files and remove retina sizes from Imagify data. + $result = $this->delete_retina_images( $attachment ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + // Create new retina files (and optimize them if they previously were). + return $this->generate_retina_images( $attachment ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** DELETE RETINA IMAGES ==================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Delete the retina images. Also removes the related Imagify data. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param bool $delete_full_image True to also delete the retina version of the full size. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function delete_retina_images( $attachment, $delete_full_image = false ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'metadata_dimensions' => 'error', + ) ); + + if ( true !== $tests ) { + return $tests; + } + + /** + * To be a bit faster we update the data at once at the end. + * + * @see Imagify_WP_Retina_2x::remove_retina_thumbnail_data_hook(). + */ + $this->prevent( 'remove_retina_image_data_by_filename' ); + + // Delete the retina thumbnails. + wr2x_delete_attachment( $attachment->get_id(), $delete_full_image ); + + $this->allow( 'remove_retina_image_data_by_filename' ); + + // Remove retina sizes from Imagify data. + $this->remove_retina_images_data( $attachment, $delete_full_image ); + + return true; + } + + /** + * Delete the retina version of the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function delete_full_retina_image( $attachment ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'metadata_file' => false, + 'metadata_sizes' => false, + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $retina_path = wr2x_get_retina( $attachment->get_original_path() ); + + if ( $retina_path ) { + // The file exists. + $this->filesystem->delete( $retina_path ); + } + + // Delete related Imagify data. + return $this->remove_size_from_imagify_data( $attachment, 'full@2x' ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** REPLACE IMAGES ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Replace an attachment (except the retina version of the full size). + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $file_path Path to the new file. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function replace_attachment( $attachment, $file_path ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'metadata_sizes' => false, + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $attachment_id = $attachment->get_id(); + $sizes = $this->get_attachment_sizes( $attachment ); + $original_path = $attachment->get_original_path(); + $dir_path = $this->filesystem->path_info( $original_path, 'dir_path' ); + + // Insert the new file (and overwrite the full size). + $moved = $this->filesystem->move( $file_path, $original_path, true ); + + if ( ! $moved ) { + return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) ); + } + + // Delete retina images. + $this->delete_retina_images( $attachment ); + + // Delete the non-retina images. + if ( $sizes ) { + foreach ( $sizes as $name => $attr ) { + $size_path = $dir_path . $attr['file']; + + if ( $this->filesystem->exists( $size_path ) && $this->filesystem->is_file( $size_path ) ) { + // If the deletion fails, we're screwed anyway since the main file has been deleted, so no need to return an error here. + $this->filesystem->delete( $size_path ); + } + } + } + + // Get some Imagify data before deleting everything. + $optimization_level = $this->get_optimization_level( $attachment ); + $full_retina_data = $attachment->get_data(); + $full_retina_data = ! empty( $full_retina_data['sizes']['full@2x'] ) ? $full_retina_data['sizes']['full@2x'] : false; + $full_retina_optimized = $full_retina_data && ! empty( $full_retina_data['success'] ); + + // Delete the Imagify data. + $attachment->delete_imagify_data(); + + // Delete the backup file. + $attachment->delete_backup(); + + // Prevent auto-optimization. + Imagify_Auto_Optimization::prevent_optimization( $attachment_id ); + + // Generate the non-retina images and the related WP metadata. + wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $original_path ) ); + + // Allow auto-optimization back. + Imagify_Auto_Optimization::allow_optimization( $attachment_id ); + + // Generate retina images (since the Imagify data has been deleted, the images won't be optimized here). + $result = $this->generate_retina_images( $attachment ); + + if ( is_wp_error( $result ) ) { + if ( $full_retina_optimized ) { + // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day. + $this->restore_full_retina_file( $attachment ); + } + + return $result; + } + + if ( $this->can_auto_optimize() ) { + if ( $full_retina_optimized ) { + // Don't optimize the retina full size, it already is. + remove_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ) ); + } + + /** + * Optimize everyone. + * + * @see Imagify_WP_Retina_2x::optimize_full_retina_version_hook() + * @see Imagify_WP_Retina_2x::optimize_retina_version_hook() + * @see Imagify_WP_Retina_2x::maybe_optimize_unauthorized_retina_version_hook(). + */ + $attachment->optimize( $optimization_level ); + + if ( $full_retina_optimized ) { + add_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ), 10, 8 ); + + if ( $attachment->is_optimized() ) { + // Put data back. + $data = $attachment->get_data(); + $data['sizes']['full@2x'] = $full_retina_data; + update_post_meta( $attachment_id, '_imagify_data', $data ); + } else { + $this->restore_full_retina_file( $attachment ); + } + } + } elseif ( $full_retina_optimized ) { + // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day. + $this->restore_full_retina_file( $attachment ); + } + + return true; + } + + /** + * Replace an attachment (except the retina version of the full size). + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $file_path Path to the new file. + * @return bool|object True on success, false if prevented, a WP_Error object on failure. + */ + public function replace_full_retina_image( $attachment, $file_path ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'metadata_file' => false, + 'metadata_sizes' => false, + ) ); + + if ( true !== $tests ) { + return $tests; + } + + // Replace the file. + $retina_path = $this->get_retina_path( $attachment->get_original_path() ); + $moved = $this->filesystem->move( $file_path, $retina_path, true ); + + if ( ! $moved ) { + return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) ); + } + + // Delete related Imagify data. + $this->remove_size_from_imagify_data( $attachment, 'full@2x' ); + + // Delete previous backup file. + $result = $this->delete_file_backup( $retina_path ); + + if ( is_wp_error( $result ) ) { + $this->filesystem->delete( $file_path ); + return $result; + } + + // Optimize. + if ( $attachment->is_optimized() && $this->can_auto_optimize() ) { + return $this->optimize_full_retina_image( $attachment ); + } + } + + + /** ----------------------------------------------------------------------------------------- */ + /** OPTIMIZE RETINA IMAGES ================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Optimize retina images. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param bool $optimize_full_size False to not optimize the retina version of the full size. + * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. + */ + public function optimize_retina_images( $attachment, $optimize_full_size = true ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'supported' => true, + 'can_optimize' => 'error', + 'metadata_dimensions' => 'error', + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + + if ( $optimize_full_size ) { + $metadata['sizes']['full'] = array( + 'file' => $this->filesystem->file_name( $metadata['file'] ), + 'width' => (int) $metadata['width'], + 'height' => (int) $metadata['height'], + 'mime-type' => get_post_mime_type( $attachment->get_id() ), + ); + } + + return $this->optimize_retina_sizes( $attachment, $metadata['sizes'] ); + } + + /** + * Optimize the full size retina image. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. + */ + public function optimize_full_retina_image( $attachment ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'supported' => true, + 'can_optimize' => 'error', + 'metadata_dimensions' => 'error', + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + + $sizes = array( + 'full' => array( + 'file' => $this->filesystem->file_name( $metadata['file'] ), + 'width' => (int) $metadata['width'], + 'height' => (int) $metadata['height'], + 'mime-type' => get_post_mime_type( $attachment->get_id() ), + ), + ); + + return $this->optimize_retina_sizes( $attachment, $sizes ); + } + + /** + * Optimize the given retina images. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param array $sizes A list of non-retina sizes, formatted like in wp_get_attachment_metadata(). + * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. + */ + public function optimize_retina_sizes( $attachment, $sizes ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'supported' => true, + 'can_optimize' => 'error', + 'metadata_dimensions' => 'error', + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $attachment_id = $attachment->get_id(); + $optimization_level = $this->get_optimization_level( $attachment ); + + /** + * Filter the retina thumbnail sizes to optimize for a given attachment. This includes the sizes disabled in Imagify’ settings. + * + * @since 1.8 + * @author Grégory Viguier + * + * @param array $sizes An array of non-retina thumbnail sizes. + * @param int $attachment_id The attachment ID. + * @param int $optimization_level The optimization level. + */ + $sizes = apply_filters( 'imagify_attachment_retina_sizes', $sizes, $attachment_id, $optimization_level ); + + if ( ! $sizes || ! is_array( $sizes ) ) { + return false; + } + + $original_dirpath = $this->filesystem->dir_path( $attachment->get_original_path() ); + + foreach ( $sizes as $size_key => $image_data ) { + $retina_path = wr2x_get_retina( $original_dirpath . $image_data['file'] ); + + if ( ! $retina_path ) { + unset( $sizes[ $size_key ] ); + continue; + } + + // The file exists. + $sizes[ $size_key ]['retina-path'] = $retina_path; + } + + if ( ! $sizes ) { + return false; + } + + $attachment->set_running_status(); + + /** + * Fires before optimizing the retina images. + * + * @since 1.8 + * @author Grégory Viguier + * + * @param int $attachment_id The attachment ID. + * @param array $sizes An array of non-retina thumbnail sizes. + * @param int $optimization_level The optimization level. + */ + do_action( 'before_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level ); + + $imagify_data = $attachment->get_data(); + + foreach ( $sizes as $size_key => $image_data ) { + $imagify_data = $this->optimize_retina_image( array( + 'data' => $imagify_data, + 'attachment' => $attachment, + 'retina_path' => $image_data['retina-path'], + 'size_key' => $size_key, + 'optimization_level' => $optimization_level, + ) ); + } + + $this->update_imagify_data( $attachment, $imagify_data ); + + /** + * Fires after optimizing the retina images. + * + * @since 1.8 + * @author Grégory Viguier + * + * @param int $attachment_id The attachment ID. + * @param array $sizes An array of non-retina thumbnail sizes. + * @param int $optimization_level The optimization level. + */ + do_action( 'after_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level ); + + $attachment->delete_running_status(); + + return true; + } + + /** + * Optimize the retina version of an image. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $args { + * An array of required arguments. + * + * @type array $data The statistics data. + * @type object $attachment An Imagify attachment. + * @type string $retina_path The path to the retina file. + * @type string $size_key The attachment size key (without '@2x'). + * @type int $optimization_level The optimization level. Optionnal. + * @type array $metadata WP metadata. If omitted, wp_get_attachment_metadata() will be used. + * } + * @return array The new optimization data. + */ + public function optimize_retina_image( $args ) { + static $backup; + + $args = array_merge( array( + 'data' => array(), + 'attachment' => false, + 'retina_path' => '', + 'size_key' => '', + 'optimization_level' => false, + 'metadata' => array(), + ), $args ); + + if ( $this->is_prevented( __FUNCTION__ ) || ! $args['retina_path'] || $this->has_filesystem_error() ) { + return $args['data']; + } + + $retina_key = $args['size_key'] . '@2x'; + + if ( isset( $args['data'][ $retina_key ] ) ) { + // Don't optimize something that already is. + return $args['data']; + } + + $disallowed = $this->size_is_disallowed( $args['size_key'] ); + $do_retina = ! $disallowed; + /** + * Allow to optimize the retina version generated by WP Retina x2. + * + * @since 1.0 + * @since 1.8 Added $args parameter. + * + * @param bool $do_retina True will allow the optimization. False to prevent it. + * @param string $args The arguments passed to the method. + */ + $do_retina = apply_filters( 'do_imagify_optimize_retina', $do_retina, $args ); + + if ( ! $do_retina ) { + if ( $disallowed ) { + $message = __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ); + } else { + $message = __( 'This size optimization has been prevented by a filter.', 'imagify' ); + } + + $args['data']['sizes'][ $retina_key ] = array( + 'success' => false, + 'error' => $message, + ); + return $args['data']; + } + + if ( ! $args['metadata'] || ! is_array( $args['metadata'] ) ) { + $args['metadata'] = wp_get_attachment_metadata( $args['attachment']->get_id() ); + } + + $is_a_copy = $this->size_is_a_full_copy( array( + 'size_name' => $args['size_key'], + 'metadata' => $args['metadata'], + 'imagify_data' => $args['data'], + 'retina_path' => $args['retina_path'], + ) ); + + if ( $is_a_copy ) { + // This thumbnail is a copy of the full size image, which is already optimized. + $args['data']['sizes'][ $retina_key ] = $args['data']['sizes']['full']; + + if ( isset( $args['data']['sizes']['full']['original_size'], $args['data']['sizes']['full']['optimized_size'] ) ) { + // Concistancy only. + $args['data']['stats']['original_size'] += $args['data']['sizes']['full']['original_size']; + $args['data']['stats']['optimized_size'] += $args['data']['sizes']['full']['optimized_size']; + } + + return $args['data']; + } + + if ( ! is_int( $args['optimization_level'] ) ) { + $args['optimization_level'] = get_imagify_option( 'optimization_level' ); + } + + // Hammer time. + $response = do_imagify( $args['retina_path'], array( + // Backup only if it's the full size. + 'backup' => 'full' === $args['size_key'], + 'optimization_level' => $args['optimization_level'], + 'context' => 'wp-retina', + ) ); + + return $args['attachment']->fill_data( $args['data'], $response, $retina_key ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** HANDLE BACKUPS ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Backup a retina file. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $file_path Path to the file. + * @return bool|object True on success, false if prevented or no need for backup, a WP_Error object on failure. + */ + public function backup_file( $file_path ) { + static $backup; + + if ( $this->is_prevented( __FUNCTION__ ) ) { + return false; + } + + if ( ! isset( $backup ) ) { + $backup = get_imagify_option( 'backup' ); + } + + if ( ! $backup ) { + return false; + } + + if ( $this->has_filesystem_error() ) { + return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) ); + } + + $upload_basedir = get_imagify_upload_basedir(); + + if ( ! $upload_basedir ) { + $file_path = make_path_relative( $file_path ); + + /* translators: %s is a file path. */ + return new WP_Error( 'upload_basedir', sprintf( __( 'The file %s could not be backed up. Image optimization aborted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); + } + + $file_path = wp_normalize_path( $file_path ); + $backup_dir = get_imagify_backup_dir_path(); + $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path ); + + if ( $this->filesystem->exists( $backup_path ) ) { + $this->filesystem->delete( $backup_path ); + } + + $backup_result = imagify_backup_file( $file_path, $backup_path ); + + if ( is_wp_error( $backup_result ) ) { + return $backup_result; + } + + return true; + } + + /** + * Delete a retina file backup. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $file_path Path to the file. + * @return bool|object True on success, false if the file doesn't exist, a WP_Error object on failure. + */ + public function delete_file_backup( $file_path ) { + $tests = $this->validate( __FUNCTION__ ); + + if ( true !== $tests ) { + return $tests; + } + + $upload_basedir = get_imagify_upload_basedir(); + + if ( ! $upload_basedir ) { + $file_path = make_path_relative( $file_path ); + + /* translators: %s is a file path. */ + return new WP_Error( 'upload_basedir', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); + } + + $file_path = wp_normalize_path( $file_path ); + $backup_dir = get_imagify_backup_dir_path(); + $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path ); + + if ( ! $this->filesystem->exists( $backup_path ) ) { + return false; + } + + $result = $this->filesystem->delete( $backup_path ); + + if ( ! $result ) { + $file_path = make_path_relative( $file_path ); + + /* translators: %s is a file path. */ + return new WP_Error( 'not_deleted', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); + } + + return true; + } + + /** + * Restore the retina version of the full size. + * This doesn't remove the Imagify data. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object True on success, false if prevented or backup doesn't exist, a WP_Error object on failure. + */ + public function restore_full_retina_file( $attachment ) { + $tests = $this->validate( __FUNCTION__, $attachment, array( + 'metadata_file' => false, + 'metadata_sizes' => false, + ) ); + + if ( true !== $tests ) { + return $tests; + } + + $has_backup = $this->full_retina_has_backup( $attachment ); + + if ( is_wp_error( $has_backup ) ) { + return $has_backup; + } + + if ( ! $has_backup ) { + return new WP_Error( 'no_backup', __( 'The retina version of the full size of this image does not have backup.', 'imagify' ) ); + } + + $file_path = $this->get_retina_path( $attachment->get_original_path() ); + $backup_path = $this->get_full_retina_backup_path( $attachment ); + + /** + * Fires before restoring the retina version of the full size. + * + * @since 1.8 + * @author Grégory Viguier + * + * @param string $backup_path Path to the backup file. + * @param string $file_path Path to the source file. + */ + do_action( 'before_imagify_restore_full_retina_file', $backup_path, $file_path ); + + // Save disc space by moving it instead of copying it. + $moved = $this->filesystem->move( $backup_path, $file_path, true ); + + /** + * Fires after restoring the retina version of the full size. + * + * @since 1.8 + * @author Grégory Viguier + * + * @param string $backup_path Path to the backup file. + * @param string $file_path Path to the source file. + * @param bool $moved Restore success. + */ + do_action( 'after_imagify_restore_full_retina_file', $backup_path, $file_path, $moved ); + + if ( ! $moved ) { + return new WP_Error( 'upload_basedir', __( 'Backup of the retina version of the full size image could not be restored.', 'imagify' ) ); + } + + return true; + } + + /** + * Get the path to the retina version of the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return string|object The path on success, a WP_Error object on failure. + */ + public function get_full_retina_backup_path( $attachment ) { + $file_path = $this->get_retina_path( $attachment->get_original_path() ); + $backup_path = get_imagify_attachment_backup_path( $file_path ); + + if ( ! $backup_path ) { + return new WP_Error( 'upload_basedir', __( 'Could not retrieve the path to the backup of the retina version of the full size image.', 'imagify' ) ); + } + + return $backup_path; + } + + /** + * Tell if the retina version of the full size has a backup. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool|object A WP_Error object on failure. + */ + public function full_retina_has_backup( $attachment ) { + $backup_path = $this->get_full_retina_backup_path( $attachment ); + + if ( is_wp_error( $backup_path ) ) { + return $backup_path; + } + + return $this->filesystem->exists( $backup_path ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** HANDLE IMAGIFY DATA ===================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Remove retina versions from Imagify data. + * It also rebuilds the attachment stats. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param bool $remove_full_size True to also remove the full size data. + */ + public function remove_retina_images_data( $attachment, $remove_full_size = false ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return; + } + + $imagify_data = $attachment->get_data(); + + if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { + return; + } + + $sizes = $this->get_attachment_sizes( $attachment ); + + if ( ! $sizes ) { + return; + } + + $update = false; + + if ( $remove_full_size && isset( $imagify_data['sizes']['full@2x'] ) ) { + unset( $imagify_data['sizes']['full@2x'] ); + $update = true; + } + + foreach ( $sizes as $size => $attr ) { + $size .= '@2x'; + + if ( isset( $imagify_data['sizes'][ $size ] ) ) { + unset( $imagify_data['sizes'][ $size ] ); + $update = true; + } + } + + if ( ! $update ) { + return; + } + + $this->update_imagify_data( $attachment, $imagify_data ); + } + + /** + * Remove a retina thumbnail from attachment's Imagify data, given the retina file name. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $retina_filename Retina thumbnail file name. + */ + public function remove_retina_image_data_by_filename( $attachment, $retina_filename ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return; + } + + $imagify_data = $attachment->get_data(); + + if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { + return; + } + + $sizes = $this->get_attachment_sizes( $attachment ); + + if ( ! $sizes ) { + return; + } + + $image_filename = str_replace( $this->get_suffix(), '.', $retina_filename ); + $size = false; + + foreach ( $sizes as $name => $attr ) { + if ( $image_filename === $attr['file'] ) { + $size = $name; + break; + } + } + + if ( ! $size || ! isset( $imagify_data['sizes'][ $size ] ) ) { + return; + } + + unset( $imagify_data['sizes'][ $size ] ); + + $this->update_imagify_data( $attachment, $imagify_data ); + } + + /** + * Rebuild the attachment stats and store the data. + * Delete all Imagify data if the sizes are empty. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param array $imagify_data Imagify data. + * @return bool True on update, false on delete or prevented. + */ + public function update_imagify_data( $attachment, $imagify_data ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return false; + } + + if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { + // No new sizes. + $attachment->delete_imagify_data(); + return false; + } + + $imagify_data['stats'] = array( + 'original_size' => 0, + 'optimized_size' => 0, + 'percent' => 0, + ); + + foreach ( $imagify_data['sizes'] as $size_data ) { + $imagify_data['stats']['original_size'] += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0; + $imagify_data['stats']['optimized_size'] += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0; + } + + if ( $imagify_data['stats']['original_size'] && $imagify_data['stats']['optimized_size'] ) { + $imagify_data['stats']['percent'] = round( ( ( $imagify_data['stats']['original_size'] - $imagify_data['stats']['optimized_size'] ) / $imagify_data['stats']['original_size'] ) * 100, 2 ); + } + + update_post_meta( $attachment->get_id(), '_imagify_data', $imagify_data ); + + return true; + } + + /** + * Remove a size from Imagify data. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $size_name Name of the size. + */ + public function remove_size_from_imagify_data( $attachment, $size_name ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return; + } + + $imagify_data = $attachment->get_data(); + + if ( ! isset( $imagify_data['sizes'][ $size_name ] ) ) { + return; + } + + unset( $imagify_data['sizes'][ $size_name ] ); + + $this->update_imagify_data( $attachment, $imagify_data ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** INTERNAL TOOLS ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Tell if a file extension is supported by WP Retina 2x. + * It uses $wr2x_core->is_supported_image() if available. + * + * @since 1.8 + * @access public + * @see $wr2x_core->is_supported_image() + * @author Grégory Viguier + * + * @param string|int $file_path Path to the file or attachment ID. + * @return bool + */ + public function is_supported_format( $file_path ) { + global $wr2x_core; + static $method; + static $results = array(); + + if ( ! $file_path ) { + return false; + } + + if ( isset( $results[ $file_path ] ) ) { + // $file_path can be a path or an attachment ID. + return $results[ $file_path ]; + } + + if ( is_int( $file_path ) ) { + $attachment_id = $file_path; + $file_path = get_attached_file( $attachment_id ); + + if ( ! $file_path ) { + $results[ $attachment_id ] = false; + return false; + } + + if ( isset( $results[ $file_path ] ) ) { + // $file_path is now a path for sure. + $results[ $attachment_id ] = $results[ $file_path ]; + return $results[ $file_path ]; + } + } + + if ( ! isset( $method ) ) { + if ( $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'is_supported_image' ) ) { + $method = array( $wr2x_core, 'is_supported_image' ); + } else { + $method = array( $this, 'is_supported_extension' ); + } + } + + // $file_path is now a path for sure. + $results[ $file_path ] = call_user_func( $method, $file_path ); + + if ( ! empty( $attachment_id ) ) { + $results[ $attachment_id ] = $results[ $file_path ]; + } + + return $results[ $file_path ]; + } + + /** + * Tell if a file extension is supported by WP Retina 2x. + * Internal version of $wr2x_core->is_supported_image(). + * + * @since 1.8 + * @access public + * @see $this->is_supported_format() + * @author Grégory Viguier + * + * @param string $file_path Path to a file. + * @return bool + */ + protected function is_supported_extension( $file_path ) { + $extension = strtolower( $this->filesystem->path_info( $file_path, 'extension' ) ); + $extensions = array( + 'jpg' => 1, + 'jpeg' => 1, + 'png' => 1, + 'gif' => 1, + ); + + return isset( $extensions[ $extension ] ); + } + + /** + * Get the path to the retina version of an image. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $file_path Path to the non-retina image. + * @return string + */ + public function get_retina_path( $file_path ) { + $path_info = $this->filesystem->path_info( $file_path ); + $suffix = rtrim( $this->get_suffix(), '.' ); + $extension = isset( $path_info['extension'] ) ? '.' . $path_info['extension'] : ''; + + return $path_info['dir_path'] . $path_info['file_base'] . $suffix . $extension; + } + + /** + * Tell if the attchment has at least one retina image. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool + */ + public function has_retina_images( $attachment ) { + $dir_path = $this->filesystem->path_info( $attachment->get_original_path(), 'dir_path' ); + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); + $sizes['full'] = array( + 'file' => $this->filesystem->file_name( $metadata['file'] ), + ); + + foreach ( $sizes as $name => $attr ) { + $size_path = $this->get_retina_path( $dir_path . $attr['file'] ); + + if ( $this->filesystem->exists( $size_path ) ) { + return true; + } + } + + return false; + } + + /** + * Prevent a method to do its job. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $method_name Name of the method to prevent. + */ + public function prevent( $method_name ) { + if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] < 1 ) { + self::$prevented[ $method_name ] = 1; + } else { + ++self::$prevented[ $method_name ]; + } + } + + /** + * Allow a method to do its job. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $method_name Name of the method to allow. + */ + public function allow( $method_name ) { + if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] <= 1 ) { + unset( self::$prevented[ $method_name ] ); + } else { + --self::$prevented[ $method_name ]; + } + } + + /** + * Tell if a method is prevented to do its job. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $method_name Name of the method. + * @return bool + */ + public function is_prevented( $method_name ) { + return ! empty( self::$prevented[ $method_name ] ); + } + + /** + * Tell if a thumbnail size is disallowed for optimization.. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $size_name The size name. + * @return bool + */ + public function size_is_disallowed( $size_name ) { + static $disallowed_sizes; + + if ( imagify_is_active_for_network() ) { + return false; + } + + if ( ! isset( $disallowed_sizes ) ) { + $disallowed_sizes = get_imagify_option( 'disallowed-sizes' ); + } + + return isset( $disallowed_sizes[ $size_name ] ); + } + + /** + * Tell if a thumbnail file is a copy of the full size image. Will return false if the full size is not optimized. + * Make sure both files exist before using this. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $args { + * An array of arguments. + * + * @type string $size_name The size name. Required. + * @type array $metadata WP metadata. Required. + * @type array $imagify_data Imagify data. Required. + * @type string $retina_path Path to the image we're testing. Required. + * @type string $full_path Path to the full size image. Optional but should be provided. + * } + * @return bool + */ + public function size_is_a_full_copy( $args ) { + $size_name = $args['size_name']; + $metadata = $args['metadata']; + $imagify_data = $args['imagify_data']; + + if ( empty( $imagify_data['sizes']['full'] ) ) { + // The full size is not optimized, so there is no point in checking if the given file is a copy. + return false; + } + + if ( ! isset( $metadata['width'], $metadata['height'], $metadata['file'] ) ) { + return false; + } + + if ( ! isset( $metadata['sizes'][ $size_name ]['width'], $metadata['sizes'][ $size_name ]['height'] ) ) { + return false; + } + + $size = $metadata['sizes'][ $size_name ]; + + if ( $size['width'] * 2 !== $metadata['width'] || $size['height'] * 2 !== $metadata['height'] ) { + // The full size image doesn't have the right dimensions. + return false; + } + + if ( empty( $args['full_path'] ) ) { + $dir_path = $this->filesystem->path_info( $args['retina_path'], 'dir_path' ); + $args['full_path'] = $dir_path . $metadata['file']; + } + + $full_hash = md5_file( $args['full_path'] ); + $retina_hash = md5_file( $args['retina_path'] ); + + return hash_equals( $full_hash, $retina_hash ); + } + + /** + * Tell if there is a filesystem error. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return bool + */ + public function has_filesystem_error() { + return ! empty( $this->filesystem->errors->errors ); + } + + /** + * Do few tests: method is not prevented, attachment is valid, filesystem has no error. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $method The name of the method using this. + * @param object $attachment An Imagify attachment. + * @param array $args A list of additional tests. + * @return bool|object True if ok, false if prevented, a WP_Error object on failure. + */ + public function validate( $method, $attachment = false, $args = array() ) { + $args = array_merge( array( + 'supported' => false, + 'can_optimize' => false, + 'metadata_dimensions' => false, + 'metadata_file' => $attachment ? 'error' : false, + 'metadata_sizes' => $attachment ? 'error' : false, + ), $args ); + + if ( $this->is_prevented( $method ) ) { + return false; + } + + if ( $attachment && ! $attachment->is_valid() ) { + return new WP_Error( 'invalid_attachment', __( 'Invalid attachment.', 'imagify' ) ); + } + + if ( $args['supported'] && ! $attachment->is_extension_supported() ) { + if ( 'error' !== $args['supported'] ) { + return false; + } + + return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); + } + + if ( $args['can_optimize'] ) { + if ( 'error' !== $args['can_optimize'] ) { + if ( ! Imagify_Requirements::is_api_key_valid() || Imagify_Requirements::is_over_quota() ) { + return false; + } + } else { + if ( ! Imagify_Requirements::is_api_key_valid() ) { + return new WP_Error( 'invalid_api_key', __( 'Your API key is not valid!', 'imagify' ) ); + } + if ( Imagify_Requirements::is_over_quota() ) { + return new WP_Error( 'over_quota', __( 'You have used all your credits!', 'imagify' ) ); + } + } + } + + if ( $this->has_filesystem_error() ) { + return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) ); + } + + if ( $args['metadata_dimensions'] ) { + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + + if ( empty( $metadata['width'] ) || empty( $metadata['height'] ) ) { + if ( 'error' !== $args['metadata_sizes'] ) { + return false; + } + + return new WP_Error( 'metadata_dimensions', __( 'This attachment lacks the required metadata.', 'imagify' ) ); + } + } + + if ( $args['metadata_file'] ) { + $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() ); + + if ( empty( $metadata['file'] ) ) { + if ( 'error' !== $args['metadata_file'] ) { + return false; + } + + return new WP_Error( 'metadata_file', __( 'This attachment lacks the required metadata.', 'imagify' ) ); + } + } + + if ( $args['metadata_sizes'] ) { + $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() ); + + if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) { + if ( 'error' !== $args['metadata_sizes'] ) { + return false; + } + + return new WP_Error( 'metadata_sizes', __( 'This attachment has no registered thumbnail sizes.', 'imagify' ) ); + } + } + + return true; + } + + /** + * Tell if Imagify can optimize the files. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return bool + */ + public function can_optimize() { + return ! $this->has_filesystem_error() && Imagify_Requirements::is_api_key_valid() && ! Imagify_Requirements::is_over_quota(); + } + + /** + * Tell if Imagify can auto-optimize the files. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return bool + */ + public function can_auto_optimize() { + return $this->can_optimize() && get_imagify_option( 'auto_optimize' ); + } + + /** + * Get thumbnail sizes from an attachment. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return array + */ + public function get_attachment_sizes( $attachment ) { + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + return ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); + } + + /** + * Get the optimization level used to optimize the given attachment. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return int The attachment optimization level. The default level if not optimized. + */ + public function get_optimization_level( $attachment ) { + static $default; + + if ( $attachment->get_status() ) { + $level = $attachment->get_optimization_level(); + + if ( is_int( $level ) ) { + return $level; + } + } + + if ( ! isset( $default ) ) { + $default = get_imagify_option( 'optimization_level' ); + } + + return $default; + } + + /** + * Get the path to the temporary file. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $file_path The optimized full-sized file path. + * @return string + */ + public function get_temporary_file_path( $file_path ) { + return $file_path . '_backup'; + } + + /** + * Backup the optimized full-sized file and replace it by the original backup file. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + */ + public function backup_optimized_file( $attachment ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return; + } + + $backup_path = $attachment->get_backup_path(); + + if ( ! $backup_path || ! $attachment->is_optimized() ) { + return; + } + + /** + * Replace the optimized full-sized file by the backup, so any optimization will not use an optimized file, but the original one. + * The optimized full-sized file is kept and renamed, and will be put back in place at the end of the optimization process. + */ + $file_path = $attachment->get_original_path(); + $tmp_file_path = $this->get_temporary_file_path( $file_path ); + + if ( $this->filesystem->exists( $file_path ) ) { + $this->filesystem->move( $file_path, $tmp_file_path, true ); + } + + $copied = $this->filesystem->copy( $backup_path, $file_path ); + + if ( ! $copied ) { + // Uh ho... + $this->filesystem->move( $tmp_file_path, $file_path, true ); + return; + } + + // Make sure the dimensions are in sync in post meta. + $this->maybe_update_image_dimensions( $attachment, $file_path ); + } + + /** + * Put the optimized full-sized file back. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + */ + public function put_optimized_file_back( $attachment ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return; + } + + $file_path = $attachment->get_original_path(); + $tmp_file_path = $this->get_temporary_file_path( $file_path ); + + if ( ! $this->filesystem->exists( $tmp_file_path ) ) { + return; + } + + $moved = $this->filesystem->move( $tmp_file_path, $file_path, true ); + + if ( ! $moved ) { + // Uh ho... + return; + } + + // Make sure the dimensions are in sync in post meta. + $this->maybe_update_image_dimensions( $attachment, $file_path ); + } + + /** + * Make sure the dimensions are in sync in post meta. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $file_path Path to the file. + * @return bool True when updated. + */ + public function maybe_update_image_dimensions( $attachment, $file_path ) { + if ( $this->is_prevented( __FUNCTION__ ) ) { + return false; + } + + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + $width = ! empty( $metadata['width'] ) ? (int) $metadata['width'] : 0; + $height = ! empty( $metadata['height'] ) ? (int) $metadata['height'] : 0; + $dimensions = $this->filesystem->get_image_size( $file_path ); + + if ( ! $dimensions ) { + return false; + } + + if ( $width === $dimensions['width'] && $height === $dimensions['height'] ) { + return false; + } + + $metadata['width'] = $dimensions['width']; + $metadata['height'] = $dimensions['height']; + + // Prevent auto-optimization. + Imagify_Auto_Optimization::prevent_optimization( $attachment_id ); + + wp_update_attachment_metadata( $attachment->get_id(), $metadata ); + + // Allow auto-optimization back. + Imagify_Auto_Optimization::allow_optimization( $attachment_id ); + return true; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** WR2X COMPAT' TOOLS ====================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the suffix added to the file name, with a trailing dot. + * Don't use it for the size name. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return string + */ + public function get_suffix() { + global $wr2x_core; + static $suffix; + + if ( ! isset( $suffix ) ) { + $suffix = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_extension' ) ? $wr2x_core->retina_extension() : '@2x.'; + } + + return $suffix; + } + + /** + * Get info about retina version. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @param string $type The type of info. Possible values are 'basic' and 'full' (for the full size). + * @return array An array containing some HTML, indexed by the attachment ID. + */ + public function get_retina_info( $attachment, $type = 'basic' ) { + global $wr2x_core; + static $can_get_info; + + if ( ! isset( $can_get_info ) ) { + $can_get_info = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_info' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info_full' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info' ); + } + + if ( ! $can_get_info ) { + return ''; + } + + $attachment_id = $attachment->get_id(); + $info = $wr2x_core->retina_info( $attachment_id ); + + if ( 'full' === $type ) { + return array( + $attachment_id => $wr2x_core->html_get_basic_retina_info_full( $attachment_id, $info ), + ); + } + + return array( + $attachment_id => $wr2x_core->html_get_basic_retina_info( $attachment_id, $info ), + ); + } + + /** + * Log. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $text Text to log. + */ + public function log( $text ) { + global $wr2x_core; + static $can_log; + + if ( ! isset( $can_log ) ) { + $can_log = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'log' ); + } + + if ( $can_log ) { + $wr2x_core->log( $text ); + } + } +} diff --git a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php b/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php new file mode 100644 index 000000000..9c332449a --- /dev/null +++ b/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php @@ -0,0 +1,681 @@ +core ) ) { + $this->core = new Imagify_WP_Retina_2x_Core(); + } + + return $this->core; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** INIT ==================================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Launch the hooks. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function init() { + // Deal with Imagify when WPR2X is working. + add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 ); + add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 ); + add_action( 'wp_ajax_wr2x_delete_full', array( $this, 'wr2x_delete_full_retina_ajax_cb' ), 5 ); + add_action( 'wp_ajax_wr2x_replace', array( $this, 'wr2x_replace_all_ajax_cb' ), 5 ); + add_action( 'wp_ajax_wr2x_upload', array( $this, 'wr2x_replace_full_retina_ajax_cb' ), 5 ); + add_action( 'imagify_assets_enqueued', array( $this, 'enqueue_scripts' ) ); + add_action( 'wr2x_retina_file_removed', array( $this, 'remove_retina_thumbnail_data_hook' ), 10, 2 ); + // Deal with Imagify when WP is working. + add_action( 'delete_attachment', array( $this, 'delete_full_retina_backup_file_hook' ) ); + // Deal with retina thumbnails when Imagify processes the "normal" images. + add_filter( 'imagify_fill_full_size_data', array( $this, 'optimize_full_retina_version_hook' ), 10, 8 ); + add_filter( 'imagify_fill_thumbnail_data', array( $this, 'optimize_retina_version_hook' ), 10, 8 ); + add_filter( 'imagify_fill_unauthorized_thumbnail_data', array( $this, 'maybe_optimize_unauthorized_retina_version_hook' ), 10, 7 ); + add_action( 'after_imagify_restore_attachment', array( $this, 'restore_retina_images_hook' ) ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** AJAX CALLBACKS ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * (Re)generate the retina thumbnails (except the full size). + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function wr2x_generate_ajax_cb() { + $this->check_nonce( 'imagify_wr2x_generate' ); + $this->check_user_capacity(); + + $attachment = $this->get_requested_attachment( 'wr2x_generate' ); + + // Delete previous retina images and recreate them. + $result = $this->get_core()->regenerate_retina_images( $attachment ); + + // Send results. + $this->maybe_send_json_error( $result ); + + $this->send_json( array( + 'results' => $this->get_core()->get_retina_info( $attachment ), + 'message' => __( 'Retina files generated.', 'imagify' ), + 'imagify_info' => $this->get_imagify_info( $attachment ), + ) ); + } + + /** + * Delete all retina images, including the one for the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function wr2x_delete_all_retina_ajax_cb() { + $this->check_nonce( 'imagify_wr2x_delete' ); + $this->check_user_capacity(); + + $attachment = $this->get_requested_attachment( 'wr2x_delete_all' ); + + // Delete the retina versions, including the full size. + $result = $this->get_core()->delete_retina_images( $attachment, true ); + + // Send results. + $this->maybe_send_json_error( $result ); + + $this->send_json( array( + 'results' => $this->get_core()->get_retina_info( $attachment ), + 'results_full' => $this->get_core()->get_retina_info( $attachment, 'full' ), + 'message' => __( 'Retina files deleted.', 'imagify' ), + ) ); + } + + /** + * Delete the retina version of the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function wr2x_delete_full_retina_ajax_cb() { + $this->check_nonce( 'imagify_wr2x_delete_full' ); + $this->check_user_capacity(); + + $attachment = $this->get_requested_attachment( 'wr2x_delete_full' ); + + $result = $this->get_core()->delete_full_retina_image( $attachment ); + + // Send results. + $this->maybe_send_json_error( $result ); + + $this->send_json( array( + 'results' => $this->get_core()->get_retina_info( $attachment, 'full' ), + 'message' => __( 'Full retina file deleted.', 'imagify' ), + ) ); + } + + /** + * Replace an attachment (except the retina version of the full size). + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function wr2x_replace_all_ajax_cb() { + $this->check_nonce( 'imagify_wr2x_replace' ); + $this->check_user_capacity(); + + $attachment = $this->get_requested_attachment( 'wr2x_replace_all' ); + $tmp_file_path = $this->get_uploaded_file_path(); + + $result = $this->get_core()->replace_attachment( $attachment, $tmp_file_path ); + + // Send results. + $this->maybe_send_json_error( $result ); + + $this->send_json( array( + 'results' => $this->get_core()->get_retina_info( $attachment ), + 'message' => __( 'Images replaced successfully.', 'imagify' ), + ) ); + } + + /** + * Upload a new retina version for the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function wr2x_replace_full_retina_ajax_cb() { + $this->check_nonce( 'imagify_wr2x_upload' ); + $this->check_user_capacity(); + + $attachment = $this->get_requested_attachment( 'wr2x_replace_full' ); + $tmp_file_path = $this->get_uploaded_file_path(); + + $result = $this->get_core()->replace_full_retina_image( $attachment, $tmp_file_path ); + + // Send results. + $this->maybe_send_json_error( $result ); + + $this->send_json( array( + 'results' => $this->get_core()->get_retina_info( $attachment ), + 'message' => __( 'Image replaced successfully.', 'imagify' ), + ) ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** OTHER HOOKS ============================================================================= */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Queue some JS to add our nonce parameter to all WR2X jQuery ajax requests. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function enqueue_scripts() { + if ( ! $this->user_can() ) { + return; + } + + $assets = Imagify_Assets::get_instance(); + + $assets->register_script( 'weakmap-polyfill', 'weakmap-polyfill', array(), '2.0.0' ); + $assets->register_script( 'formdata-polyfill', 'formdata-polyfill', array( 'weakmap-polyfill' ), '3.0.10-beta' ); + $assets->register_script( 'wp-retina-2x', 'imagify-wp-retina-2x', array( 'formdata-polyfill', 'jquery' ) ); + + if ( imagify_is_screen( 'library' ) || imagify_is_screen( 'media_page_wp-retina-2x' ) ) { + $assets->localize_script( 'wp-retina-2x', 'imagifyRetina2x', array( + 'wr2x_generate' => wp_create_nonce( 'imagify_wr2x_generate' ), + 'wr2x_delete' => wp_create_nonce( 'imagify_wr2x_delete' ), + 'wr2x_delete_full' => wp_create_nonce( 'imagify_wr2x_delete_full' ), + 'wr2x_replace' => wp_create_nonce( 'imagify_wr2x_replace' ), + 'wr2x_upload' => wp_create_nonce( 'imagify_wr2x_upload' ), + ) ); + $assets->enqueue( 'wp-retina-2x' ); + } + } + + /** + * After a retina thumbnail is deleted, remove its Imagify data. + * This should be useless since we replaced every AJAX callbacks. + * + * @since 1.8 + * @access public + * @see wr2x_delete_attachment() + * @author Grégory Viguier + * + * @param int $attachment_id An attachment ID. + * @param string $retina_filename The retina thumbnail file name. + */ + public function remove_retina_thumbnail_data_hook( $attachment_id, $retina_filename ) { + $attachment = get_imagify_attachment( 'wp', $attachment_id, 'wr2x_delete' ); + + $this->get_core()->remove_retina_image_data_by_filename( $attachment, $retina_filename ); + } + + /** + * Delete the backup of the retina version of the full size file when an attachement is deleted. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param int $attachment_id An attachment ID. + */ + public function delete_full_retina_backup_file_hook( $attachment_id ) { + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + return; + } + + $attachment = get_imagify_attachment( 'wp', $attachment_id, 'delete_attachment' ); + $retina_path = $this->get_core()->get_retina_path( $attachment->get_original_path() ); + + if ( $retina_path ) { + $this->get_core()->delete_file_backup( $retina_path ); + } + } + + /** + * Filter the optimization data of the full size. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $data The statistics data. + * @param object $response The API response. + * @param int $attachment_id The attachment ID. + * @param string $path The attachment path. + * @param string $url The attachment URL. + * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for oncistancy with other filters. + * @param int $optimization_level The optimization level. + * @param array $metadata WP metadata. + * @return array $data The new optimization data. + */ + public function optimize_full_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + return $data; + } + + $attachment = get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' ); + + return $this->get_core()->optimize_retina_image( array( + 'data' => $data, + 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' ), + 'retina_path' => wr2x_get_retina( $path ), + 'size_key' => $size_key, + 'optimization_level' => $optimization_level, + 'metadata' => $metadata, + ) ); + } + + /** + * Filter the optimization data of each thumbnail. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $data The statistics data. + * @param object $response The API response. + * @param int $attachment_id The attachment ID. + * @param string $path The thumbnail path. + * @param string $url The thumbnail URL. + * @param string $size_key The thumbnail size key. + * @param int $optimization_level The optimization level. + * @param array $metadata WP metadata. + * @return array $data The new optimization data. + */ + public function optimize_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + return $data; + } + + return $this->get_core()->optimize_retina_image( array( + 'data' => $data, + 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_retina_version_hook' ), + 'retina_path' => wr2x_get_retina( $path ), + 'size_key' => $size_key, + 'optimization_level' => $optimization_level, + 'metadata' => $metadata, + ) ); + } + + /** + * If a thumbnail size is disallowed in Imagify' settings, we can still try to optimize its "@2x" version. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $data The statistics data. + * @param int $attachment_id The attachment ID. + * @param string $path The thumbnail path. + * @param string $url The thumbnail URL. + * @param string $size_key The thumbnail size key. + * @param int $optimization_level The optimization level. + * @param array $metadata WP metadata. + * @return array $data The new optimization data. + */ + public function maybe_optimize_unauthorized_retina_version_hook( $data, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + return $data; + } + + return $this->get_core()->optimize_retina_image( array( + 'data' => $data, + 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'maybe_optimize_unauthorized_retina_version_hook' ), + 'retina_path' => wr2x_get_retina( $path ), + 'size_key' => $size_key, + 'optimization_level' => $optimization_level, + 'metadata' => $metadata, + ) ); + } + + /** + * Delete previous retina images and recreate them. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param int $attachment_id An attachment ID. + */ + public function restore_retina_images_hook( $attachment_id ) { + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + return; + } + + $attachment = get_imagify_attachment( 'wp', $attachment_id, 'restore_retina_images_hook' ); + + if ( ! $this->get_core()->has_retina_images( $attachment ) ) { + return; + } + + // At this point, previous Imagify data has been removed. + $this->get_core()->regenerate_retina_images( $attachment ); + $this->get_core()->restore_full_retina_file( $attachment ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** INTERNAL TOOLS ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Check for nonce. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $action Action nonce. + * @param string|bool $query_arg Optional. Key to check for the nonce in `$_REQUEST`. If false, `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce' (in that order). Default false. + */ + public function check_nonce( $action, $query_arg = 'imagify_nonce' ) { + if ( ! check_ajax_referer( $action, $query_arg, false ) ) { + $this->send_json( array( + 'success' => false, + 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ), + ) ); + } + } + + /** + * Check for user capacity. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function check_user_capacity() { + if ( ! $this->user_can() ) { + $this->send_json( array( + 'success' => false, + 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ), + ) ); + } + } + + /** + * Tell if the current user can re-optimize files. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + */ + public function user_can() { + return imagify_current_user_can( 'auto-optimize' ); + } + + /** + * Shorthand to get the attachment ID sent via $_POST. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param string $context The context to use in get_imagify_attachment(). + * @param string $key The $_POST key. + * @return int $attachment_id + */ + public function get_requested_attachment( $context, $key = 'attachmentId' ) { + $attachment_id = filter_input( INPUT_POST, $key, FILTER_VALIDATE_INT ); + + if ( $attachment_id <= 0 ) { + $this->send_json( array( + 'success' => false, + 'message' => __( 'The attachment ID is missing.', 'imagify' ), + ) ); + } + + if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + $this->send_json( array( + 'success' => false, + 'message' => __( 'This format is not supported.', 'imagify' ), + ) ); + } + + $attachment = get_imagify_attachment( 'wp', $attachment_id, $context ); + + if ( ! $this->has_required_metadata( $attachment ) ) { + $this->send_json( array( + 'success' => false, + 'message' => __( 'This attachment lacks the required metadata.', 'imagify' ), + ) ); + } + + return $attachment; + } + + /** + * Shorthand to get the path to the uploaded file. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return string Path to the temporary file. + */ + public function get_uploaded_file_path() { + $tmp_file_path = ! empty( $_FILES['file']['tmp_name'] ) ? wp_unslash( $_FILES['file']['tmp_name'] ) : ''; + $tmp_file_path = $tmp_file_path && is_uploaded_file( $tmp_file_path ) ? $tmp_file_path : ''; + $filesystem = Imagify_Filesystem::get_instance(); + + if ( ! $tmp_file_path || ! $filesystem->is_image( $tmp_file_path ) ) { + $this->get_core()->log( 'The file is not an image or the upload went wrong.' ); + $filesystem->delete( $tmp_file_path ); + + $this->send_json_string( array( + 'success' => false, + 'message' => __( 'The file is not an image or the upload went wrong.', 'imagify' ), + ) ); + } + + $file_name = filter_input( INPUT_POST, 'filename', FILTER_SANITIZE_STRING ); + $file_data = wp_check_filetype_and_ext( $tmp_file_path, $file_name ); + + if ( empty( $file_data['ext'] ) ) { + $this->get_core()->log( 'You cannot use this file (wrong extension? wrong type?).' ); + $filesystem->delete( $tmp_file_path ); + + $this->send_json_string( array( + 'success' => false, + 'message' => __( 'You cannot use this file (wrong extension? wrong type?).', 'imagify' ), + ) ); + } + + $this->get_core()->log( 'The temporary file was written successfully.' ); + + return $tmp_file_path; + } + + /** + * Tell if Imagify's column content has been requested. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @return bool + */ + public function needs_info() { + return filter_input( INPUT_POST, 'imagify_info', FILTER_VALIDATE_INT ) === 1; + } + + /** + * Tell if the attachment has the required WP metadata. + * + * @since 1.8 + * @access public + * @see $wr2x_core->is_image_meta() + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return bool + */ + public function has_required_metadata( $attachment ) { + if ( ! $attachment->has_required_metadata() ) { + return false; + } + + $metadata = wp_get_attachment_metadata( $attachment->get_id() ); + + if ( ! isset( $metadata['sizes'], $metadata['width'], $metadata['height'] ) ) { + return false; + } + + return is_array( $metadata['sizes'] ); + } + + /** + * Get info about Imagify. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param object $attachment An Imagify attachment. + * @return array An array containing some HTML, indexed by the attachment ID. + */ + public function get_imagify_info( $attachment ) { + if ( ! $this->needs_info() ) { + return array(); + } + + return array( + $attachment->get_id() => get_imagify_media_column_content( $attachment ), + ); + } + + /** + * Send a JSON response back to an Ajax request. + * It sends a "success" by default. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param array $data An array of data to print and die. + */ + public function send_json( $data ) { + // Use the same JSON format than WPR2X. + $data = array_merge( array( + 'success' => true, + 'message' => '', + 'source' => 'imagify', + 'context' => 'wr2x', + ), $data ); + + echo wp_json_encode( $data ); + die; + } + + /** + * Send a JSON error response if the given argument is a WP_Error object. + * + * @since 1.8 + * @access public + * @author Grégory Viguier + * + * @param mixed $result Result of an operation. + */ + public function maybe_send_json_error( $result ) { + if ( ! is_wp_error( $result ) ) { + return; + } + + // Oh no. + $this->send_json( array( + 'success' => false, + 'message' => $result->get_error_message(), + ) ); + } +} diff --git a/inc/3rd-party/perfect-images/wp-retina-2x.php b/inc/3rd-party/perfect-images/wp-retina-2x.php new file mode 100755 index 000000000..067d2f956 --- /dev/null +++ b/inc/3rd-party/perfect-images/wp-retina-2x.php @@ -0,0 +1,8 @@ +init(); From de402a0f31964f9d1e2f96bb4603d11d4150c41a Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 31 Jan 2022 10:53:40 -0500 Subject: [PATCH 02/21] Update namespacing, classnames and autoloading PHPCS - update minimum PHP version to 7.0 Rename wp-retina-2x classes to PerfectImages and add namespace Add PerfectImages namespace to composer PSR4 Use new class names in 3rd-party PerfectImages loader file Add PerfectImages fileloader to 3rd-party required files Stop using deprecated user_can() check - use WP context user check instead Update asset script localization to use updated enqueue --- composer.json | 1 + inc/3rd-party/3rd-party.php | 1 + .../PerfectImages.php} | 55 ++++++------------- .../PerfectImagesCore.php} | 5 +- inc/3rd-party/perfect-images/wp-retina-2x.php | 5 +- inc/functions/common.php | 2 +- phpcs.xml | 6 +- 7 files changed, 30 insertions(+), 45 deletions(-) rename inc/3rd-party/perfect-images/{inc/classes/class-imagify-wp-retina-2x.php => classes/PerfectImages.php} (95%) rename inc/3rd-party/perfect-images/{inc/classes/class-imagify-wp-retina-2x-core.php => classes/PerfectImagesCore.php} (99%) diff --git a/composer.json b/composer.json index 2a017ddb7..0a9f6d213 100644 --- a/composer.json +++ b/composer.json @@ -50,6 +50,7 @@ "Imagify\\ThirdParty\\EnableMediaReplace\\": "inc/3rd-party/enable-media-replace/classes/", "Imagify\\ThirdParty\\FormidablePro\\": "inc/3rd-party/formidable-pro/classes/", "Imagify\\ThirdParty\\NGG\\": "inc/3rd-party/nextgen-gallery/classes/", + "Imagify\\ThirdParty\\PerfectImages\\": "inc/3rd-party/perfect-images/classes", "Imagify\\ThirdParty\\RegenerateThumbnails\\": "inc/3rd-party/regenerate-thumbnails/classes/", "Imagify\\ThirdParty\\WPRocket\\": "inc/3rd-party/wp-rocket/classes/" }, diff --git a/inc/3rd-party/3rd-party.php b/inc/3rd-party/3rd-party.php index 35190d7bb..09eb224b5 100755 --- a/inc/3rd-party/3rd-party.php +++ b/inc/3rd-party/3rd-party.php @@ -9,6 +9,7 @@ require IMAGIFY_PATH . 'inc/3rd-party/enable-media-replace/enable-media-replace.php'; require IMAGIFY_PATH . 'inc/3rd-party/formidable-pro/formidable-pro.php'; require IMAGIFY_PATH . 'inc/3rd-party/nextgen-gallery/nextgen-gallery.php'; +require IMAGIFY_PATH . 'inc/3rd-party/perfect-images/wp-retina-2x.php'; require IMAGIFY_PATH . 'inc/3rd-party/regenerate-thumbnails/regenerate-thumbnails.php'; require IMAGIFY_PATH . 'inc/3rd-party/screets-lc.php'; require IMAGIFY_PATH . 'inc/3rd-party/wp-real-media-library.php'; diff --git a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php similarity index 95% rename from inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php rename to inc/3rd-party/perfect-images/classes/PerfectImages.php index 9c332449a..bdd4eeb24 100644 --- a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -1,5 +1,8 @@ core ) ) { - $this->core = new Imagify_WP_Retina_2x_Core(); + $this->core = new PerfectImagesCore(); } return $this->core; @@ -277,8 +265,7 @@ public function enqueue_scripts() { 'wr2x_delete_full' => wp_create_nonce( 'imagify_wr2x_delete_full' ), 'wr2x_replace' => wp_create_nonce( 'imagify_wr2x_replace' ), 'wr2x_upload' => wp_create_nonce( 'imagify_wr2x_upload' ), - ) ); - $assets->enqueue( 'wp-retina-2x' ); + ) )->enqueue(); } } @@ -470,12 +457,8 @@ public function check_nonce( $action, $query_arg = 'imagify_nonce' ) { /** * Check for user capacity. - * - * @since 1.8 - * @access public - * @author Grégory Viguier */ - public function check_user_capacity() { + private function check_user_capacity() { if ( ! $this->user_can() ) { $this->send_json( array( 'success' => false, @@ -487,12 +470,10 @@ public function check_user_capacity() { /** * Tell if the current user can re-optimize files. * - * @since 1.8 - * @access public - * @author Grégory Viguier + * @return bool */ - public function user_can() { - return imagify_current_user_can( 'auto-optimize' ); + private function user_can(): bool { + return imagify_get_context( 'wp' )->current_user_can( 'auto-optimize' ); } /** diff --git a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php b/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php similarity index 99% rename from inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php rename to inc/3rd-party/perfect-images/classes/PerfectImagesCore.php index 1916d69ca..0f8b11a2e 100644 --- a/inc/3rd-party/perfect-images/inc/classes/class-imagify-wp-retina-2x-core.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php @@ -1,5 +1,6 @@ init(); +PerfectImages::get_instance()->init(); diff --git a/inc/functions/common.php b/inc/functions/common.php index 680c27d65..ef72a3d75 100755 --- a/inc/functions/common.php +++ b/inc/functions/common.php @@ -55,7 +55,7 @@ function imagify_sanitize_context( $context ) { * @since 1.9 * @author Grégory Viguier * - * @param string $context The context name. Default values are 'wp' and 'custom-folders'. + * @param string $context The context name. Accepted values are 'wp' and 'custom-folders'. * @return ContextInterface The context instance. */ function imagify_get_context( $context ) { diff --git a/phpcs.xml b/phpcs.xml index aee22fdfc..d59591b54 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -27,9 +27,9 @@ - - - + + + From 48bce9e4ce9438658bef4794794148c6d26ab203 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 31 Jan 2022 13:14:17 -0500 Subject: [PATCH 03/21] Use Imagify_Filesystem --- inc/3rd-party/perfect-images/classes/PerfectImagesCore.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php b/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php index 0f8b11a2e..047b479e4 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php @@ -2,6 +2,8 @@ namespace Imagify\ThirdParty\PerfectImages; +use Imagify_Filesystem; + /** * Class that handles all the main tools for compatibility with WP Retina 2x plugin. * From 4ffeb21d5bbe2e47748ac232cdc51313326a67b7 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 31 Jan 2022 13:39:13 -0500 Subject: [PATCH 04/21] Add back js assets previously removed The 3 assets here were included by the previous compatibility. (Not sure yet if we will still need them, but adding them back because they are still trying to be loaded on library page -- until we can sort it out.) --- assets/js/formdata-polyfill.js | 390 ++++++++++++++++++++++++++++++ assets/js/imagify-wp-retina-2x.js | 87 +++++++ assets/js/weakmap-polyfill.js | 144 +++++++++++ 3 files changed, 621 insertions(+) create mode 100644 assets/js/formdata-polyfill.js create mode 100644 assets/js/imagify-wp-retina-2x.js create mode 100755 assets/js/weakmap-polyfill.js diff --git a/assets/js/formdata-polyfill.js b/assets/js/formdata-polyfill.js new file mode 100644 index 000000000..b54995c3b --- /dev/null +++ b/assets/js/formdata-polyfill.js @@ -0,0 +1,390 @@ +if (typeof FormData === 'undefined' || !FormData.prototype.keys) { + const global = typeof window === 'object' + ? window : typeof self === 'object' + ? self : this + + // keep a reference to native implementation + const _FormData = global.FormData + + // To be monkey patched + const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send + const _fetch = global.Request && global.fetch + + // Unable to patch Request constructor correctly + // const _Request = global.Request + // only way is to use ES6 class extend + // https://github.com/babel/babel/issues/1966 + + const stringTag = global.Symbol && Symbol.toStringTag + const map = new WeakMap + const wm = o => map.get(o) + const arrayFrom = Array.from || (obj => [].slice.call(obj)) + + // Add missing stringTags to blob and files + if (stringTag) { + if (!Blob.prototype[stringTag]) { + Blob.prototype[stringTag] = 'Blob' + } + + if ('File' in global && !File.prototype[stringTag]) { + File.prototype[stringTag] = 'File' + } + } + + // Fix so you can construct your own File + try { + new File([], '') + } catch (a) { + global.File = function(b, d, c) { + const blob = new Blob(b, c) + const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date + + Object.defineProperties(blob, { + name: { + value: d + }, + lastModifiedDate: { + value: t + }, + lastModified: { + value: +t + }, + toString: { + value() { + return '[object File]' + } + } + }) + + if (stringTag) { + Object.defineProperty(blob, stringTag, { + value: 'File' + }) + } + + return blob + } + } + + function normalizeValue([value, filename]) { + if (value instanceof Blob) + // Should always returns a new File instance + // console.assert(fd.get(x) !== fd.get(x)) + value = new File([value], filename, { + type: value.type, + lastModified: value.lastModified + }) + + return value + } + + function stringify(name) { + if (!arguments.length) + throw new TypeError('1 argument required, but only 0 present.') + + return [name + ''] + } + + function normalizeArgs(name, value, filename) { + if (arguments.length < 2) + throw new TypeError( + `2 arguments required, but only ${arguments.length} present.` + ) + + return value instanceof Blob + // normalize name and filename if adding an attachment + ? [name + '', value, filename !== undefined + ? filename + '' // Cast filename to string if 3th arg isn't undefined + : typeof value.name === 'string' // if name prop exist + ? value.name // Use File.name + : 'Blob'] // otherwise fallback to Blob + + // If no attachment, just cast the args to strings + : [name + '', value + ''] + } + + /** + * @implements {Iterable} + */ + class FormDataPolyfill { + + /** + * FormData class + * + * @param {HTMLElement=} form + */ + constructor(form) { + map.set(this, Object.create(null)) + + if (!form) + return this + + for (let elm of arrayFrom(form.elements)) { + if (!elm.name || elm.disabled) continue + + if (elm.type === 'file') + for (let file of elm.files) + this.append(elm.name, file) + else if (elm.type === 'select-multiple' || elm.type === 'select-one') + for (let opt of arrayFrom(elm.options)) + opt.selected && this.append(elm.name, opt.value) + else if (elm.type === 'checkbox' || elm.type === 'radio') { + if (elm.checked) this.append(elm.name, elm.value) + } else + this.append(elm.name, elm.value) + } + } + + + /** + * Append a field + * + * @param {String} name field name + * @param {String|Blob|File} value string / blob / file + * @param {String=} filename filename to use with blob + * @return {Undefined} + */ + append(name, value, filename) { + const map = wm(this) + + if (!map[name]) + map[name] = [] + + map[name].push([value, filename]) + } + + + /** + * Delete all fields values given name + * + * @param {String} name Field name + * @return {Undefined} + */ + delete(name) { + delete wm(this)[name] + } + + + /** + * Iterate over all fields as [name, value] + * + * @return {Iterator} + */ + *entries() { + const map = wm(this) + + for (let name in map) + for (let value of map[name]) + yield [name, normalizeValue(value)] + } + + /** + * Iterate over all fields + * + * @param {Function} callback Executed for each item with parameters (value, name, thisArg) + * @param {Object=} thisArg `this` context for callback function + * @return {Undefined} + */ + forEach(callback, thisArg) { + for (let [name, value] of this) + callback.call(thisArg, value, name, this) + } + + + /** + * Return first field value given name + * or null if non existen + * + * @param {String} name Field name + * @return {String|File|null} value Fields value + */ + get(name) { + const map = wm(this) + return map[name] ? normalizeValue(map[name][0]) : null + } + + + /** + * Return all fields values given name + * + * @param {String} name Fields name + * @return {Array} [{String|File}] + */ + getAll(name) { + return (wm(this)[name] || []).map(normalizeValue) + } + + + /** + * Check for field name existence + * + * @param {String} name Field name + * @return {boolean} + */ + has(name) { + return name in wm(this) + } + + + /** + * Iterate over all fields name + * + * @return {Iterator} + */ + *keys() { + for (let [name] of this) + yield name + } + + + /** + * Overwrite all values given name + * + * @param {String} name Filed name + * @param {String} value Field value + * @param {String=} filename Filename (optional) + * @return {Undefined} + */ + set(name, value, filename) { + wm(this)[name] = [[value, filename]] + } + + + /** + * Iterate over all fields + * + * @return {Iterator} + */ + *values() { + for (let [name, value] of this) + yield value + } + + + /** + * Return a native (perhaps degraded) FormData with only a `append` method + * Can throw if it's not supported + * + * @return {FormData} + */ + ['_asNative']() { + const fd = new _FormData + + for (let [name, value] of this) + fd.append(name, value) + + return fd + } + + + /** + * [_blob description] + * + * @return {Blob} [description] + */ + ['_blob']() { + const boundary = '----formdata-polyfill-' + Math.random() + const chunks = [] + + for (let [name, value] of this) { + chunks.push(`--${boundary}\r\n`) + + if (value instanceof Blob) { + chunks.push( + `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`, + `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`, + value, + '\r\n' + ) + } else { + chunks.push( + `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n` + ) + } + } + + chunks.push(`--${boundary}--`) + + return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary}) + } + + + /** + * The class itself is iterable + * alias for formdata.entries() + * + * @return {Iterator} + */ + [Symbol.iterator]() { + return this.entries() + } + + + /** + * Create the default string description. + * + * @return {String} [object FormData] + */ + toString() { + return '[object FormData]' + } + } + + + if (stringTag) { + /** + * Create the default string description. + * It is accessed internally by the Object.prototype.toString(). + * + * @return {String} FormData + */ + FormDataPolyfill.prototype[stringTag] = 'FormData' + } + + const decorations = [ + ['append', normalizeArgs], + ['delete', stringify], + ['get', stringify], + ['getAll', stringify], + ['has', stringify], + ['set', normalizeArgs] + ] + + decorations.forEach(arr => { + const orig = FormDataPolyfill.prototype[arr[0]] + FormDataPolyfill.prototype[arr[0]] = function() { + return orig.apply(this, arr[1].apply(this, arrayFrom(arguments))) + } + }) + + // Patch xhr's send method to call _blob transparently + if (_send) { + XMLHttpRequest.prototype.send = function(data) { + // I would check if Content-Type isn't already set + // But xhr lacks getRequestHeaders functionallity + // https://github.com/jimmywarting/FormData/issues/44 + if (data instanceof FormDataPolyfill) { + const blob = data['_blob']() + this.setRequestHeader('Content-Type', blob.type) + _send.call(this, blob) + } else { + _send.call(this, data) + } + } + } + + // Patch fetch's function to call _blob transparently + if (_fetch) { + const _fetch = global.fetch + + global.fetch = function(input, init) { + if (init && init.body && init.body instanceof FormDataPolyfill) { + init.body = init.body['_blob']() + } + + return _fetch(input, init) + } + } + + global['FormData'] = FormDataPolyfill +} diff --git a/assets/js/imagify-wp-retina-2x.js b/assets/js/imagify-wp-retina-2x.js new file mode 100644 index 000000000..eecc9c7c2 --- /dev/null +++ b/assets/js/imagify-wp-retina-2x.js @@ -0,0 +1,87 @@ +window.imagify = window.imagify || {}; + +jQuery.extend( window.imagify, { + retina2x: { + hookAjax: function() { + jQuery( document ).ajaxSend( this.addData ).ajaxSuccess( this.updateInfo ); + }, + addData: function( e, jqXHR, settings ) { + var isString, isFormData, actions, action, id; + + if ( ! settings.data ) { + return; + } + + isString = typeof settings.data === 'string'; + isFormData = settings.data instanceof FormData; + actions = window.imagifyRetina2x; + + if ( isString ) { + action = settings.data.match( /(?:^|&)action=([^&]+)/ ); + action = action ? action[1] : false; + id = settings.data.match( /(?:^|&)attachmentId=([^&]+)/ ); + id = id ? parseInt( id[1], 10 ) : 0; + } else if ( isFormData ) { + action = settings.data.get( 'action' ); + id = settings.data.get( 'attachmentId' ); + } else { + action = settings.data.action; + id = settings.data.id; + } + + if ( ! action || ! actions[ action ] ) { + return; + } + + if ( ! jQuery( '.imagify_optimized_file' ).length ) { + id = 0; + } + + if ( isString ) { + settings.data += '&imagify_nonce=' + actions[ action ]; + + if ( id > 0 ) { + settings.data += '&imagify_info=1'; + } + } else if ( isFormData ) { + settings.data.append( 'imagify_nonce', actions[ action ] ); + + if ( id > 0 ) { + settings.data.append( 'imagify_info', 1 ); + } + } else { + settings.data = jQuery.extend( settings.data, { 'imagify_nonce': actions[ action ] } ); + + if ( id > 0 ) { + settings.data = jQuery.extend( settings.data, { 'imagify_info': 1 } ); + } + } + }, + updateInfo: function( e, jqXHR, settings, data ) { + if ( typeof settings.data !== 'string' ) { + return; + } + + try { + data = jQuery.parseJSON( data ); + } + catch ( error ) { + return; + } + + if ( 'imagify' !== data.source || 'wr2x' !== data.context || ! data.imagify_info ) { + return; + } + + jQuery.each( data.imagify_info, function ( id, html ) { + var $wrapper = jQuery( '#post-' + id ).children( '.imagify_optimized_file' ); + + if ( $wrapper.length ) { + $wrapper.html( html ); + } + } ); + } + } +} ); + +window.imagify.retina2x.hookAjax(); diff --git a/assets/js/weakmap-polyfill.js b/assets/js/weakmap-polyfill.js new file mode 100755 index 000000000..d304b5aa5 --- /dev/null +++ b/assets/js/weakmap-polyfill.js @@ -0,0 +1,144 @@ +/*! + * weakmap-polyfill v2.0.0 - ECMAScript6 WeakMap polyfill + * https://github.com/polygonplanet/weakmap-polyfill + * Copyright (c) 2015-2016 polygon planet + * @license MIT + */ + +(function(self) { + 'use strict'; + + if (self.WeakMap) { + return; + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + var defineProperty = function(object, name, value) { + if (Object.defineProperty) { + Object.defineProperty(object, name, { + configurable: true, + writable: true, + value: value + }); + } else { + object[name] = value; + } + }; + + self.WeakMap = (function() { + + // ECMA-262 23.3 WeakMap Objects + function WeakMap() { + if (this === void 0) { + throw new TypeError("Constructor WeakMap requires 'new'"); + } + + defineProperty(this, '_id', genId('_WeakMap')); + + // ECMA-262 23.3.1.1 WeakMap([iterable]) + if (arguments.length > 0) { + // Currently, WeakMap `iterable` argument is not supported + throw new TypeError('WeakMap iterable is not supported'); + } + } + + // ECMA-262 23.3.3.2 WeakMap.prototype.delete(key) + defineProperty(WeakMap.prototype, 'delete', function(key) { + checkInstance(this, 'delete'); + + if (!isObject(key)) { + return false; + } + + var entry = key[this._id]; + if (entry && entry[0] === key) { + delete key[this._id]; + return true; + } + + return false; + }); + + // ECMA-262 23.3.3.3 WeakMap.prototype.get(key) + defineProperty(WeakMap.prototype, 'get', function(key) { + checkInstance(this, 'get'); + + if (!isObject(key)) { + return void 0; + } + + var entry = key[this._id]; + if (entry && entry[0] === key) { + return entry[1]; + } + + return void 0; + }); + + // ECMA-262 23.3.3.4 WeakMap.prototype.has(key) + defineProperty(WeakMap.prototype, 'has', function(key) { + checkInstance(this, 'has'); + + if (!isObject(key)) { + return false; + } + + var entry = key[this._id]; + if (entry && entry[0] === key) { + return true; + } + + return false; + }); + + // ECMA-262 23.3.3.5 WeakMap.prototype.set(key, value) + defineProperty(WeakMap.prototype, 'set', function(key, value) { + checkInstance(this, 'set'); + + if (!isObject(key)) { + throw new TypeError('Invalid value used as weak map key'); + } + + var entry = key[this._id]; + if (entry && entry[0] === key) { + entry[1] = value; + return this; + } + + defineProperty(key, this._id, [key, value]); + return this; + }); + + + function checkInstance(x, methodName) { + if (!isObject(x) || !hasOwnProperty.call(x, '_id')) { + throw new TypeError( + methodName + ' method called on incompatible receiver ' + + typeof x + ); + } + } + + function genId(prefix) { + return prefix + '_' + rand() + '.' + rand(); + } + + function rand() { + return Math.random().toString().substring(2); + } + + + defineProperty(WeakMap, '_polyfill', true); + return WeakMap; + })(); + + + function isObject(x) { + return Object(x) === x; + } + +})( + typeof self !== 'undefined' ? self : + typeof window !== 'undefined' ? window : + typeof global !== 'undefined' ? global : this +); From 33b21c364c5f4fa5d4089274a9beca9218bbc0aa Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Wed, 2 Feb 2022 12:33:19 -0500 Subject: [PATCH 05/21] Use min WP version 5.3 for phpcs The WP min version is set in the plugin.php to 5.3 -- phpcs should match. --- phpcs.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index d59591b54..df2c4046b 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -27,9 +27,9 @@ - + - + From 77d47533b1074e4bdf3b9cefa036e0826cbfb9bc Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Wed, 2 Feb 2022 12:35:06 -0500 Subject: [PATCH 06/21] Import used trait in Media\WP class --- classes/Media/WP.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/Media/WP.php b/classes/Media/WP.php index 1bc3d7671..81bbebc23 100644 --- a/classes/Media/WP.php +++ b/classes/Media/WP.php @@ -1,6 +1,8 @@ Date: Wed, 2 Feb 2022 12:36:24 -0500 Subject: [PATCH 07/21] Import used trait in AbstractProcess --- classes/Optimization/Process/AbstractProcess.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classes/Optimization/Process/AbstractProcess.php b/classes/Optimization/Process/AbstractProcess.php index bbff55249..ac3aead5d 100644 --- a/classes/Optimization/Process/AbstractProcess.php +++ b/classes/Optimization/Process/AbstractProcess.php @@ -1,6 +1,7 @@ Date: Wed, 2 Feb 2022 12:50:05 -0500 Subject: [PATCH 08/21] Add PerfectImages methods for retina generation Add first a new property `$retina_sizes[]` to store some size info when Perfect Images generates a new retina file. Comment out (for now) the old ajax hooks, as Perfect Image no longer uses ajax. Add 3 methods: 1. `add_retina_sizes(int $media_id, string $retina_file, string $size_name): void` hooked to Perfect Images `wr2x_retina_file_added `, will store the media ID, retina filepath, and size name in our new `$retina_sizes` property whenever Perfect Images generates a new retina file. 2. `optimize_retina_sizes(int $media_id): void` hooked to Perfect Images `wr2x_generate_retina`, will find all the retina sizes added at the end of a Perfect Images retina generation process for a media ID (as stored in our class property) and start a new Imagify Optimization process for the newly generated retina files. 3. `add_retina_sizes_meta(array $sizes): array` hooked to Imagify's `imagify_media_files` will filter the imagify Media size meta to include the retina sizes, and flag the new sizes as being allowed to optimize. --- .../perfect-images/classes/PerfectImages.php | 119 +++++++++++++++--- 1 file changed, 105 insertions(+), 14 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index bdd4eeb24..c8ba4937e 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -2,7 +2,13 @@ namespace Imagify\ThirdParty\PerfectImages; +use Imagify\Media\MediaInterface; +use Imagify\Optimization\Process\WP as Process; +use Imagify\Optimization\Data\WP as Data; +use Imagify\Media\WP as Media; use Imagify_Assets; +use Imagify_Options; +use Imagify_Settings; /** * Class that handles compatibility with WP Retina 2x plugin. @@ -26,6 +32,12 @@ class PerfectImages { */ protected static $instance; + /** + * Retina sizes to be optimized. + * + * @var array + */ + private $retina_sizes = []; /** ----------------------------------------------------------------------------------------- */ /** INSTANCE ================================================================================ */ @@ -87,23 +99,102 @@ public function get_core() { * @author Grégory Viguier */ public function init() { - // Deal with Imagify when WPR2X is working. - add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 ); - add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 ); - add_action( 'wp_ajax_wr2x_delete_full', array( $this, 'wr2x_delete_full_retina_ajax_cb' ), 5 ); - add_action( 'wp_ajax_wr2x_replace', array( $this, 'wr2x_replace_all_ajax_cb' ), 5 ); - add_action( 'wp_ajax_wr2x_upload', array( $this, 'wr2x_replace_full_retina_ajax_cb' ), 5 ); add_action( 'imagify_assets_enqueued', array( $this, 'enqueue_scripts' ) ); - add_action( 'wr2x_retina_file_removed', array( $this, 'remove_retina_thumbnail_data_hook' ), 10, 2 ); - // Deal with Imagify when WP is working. - add_action( 'delete_attachment', array( $this, 'delete_full_retina_backup_file_hook' ) ); - // Deal with retina thumbnails when Imagify processes the "normal" images. - add_filter( 'imagify_fill_full_size_data', array( $this, 'optimize_full_retina_version_hook' ), 10, 8 ); - add_filter( 'imagify_fill_thumbnail_data', array( $this, 'optimize_retina_version_hook' ), 10, 8 ); - add_filter( 'imagify_fill_unauthorized_thumbnail_data', array( $this, 'maybe_optimize_unauthorized_retina_version_hook' ), 10, 7 ); - add_action( 'after_imagify_restore_attachment', array( $this, 'restore_retina_images_hook' ) ); + + add_action( 'wr2x_retina_file_added', [ $this, 'add_retina_size'], 10, 3 ); + add_action( 'wr2x_generate_retina', [ $this, 'optimize_retina_sizes'] ); + add_action( 'imagify_media_files', [ $this, 'add_retina_sizes_meta'] ); + + // Deal with Imagify when WPR2X is working. +// add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 ); +// add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 ); +// add_action( 'wp_ajax_wr2x_delete_full', array( $this, 'wr2x_delete_full_retina_ajax_cb' ), 5 ); +// add_action( 'wp_ajax_wr2x_replace', array( $this, 'wr2x_replace_all_ajax_cb' ), 5 ); +// add_action( 'wp_ajax_wr2x_upload', array( $this, 'wr2x_replace_full_retina_ajax_cb' ), 5 ); +// add_action( 'wr2x_retina_file_removed', array( $this, 'remove_retina_thumbnail_data_hook' ), 10, 2 ); +// // Deal with Imagify when WP is working. +// add_action( 'delete_attachment', array( $this, 'delete_full_retina_backup_file_hook' ) ); +// // Deal with retina thumbnails when Imagify processes the "normal" images. +// add_filter( 'imagify_fill_full_size_data', array( $this, 'optimize_full_retina_version_hook' ), 10, 8 ); +// add_filter( 'imagify_fill_thumbnail_data', array( $this, 'optimize_retina_version_hook' ), 10, 8 ); +// add_filter( 'imagify_fill_unauthorized_thumbnail_data', array( $this, 'maybe_optimize_unauthorized_retina_version_hook' ), 10, 7 ); +// add_action( 'after_imagify_restore_attachment', array( $this, 'restore_retina_images_hook' ) ); + } + + /** + * Add a newly generated retina size that will need to be optimized. + * + * @hooked wr2x_retina_file_added + * + * @param int $media_id + * @param string $retina_file + * @param string $size_name + */ + public function add_retina_size( int $media_id, string $retina_file, string $size_name ) { + $this->retina_sizes[] = [ + 'media_id' => $media_id, + 'retina_file' => $retina_file, + 'size_name' => $size_name, + ]; } + /** + * Optimize newly generated retina sizes. + * + * @hooked wr2x_generate_retina + * + * @param int $media_id The attachment id of the retina images to optimize. + */ + public function optimize_retina_sizes( int $media_id ) { + $sizes = []; + + foreach ( $this->retina_sizes as $size ) { + if ( $media_id === $size['media_id'] ) { + $sizes[] = $size['size_name'] . '@2x'; + } + } + + if ( empty( $sizes ) ) { + return; + } + + $process = new Process( new Data( new Media( $media_id ) ) ); + + $media_opt_level = $process->get_data()->get_optimization_level(); + $optimization_level = $media_opt_level ?: Imagify_Options::get_instance()->get( 'optimization_level' ); + + $process->optimize_sizes( $sizes, $optimization_level ); + } + + /** + * Filter a Media's get_media_files() response to include retina size data. + * + * @hooked imagify_media_files + * + * @param array $sizes The Media's size data. + * + * @return array Sizes data that includes retina sizes. + */ + public function add_retina_sizes_meta( array $sizes ): array { + foreach ( $sizes as $size => $size_data ) { + $retina_path = wr2x_get_retina( $size_data['path'] ); + + if ( empty( $retina_path ) ) { + continue; + } + + $sizes[$size . '@2x'] = [ + 'size' => $size . '@2x', + 'path' => $retina_path, + 'width' => $size_data['width'] * 2, + 'height' => $size_data['height'] * 2, + 'mime-type' => $size_data['mime-type'], + 'disabled' => false, + ]; + } + + return $sizes; + } /** ----------------------------------------------------------------------------------------- */ /** AJAX CALLBACKS ========================================================================== */ From 1063b72d47d42ba550cfa24955cd0ed027e894b2 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Fri, 4 Feb 2022 10:55:57 -0500 Subject: [PATCH 09/21] Add methods for deleting retinas We add 4 methods here, 2 public, and 2 private helpers: The 2 public methods are both hooked on Perfect Images `wr2x_retina_file_removed` action. 1. `remove_retina_webp_size(int $media_id, string $retina_file ): void` will check for any webp versions of the retina file that was deleted and remove it if it exists. Note we run this even if Imagify's webp option is not currently enabled, because it may have been enabled at the time the retina was generated, and disabled since. This uses the private helper method: 2. `get_retina_webp_filepath(string $attachment_file, string $retina_file): string`. This method handles the subtask of getting the correct webp file path based on the attachment's original filepath and the retina image's filename. 3. `remove_imagify_retina_data(int $media_id, string $retina_file): void` will purge optimization data imagify uses to track the status of retina images it has optimized. Two entries have to be checked and removed for each retina image size that's been deleted: the optimization data generated when we optimized the @2x image Perfect Images created, and the @2x@imagify-webp data for the webp version (if any) that Imagify created in connection with that retina size. This uses the private helper method: 4. `get_retina_imagify_data_size_names(array $sizes, string $origina_size_name): array`. This method compares the sizes information (formatted per `wp_get_attachment_metadata()` to the original filename of an attachment that included a retina image size, and gives us an array of all the related size names that might be present in Imagify's optimization data for the attachment. --- .../perfect-images/classes/PerfectImages.php | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index c8ba4937e..c3cdda09a 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -2,13 +2,11 @@ namespace Imagify\ThirdParty\PerfectImages; -use Imagify\Media\MediaInterface; use Imagify\Optimization\Process\WP as Process; use Imagify\Optimization\Data\WP as Data; use Imagify\Media\WP as Media; use Imagify_Assets; use Imagify_Options; -use Imagify_Settings; /** * Class that handles compatibility with WP Retina 2x plugin. @@ -105,6 +103,9 @@ public function init() { add_action( 'wr2x_generate_retina', [ $this, 'optimize_retina_sizes'] ); add_action( 'imagify_media_files', [ $this, 'add_retina_sizes_meta'] ); + add_action( 'wr2x_retina_file_removed', [ $this, 'remove_retina_webp_size'], 10, 2 ); + add_action( 'wr2x_retina_file_removed', [ $this, 'remove_imagify_retina_data'], 10, 2 ); + // Deal with Imagify when WPR2X is working. // add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 ); // add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 ); @@ -196,6 +197,52 @@ public function add_retina_sizes_meta( array $sizes ): array { return $sizes; } + /** + * Remove a retina-related webp file whose Perfect Images retina version has been deleted. + * + * @hooked wr2x_retina_file_removed + * + * @param int $media_id The media attachment ID. + * @param string $retina_file The retina filepath. + */ + public function remove_retina_webp_size( int $media_id, string $retina_file ) { + $meta = wp_get_attachment_metadata( $media_id ); + + $retina_webp_filepath = $this->get_retina_webp_filepath( $meta['file'], $retina_file ); + + if ( file_exists( $retina_webp_filepath ) ) { + unlink( $retina_webp_filepath ); + } + } + + /** + * Remove retina-related imagify data concerning a deleted Perfect Images retina file. + * + * @hooked wr2x_retina_file_removed + * + * @param int $media_id The media attachment ID. + * @param string $retina_file The retina filepath. + */ + public function remove_imagify_retina_data( int $media_id, string $retina_file ) { + $meta = wp_get_attachment_metadata( $media_id ); + $retina_file_info = pathinfo( $retina_file ); + $original_size_name = preg_replace( + '/@2x/', + '', + $retina_file_info['filename'] + ) + . '.' . $retina_file_info['extension']; + + $imagify_size_names = $this->get_retina_imagify_data_size_names( $meta['sizes'], $original_size_name ); + + if ( empty( $imagify_size_names ) ) { + return; + } + + $imagify_data = new Data( new Media( $media_id ) ); + $imagify_data->delete_sizes_optimization_data( $imagify_size_names ); + } + /** ----------------------------------------------------------------------------------------- */ /** AJAX CALLBACKS ========================================================================== */ /** ----------------------------------------------------------------------------------------- */ @@ -750,4 +797,47 @@ public function maybe_send_json_error( $result ) { 'message' => $result->get_error_message(), ) ); } + + /** + * Get the retina webp filepath associated with a Perfect Images retina file. + * + * @param string $attachment_file The attachment file from WP's attachment meta. + * @param string $retina_file The retina file from Perfect Images. + * + * @return string The full retina-webp file path. + */ + private function get_retina_webp_filepath( string $attachment_file, string $retina_file ): string { + $pathinfo = pathinfo( $attachment_file ); + $directory = $pathinfo['dirname']; + $uploads = wp_upload_dir(); + $basedir = $uploads['basedir']; + $retina_webp_filepath = trailingslashit( $basedir ) . trailingslashit( $directory ) . $retina_file . '.webp'; + + return $retina_webp_filepath; + } + + /** + * Get size names as used in an Imagify::AbstractData instance for retina images. + * + * Given an array of size data items from WP's attachment meta, + * and the filename of the original image derived from a Perfect Images retina filename, + * we get a list of size names for all retina-size images that will be found in Imagify's Data instance. + * + * @param array $sizes Sizes from WP's attachment meta. + * @param string $original_size_name The original image filename from which Perfect Images has created a retina filename. + * + * @return array A list of image size names related to the retina file in an Imagify Data Instance. + */ + private function get_retina_imagify_data_size_names( array $sizes, string $original_size_name ): array { + $imagify_size_names = []; + + foreach ( $sizes as $size => $size_data ) { + if ( $original_size_name === $size_data['file'] ) { + $imagify_size_names[] = $size . '@2x'; + $imagify_size_names[] = $size . '@2x@imagify-webp'; + } + } + + return $imagify_size_names; + } } From a4e0adbd91614f51ab696d1d9de8221bdc22ca09 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Fri, 4 Feb 2022 16:02:06 -0500 Subject: [PATCH 10/21] Add re-optimization of images after regeneration Adds a method to restore a full-size image from the backed up original. Adds a method to replace back the previously optimized full-size image after image regeneration. Adds a method to send regenerated images to the queue for re-optimization. We also hook the restore/replace before and after the generation of retina sizes to insure that the original is being used to generate the retinas as well. --- .../perfect-images/classes/PerfectImages.php | 701 ++++-------------- 1 file changed, 132 insertions(+), 569 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index c3cdda09a..c8c1b6c70 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -6,7 +6,9 @@ use Imagify\Optimization\Data\WP as Data; use Imagify\Media\WP as Media; use Imagify_Assets; +use Imagify_Filesystem; use Imagify_Options; +use WP_Rocket\Admin\Options_Data; /** * Class that handles compatibility with WP Retina 2x plugin. @@ -26,9 +28,16 @@ class PerfectImages { /** * The single instance of this class. * - * @var object PerfectImages + * @var PerfectImages */ - protected static $instance; + private static $instance; + + /** + * An Imagify Filesystem instance. + * + * @var Imagify_Filesystem + */ + private $filesystem; /** * Retina sizes to be optimized. @@ -44,11 +53,11 @@ class PerfectImages { /** * Get the main Instance. * - * @since 1.8 - * @access public + * @return object Main instance. * @author Grégory Viguier * - * @return object Main instance. + * @since 1.8 + * @access public */ public static function get_instance() { if ( ! isset( self::$instance ) ) { @@ -60,21 +69,19 @@ public static function get_instance() { /** * The constructor. - * - * @since 1.8 - * @access protected - * @author Grégory Viguier */ - protected function __construct() {} + protected function __construct() { + $this->filesystem = Imagify_Filesystem::get_instance(); + } /** * Get the core Instance. * - * @since 1.8 - * @access public + * @return object Imagify_WP_Retina_2x_Core instance. * @author Grégory Viguier * - * @return object Imagify_WP_Retina_2x_Core instance. + * @since 1.8 + * @access public */ public function get_core() { if ( ! isset( $this->core ) ) { @@ -97,29 +104,61 @@ public function get_core() { * @author Grégory Viguier */ public function init() { - add_action( 'imagify_assets_enqueued', array( $this, 'enqueue_scripts' ) ); - - add_action( 'wr2x_retina_file_added', [ $this, 'add_retina_size'], 10, 3 ); - add_action( 'wr2x_generate_retina', [ $this, 'optimize_retina_sizes'] ); - add_action( 'imagify_media_files', [ $this, 'add_retina_sizes_meta'] ); - - add_action( 'wr2x_retina_file_removed', [ $this, 'remove_retina_webp_size'], 10, 2 ); - add_action( 'wr2x_retina_file_removed', [ $this, 'remove_imagify_retina_data'], 10, 2 ); - - // Deal with Imagify when WPR2X is working. -// add_action( 'wp_ajax_wr2x_generate', array( $this, 'wr2x_generate_ajax_cb' ), 5 ); -// add_action( 'wp_ajax_wr2x_delete', array( $this, 'wr2x_delete_all_retina_ajax_cb' ), 5 ); -// add_action( 'wp_ajax_wr2x_delete_full', array( $this, 'wr2x_delete_full_retina_ajax_cb' ), 5 ); -// add_action( 'wp_ajax_wr2x_replace', array( $this, 'wr2x_replace_all_ajax_cb' ), 5 ); -// add_action( 'wp_ajax_wr2x_upload', array( $this, 'wr2x_replace_full_retina_ajax_cb' ), 5 ); -// add_action( 'wr2x_retina_file_removed', array( $this, 'remove_retina_thumbnail_data_hook' ), 10, 2 ); -// // Deal with Imagify when WP is working. -// add_action( 'delete_attachment', array( $this, 'delete_full_retina_backup_file_hook' ) ); -// // Deal with retina thumbnails when Imagify processes the "normal" images. -// add_filter( 'imagify_fill_full_size_data', array( $this, 'optimize_full_retina_version_hook' ), 10, 8 ); -// add_filter( 'imagify_fill_thumbnail_data', array( $this, 'optimize_retina_version_hook' ), 10, 8 ); -// add_filter( 'imagify_fill_unauthorized_thumbnail_data', array( $this, 'maybe_optimize_unauthorized_retina_version_hook' ), 10, 7 ); -// add_action( 'after_imagify_restore_attachment', array( $this, 'restore_retina_images_hook' ) ); + add_action( 'wr2x_before_regenerate', [ $this, 'restore_originally_uploaded_image' ] ); + add_action( 'wr2x_retina_file_added', [ $this, 'add_retina_size' ], 10, 3 ); + add_action( 'wr2x_generate_retina', [ $this, 'optimize_retina_sizes' ] ); + add_action( 'wr2x_generate_retina', [ $this, 'reset_optimized_full_size_image' ] ); + add_action( 'imagify_media_files', [ $this, 'add_retina_sizes_meta' ] ); + + add_action( 'wr2x_retina_file_removed', [ $this, 'remove_retina_webp_size' ], 10, 2 ); + add_action( 'wr2x_retina_file_removed', [ $this, 'remove_imagify_retina_data' ], 10, 2 ); + + add_action( 'wr2x_before_generate_thumbnails', [ $this, 'restore_originally_uploaded_image' ] ); + add_action( 'wr2x_generate_thumbnails', [ $this, 'reoptimize_regenerated_images' ] ); + add_action( 'wr2x_generate_thumbnails', [ $this, 'reset_optimized_full_size_image' ] ); + } + + /** + * Restore the optimized full-sized file and replace it by the original backup file. + * + * This is to have the original user-uploaded (rather than the optimized full-size) image is in place + * when Perfect Images (re)generates any new images. + * + * @param int $media_id A media attachment ID. + */ + public function restore_originally_uploaded_image( int $media_id ) { + $media = new Media( $media_id ); + + if ( empty( $fullsize_path = $media->get_raw_fullsize_path() ) ) { + return; + } + + if ( empty( $original_path = $media->get_original_path() ) ) { + return; + } + + /** If these are not the same, WP will rebuild the full-size from the original along with the thumbnails. */ + if ( $fullsize_path !== $original_path ) { + return; + } + + if ( empty( $backup_path = $media->get_raw_backup_path() ) ) { + return; + } + + $data = new Data( $media ); + + if ( ! $data->is_optimized() ) { + return; + } + + $tmp_file_path = $this->get_temporary_file_path( $fullsize_path ); + + if ( $this->filesystem->exists( $fullsize_path ) ) { + $this->filesystem->move( $fullsize_path, $tmp_file_path, true ); + } + + $this->filesystem->copy( $backup_path, $fullsize_path ); } /** @@ -133,9 +172,9 @@ public function init() { */ public function add_retina_size( int $media_id, string $retina_file, string $size_name ) { $this->retina_sizes[] = [ - 'media_id' => $media_id, + 'media_id' => $media_id, 'retina_file' => $retina_file, - 'size_name' => $size_name, + 'size_name' => $size_name, ]; } @@ -161,7 +200,7 @@ public function optimize_retina_sizes( int $media_id ) { $process = new Process( new Data( new Media( $media_id ) ) ); - $media_opt_level = $process->get_data()->get_optimization_level(); + $media_opt_level = $process->get_data()->get_optimization_level(); $optimization_level = $media_opt_level ?: Imagify_Options::get_instance()->get( 'optimization_level' ); $process->optimize_sizes( $sizes, $optimization_level ); @@ -172,7 +211,7 @@ public function optimize_retina_sizes( int $media_id ) { * * @hooked imagify_media_files * - * @param array $sizes The Media's size data. + * @param array $sizes The Media's size data. * * @return array Sizes data that includes retina sizes. */ @@ -184,7 +223,7 @@ public function add_retina_sizes_meta( array $sizes ): array { continue; } - $sizes[$size . '@2x'] = [ + $sizes[ $size . '@2x' ] = [ 'size' => $size . '@2x', 'path' => $retina_path, 'width' => $size_data['width'] * 2, @@ -243,566 +282,79 @@ public function remove_imagify_retina_data( int $media_id, string $retina_file ) $imagify_data->delete_sizes_optimization_data( $imagify_size_names ); } - /** ----------------------------------------------------------------------------------------- */ - /** AJAX CALLBACKS ========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - /** - * (Re)generate the retina thumbnails (except the full size). + * Reoptimize regenerated thumbnail images. * - * @since 1.8 - * @access public - * @author Grégory Viguier + * @param int $media_id The attachment ID of the media being processed. */ - public function wr2x_generate_ajax_cb() { - $this->check_nonce( 'imagify_wr2x_generate' ); - $this->check_user_capacity(); - - $attachment = $this->get_requested_attachment( 'wr2x_generate' ); - - // Delete previous retina images and recreate them. - $result = $this->get_core()->regenerate_retina_images( $attachment ); - - // Send results. - $this->maybe_send_json_error( $result ); - - $this->send_json( array( - 'results' => $this->get_core()->get_retina_info( $attachment ), - 'message' => __( 'Retina files generated.', 'imagify' ), - 'imagify_info' => $this->get_imagify_info( $attachment ), - ) ); - } - - /** - * Delete all retina images, including the one for the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - */ - public function wr2x_delete_all_retina_ajax_cb() { - $this->check_nonce( 'imagify_wr2x_delete' ); - $this->check_user_capacity(); - - $attachment = $this->get_requested_attachment( 'wr2x_delete_all' ); - - // Delete the retina versions, including the full size. - $result = $this->get_core()->delete_retina_images( $attachment, true ); - - // Send results. - $this->maybe_send_json_error( $result ); - - $this->send_json( array( - 'results' => $this->get_core()->get_retina_info( $attachment ), - 'results_full' => $this->get_core()->get_retina_info( $attachment, 'full' ), - 'message' => __( 'Retina files deleted.', 'imagify' ), - ) ); - } - - /** - * Delete the retina version of the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - */ - public function wr2x_delete_full_retina_ajax_cb() { - $this->check_nonce( 'imagify_wr2x_delete_full' ); - $this->check_user_capacity(); - - $attachment = $this->get_requested_attachment( 'wr2x_delete_full' ); - - $result = $this->get_core()->delete_full_retina_image( $attachment ); - - // Send results. - $this->maybe_send_json_error( $result ); - - $this->send_json( array( - 'results' => $this->get_core()->get_retina_info( $attachment, 'full' ), - 'message' => __( 'Full retina file deleted.', 'imagify' ), - ) ); - } - - /** - * Replace an attachment (except the retina version of the full size). - * - * @since 1.8 - * @access public - * @author Grégory Viguier - */ - public function wr2x_replace_all_ajax_cb() { - $this->check_nonce( 'imagify_wr2x_replace' ); - $this->check_user_capacity(); - - $attachment = $this->get_requested_attachment( 'wr2x_replace_all' ); - $tmp_file_path = $this->get_uploaded_file_path(); - - $result = $this->get_core()->replace_attachment( $attachment, $tmp_file_path ); - - // Send results. - $this->maybe_send_json_error( $result ); - - $this->send_json( array( - 'results' => $this->get_core()->get_retina_info( $attachment ), - 'message' => __( 'Images replaced successfully.', 'imagify' ), - ) ); - } - - /** - * Upload a new retina version for the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - */ - public function wr2x_replace_full_retina_ajax_cb() { - $this->check_nonce( 'imagify_wr2x_upload' ); - $this->check_user_capacity(); - - $attachment = $this->get_requested_attachment( 'wr2x_replace_full' ); - $tmp_file_path = $this->get_uploaded_file_path(); - - $result = $this->get_core()->replace_full_retina_image( $attachment, $tmp_file_path ); - - // Send results. - $this->maybe_send_json_error( $result ); - - $this->send_json( array( - 'results' => $this->get_core()->get_retina_info( $attachment ), - 'message' => __( 'Image replaced successfully.', 'imagify' ), - ) ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** OTHER HOOKS ============================================================================= */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Queue some JS to add our nonce parameter to all WR2X jQuery ajax requests. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - */ - public function enqueue_scripts() { - if ( ! $this->user_can() ) { - return; - } - - $assets = Imagify_Assets::get_instance(); + public function reoptimize_regenerated_images( int $media_id ) { + $meta = wp_get_attachment_metadata( $media_id ); + $process = new Process( new Data( new Media( $media_id ) ) ); - $assets->register_script( 'weakmap-polyfill', 'weakmap-polyfill', array(), '2.0.0' ); - $assets->register_script( 'formdata-polyfill', 'formdata-polyfill', array( 'weakmap-polyfill' ), '3.0.10-beta' ); - $assets->register_script( 'wp-retina-2x', 'imagify-wp-retina-2x', array( 'formdata-polyfill', 'jquery' ) ); + $sizes = isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ? $meta['sizes'] : []; + $media = $process->get_media(); + $fullsize_path = $media->get_raw_fullsize_path(); - if ( imagify_is_screen( 'library' ) || imagify_is_screen( 'media_page_wp-retina-2x' ) ) { - $assets->localize_script( 'wp-retina-2x', 'imagifyRetina2x', array( - 'wr2x_generate' => wp_create_nonce( 'imagify_wr2x_generate' ), - 'wr2x_delete' => wp_create_nonce( 'imagify_wr2x_delete' ), - 'wr2x_delete_full' => wp_create_nonce( 'imagify_wr2x_delete_full' ), - 'wr2x_replace' => wp_create_nonce( 'imagify_wr2x_replace' ), - 'wr2x_upload' => wp_create_nonce( 'imagify_wr2x_upload' ), - ) )->enqueue(); + /** If full-size and original are not the same, we will need to re-optimize the full size, too. */ + if ( $fullsize_path && $media->get_original_path() !== $fullsize_path ) { + $sizes['full'] = []; } - } - - /** - * After a retina thumbnail is deleted, remove its Imagify data. - * This should be useless since we replaced every AJAX callbacks. - * - * @since 1.8 - * @access public - * @see wr2x_delete_attachment() - * @author Grégory Viguier - * - * @param int $attachment_id An attachment ID. - * @param string $retina_filename The retina thumbnail file name. - */ - public function remove_retina_thumbnail_data_hook( $attachment_id, $retina_filename ) { - $attachment = get_imagify_attachment( 'wp', $attachment_id, 'wr2x_delete' ); - $this->get_core()->remove_retina_image_data_by_filename( $attachment, $retina_filename ); - } - - /** - * Delete the backup of the retina version of the full size file when an attachement is deleted. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param int $attachment_id An attachment ID. - */ - public function delete_full_retina_backup_file_hook( $attachment_id ) { - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { + if ( ! $sizes ) { return; } - $attachment = get_imagify_attachment( 'wp', $attachment_id, 'delete_attachment' ); - $retina_path = $this->get_core()->get_retina_path( $attachment->get_original_path() ); - - if ( $retina_path ) { - $this->get_core()->delete_file_backup( $retina_path ); - } - } - - /** - * Filter the optimization data of the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $data The statistics data. - * @param object $response The API response. - * @param int $attachment_id The attachment ID. - * @param string $path The attachment path. - * @param string $url The attachment URL. - * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for oncistancy with other filters. - * @param int $optimization_level The optimization level. - * @param array $metadata WP metadata. - * @return array $data The new optimization data. - */ - public function optimize_full_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { - return $data; - } + /** + * Optimize the sizes that have been regenerated. + */ + // If the media has WebP versions, recreate them for the sizes that have been regenerated. + $optimization_data = $process->get_data()->get_optimization_data(); - $attachment = get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' ); + if ( ! empty( $optimization_data['sizes'] ) ) { + foreach ( $optimization_data['sizes'] as $size_name => $size_data ) { + $non_webp_size_name = $process->is_size_webp( $size_name ); - return $this->get_core()->optimize_retina_image( array( - 'data' => $data, - 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_full_retina_version_hook' ), - 'retina_path' => wr2x_get_retina( $path ), - 'size_key' => $size_key, - 'optimization_level' => $optimization_level, - 'metadata' => $metadata, - ) ); - } + if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) { + continue; + } - /** - * Filter the optimization data of each thumbnail. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $data The statistics data. - * @param object $response The API response. - * @param int $attachment_id The attachment ID. - * @param string $path The thumbnail path. - * @param string $url The thumbnail URL. - * @param string $size_key The thumbnail size key. - * @param int $optimization_level The optimization level. - * @param array $metadata WP metadata. - * @return array $data The new optimization data. - */ - public function optimize_retina_version_hook( $data, $response, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { - return $data; + // Add the WebP size. + $sizes[ $size_name ] = []; + } } - return $this->get_core()->optimize_retina_image( array( - 'data' => $data, - 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'optimize_retina_version_hook' ), - 'retina_path' => wr2x_get_retina( $path ), - 'size_key' => $size_key, - 'optimization_level' => $optimization_level, - 'metadata' => $metadata, - ) ); - } + $sizes = array_keys( $sizes ); + $optimization_level = $process->get_data()->get_optimization_level(); - /** - * If a thumbnail size is disallowed in Imagify' settings, we can still try to optimize its "@2x" version. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $data The statistics data. - * @param int $attachment_id The attachment ID. - * @param string $path The thumbnail path. - * @param string $url The thumbnail URL. - * @param string $size_key The thumbnail size key. - * @param int $optimization_level The optimization level. - * @param array $metadata WP metadata. - * @return array $data The new optimization data. - */ - public function maybe_optimize_unauthorized_retina_version_hook( $data, $attachment_id, $path, $url, $size_key, $optimization_level, $metadata ) { - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { - return $data; - } - - return $this->get_core()->optimize_retina_image( array( - 'data' => $data, - 'attachment' => get_imagify_attachment( 'wp', $attachment_id, 'maybe_optimize_unauthorized_retina_version_hook' ), - 'retina_path' => wr2x_get_retina( $path ), - 'size_key' => $size_key, - 'optimization_level' => $optimization_level, - 'metadata' => $metadata, - ) ); + // Delete related optimization data or nothing will be optimized. + $process->get_data()->delete_sizes_optimization_data( $sizes ); + $process->optimize_sizes( $sizes, $optimization_level ); } /** - * Delete previous retina images and recreate them. - * - * @since 1.8 - * @access public - * @author Grégory Viguier + * Put the optimized full-sized file back. * - * @param int $attachment_id An attachment ID. + * @param int $media_id A media attachment ID. */ - public function restore_retina_images_hook( $attachment_id ) { - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { - return; - } + public function reset_optimized_full_size_image( int $media_id ) { + $media = new Media( $media_id ); - $attachment = get_imagify_attachment( 'wp', $attachment_id, 'restore_retina_images_hook' ); + $file_path = $media->get_raw_original_path(); + $tmp_file_path = $this->get_temporary_file_path( $file_path ); - if ( ! $this->get_core()->has_retina_images( $attachment ) ) { + if ( ! $this->filesystem->exists( $tmp_file_path ) ) { return; } - // At this point, previous Imagify data has been removed. - $this->get_core()->regenerate_retina_images( $attachment ); - $this->get_core()->restore_full_retina_file( $attachment ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** INTERNAL TOOLS ========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Check for nonce. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $action Action nonce. - * @param string|bool $query_arg Optional. Key to check for the nonce in `$_REQUEST`. If false, `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce' (in that order). Default false. - */ - public function check_nonce( $action, $query_arg = 'imagify_nonce' ) { - if ( ! check_ajax_referer( $action, $query_arg, false ) ) { - $this->send_json( array( - 'success' => false, - 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ), - ) ); - } - } - - /** - * Check for user capacity. - */ - private function check_user_capacity() { - if ( ! $this->user_can() ) { - $this->send_json( array( - 'success' => false, - 'message' => __( 'Sorry, you are not allowed to do that.', 'imagify' ), - ) ); - } + $this->filesystem->move( $tmp_file_path, $file_path, true ); } - /** - * Tell if the current user can re-optimize files. - * - * @return bool - */ - private function user_can(): bool { - return imagify_get_context( 'wp' )->current_user_can( 'auto-optimize' ); - } - - /** - * Shorthand to get the attachment ID sent via $_POST. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $context The context to use in get_imagify_attachment(). - * @param string $key The $_POST key. - * @return int $attachment_id - */ - public function get_requested_attachment( $context, $key = 'attachmentId' ) { - $attachment_id = filter_input( INPUT_POST, $key, FILTER_VALIDATE_INT ); - - if ( $attachment_id <= 0 ) { - $this->send_json( array( - 'success' => false, - 'message' => __( 'The attachment ID is missing.', 'imagify' ), - ) ); - } - - if ( ! $this->get_core()->is_supported_format( $attachment_id ) ) { - $this->send_json( array( - 'success' => false, - 'message' => __( 'This format is not supported.', 'imagify' ), - ) ); - } - - $attachment = get_imagify_attachment( 'wp', $attachment_id, $context ); - - if ( ! $this->has_required_metadata( $attachment ) ) { - $this->send_json( array( - 'success' => false, - 'message' => __( 'This attachment lacks the required metadata.', 'imagify' ), - ) ); - } - - return $attachment; - } - - /** - * Shorthand to get the path to the uploaded file. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return string Path to the temporary file. - */ - public function get_uploaded_file_path() { - $tmp_file_path = ! empty( $_FILES['file']['tmp_name'] ) ? wp_unslash( $_FILES['file']['tmp_name'] ) : ''; - $tmp_file_path = $tmp_file_path && is_uploaded_file( $tmp_file_path ) ? $tmp_file_path : ''; - $filesystem = Imagify_Filesystem::get_instance(); - - if ( ! $tmp_file_path || ! $filesystem->is_image( $tmp_file_path ) ) { - $this->get_core()->log( 'The file is not an image or the upload went wrong.' ); - $filesystem->delete( $tmp_file_path ); - - $this->send_json_string( array( - 'success' => false, - 'message' => __( 'The file is not an image or the upload went wrong.', 'imagify' ), - ) ); - } - - $file_name = filter_input( INPUT_POST, 'filename', FILTER_SANITIZE_STRING ); - $file_data = wp_check_filetype_and_ext( $tmp_file_path, $file_name ); - - if ( empty( $file_data['ext'] ) ) { - $this->get_core()->log( 'You cannot use this file (wrong extension? wrong type?).' ); - $filesystem->delete( $tmp_file_path ); - - $this->send_json_string( array( - 'success' => false, - 'message' => __( 'You cannot use this file (wrong extension? wrong type?).', 'imagify' ), - ) ); - } - - $this->get_core()->log( 'The temporary file was written successfully.' ); - - return $tmp_file_path; - } - - /** - * Tell if Imagify's column content has been requested. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return bool - */ - public function needs_info() { - return filter_input( INPUT_POST, 'imagify_info', FILTER_VALIDATE_INT ) === 1; - } - - /** - * Tell if the attachment has the required WP metadata. - * - * @since 1.8 - * @access public - * @see $wr2x_core->is_image_meta() - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool - */ - public function has_required_metadata( $attachment ) { - if ( ! $attachment->has_required_metadata() ) { - return false; - } - - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - - if ( ! isset( $metadata['sizes'], $metadata['width'], $metadata['height'] ) ) { - return false; - } - - return is_array( $metadata['sizes'] ); - } - - /** - * Get info about Imagify. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return array An array containing some HTML, indexed by the attachment ID. - */ - public function get_imagify_info( $attachment ) { - if ( ! $this->needs_info() ) { - return array(); - } - - return array( - $attachment->get_id() => get_imagify_media_column_content( $attachment ), - ); - } - - /** - * Send a JSON response back to an Ajax request. - * It sends a "success" by default. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $data An array of data to print and die. - */ - public function send_json( $data ) { - // Use the same JSON format than WPR2X. - $data = array_merge( array( - 'success' => true, - 'message' => '', - 'source' => 'imagify', - 'context' => 'wr2x', - ), $data ); - - echo wp_json_encode( $data ); - die; - } - - /** - * Send a JSON error response if the given argument is a WP_Error object. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param mixed $result Result of an operation. - */ - public function maybe_send_json_error( $result ) { - if ( ! is_wp_error( $result ) ) { - return; - } - - // Oh no. - $this->send_json( array( - 'success' => false, - 'message' => $result->get_error_message(), - ) ); - } /** * Get the retina webp filepath associated with a Perfect Images retina file. * * @param string $attachment_file The attachment file from WP's attachment meta. - * @param string $retina_file The retina file from Perfect Images. + * @param string $retina_file The retina file from Perfect Images. * * @return string The full retina-webp file path. */ @@ -823,7 +375,7 @@ private function get_retina_webp_filepath( string $attachment_file, string $reti * and the filename of the original image derived from a Perfect Images retina filename, * we get a list of size names for all retina-size images that will be found in Imagify's Data instance. * - * @param array $sizes Sizes from WP's attachment meta. + * @param array $sizes Sizes from WP's attachment meta. * @param string $original_size_name The original image filename from which Perfect Images has created a retina filename. * * @return array A list of image size names related to the retina file in an Imagify Data Instance. @@ -840,4 +392,15 @@ private function get_retina_imagify_data_size_names( array $sizes, string $origi return $imagify_size_names; } + + /** + * Get the path to the temporary file. + * + * @param string $file_path The optimized full-sized file path. + * + * @return string A temporary file path for the optimized full-sized file. + */ + private function get_temporary_file_path( $file_path ) { + return $file_path . '_backup'; + } } From 33c212f57382ffb70d11a00c27b2c51b117ea391 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 11:15:45 -0500 Subject: [PATCH 11/21] Remove unused js assets These were added back in on 4ffeb21d just in case. It turns out we don't need them any more. --- assets/js/formdata-polyfill.js | 390 ------------------------------ assets/js/imagify-wp-retina-2x.js | 87 ------- assets/js/weakmap-polyfill.js | 144 ----------- 3 files changed, 621 deletions(-) delete mode 100644 assets/js/formdata-polyfill.js delete mode 100644 assets/js/imagify-wp-retina-2x.js delete mode 100755 assets/js/weakmap-polyfill.js diff --git a/assets/js/formdata-polyfill.js b/assets/js/formdata-polyfill.js deleted file mode 100644 index b54995c3b..000000000 --- a/assets/js/formdata-polyfill.js +++ /dev/null @@ -1,390 +0,0 @@ -if (typeof FormData === 'undefined' || !FormData.prototype.keys) { - const global = typeof window === 'object' - ? window : typeof self === 'object' - ? self : this - - // keep a reference to native implementation - const _FormData = global.FormData - - // To be monkey patched - const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send - const _fetch = global.Request && global.fetch - - // Unable to patch Request constructor correctly - // const _Request = global.Request - // only way is to use ES6 class extend - // https://github.com/babel/babel/issues/1966 - - const stringTag = global.Symbol && Symbol.toStringTag - const map = new WeakMap - const wm = o => map.get(o) - const arrayFrom = Array.from || (obj => [].slice.call(obj)) - - // Add missing stringTags to blob and files - if (stringTag) { - if (!Blob.prototype[stringTag]) { - Blob.prototype[stringTag] = 'Blob' - } - - if ('File' in global && !File.prototype[stringTag]) { - File.prototype[stringTag] = 'File' - } - } - - // Fix so you can construct your own File - try { - new File([], '') - } catch (a) { - global.File = function(b, d, c) { - const blob = new Blob(b, c) - const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date - - Object.defineProperties(blob, { - name: { - value: d - }, - lastModifiedDate: { - value: t - }, - lastModified: { - value: +t - }, - toString: { - value() { - return '[object File]' - } - } - }) - - if (stringTag) { - Object.defineProperty(blob, stringTag, { - value: 'File' - }) - } - - return blob - } - } - - function normalizeValue([value, filename]) { - if (value instanceof Blob) - // Should always returns a new File instance - // console.assert(fd.get(x) !== fd.get(x)) - value = new File([value], filename, { - type: value.type, - lastModified: value.lastModified - }) - - return value - } - - function stringify(name) { - if (!arguments.length) - throw new TypeError('1 argument required, but only 0 present.') - - return [name + ''] - } - - function normalizeArgs(name, value, filename) { - if (arguments.length < 2) - throw new TypeError( - `2 arguments required, but only ${arguments.length} present.` - ) - - return value instanceof Blob - // normalize name and filename if adding an attachment - ? [name + '', value, filename !== undefined - ? filename + '' // Cast filename to string if 3th arg isn't undefined - : typeof value.name === 'string' // if name prop exist - ? value.name // Use File.name - : 'Blob'] // otherwise fallback to Blob - - // If no attachment, just cast the args to strings - : [name + '', value + ''] - } - - /** - * @implements {Iterable} - */ - class FormDataPolyfill { - - /** - * FormData class - * - * @param {HTMLElement=} form - */ - constructor(form) { - map.set(this, Object.create(null)) - - if (!form) - return this - - for (let elm of arrayFrom(form.elements)) { - if (!elm.name || elm.disabled) continue - - if (elm.type === 'file') - for (let file of elm.files) - this.append(elm.name, file) - else if (elm.type === 'select-multiple' || elm.type === 'select-one') - for (let opt of arrayFrom(elm.options)) - opt.selected && this.append(elm.name, opt.value) - else if (elm.type === 'checkbox' || elm.type === 'radio') { - if (elm.checked) this.append(elm.name, elm.value) - } else - this.append(elm.name, elm.value) - } - } - - - /** - * Append a field - * - * @param {String} name field name - * @param {String|Blob|File} value string / blob / file - * @param {String=} filename filename to use with blob - * @return {Undefined} - */ - append(name, value, filename) { - const map = wm(this) - - if (!map[name]) - map[name] = [] - - map[name].push([value, filename]) - } - - - /** - * Delete all fields values given name - * - * @param {String} name Field name - * @return {Undefined} - */ - delete(name) { - delete wm(this)[name] - } - - - /** - * Iterate over all fields as [name, value] - * - * @return {Iterator} - */ - *entries() { - const map = wm(this) - - for (let name in map) - for (let value of map[name]) - yield [name, normalizeValue(value)] - } - - /** - * Iterate over all fields - * - * @param {Function} callback Executed for each item with parameters (value, name, thisArg) - * @param {Object=} thisArg `this` context for callback function - * @return {Undefined} - */ - forEach(callback, thisArg) { - for (let [name, value] of this) - callback.call(thisArg, value, name, this) - } - - - /** - * Return first field value given name - * or null if non existen - * - * @param {String} name Field name - * @return {String|File|null} value Fields value - */ - get(name) { - const map = wm(this) - return map[name] ? normalizeValue(map[name][0]) : null - } - - - /** - * Return all fields values given name - * - * @param {String} name Fields name - * @return {Array} [{String|File}] - */ - getAll(name) { - return (wm(this)[name] || []).map(normalizeValue) - } - - - /** - * Check for field name existence - * - * @param {String} name Field name - * @return {boolean} - */ - has(name) { - return name in wm(this) - } - - - /** - * Iterate over all fields name - * - * @return {Iterator} - */ - *keys() { - for (let [name] of this) - yield name - } - - - /** - * Overwrite all values given name - * - * @param {String} name Filed name - * @param {String} value Field value - * @param {String=} filename Filename (optional) - * @return {Undefined} - */ - set(name, value, filename) { - wm(this)[name] = [[value, filename]] - } - - - /** - * Iterate over all fields - * - * @return {Iterator} - */ - *values() { - for (let [name, value] of this) - yield value - } - - - /** - * Return a native (perhaps degraded) FormData with only a `append` method - * Can throw if it's not supported - * - * @return {FormData} - */ - ['_asNative']() { - const fd = new _FormData - - for (let [name, value] of this) - fd.append(name, value) - - return fd - } - - - /** - * [_blob description] - * - * @return {Blob} [description] - */ - ['_blob']() { - const boundary = '----formdata-polyfill-' + Math.random() - const chunks = [] - - for (let [name, value] of this) { - chunks.push(`--${boundary}\r\n`) - - if (value instanceof Blob) { - chunks.push( - `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`, - `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`, - value, - '\r\n' - ) - } else { - chunks.push( - `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n` - ) - } - } - - chunks.push(`--${boundary}--`) - - return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary}) - } - - - /** - * The class itself is iterable - * alias for formdata.entries() - * - * @return {Iterator} - */ - [Symbol.iterator]() { - return this.entries() - } - - - /** - * Create the default string description. - * - * @return {String} [object FormData] - */ - toString() { - return '[object FormData]' - } - } - - - if (stringTag) { - /** - * Create the default string description. - * It is accessed internally by the Object.prototype.toString(). - * - * @return {String} FormData - */ - FormDataPolyfill.prototype[stringTag] = 'FormData' - } - - const decorations = [ - ['append', normalizeArgs], - ['delete', stringify], - ['get', stringify], - ['getAll', stringify], - ['has', stringify], - ['set', normalizeArgs] - ] - - decorations.forEach(arr => { - const orig = FormDataPolyfill.prototype[arr[0]] - FormDataPolyfill.prototype[arr[0]] = function() { - return orig.apply(this, arr[1].apply(this, arrayFrom(arguments))) - } - }) - - // Patch xhr's send method to call _blob transparently - if (_send) { - XMLHttpRequest.prototype.send = function(data) { - // I would check if Content-Type isn't already set - // But xhr lacks getRequestHeaders functionallity - // https://github.com/jimmywarting/FormData/issues/44 - if (data instanceof FormDataPolyfill) { - const blob = data['_blob']() - this.setRequestHeader('Content-Type', blob.type) - _send.call(this, blob) - } else { - _send.call(this, data) - } - } - } - - // Patch fetch's function to call _blob transparently - if (_fetch) { - const _fetch = global.fetch - - global.fetch = function(input, init) { - if (init && init.body && init.body instanceof FormDataPolyfill) { - init.body = init.body['_blob']() - } - - return _fetch(input, init) - } - } - - global['FormData'] = FormDataPolyfill -} diff --git a/assets/js/imagify-wp-retina-2x.js b/assets/js/imagify-wp-retina-2x.js deleted file mode 100644 index eecc9c7c2..000000000 --- a/assets/js/imagify-wp-retina-2x.js +++ /dev/null @@ -1,87 +0,0 @@ -window.imagify = window.imagify || {}; - -jQuery.extend( window.imagify, { - retina2x: { - hookAjax: function() { - jQuery( document ).ajaxSend( this.addData ).ajaxSuccess( this.updateInfo ); - }, - addData: function( e, jqXHR, settings ) { - var isString, isFormData, actions, action, id; - - if ( ! settings.data ) { - return; - } - - isString = typeof settings.data === 'string'; - isFormData = settings.data instanceof FormData; - actions = window.imagifyRetina2x; - - if ( isString ) { - action = settings.data.match( /(?:^|&)action=([^&]+)/ ); - action = action ? action[1] : false; - id = settings.data.match( /(?:^|&)attachmentId=([^&]+)/ ); - id = id ? parseInt( id[1], 10 ) : 0; - } else if ( isFormData ) { - action = settings.data.get( 'action' ); - id = settings.data.get( 'attachmentId' ); - } else { - action = settings.data.action; - id = settings.data.id; - } - - if ( ! action || ! actions[ action ] ) { - return; - } - - if ( ! jQuery( '.imagify_optimized_file' ).length ) { - id = 0; - } - - if ( isString ) { - settings.data += '&imagify_nonce=' + actions[ action ]; - - if ( id > 0 ) { - settings.data += '&imagify_info=1'; - } - } else if ( isFormData ) { - settings.data.append( 'imagify_nonce', actions[ action ] ); - - if ( id > 0 ) { - settings.data.append( 'imagify_info', 1 ); - } - } else { - settings.data = jQuery.extend( settings.data, { 'imagify_nonce': actions[ action ] } ); - - if ( id > 0 ) { - settings.data = jQuery.extend( settings.data, { 'imagify_info': 1 } ); - } - } - }, - updateInfo: function( e, jqXHR, settings, data ) { - if ( typeof settings.data !== 'string' ) { - return; - } - - try { - data = jQuery.parseJSON( data ); - } - catch ( error ) { - return; - } - - if ( 'imagify' !== data.source || 'wr2x' !== data.context || ! data.imagify_info ) { - return; - } - - jQuery.each( data.imagify_info, function ( id, html ) { - var $wrapper = jQuery( '#post-' + id ).children( '.imagify_optimized_file' ); - - if ( $wrapper.length ) { - $wrapper.html( html ); - } - } ); - } - } -} ); - -window.imagify.retina2x.hookAjax(); diff --git a/assets/js/weakmap-polyfill.js b/assets/js/weakmap-polyfill.js deleted file mode 100755 index d304b5aa5..000000000 --- a/assets/js/weakmap-polyfill.js +++ /dev/null @@ -1,144 +0,0 @@ -/*! - * weakmap-polyfill v2.0.0 - ECMAScript6 WeakMap polyfill - * https://github.com/polygonplanet/weakmap-polyfill - * Copyright (c) 2015-2016 polygon planet - * @license MIT - */ - -(function(self) { - 'use strict'; - - if (self.WeakMap) { - return; - } - - var hasOwnProperty = Object.prototype.hasOwnProperty; - var defineProperty = function(object, name, value) { - if (Object.defineProperty) { - Object.defineProperty(object, name, { - configurable: true, - writable: true, - value: value - }); - } else { - object[name] = value; - } - }; - - self.WeakMap = (function() { - - // ECMA-262 23.3 WeakMap Objects - function WeakMap() { - if (this === void 0) { - throw new TypeError("Constructor WeakMap requires 'new'"); - } - - defineProperty(this, '_id', genId('_WeakMap')); - - // ECMA-262 23.3.1.1 WeakMap([iterable]) - if (arguments.length > 0) { - // Currently, WeakMap `iterable` argument is not supported - throw new TypeError('WeakMap iterable is not supported'); - } - } - - // ECMA-262 23.3.3.2 WeakMap.prototype.delete(key) - defineProperty(WeakMap.prototype, 'delete', function(key) { - checkInstance(this, 'delete'); - - if (!isObject(key)) { - return false; - } - - var entry = key[this._id]; - if (entry && entry[0] === key) { - delete key[this._id]; - return true; - } - - return false; - }); - - // ECMA-262 23.3.3.3 WeakMap.prototype.get(key) - defineProperty(WeakMap.prototype, 'get', function(key) { - checkInstance(this, 'get'); - - if (!isObject(key)) { - return void 0; - } - - var entry = key[this._id]; - if (entry && entry[0] === key) { - return entry[1]; - } - - return void 0; - }); - - // ECMA-262 23.3.3.4 WeakMap.prototype.has(key) - defineProperty(WeakMap.prototype, 'has', function(key) { - checkInstance(this, 'has'); - - if (!isObject(key)) { - return false; - } - - var entry = key[this._id]; - if (entry && entry[0] === key) { - return true; - } - - return false; - }); - - // ECMA-262 23.3.3.5 WeakMap.prototype.set(key, value) - defineProperty(WeakMap.prototype, 'set', function(key, value) { - checkInstance(this, 'set'); - - if (!isObject(key)) { - throw new TypeError('Invalid value used as weak map key'); - } - - var entry = key[this._id]; - if (entry && entry[0] === key) { - entry[1] = value; - return this; - } - - defineProperty(key, this._id, [key, value]); - return this; - }); - - - function checkInstance(x, methodName) { - if (!isObject(x) || !hasOwnProperty.call(x, '_id')) { - throw new TypeError( - methodName + ' method called on incompatible receiver ' + - typeof x - ); - } - } - - function genId(prefix) { - return prefix + '_' + rand() + '.' + rand(); - } - - function rand() { - return Math.random().toString().substring(2); - } - - - defineProperty(WeakMap, '_polyfill', true); - return WeakMap; - })(); - - - function isObject(x) { - return Object(x) === x; - } - -})( - typeof self !== 'undefined' ? self : - typeof window !== 'undefined' ? window : - typeof global !== 'undefined' ? global : this -); From 3ee9a9294c55866468f0c7378e0648c9b6092dbf Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 11:24:39 -0500 Subject: [PATCH 12/21] Remove PerfectImagesCore All Perfect Images functionality is handled in the PerfectImages class now. The PerfectImagesCore is dead code. --- .../classes/PerfectImagesCore.php | 1697 ----------------- 1 file changed, 1697 deletions(-) delete mode 100644 inc/3rd-party/perfect-images/classes/PerfectImagesCore.php diff --git a/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php b/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php deleted file mode 100644 index 047b479e4..000000000 --- a/inc/3rd-party/perfect-images/classes/PerfectImagesCore.php +++ /dev/null @@ -1,1697 +0,0 @@ -filesystem = Imagify_Filesystem::get_instance(); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** GENERATE RETINA IMAGES ================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Generate retina images (except full size), and optimize them if the non-retina images are. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function generate_retina_images( $attachment ) { - $tests = $this->validate( __FUNCTION__, $attachment ); - - if ( true !== $tests ) { - return $tests; - } - - // Backup the optimized full-sized image and replace it by the original backup file, so it can be used to create new retina images. - $this->backup_optimized_file( $attachment ); - - if ( ! $this->filesystem->exists( $attachment->get_original_path() ) ) { - return new WP_Error( 'file_missing', 'The main file does not exist.' ); - } - - // Create retina images. - wr2x_generate_images( wp_get_attachment_metadata( $attachment->get_id() ) ); - - // Put the optimized full-sized file back. - $this->put_optimized_file_back( $attachment ); - - /** - * If the non-retina images are optimized by Imagify (or at least the user wanted it to be optimized at some point, and now has a "already optimized" or "error" status), optimize newly created retina files. - * If the retina version of the full size exists and is not optimized yet, it will be processed as well. - */ - if ( $attachment->is_optimized() && $this->can_auto_optimize() ) { - $this->optimize_retina_images( $attachment ); - } - - return true; - } - - /** - * Delete previous retina images and recreate them (except full size), and optimize them if they previously were. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function regenerate_retina_images( $attachment ) { - $tests = $this->validate( __FUNCTION__, $attachment ); - - if ( true !== $tests ) { - return $tests; - } - - // Delete the retina files and remove retina sizes from Imagify data. - $result = $this->delete_retina_images( $attachment ); - - if ( is_wp_error( $result ) ) { - return $result; - } - - // Create new retina files (and optimize them if they previously were). - return $this->generate_retina_images( $attachment ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** DELETE RETINA IMAGES ==================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Delete the retina images. Also removes the related Imagify data. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param bool $delete_full_image True to also delete the retina version of the full size. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function delete_retina_images( $attachment, $delete_full_image = false ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'metadata_dimensions' => 'error', - ) ); - - if ( true !== $tests ) { - return $tests; - } - - /** - * To be a bit faster we update the data at once at the end. - * - * @see Imagify_WP_Retina_2x::remove_retina_thumbnail_data_hook(). - */ - $this->prevent( 'remove_retina_image_data_by_filename' ); - - // Delete the retina thumbnails. - wr2x_delete_attachment( $attachment->get_id(), $delete_full_image ); - - $this->allow( 'remove_retina_image_data_by_filename' ); - - // Remove retina sizes from Imagify data. - $this->remove_retina_images_data( $attachment, $delete_full_image ); - - return true; - } - - /** - * Delete the retina version of the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function delete_full_retina_image( $attachment ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'metadata_file' => false, - 'metadata_sizes' => false, - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $retina_path = wr2x_get_retina( $attachment->get_original_path() ); - - if ( $retina_path ) { - // The file exists. - $this->filesystem->delete( $retina_path ); - } - - // Delete related Imagify data. - return $this->remove_size_from_imagify_data( $attachment, 'full@2x' ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** REPLACE IMAGES ========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Replace an attachment (except the retina version of the full size). - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $file_path Path to the new file. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function replace_attachment( $attachment, $file_path ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'metadata_sizes' => false, - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $attachment_id = $attachment->get_id(); - $sizes = $this->get_attachment_sizes( $attachment ); - $original_path = $attachment->get_original_path(); - $dir_path = $this->filesystem->path_info( $original_path, 'dir_path' ); - - // Insert the new file (and overwrite the full size). - $moved = $this->filesystem->move( $file_path, $original_path, true ); - - if ( ! $moved ) { - return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) ); - } - - // Delete retina images. - $this->delete_retina_images( $attachment ); - - // Delete the non-retina images. - if ( $sizes ) { - foreach ( $sizes as $name => $attr ) { - $size_path = $dir_path . $attr['file']; - - if ( $this->filesystem->exists( $size_path ) && $this->filesystem->is_file( $size_path ) ) { - // If the deletion fails, we're screwed anyway since the main file has been deleted, so no need to return an error here. - $this->filesystem->delete( $size_path ); - } - } - } - - // Get some Imagify data before deleting everything. - $optimization_level = $this->get_optimization_level( $attachment ); - $full_retina_data = $attachment->get_data(); - $full_retina_data = ! empty( $full_retina_data['sizes']['full@2x'] ) ? $full_retina_data['sizes']['full@2x'] : false; - $full_retina_optimized = $full_retina_data && ! empty( $full_retina_data['success'] ); - - // Delete the Imagify data. - $attachment->delete_imagify_data(); - - // Delete the backup file. - $attachment->delete_backup(); - - // Prevent auto-optimization. - Imagify_Auto_Optimization::prevent_optimization( $attachment_id ); - - // Generate the non-retina images and the related WP metadata. - wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $original_path ) ); - - // Allow auto-optimization back. - Imagify_Auto_Optimization::allow_optimization( $attachment_id ); - - // Generate retina images (since the Imagify data has been deleted, the images won't be optimized here). - $result = $this->generate_retina_images( $attachment ); - - if ( is_wp_error( $result ) ) { - if ( $full_retina_optimized ) { - // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day. - $this->restore_full_retina_file( $attachment ); - } - - return $result; - } - - if ( $this->can_auto_optimize() ) { - if ( $full_retina_optimized ) { - // Don't optimize the retina full size, it already is. - remove_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ) ); - } - - /** - * Optimize everyone. - * - * @see Imagify_WP_Retina_2x::optimize_full_retina_version_hook() - * @see Imagify_WP_Retina_2x::optimize_retina_version_hook() - * @see Imagify_WP_Retina_2x::maybe_optimize_unauthorized_retina_version_hook(). - */ - $attachment->optimize( $optimization_level ); - - if ( $full_retina_optimized ) { - add_filter( 'imagify_fill_full_size_data', array( Imagify_WP_Retina_2x::get_instance(), 'optimize_full_retina_version_hook' ), 10, 8 ); - - if ( $attachment->is_optimized() ) { - // Put data back. - $data = $attachment->get_data(); - $data['sizes']['full@2x'] = $full_retina_data; - update_post_meta( $attachment_id, '_imagify_data', $data ); - } else { - $this->restore_full_retina_file( $attachment ); - } - } - } elseif ( $full_retina_optimized ) { - // The retina version of the full size is optimized: restore it overwise the user may optimize it again some day. - $this->restore_full_retina_file( $attachment ); - } - - return true; - } - - /** - * Replace an attachment (except the retina version of the full size). - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $file_path Path to the new file. - * @return bool|object True on success, false if prevented, a WP_Error object on failure. - */ - public function replace_full_retina_image( $attachment, $file_path ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'metadata_file' => false, - 'metadata_sizes' => false, - ) ); - - if ( true !== $tests ) { - return $tests; - } - - // Replace the file. - $retina_path = $this->get_retina_path( $attachment->get_original_path() ); - $moved = $this->filesystem->move( $file_path, $retina_path, true ); - - if ( ! $moved ) { - return new WP_Error( 'not_writable', __( 'Replacement failed.', 'imagify' ) ); - } - - // Delete related Imagify data. - $this->remove_size_from_imagify_data( $attachment, 'full@2x' ); - - // Delete previous backup file. - $result = $this->delete_file_backup( $retina_path ); - - if ( is_wp_error( $result ) ) { - $this->filesystem->delete( $file_path ); - return $result; - } - - // Optimize. - if ( $attachment->is_optimized() && $this->can_auto_optimize() ) { - return $this->optimize_full_retina_image( $attachment ); - } - } - - - /** ----------------------------------------------------------------------------------------- */ - /** OPTIMIZE RETINA IMAGES ================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Optimize retina images. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param bool $optimize_full_size False to not optimize the retina version of the full size. - * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. - */ - public function optimize_retina_images( $attachment, $optimize_full_size = true ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'supported' => true, - 'can_optimize' => 'error', - 'metadata_dimensions' => 'error', - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - - if ( $optimize_full_size ) { - $metadata['sizes']['full'] = array( - 'file' => $this->filesystem->file_name( $metadata['file'] ), - 'width' => (int) $metadata['width'], - 'height' => (int) $metadata['height'], - 'mime-type' => get_post_mime_type( $attachment->get_id() ), - ); - } - - return $this->optimize_retina_sizes( $attachment, $metadata['sizes'] ); - } - - /** - * Optimize the full size retina image. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. - */ - public function optimize_full_retina_image( $attachment ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'supported' => true, - 'can_optimize' => 'error', - 'metadata_dimensions' => 'error', - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - - $sizes = array( - 'full' => array( - 'file' => $this->filesystem->file_name( $metadata['file'] ), - 'width' => (int) $metadata['width'], - 'height' => (int) $metadata['height'], - 'mime-type' => get_post_mime_type( $attachment->get_id() ), - ), - ); - - return $this->optimize_retina_sizes( $attachment, $sizes ); - } - - /** - * Optimize the given retina images. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param array $sizes A list of non-retina sizes, formatted like in wp_get_attachment_metadata(). - * @return bool|object True on success, false if prevented or not supported or no sizes, a WP_Error object on failure. - */ - public function optimize_retina_sizes( $attachment, $sizes ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'supported' => true, - 'can_optimize' => 'error', - 'metadata_dimensions' => 'error', - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $attachment_id = $attachment->get_id(); - $optimization_level = $this->get_optimization_level( $attachment ); - - /** - * Filter the retina thumbnail sizes to optimize for a given attachment. This includes the sizes disabled in Imagify’ settings. - * - * @since 1.8 - * @author Grégory Viguier - * - * @param array $sizes An array of non-retina thumbnail sizes. - * @param int $attachment_id The attachment ID. - * @param int $optimization_level The optimization level. - */ - $sizes = apply_filters( 'imagify_attachment_retina_sizes', $sizes, $attachment_id, $optimization_level ); - - if ( ! $sizes || ! is_array( $sizes ) ) { - return false; - } - - $original_dirpath = $this->filesystem->dir_path( $attachment->get_original_path() ); - - foreach ( $sizes as $size_key => $image_data ) { - $retina_path = wr2x_get_retina( $original_dirpath . $image_data['file'] ); - - if ( ! $retina_path ) { - unset( $sizes[ $size_key ] ); - continue; - } - - // The file exists. - $sizes[ $size_key ]['retina-path'] = $retina_path; - } - - if ( ! $sizes ) { - return false; - } - - $attachment->set_running_status(); - - /** - * Fires before optimizing the retina images. - * - * @since 1.8 - * @author Grégory Viguier - * - * @param int $attachment_id The attachment ID. - * @param array $sizes An array of non-retina thumbnail sizes. - * @param int $optimization_level The optimization level. - */ - do_action( 'before_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level ); - - $imagify_data = $attachment->get_data(); - - foreach ( $sizes as $size_key => $image_data ) { - $imagify_data = $this->optimize_retina_image( array( - 'data' => $imagify_data, - 'attachment' => $attachment, - 'retina_path' => $image_data['retina-path'], - 'size_key' => $size_key, - 'optimization_level' => $optimization_level, - ) ); - } - - $this->update_imagify_data( $attachment, $imagify_data ); - - /** - * Fires after optimizing the retina images. - * - * @since 1.8 - * @author Grégory Viguier - * - * @param int $attachment_id The attachment ID. - * @param array $sizes An array of non-retina thumbnail sizes. - * @param int $optimization_level The optimization level. - */ - do_action( 'after_imagify_optimize_retina_images', $attachment_id, $sizes, $optimization_level ); - - $attachment->delete_running_status(); - - return true; - } - - /** - * Optimize the retina version of an image. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $args { - * An array of required arguments. - * - * @type array $data The statistics data. - * @type object $attachment An Imagify attachment. - * @type string $retina_path The path to the retina file. - * @type string $size_key The attachment size key (without '@2x'). - * @type int $optimization_level The optimization level. Optionnal. - * @type array $metadata WP metadata. If omitted, wp_get_attachment_metadata() will be used. - * } - * @return array The new optimization data. - */ - public function optimize_retina_image( $args ) { - static $backup; - - $args = array_merge( array( - 'data' => array(), - 'attachment' => false, - 'retina_path' => '', - 'size_key' => '', - 'optimization_level' => false, - 'metadata' => array(), - ), $args ); - - if ( $this->is_prevented( __FUNCTION__ ) || ! $args['retina_path'] || $this->has_filesystem_error() ) { - return $args['data']; - } - - $retina_key = $args['size_key'] . '@2x'; - - if ( isset( $args['data'][ $retina_key ] ) ) { - // Don't optimize something that already is. - return $args['data']; - } - - $disallowed = $this->size_is_disallowed( $args['size_key'] ); - $do_retina = ! $disallowed; - /** - * Allow to optimize the retina version generated by WP Retina x2. - * - * @since 1.0 - * @since 1.8 Added $args parameter. - * - * @param bool $do_retina True will allow the optimization. False to prevent it. - * @param string $args The arguments passed to the method. - */ - $do_retina = apply_filters( 'do_imagify_optimize_retina', $do_retina, $args ); - - if ( ! $do_retina ) { - if ( $disallowed ) { - $message = __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ); - } else { - $message = __( 'This size optimization has been prevented by a filter.', 'imagify' ); - } - - $args['data']['sizes'][ $retina_key ] = array( - 'success' => false, - 'error' => $message, - ); - return $args['data']; - } - - if ( ! $args['metadata'] || ! is_array( $args['metadata'] ) ) { - $args['metadata'] = wp_get_attachment_metadata( $args['attachment']->get_id() ); - } - - $is_a_copy = $this->size_is_a_full_copy( array( - 'size_name' => $args['size_key'], - 'metadata' => $args['metadata'], - 'imagify_data' => $args['data'], - 'retina_path' => $args['retina_path'], - ) ); - - if ( $is_a_copy ) { - // This thumbnail is a copy of the full size image, which is already optimized. - $args['data']['sizes'][ $retina_key ] = $args['data']['sizes']['full']; - - if ( isset( $args['data']['sizes']['full']['original_size'], $args['data']['sizes']['full']['optimized_size'] ) ) { - // Concistancy only. - $args['data']['stats']['original_size'] += $args['data']['sizes']['full']['original_size']; - $args['data']['stats']['optimized_size'] += $args['data']['sizes']['full']['optimized_size']; - } - - return $args['data']; - } - - if ( ! is_int( $args['optimization_level'] ) ) { - $args['optimization_level'] = get_imagify_option( 'optimization_level' ); - } - - // Hammer time. - $response = do_imagify( $args['retina_path'], array( - // Backup only if it's the full size. - 'backup' => 'full' === $args['size_key'], - 'optimization_level' => $args['optimization_level'], - 'context' => 'wp-retina', - ) ); - - return $args['attachment']->fill_data( $args['data'], $response, $retina_key ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** HANDLE BACKUPS ========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Backup a retina file. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $file_path Path to the file. - * @return bool|object True on success, false if prevented or no need for backup, a WP_Error object on failure. - */ - public function backup_file( $file_path ) { - static $backup; - - if ( $this->is_prevented( __FUNCTION__ ) ) { - return false; - } - - if ( ! isset( $backup ) ) { - $backup = get_imagify_option( 'backup' ); - } - - if ( ! $backup ) { - return false; - } - - if ( $this->has_filesystem_error() ) { - return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) ); - } - - $upload_basedir = get_imagify_upload_basedir(); - - if ( ! $upload_basedir ) { - $file_path = make_path_relative( $file_path ); - - /* translators: %s is a file path. */ - return new WP_Error( 'upload_basedir', sprintf( __( 'The file %s could not be backed up. Image optimization aborted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); - } - - $file_path = wp_normalize_path( $file_path ); - $backup_dir = get_imagify_backup_dir_path(); - $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path ); - - if ( $this->filesystem->exists( $backup_path ) ) { - $this->filesystem->delete( $backup_path ); - } - - $backup_result = imagify_backup_file( $file_path, $backup_path ); - - if ( is_wp_error( $backup_result ) ) { - return $backup_result; - } - - return true; - } - - /** - * Delete a retina file backup. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $file_path Path to the file. - * @return bool|object True on success, false if the file doesn't exist, a WP_Error object on failure. - */ - public function delete_file_backup( $file_path ) { - $tests = $this->validate( __FUNCTION__ ); - - if ( true !== $tests ) { - return $tests; - } - - $upload_basedir = get_imagify_upload_basedir(); - - if ( ! $upload_basedir ) { - $file_path = make_path_relative( $file_path ); - - /* translators: %s is a file path. */ - return new WP_Error( 'upload_basedir', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); - } - - $file_path = wp_normalize_path( $file_path ); - $backup_dir = get_imagify_backup_dir_path(); - $backup_path = str_replace( $upload_basedir, $backup_dir, $file_path ); - - if ( ! $this->filesystem->exists( $backup_path ) ) { - return false; - } - - $result = $this->filesystem->delete( $backup_path ); - - if ( ! $result ) { - $file_path = make_path_relative( $file_path ); - - /* translators: %s is a file path. */ - return new WP_Error( 'not_deleted', sprintf( __( 'Previous backup file for %s could not be deleted.', 'imagify' ), '' . esc_html( $file_path ) . '' ) ); - } - - return true; - } - - /** - * Restore the retina version of the full size. - * This doesn't remove the Imagify data. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object True on success, false if prevented or backup doesn't exist, a WP_Error object on failure. - */ - public function restore_full_retina_file( $attachment ) { - $tests = $this->validate( __FUNCTION__, $attachment, array( - 'metadata_file' => false, - 'metadata_sizes' => false, - ) ); - - if ( true !== $tests ) { - return $tests; - } - - $has_backup = $this->full_retina_has_backup( $attachment ); - - if ( is_wp_error( $has_backup ) ) { - return $has_backup; - } - - if ( ! $has_backup ) { - return new WP_Error( 'no_backup', __( 'The retina version of the full size of this image does not have backup.', 'imagify' ) ); - } - - $file_path = $this->get_retina_path( $attachment->get_original_path() ); - $backup_path = $this->get_full_retina_backup_path( $attachment ); - - /** - * Fires before restoring the retina version of the full size. - * - * @since 1.8 - * @author Grégory Viguier - * - * @param string $backup_path Path to the backup file. - * @param string $file_path Path to the source file. - */ - do_action( 'before_imagify_restore_full_retina_file', $backup_path, $file_path ); - - // Save disc space by moving it instead of copying it. - $moved = $this->filesystem->move( $backup_path, $file_path, true ); - - /** - * Fires after restoring the retina version of the full size. - * - * @since 1.8 - * @author Grégory Viguier - * - * @param string $backup_path Path to the backup file. - * @param string $file_path Path to the source file. - * @param bool $moved Restore success. - */ - do_action( 'after_imagify_restore_full_retina_file', $backup_path, $file_path, $moved ); - - if ( ! $moved ) { - return new WP_Error( 'upload_basedir', __( 'Backup of the retina version of the full size image could not be restored.', 'imagify' ) ); - } - - return true; - } - - /** - * Get the path to the retina version of the full size. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return string|object The path on success, a WP_Error object on failure. - */ - public function get_full_retina_backup_path( $attachment ) { - $file_path = $this->get_retina_path( $attachment->get_original_path() ); - $backup_path = get_imagify_attachment_backup_path( $file_path ); - - if ( ! $backup_path ) { - return new WP_Error( 'upload_basedir', __( 'Could not retrieve the path to the backup of the retina version of the full size image.', 'imagify' ) ); - } - - return $backup_path; - } - - /** - * Tell if the retina version of the full size has a backup. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool|object A WP_Error object on failure. - */ - public function full_retina_has_backup( $attachment ) { - $backup_path = $this->get_full_retina_backup_path( $attachment ); - - if ( is_wp_error( $backup_path ) ) { - return $backup_path; - } - - return $this->filesystem->exists( $backup_path ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** HANDLE IMAGIFY DATA ===================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Remove retina versions from Imagify data. - * It also rebuilds the attachment stats. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param bool $remove_full_size True to also remove the full size data. - */ - public function remove_retina_images_data( $attachment, $remove_full_size = false ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return; - } - - $imagify_data = $attachment->get_data(); - - if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { - return; - } - - $sizes = $this->get_attachment_sizes( $attachment ); - - if ( ! $sizes ) { - return; - } - - $update = false; - - if ( $remove_full_size && isset( $imagify_data['sizes']['full@2x'] ) ) { - unset( $imagify_data['sizes']['full@2x'] ); - $update = true; - } - - foreach ( $sizes as $size => $attr ) { - $size .= '@2x'; - - if ( isset( $imagify_data['sizes'][ $size ] ) ) { - unset( $imagify_data['sizes'][ $size ] ); - $update = true; - } - } - - if ( ! $update ) { - return; - } - - $this->update_imagify_data( $attachment, $imagify_data ); - } - - /** - * Remove a retina thumbnail from attachment's Imagify data, given the retina file name. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $retina_filename Retina thumbnail file name. - */ - public function remove_retina_image_data_by_filename( $attachment, $retina_filename ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return; - } - - $imagify_data = $attachment->get_data(); - - if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { - return; - } - - $sizes = $this->get_attachment_sizes( $attachment ); - - if ( ! $sizes ) { - return; - } - - $image_filename = str_replace( $this->get_suffix(), '.', $retina_filename ); - $size = false; - - foreach ( $sizes as $name => $attr ) { - if ( $image_filename === $attr['file'] ) { - $size = $name; - break; - } - } - - if ( ! $size || ! isset( $imagify_data['sizes'][ $size ] ) ) { - return; - } - - unset( $imagify_data['sizes'][ $size ] ); - - $this->update_imagify_data( $attachment, $imagify_data ); - } - - /** - * Rebuild the attachment stats and store the data. - * Delete all Imagify data if the sizes are empty. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param array $imagify_data Imagify data. - * @return bool True on update, false on delete or prevented. - */ - public function update_imagify_data( $attachment, $imagify_data ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return false; - } - - if ( empty( $imagify_data['sizes'] ) || ! is_array( $imagify_data['sizes'] ) ) { - // No new sizes. - $attachment->delete_imagify_data(); - return false; - } - - $imagify_data['stats'] = array( - 'original_size' => 0, - 'optimized_size' => 0, - 'percent' => 0, - ); - - foreach ( $imagify_data['sizes'] as $size_data ) { - $imagify_data['stats']['original_size'] += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0; - $imagify_data['stats']['optimized_size'] += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0; - } - - if ( $imagify_data['stats']['original_size'] && $imagify_data['stats']['optimized_size'] ) { - $imagify_data['stats']['percent'] = round( ( ( $imagify_data['stats']['original_size'] - $imagify_data['stats']['optimized_size'] ) / $imagify_data['stats']['original_size'] ) * 100, 2 ); - } - - update_post_meta( $attachment->get_id(), '_imagify_data', $imagify_data ); - - return true; - } - - /** - * Remove a size from Imagify data. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $size_name Name of the size. - */ - public function remove_size_from_imagify_data( $attachment, $size_name ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return; - } - - $imagify_data = $attachment->get_data(); - - if ( ! isset( $imagify_data['sizes'][ $size_name ] ) ) { - return; - } - - unset( $imagify_data['sizes'][ $size_name ] ); - - $this->update_imagify_data( $attachment, $imagify_data ); - } - - - /** ----------------------------------------------------------------------------------------- */ - /** INTERNAL TOOLS ========================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Tell if a file extension is supported by WP Retina 2x. - * It uses $wr2x_core->is_supported_image() if available. - * - * @since 1.8 - * @access public - * @see $wr2x_core->is_supported_image() - * @author Grégory Viguier - * - * @param string|int $file_path Path to the file or attachment ID. - * @return bool - */ - public function is_supported_format( $file_path ) { - global $wr2x_core; - static $method; - static $results = array(); - - if ( ! $file_path ) { - return false; - } - - if ( isset( $results[ $file_path ] ) ) { - // $file_path can be a path or an attachment ID. - return $results[ $file_path ]; - } - - if ( is_int( $file_path ) ) { - $attachment_id = $file_path; - $file_path = get_attached_file( $attachment_id ); - - if ( ! $file_path ) { - $results[ $attachment_id ] = false; - return false; - } - - if ( isset( $results[ $file_path ] ) ) { - // $file_path is now a path for sure. - $results[ $attachment_id ] = $results[ $file_path ]; - return $results[ $file_path ]; - } - } - - if ( ! isset( $method ) ) { - if ( $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'is_supported_image' ) ) { - $method = array( $wr2x_core, 'is_supported_image' ); - } else { - $method = array( $this, 'is_supported_extension' ); - } - } - - // $file_path is now a path for sure. - $results[ $file_path ] = call_user_func( $method, $file_path ); - - if ( ! empty( $attachment_id ) ) { - $results[ $attachment_id ] = $results[ $file_path ]; - } - - return $results[ $file_path ]; - } - - /** - * Tell if a file extension is supported by WP Retina 2x. - * Internal version of $wr2x_core->is_supported_image(). - * - * @since 1.8 - * @access public - * @see $this->is_supported_format() - * @author Grégory Viguier - * - * @param string $file_path Path to a file. - * @return bool - */ - protected function is_supported_extension( $file_path ) { - $extension = strtolower( $this->filesystem->path_info( $file_path, 'extension' ) ); - $extensions = array( - 'jpg' => 1, - 'jpeg' => 1, - 'png' => 1, - 'gif' => 1, - ); - - return isset( $extensions[ $extension ] ); - } - - /** - * Get the path to the retina version of an image. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $file_path Path to the non-retina image. - * @return string - */ - public function get_retina_path( $file_path ) { - $path_info = $this->filesystem->path_info( $file_path ); - $suffix = rtrim( $this->get_suffix(), '.' ); - $extension = isset( $path_info['extension'] ) ? '.' . $path_info['extension'] : ''; - - return $path_info['dir_path'] . $path_info['file_base'] . $suffix . $extension; - } - - /** - * Tell if the attchment has at least one retina image. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return bool - */ - public function has_retina_images( $attachment ) { - $dir_path = $this->filesystem->path_info( $attachment->get_original_path(), 'dir_path' ); - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); - $sizes['full'] = array( - 'file' => $this->filesystem->file_name( $metadata['file'] ), - ); - - foreach ( $sizes as $name => $attr ) { - $size_path = $this->get_retina_path( $dir_path . $attr['file'] ); - - if ( $this->filesystem->exists( $size_path ) ) { - return true; - } - } - - return false; - } - - /** - * Prevent a method to do its job. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $method_name Name of the method to prevent. - */ - public function prevent( $method_name ) { - if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] < 1 ) { - self::$prevented[ $method_name ] = 1; - } else { - ++self::$prevented[ $method_name ]; - } - } - - /** - * Allow a method to do its job. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $method_name Name of the method to allow. - */ - public function allow( $method_name ) { - if ( empty( self::$prevented[ $method_name ] ) || self::$prevented[ $method_name ] <= 1 ) { - unset( self::$prevented[ $method_name ] ); - } else { - --self::$prevented[ $method_name ]; - } - } - - /** - * Tell if a method is prevented to do its job. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $method_name Name of the method. - * @return bool - */ - public function is_prevented( $method_name ) { - return ! empty( self::$prevented[ $method_name ] ); - } - - /** - * Tell if a thumbnail size is disallowed for optimization.. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $size_name The size name. - * @return bool - */ - public function size_is_disallowed( $size_name ) { - static $disallowed_sizes; - - if ( imagify_is_active_for_network() ) { - return false; - } - - if ( ! isset( $disallowed_sizes ) ) { - $disallowed_sizes = get_imagify_option( 'disallowed-sizes' ); - } - - return isset( $disallowed_sizes[ $size_name ] ); - } - - /** - * Tell if a thumbnail file is a copy of the full size image. Will return false if the full size is not optimized. - * Make sure both files exist before using this. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param array $args { - * An array of arguments. - * - * @type string $size_name The size name. Required. - * @type array $metadata WP metadata. Required. - * @type array $imagify_data Imagify data. Required. - * @type string $retina_path Path to the image we're testing. Required. - * @type string $full_path Path to the full size image. Optional but should be provided. - * } - * @return bool - */ - public function size_is_a_full_copy( $args ) { - $size_name = $args['size_name']; - $metadata = $args['metadata']; - $imagify_data = $args['imagify_data']; - - if ( empty( $imagify_data['sizes']['full'] ) ) { - // The full size is not optimized, so there is no point in checking if the given file is a copy. - return false; - } - - if ( ! isset( $metadata['width'], $metadata['height'], $metadata['file'] ) ) { - return false; - } - - if ( ! isset( $metadata['sizes'][ $size_name ]['width'], $metadata['sizes'][ $size_name ]['height'] ) ) { - return false; - } - - $size = $metadata['sizes'][ $size_name ]; - - if ( $size['width'] * 2 !== $metadata['width'] || $size['height'] * 2 !== $metadata['height'] ) { - // The full size image doesn't have the right dimensions. - return false; - } - - if ( empty( $args['full_path'] ) ) { - $dir_path = $this->filesystem->path_info( $args['retina_path'], 'dir_path' ); - $args['full_path'] = $dir_path . $metadata['file']; - } - - $full_hash = md5_file( $args['full_path'] ); - $retina_hash = md5_file( $args['retina_path'] ); - - return hash_equals( $full_hash, $retina_hash ); - } - - /** - * Tell if there is a filesystem error. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return bool - */ - public function has_filesystem_error() { - return ! empty( $this->filesystem->errors->errors ); - } - - /** - * Do few tests: method is not prevented, attachment is valid, filesystem has no error. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $method The name of the method using this. - * @param object $attachment An Imagify attachment. - * @param array $args A list of additional tests. - * @return bool|object True if ok, false if prevented, a WP_Error object on failure. - */ - public function validate( $method, $attachment = false, $args = array() ) { - $args = array_merge( array( - 'supported' => false, - 'can_optimize' => false, - 'metadata_dimensions' => false, - 'metadata_file' => $attachment ? 'error' : false, - 'metadata_sizes' => $attachment ? 'error' : false, - ), $args ); - - if ( $this->is_prevented( $method ) ) { - return false; - } - - if ( $attachment && ! $attachment->is_valid() ) { - return new WP_Error( 'invalid_attachment', __( 'Invalid attachment.', 'imagify' ) ); - } - - if ( $args['supported'] && ! $attachment->is_extension_supported() ) { - if ( 'error' !== $args['supported'] ) { - return false; - } - - return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); - } - - if ( $args['can_optimize'] ) { - if ( 'error' !== $args['can_optimize'] ) { - if ( ! Imagify_Requirements::is_api_key_valid() || Imagify_Requirements::is_over_quota() ) { - return false; - } - } else { - if ( ! Imagify_Requirements::is_api_key_valid() ) { - return new WP_Error( 'invalid_api_key', __( 'Your API key is not valid!', 'imagify' ) ); - } - if ( Imagify_Requirements::is_over_quota() ) { - return new WP_Error( 'over_quota', __( 'You have used all your credits!', 'imagify' ) ); - } - } - } - - if ( $this->has_filesystem_error() ) { - return new WP_Error( 'filesystem', __( 'Filesystem error.', 'imagify' ) ); - } - - if ( $args['metadata_dimensions'] ) { - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - - if ( empty( $metadata['width'] ) || empty( $metadata['height'] ) ) { - if ( 'error' !== $args['metadata_sizes'] ) { - return false; - } - - return new WP_Error( 'metadata_dimensions', __( 'This attachment lacks the required metadata.', 'imagify' ) ); - } - } - - if ( $args['metadata_file'] ) { - $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() ); - - if ( empty( $metadata['file'] ) ) { - if ( 'error' !== $args['metadata_file'] ) { - return false; - } - - return new WP_Error( 'metadata_file', __( 'This attachment lacks the required metadata.', 'imagify' ) ); - } - } - - if ( $args['metadata_sizes'] ) { - $metadata = isset( $metadata ) ? $metadata : wp_get_attachment_metadata( $attachment->get_id() ); - - if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) { - if ( 'error' !== $args['metadata_sizes'] ) { - return false; - } - - return new WP_Error( 'metadata_sizes', __( 'This attachment has no registered thumbnail sizes.', 'imagify' ) ); - } - } - - return true; - } - - /** - * Tell if Imagify can optimize the files. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return bool - */ - public function can_optimize() { - return ! $this->has_filesystem_error() && Imagify_Requirements::is_api_key_valid() && ! Imagify_Requirements::is_over_quota(); - } - - /** - * Tell if Imagify can auto-optimize the files. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return bool - */ - public function can_auto_optimize() { - return $this->can_optimize() && get_imagify_option( 'auto_optimize' ); - } - - /** - * Get thumbnail sizes from an attachment. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return array - */ - public function get_attachment_sizes( $attachment ) { - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - return ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); - } - - /** - * Get the optimization level used to optimize the given attachment. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @return int The attachment optimization level. The default level if not optimized. - */ - public function get_optimization_level( $attachment ) { - static $default; - - if ( $attachment->get_status() ) { - $level = $attachment->get_optimization_level(); - - if ( is_int( $level ) ) { - return $level; - } - } - - if ( ! isset( $default ) ) { - $default = get_imagify_option( 'optimization_level' ); - } - - return $default; - } - - /** - * Get the path to the temporary file. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $file_path The optimized full-sized file path. - * @return string - */ - public function get_temporary_file_path( $file_path ) { - return $file_path . '_backup'; - } - - /** - * Backup the optimized full-sized file and replace it by the original backup file. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - */ - public function backup_optimized_file( $attachment ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return; - } - - $backup_path = $attachment->get_backup_path(); - - if ( ! $backup_path || ! $attachment->is_optimized() ) { - return; - } - - /** - * Replace the optimized full-sized file by the backup, so any optimization will not use an optimized file, but the original one. - * The optimized full-sized file is kept and renamed, and will be put back in place at the end of the optimization process. - */ - $file_path = $attachment->get_original_path(); - $tmp_file_path = $this->get_temporary_file_path( $file_path ); - - if ( $this->filesystem->exists( $file_path ) ) { - $this->filesystem->move( $file_path, $tmp_file_path, true ); - } - - $copied = $this->filesystem->copy( $backup_path, $file_path ); - - if ( ! $copied ) { - // Uh ho... - $this->filesystem->move( $tmp_file_path, $file_path, true ); - return; - } - - // Make sure the dimensions are in sync in post meta. - $this->maybe_update_image_dimensions( $attachment, $file_path ); - } - - /** - * Put the optimized full-sized file back. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - */ - public function put_optimized_file_back( $attachment ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return; - } - - $file_path = $attachment->get_original_path(); - $tmp_file_path = $this->get_temporary_file_path( $file_path ); - - if ( ! $this->filesystem->exists( $tmp_file_path ) ) { - return; - } - - $moved = $this->filesystem->move( $tmp_file_path, $file_path, true ); - - if ( ! $moved ) { - // Uh ho... - return; - } - - // Make sure the dimensions are in sync in post meta. - $this->maybe_update_image_dimensions( $attachment, $file_path ); - } - - /** - * Make sure the dimensions are in sync in post meta. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $file_path Path to the file. - * @return bool True when updated. - */ - public function maybe_update_image_dimensions( $attachment, $file_path ) { - if ( $this->is_prevented( __FUNCTION__ ) ) { - return false; - } - - $metadata = wp_get_attachment_metadata( $attachment->get_id() ); - $width = ! empty( $metadata['width'] ) ? (int) $metadata['width'] : 0; - $height = ! empty( $metadata['height'] ) ? (int) $metadata['height'] : 0; - $dimensions = $this->filesystem->get_image_size( $file_path ); - - if ( ! $dimensions ) { - return false; - } - - if ( $width === $dimensions['width'] && $height === $dimensions['height'] ) { - return false; - } - - $metadata['width'] = $dimensions['width']; - $metadata['height'] = $dimensions['height']; - - // Prevent auto-optimization. - Imagify_Auto_Optimization::prevent_optimization( $attachment_id ); - - wp_update_attachment_metadata( $attachment->get_id(), $metadata ); - - // Allow auto-optimization back. - Imagify_Auto_Optimization::allow_optimization( $attachment_id ); - return true; - } - - - /** ----------------------------------------------------------------------------------------- */ - /** WR2X COMPAT' TOOLS ====================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - - /** - * Get the suffix added to the file name, with a trailing dot. - * Don't use it for the size name. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @return string - */ - public function get_suffix() { - global $wr2x_core; - static $suffix; - - if ( ! isset( $suffix ) ) { - $suffix = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_extension' ) ? $wr2x_core->retina_extension() : '@2x.'; - } - - return $suffix; - } - - /** - * Get info about retina version. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param object $attachment An Imagify attachment. - * @param string $type The type of info. Possible values are 'basic' and 'full' (for the full size). - * @return array An array containing some HTML, indexed by the attachment ID. - */ - public function get_retina_info( $attachment, $type = 'basic' ) { - global $wr2x_core; - static $can_get_info; - - if ( ! isset( $can_get_info ) ) { - $can_get_info = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'retina_info' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info_full' ) && method_exists( $wr2x_core, 'html_get_basic_retina_info' ); - } - - if ( ! $can_get_info ) { - return ''; - } - - $attachment_id = $attachment->get_id(); - $info = $wr2x_core->retina_info( $attachment_id ); - - if ( 'full' === $type ) { - return array( - $attachment_id => $wr2x_core->html_get_basic_retina_info_full( $attachment_id, $info ), - ); - } - - return array( - $attachment_id => $wr2x_core->html_get_basic_retina_info( $attachment_id, $info ), - ); - } - - /** - * Log. - * - * @since 1.8 - * @access public - * @author Grégory Viguier - * - * @param string $text Text to log. - */ - public function log( $text ) { - global $wr2x_core; - static $can_log; - - if ( ! isset( $can_log ) ) { - $can_log = $wr2x_core && is_object( $wr2x_core ) && method_exists( $wr2x_core, 'log' ); - } - - if ( $can_log ) { - $wr2x_core->log( $text ); - } - } -} From 773e60ec80353000e69096de19015a607fe50661 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 11:26:16 -0500 Subject: [PATCH 13/21] Remove PerfectImagesCore from main class Also, cleans up some phpcs spacing and comment formatting. --- .../perfect-images/classes/PerfectImages.php | 48 +++---------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index c8c1b6c70..d4fcddf91 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -5,10 +5,8 @@ use Imagify\Optimization\Process\WP as Process; use Imagify\Optimization\Data\WP as Data; use Imagify\Media\WP as Media; -use Imagify_Assets; use Imagify_Filesystem; use Imagify_Options; -use WP_Rocket\Admin\Options_Data; /** * Class that handles compatibility with WP Retina 2x plugin. @@ -18,13 +16,6 @@ */ class PerfectImages { - /** - * Core instance. - * - * @var object PerfectImagesCore - */ - protected $core; - /** * The single instance of this class. * @@ -46,10 +37,6 @@ class PerfectImages { */ private $retina_sizes = []; - /** ----------------------------------------------------------------------------------------- */ - /** INSTANCE ================================================================================ */ - /** ----------------------------------------------------------------------------------------- */ - /** * Get the main Instance. * @@ -74,28 +61,6 @@ protected function __construct() { $this->filesystem = Imagify_Filesystem::get_instance(); } - /** - * Get the core Instance. - * - * @return object Imagify_WP_Retina_2x_Core instance. - * @author Grégory Viguier - * - * @since 1.8 - * @access public - */ - public function get_core() { - if ( ! isset( $this->core ) ) { - $this->core = new PerfectImagesCore(); - } - - return $this->core; - } - - - /** ----------------------------------------------------------------------------------------- */ - /** INIT ==================================================================================== */ - /** ----------------------------------------------------------------------------------------- */ - /** * Launch the hooks. * @@ -359,13 +324,12 @@ public function reset_optimized_full_size_image( int $media_id ) { * @return string The full retina-webp file path. */ private function get_retina_webp_filepath( string $attachment_file, string $retina_file ): string { - $pathinfo = pathinfo( $attachment_file ); - $directory = $pathinfo['dirname']; - $uploads = wp_upload_dir(); - $basedir = $uploads['basedir']; - $retina_webp_filepath = trailingslashit( $basedir ) . trailingslashit( $directory ) . $retina_file . '.webp'; + $pathinfo = pathinfo( $attachment_file ); + $directory = $pathinfo['dirname']; + $uploads = wp_upload_dir(); + $basedir = $uploads['basedir']; - return $retina_webp_filepath; + return trailingslashit( $basedir ) . trailingslashit( $directory ) . $retina_file . '.webp'; } /** @@ -400,7 +364,7 @@ private function get_retina_imagify_data_size_names( array $sizes, string $origi * * @return string A temporary file path for the optimized full-sized file. */ - private function get_temporary_file_path( $file_path ) { + private function get_temporary_file_path( string $file_path ): string { return $file_path . '_backup'; } } From 1107dc041df7e55361b1f543fe28108e35155572 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 11:35:13 -0500 Subject: [PATCH 14/21] Clean up comments and spacing --- .../perfect-images/classes/PerfectImages.php | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index d4fcddf91..a84c9f599 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -40,13 +40,9 @@ class PerfectImages { /** * Get the main Instance. * - * @return object Main instance. - * @author Grégory Viguier - * - * @since 1.8 - * @access public + * @return PerfectImages Main instance. */ - public static function get_instance() { + public static function get_instance(): PerfectImages { if ( ! isset( self::$instance ) ) { self::$instance = new self(); } @@ -62,11 +58,7 @@ protected function __construct() { } /** - * Launch the hooks. - * - * @since 1.8 - * @access public - * @author Grégory Viguier + * Initialize the hooks. */ public function init() { add_action( 'wr2x_before_regenerate', [ $this, 'restore_originally_uploaded_image' ] ); @@ -131,9 +123,9 @@ public function restore_originally_uploaded_image( int $media_id ) { * * @hooked wr2x_retina_file_added * - * @param int $media_id - * @param string $retina_file - * @param string $size_name + * @param int $media_id The media attachment ID. + * @param string $retina_file The retina filename. + * @param string $size_name The size name. */ public function add_retina_size( int $media_id, string $retina_file, string $size_name ) { $this->retina_sizes[] = [ @@ -231,11 +223,10 @@ public function remove_imagify_retina_data( int $media_id, string $retina_file ) $meta = wp_get_attachment_metadata( $media_id ); $retina_file_info = pathinfo( $retina_file ); $original_size_name = preg_replace( - '/@2x/', - '', - $retina_file_info['filename'] - ) - . '.' . $retina_file_info['extension']; + '/@2x/', + '', + $retina_file_info['filename'] + ) . '.' . $retina_file_info['extension']; $imagify_size_names = $this->get_retina_imagify_data_size_names( $meta['sizes'], $original_size_name ); From 05f3d8a4717bf0182b2c2ecf8ddaae342c4541a0 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 11:35:57 -0500 Subject: [PATCH 15/21] Extract assignments from within comparisons --- .../perfect-images/classes/PerfectImages.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index a84c9f599..acb6d3672 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -86,11 +86,15 @@ public function init() { public function restore_originally_uploaded_image( int $media_id ) { $media = new Media( $media_id ); - if ( empty( $fullsize_path = $media->get_raw_fullsize_path() ) ) { + $fullsize_path = $media->get_raw_fullsize_path(); + + if ( empty( $fullsize_path ) ) { return; } - if ( empty( $original_path = $media->get_original_path() ) ) { + $original_path = $media->get_original_path(); + + if ( empty( $original_path ) ) { return; } @@ -99,7 +103,9 @@ public function restore_originally_uploaded_image( int $media_id ) { return; } - if ( empty( $backup_path = $media->get_raw_backup_path() ) ) { + $backup_path = $media->get_raw_backup_path(); + + if ( empty( $backup_path ) ) { return; } From 830c391f6ac4226707ce366b0a1358bdcc18e166 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Mon, 7 Feb 2022 13:49:12 -0500 Subject: [PATCH 16/21] Update PerfectImages class docblock --- inc/3rd-party/perfect-images/classes/PerfectImages.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index acb6d3672..b11c9b824 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -9,10 +9,7 @@ use Imagify_Options; /** - * Class that handles compatibility with WP Retina 2x plugin. - * - * @since 1.8 - * @author Grégory Viguier + * Class that handles compatibility with Perfect Images plugin. */ class PerfectImages { From c8cc1353bd1b8faf5006da1ae0493894bffd4da1 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Tue, 8 Feb 2022 07:20:14 -0500 Subject: [PATCH 17/21] Check for 3rd-party function before using it --- inc/3rd-party/perfect-images/classes/PerfectImages.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index b11c9b824..7f063cebc 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -176,6 +176,10 @@ public function optimize_retina_sizes( int $media_id ) { * @return array Sizes data that includes retina sizes. */ public function add_retina_sizes_meta( array $sizes ): array { + if ( ! function_exists( 'wr2x_get_retina' ) ) { + return $sizes; + } + foreach ( $sizes as $size => $size_data ) { $retina_path = wr2x_get_retina( $size_data['path'] ); From e4eff0d46da6b62b0161043cdc15bf0e9d42027f Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Tue, 8 Feb 2022 07:24:38 -0500 Subject: [PATCH 18/21] Use array_keys to get size names directly We're not going to use the related data in this case. Just get the keys and eliminate the unused variable inside foreach. --- inc/3rd-party/perfect-images/classes/PerfectImages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index 7f063cebc..fe3c7b761 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -274,7 +274,7 @@ public function reoptimize_regenerated_images( int $media_id ) { $optimization_data = $process->get_data()->get_optimization_data(); if ( ! empty( $optimization_data['sizes'] ) ) { - foreach ( $optimization_data['sizes'] as $size_name => $size_data ) { + foreach ( array_keys( $optimization_data['sizes'] ) as $size_name ) { $non_webp_size_name = $process->is_size_webp( $size_name ); if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) { From a9ab001f51f244c8874a604b40f3b521f51bde63 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Tue, 8 Feb 2022 08:26:03 -0500 Subject: [PATCH 19/21] Extract can_restore_original() method The cyclomatic complexity (and readability) of `restore_originally_uploaded_image()` was 10. All of that is due to checking the various conditions before doing the restore. This extracts all the checking to a helper method. --- .../perfect-images/classes/PerfectImages.php | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index fe3c7b761..3526f7cb5 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -75,7 +75,7 @@ public function init() { /** * Restore the optimized full-sized file and replace it by the original backup file. * - * This is to have the original user-uploaded (rather than the optimized full-size) image is in place + * This is to have the original user-uploaded (rather than the optimized full-size) image in place * when Perfect Images (re)generates any new images. * * @param int $media_id A media attachment ID. @@ -83,35 +83,12 @@ public function init() { public function restore_originally_uploaded_image( int $media_id ) { $media = new Media( $media_id ); - $fullsize_path = $media->get_raw_fullsize_path(); - - if ( empty( $fullsize_path ) ) { - return; - } - - $original_path = $media->get_original_path(); - - if ( empty( $original_path ) ) { - return; - } - - /** If these are not the same, WP will rebuild the full-size from the original along with the thumbnails. */ - if ( $fullsize_path !== $original_path ) { + if ( ! $this->can_restore_original( $media ) ) { return; } + $fullsize_path = $media->get_raw_fullsize_path(); $backup_path = $media->get_raw_backup_path(); - - if ( empty( $backup_path ) ) { - return; - } - - $data = new Data( $media ); - - if ( ! $data->is_optimized() ) { - return; - } - $tmp_file_path = $this->get_temporary_file_path( $fullsize_path ); if ( $this->filesystem->exists( $fullsize_path ) ) { @@ -312,6 +289,29 @@ public function reset_optimized_full_size_image( int $media_id ) { $this->filesystem->move( $tmp_file_path, $file_path, true ); } + /** + * Check that we can restore an originally uploaded file to the full-size image path. + * + * To restore, all the following conditions must all be true: + * 1. We must have previously optimized the image, + * 2. We must have path info for the original, full-size, and backup paths, and + * 3. Original path is the same as the full-size math (otherwise, WP will create a new full-size from the original). + * + * @param Media $media An Imagify Media Instance. + * + * @return bool + */ + private function can_restore_original( Media $media ): bool { + $data = new Data( $media ); + $fullsize_path = $media->get_raw_fullsize_path(); + $original_path = $media->get_original_path(); + + return $data->is_optimized() && + ! empty( $fullsize_path ) && + ! empty( $original_path ) && + $fullsize_path === $original_path && + ! empty( $media->get_raw_backup_path() ); + } /** * Get the retina webp filepath associated with a Perfect Images retina file. From 142fd83e57007ff67cc22f3e56ecc4a090d01568 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Tue, 8 Feb 2022 08:52:33 -0500 Subject: [PATCH 20/21] Extract add_webp_sizes() to helper method Refactors the process for getting the needed webp sizes for optimization to it's own method. --- .../perfect-images/classes/PerfectImages.php | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index 3526f7cb5..21145586d 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -251,16 +251,7 @@ public function reoptimize_regenerated_images( int $media_id ) { $optimization_data = $process->get_data()->get_optimization_data(); if ( ! empty( $optimization_data['sizes'] ) ) { - foreach ( array_keys( $optimization_data['sizes'] ) as $size_name ) { - $non_webp_size_name = $process->is_size_webp( $size_name ); - - if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) { - continue; - } - - // Add the WebP size. - $sizes[ $size_name ] = []; - } + $sizes = $this->add_webp_sizes( $optimization_data, $process, $sizes ); } $sizes = array_keys( $sizes ); @@ -365,4 +356,27 @@ private function get_retina_imagify_data_size_names( array $sizes, string $origi private function get_temporary_file_path( string $file_path ): string { return $file_path . '_backup'; } + + /** + * Add webp sizes names to the sizes to be processed. + * + * @param array $optimization_data Optimization data. + * @param Process $process The Imagify Process instance. + * @param array $sizes The names of sizes to be processed. + * + * @return array Sizes array with any webp names added. + */ + private function add_webp_sizes( array $optimization_data, Process $process, array $sizes ): array { + foreach ( array_keys( $optimization_data['sizes'] ) as $size_name ) { + $non_webp_size_name = $process->is_size_webp( $size_name ); + + if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) { + continue; + } + + $sizes[ $size_name ] = []; + } + + return $sizes; + } } From 8fc613f1c6cee737f658e3bdc99ca6a019f765f8 Mon Sep 17 00:00:00 2001 From: Caspar Green Date: Fri, 11 Feb 2022 15:25:43 -0500 Subject: [PATCH 21/21] Check for new upload before doing retina process For new uploads, we're going optimize everything after all the other things run their stuff. In this case we don't want to start a proces for this media now -- doing so will "Lock" the media when the full process tries to run in a few moments, and while retinas will be processed nothing else will be included! --- inc/3rd-party/perfect-images/classes/PerfectImages.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/inc/3rd-party/perfect-images/classes/PerfectImages.php b/inc/3rd-party/perfect-images/classes/PerfectImages.php index 21145586d..ea13a1874 100644 --- a/inc/3rd-party/perfect-images/classes/PerfectImages.php +++ b/inc/3rd-party/perfect-images/classes/PerfectImages.php @@ -123,6 +123,13 @@ public function add_retina_size( int $media_id, string $retina_file, string $siz * @param int $media_id The attachment id of the retina images to optimize. */ public function optimize_retina_sizes( int $media_id ) { + $process = new Process( new Data( new Media( $media_id ) ) ); + + // if this is a new upload, bail out -- we'll optimize everything after the upload completes. + if ( empty( $process->get_data()->get_optimization_data()['sizes'] ) ) { + return; + } + $sizes = []; foreach ( $this->retina_sizes as $size ) { @@ -135,8 +142,6 @@ public function optimize_retina_sizes( int $media_id ) { return; } - $process = new Process( new Data( new Media( $media_id ) ) ); - $media_opt_level = $process->get_data()->get_optimization_level(); $optimization_level = $media_opt_level ?: Imagify_Options::get_instance()->get( 'optimization_level' );