get_original_path() ); } /** * Get the attachment optimization data. * * @since 1.0 * @access public * * @return array */ public function get_data() { $data = get_post_meta( $this->id, '_imagify_data', true ); return is_array( $data ) ? $data : array(); } /** * Get the attachment optimization level. * * @since 1.0 * @access public * * @return int */ public function get_optimization_level() { $level = get_post_meta( $this->id, '_imagify_optimization_level', true ); return false !== $level ? (int) $level : false; } /** * Get the attachment optimization status (success or error). * * @since 1.0 * @access public * * @return string */ public function get_status() { $status = get_post_meta( $this->id, '_imagify_status', true ); return is_string( $status ) ? $status : ''; } /** * Get the original attachment path. * * @since 1.0 * @access public * * @return string */ public function get_original_path() { return get_attached_file( $this->id ); } /** * Get the original attachment URL. * * @since 1.0 * @access public * * @return string */ public function get_original_url() { return wp_get_attachment_url( $this->id ); } /** * Get width and height of the original image. * * @since 1.7 * @author Grégory Viguier * @access public * * @return array */ public function get_dimensions() { if ( ! $this->is_image() ) { return parent::get_dimensions(); } $values = wp_get_attachment_image_src( $this->id, 'full' ); return array( 'width' => $values[1], 'height' => $values[2], ); } /** * Update the metadata size of the attachment. * * @since 1.2 * @access public * * @return bool */ public function update_metadata_size() { // Check if the attachment extension is allowed. if ( ! $this->is_extension_supported() || ! $this->is_image() ) { return false; } $size = $this->filesystem->get_image_size( $this->get_original_path() ); if ( ! $size ) { return false; } /** * Triggered before updating an image width and height into its metadata. * * @since 1.8.4 * @see Imagify_Filesystem->get_image_size() * @author Grégory Viguier * * @param int $attachment_id The attachment ID. * @param array $size { * An array with, among other data: * * @type int $width The image width. * @type int $height The image height. * } */ do_action( 'before_imagify_update_metadata_size', $this->id, $size ); $metadata = wp_get_attachment_metadata( $this->id ); $metadata['width'] = $size['width']; $metadata['height'] = $size['height']; wp_update_attachment_metadata( $this->id, $metadata ); /** * Triggered after updating an image width and height into its metadata. * * @since 1.8.4 * @see Imagify_Filesystem->get_image_size() * @author Grégory Viguier * * @param int $attachment_id The attachment ID. * @param array $size { * An array with, among other data: * * @type int $width The image width. * @type int $height The image height. * } */ do_action( 'after_imagify_update_metadata_size', $this->id, $size ); return true; } /** * Fills statistics data with values from $data array. * * @since 1.0 * @since 1.6.5 Not static anymore. * @since 1.6.6 Removed the attachment ID parameter. * @since 1.7 Removed the image URL parameter. * @access public * * @param array $data The statistics data. * @param object $response The API response. * @param string $size The attachment size key. * @return bool|array False if the original size has an error or an array contains the data for other result. */ public function fill_data( $data, $response, $size = 'full' ) { $data = is_array( $data ) ? $data : array(); $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array(); if ( empty( $data['stats'] ) ) { $data['stats'] = array( 'original_size' => 0, 'optimized_size' => 0, 'percent' => 0, ); } if ( is_wp_error( $response ) ) { $error = $response->get_error_message(); $error_status = 'error'; $data['sizes'][ $size ] = array( 'success' => false, 'error' => $error, ); // Update the error status for the original size. if ( 'full' === $size ) { update_post_meta( $this->id, '_imagify_data', $data ); if ( false !== strpos( $error, 'This image is already compressed' ) ) { $error_status = 'already_optimized'; } update_post_meta( $this->id, '_imagify_status', $error_status ); return false; } } else { $response = (object) array_merge( array( 'original_size' => 0, 'new_size' => 0, 'percent' => 0, ), (array) $response ); $data['sizes'][ $size ] = array( 'success' => true, 'original_size' => $response->original_size, 'optimized_size' => $response->new_size, 'percent' => $response->percent, ); $data['stats']['original_size'] += $response->original_size; $data['stats']['optimized_size'] += $response->new_size; } // End if(). return $data; } /** * Create a thumbnail if it doesn't exist. * * @since 1.6.10 * @access protected * @author Grégory Viguier * * @param array $thumbnail_data The thumbnail data (width, height, crop, name, file). * @return bool|array|object True if the file exists. An array of thumbnail data if the file has just been created (width, height, crop, file). A WP_Error object on error. */ protected function create_thumbnail( $thumbnail_data ) { $thumbnail_size = $thumbnail_data['name']; $metadata = wp_get_attachment_metadata( $this->id ); $metadata_sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); $original_dirname = $this->filesystem->dir_path( $this->get_original_path() ); $thumbnail_path = $original_dirname . $thumbnail_data['file']; if ( ! empty( $metadata_sizes[ $thumbnail_size ] ) && $this->filesystem->exists( $thumbnail_path ) ) { $this->filesystem->chmod_file( $thumbnail_path ); return true; } // Get the editor. $editor = $this->get_editor( $this->get_backup_path() ); if ( is_wp_error( $editor ) ) { return $editor; } // Create the file. $result = $editor->multi_resize( array( $thumbnail_size => $thumbnail_data ) ); if ( ! $result ) { return new WP_Error( 'image_resize_error' ); } // The file name can change from what we expected (1px wider, etc). $backup_dirname = $this->filesystem->dir_path( $this->get_backup_path() ); $backup_thumb_path = $backup_dirname . $result[ $thumbnail_size ]['file']; $thumbnail_path = $original_dirname . $result[ $thumbnail_size ]['file']; // Since we used the backup image as source, the new image is still in the backup folder, we need to move it. $moved = $this->filesystem->move( $backup_thumb_path, $thumbnail_path, true ); if ( ! $moved ) { return new WP_Error( 'image_resize_error' ); } return reset( $result ); } /** * Create all missing thumbnails if they don't exist and update the attachment metadata. * * @since 1.6.10 * @access protected * @author Grégory Viguier * * @param array $missing_sizes An array of thumbnail data (width, height, crop, name, file) for each thumbnail size. * @return array An array of thumbnail data (width, height, crop, file). */ protected function create_missing_thumbnails( $missing_sizes ) { if ( ! $missing_sizes || ! $this->is_image() ) { return array(); } $metadata = wp_get_attachment_metadata( $this->id ); $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); $thumbnail_new_datas = array(); $thumbnail_metadatas = array(); // Create the missing thumbnails. foreach ( $missing_sizes as $size_name => $thumbnail_data ) { $result = $this->create_thumbnail( $thumbnail_data ); if ( is_array( $result ) ) { // New file. $thumbnail_new_datas[ $size_name ] = $result; unset( $thumbnail_new_datas[ $size_name ]['name'] ); } elseif ( true === $result ) { // The file already exists. $thumbnail_metadatas[ $size_name ] = $metadata['sizes'][ $size_name ]; } } // Save the new data into the attachment metadata. if ( $thumbnail_new_datas ) { $metadata['sizes'] = array_merge( $metadata['sizes'], $thumbnail_new_datas ); /** * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks. */ update_post_meta( $this->id, '_wp_attachment_metadata', $metadata ); } return array_merge( $thumbnail_metadatas, $thumbnail_new_datas ); } /** * Optimize all sizes with Imagify. * * @since 1.0 * @access public * * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal). * @param array $metadata The attachment meta data. * @return array $optimized_data The optimization data. */ public function optimize( $optimization_level = null, $metadata = array() ) { // Check if the attachment extension is allowed. if ( ! $this->is_extension_supported() ) { return; } $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' ); $metadata = $metadata ? $metadata : wp_get_attachment_metadata( $this->id ); $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) && $this->is_image() ? $metadata['sizes'] : array(); // To avoid issue with "original_size" at 0 in "_imagify_data". if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) { $this->delete_imagify_data(); } // Check if the full size is already optimized. if ( $this->is_optimized() && $this->get_optimization_level() === $optimization_level ) { return; } // Get file path & URL for original image. $attachment_path = $this->get_original_path(); $attachment_url = $this->get_original_url(); $attachment_original_size = $this->get_original_size( false ); /** * Fires before optimizing an attachment. * * @since 1.0 * * @param int $id The attachment ID. */ do_action( 'before_imagify_optimize_attachment', $this->id ); $this->set_running_status(); // Get the resize values for the original size. $resized = false; $do_resize = $this->is_image() && get_imagify_option( 'resize_larger' ); if ( $do_resize ) { $resize_width = get_imagify_option( 'resize_larger_w' ); $attachment_size = $this->filesystem->get_image_size( $attachment_path ); if ( $attachment_size && $resize_width < $attachment_size['width'] ) { $resized_attachment_path = $this->resize( $attachment_path, $attachment_size, $resize_width ); if ( ! is_wp_error( $resized_attachment_path ) ) { // TODO (@Greg): Send an error message if the backup fails. imagify_backup_file( $attachment_path ); $this->filesystem->move( $resized_attachment_path, $attachment_path, true ); $resized = true; } } } // Optimize the original size. $response = do_imagify( $attachment_path, array( 'optimization_level' => $optimization_level, 'context' => $this->get_context(), 'resized' => $resized, 'original_size' => $attachment_original_size, ) ); $data = $this->fill_data( null, $response ); /** * Filter the optimization data of the full size. * * @since 1.8 * @author Grégory Viguier * * @param array $data The statistics data. * @param object $response The API response. * @param int $id The attachment ID. * @param string $attachment_path The attachment path. * @param string $attachment_url The attachment URL. * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for concistancy with other filters. * @param int $optimization_level The optimization level. * @param array $metadata WP metadata. */ $data = apply_filters( 'imagify_fill_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level, $metadata ); // Save the optimization level. update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level ); // If we resized the original with success, we have to update the attachment metadata. // If not, WordPress keeps the old attachment size. if ( $resized ) { $this->update_metadata_size(); } if ( ! $data ) { $this->delete_running_status(); return; } // Optimize all thumbnails. if ( $sizes ) { $disallowed_sizes = get_imagify_option( 'disallowed-sizes' ); $is_active_for_network = imagify_is_active_for_network(); $attachment_path_dirname = $this->filesystem->dir_path( $attachment_path ); $attachment_url_dirname = $this->filesystem->dir_path( $attachment_url ); foreach ( $sizes as $size_key => $size_data ) { $thumbnail_path = $attachment_path_dirname . $size_data['file']; $thumbnail_url = $attachment_url_dirname . $size_data['file']; // Check if this size has to be optimized. if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) ) { $data['sizes'][ $size_key ] = array( 'success' => false, 'error' => __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ), ); /** * Filter the optimization data of an unauthorized thumbnail. * * @since 1.8 * @author Grégory Viguier * * @param array $data The statistics data. * @param int $id The attachment ID. * @param string $thumbnail_path The thumbnail path. * @param string $thumbnail_url The thumbnail URL. * @param string $size_key The thumbnail size key. * @param int $optimization_level The optimization level. * @param array $metadata WP metadata. */ $data = apply_filters( 'imagify_fill_unauthorized_thumbnail_data', $data, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata ); continue; } // Optimize the thumbnail size. $response = do_imagify( $thumbnail_path, array( 'backup' => false, 'optimization_level' => $optimization_level, 'context' => $this->get_context(), ) ); $data = $this->fill_data( $data, $response, $size_key ); /** * Filter the optimization data of a specific thumbnail. * * @since 1.0 * @since 1.8 Added $metadata. * * @param array $data The statistics data. * @param object $response The API response. * @param int $id The attachment ID. * @param string $thumbnail_path The thumbnail path. * @param string $thumbnail_url The thumbnail URL. * @param string $size_key The thumbnail size key. * @param int $optimization_level The optimization level. * @param array $metadata WP metadata. */ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata ); } // End foreach(). } // End if(). $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 ); update_post_meta( $this->id, '_imagify_data', $data ); update_post_meta( $this->id, '_imagify_status', 'success' ); $optimized_data = $this->get_data(); /** * Fires after optimizing an attachment. * * @since 1.0 * * @param int $id The attachment ID. * @param array $optimized_data The optimization data. */ do_action( 'after_imagify_optimize_attachment', $this->id, $optimized_data ); $this->delete_running_status(); return $optimized_data; } /** * Optimize missing thumbnail sizes with Imagify. * * @since 1.6.10 * @access public * @author Grégory Viguier * * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal). * @return array|object An array of thumbnail data, size by size. A WP_Error object on failure. */ public function optimize_missing_thumbnails( $optimization_level = null ) { // Check if the attachment extension is allowed. if ( ! $this->is_extension_supported() || ! $this->is_image() ) { return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); } $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' ); $missing_sizes = $this->get_unoptimized_sizes(); if ( ! $missing_sizes ) { // We have everything we need. return array(); } // Stop the process if there is no backup file to use. if ( ! $this->has_backup() ) { return new WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) ); } /** * Fires before optimizing the missing thumbnails. * * @since 1.6.10 * @author Grégory Viguier * @see $this->get_unoptimized_sizes() * * @param int $id The attachment ID. * @param array $missing_sizes An array of the missing sizes. */ do_action( 'before_imagify_optimize_missing_thumbnails', $this->id, $missing_sizes ); $this->set_running_status(); $errors = new WP_Error(); // Create the missing thumbnails. $result_sizes = $this->create_missing_thumbnails( $missing_sizes ); $failed_sizes = array_diff_key( $missing_sizes, $result_sizes ); if ( $failed_sizes ) { $failed_count = count( $failed_sizes ); /* translators: %d is a number of thumbnails. */ $error_message = _n( '%d thumbnail failed to be created', '%d thumbnails failed to be created', $failed_count, 'imagify' ); $error_message = sprintf( $error_message, $failed_count ); $errors->add( 'image_resize_error', $error_message, array( 'nbr_failed' => $failed_count, 'sizes_failed' => $failed_sizes, 'sizes_succeeded' => $result_sizes, ) ); } if ( ! $result_sizes ) { $this->delete_running_status(); return $errors; } // Optimize. $imagify_data = $this->get_data(); $original_dirname = $this->filesystem->dir_path( $this->get_original_path() ); $orig_url_dirname = $this->filesystem->dir_path( $this->get_original_url() ); foreach ( $result_sizes as $size_name => $thumbnail_data ) { $thumbnail_path = $original_dirname . $thumbnail_data['file']; $thumbnail_url = $orig_url_dirname . $thumbnail_data['file']; // Optimize the thumbnail size. $response = do_imagify( $thumbnail_path, array( 'backup' => false, 'optimization_level' => $optimization_level, 'context' => $this->get_context(), ) ); $imagify_data = $this->fill_data( $imagify_data, $response, $size_name ); $metadata = wp_get_attachment_metadata( $this->id ); /** This filter is documented in inc/classes/class-imagify-attachment.php. */ $imagify_data = apply_filters( 'imagify_fill_thumbnail_data', $imagify_data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_name, $optimization_level, $metadata ); } // Save Imagify data. $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( $this->id, '_imagify_data', $imagify_data ); /** * Fires after optimizing the missing thumbnails. * * @since 1.6.10 * @author Grégory Viguier * @see $this->create_missing_thumbnails() * * @param int $id The attachment ID. * @param array $result_sizes An array of created thumbnails. * @param object $errors A WP_Error object that stores thumbnail creation failures. */ do_action( 'after_imagify_optimize_missing_thumbnails', $this->id, $result_sizes, $errors ); $this->delete_running_status(); // Return the result. if ( $errors->get_error_codes() ) { return $errors; } return $result_sizes; } /** * Re-optimize the given thumbnail sizes to the same level. * Before doing this, the given sizes must be restored. * * @since 1.7.1 * @access public * @author Grégory Viguier * * @param array $sizes The sizes to optimize. * @return array|void A WP_Error object on failure. */ public function reoptimize_thumbnails( $sizes ) { // Check if the attachment extension is allowed. if ( ! $this->is_extension_supported() || ! $this->is_image() ) { return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); } if ( ! $sizes || ! is_array( $sizes ) ) { return; } /** * Fires before re-optimizing some thumbnails of an attachment. * * @since 1.7.1 * @author Grégory Viguier * * @param int $id The attachment ID. * @param array $sizes The sizes to optimize. */ do_action( 'before_imagify_reoptimize_attachment_thumbnails', $this->id, $sizes ); $this->set_running_status(); $data = $this->get_data(); $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array(); foreach ( $sizes as $size_key => $size_data ) { // In case it's a disallowed size, fill in the new data. If it's not, it will be overwritten by $this->fill_data() later. $data['sizes'][ $size_key ] = array( 'success' => false, 'error' => __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ), ); } // Update global attachment stats. $data['stats'] = array( 'original_size' => 0, 'optimized_size' => 0, 'percent' => 0, ); foreach ( $data['sizes'] as $size_data ) { if ( ! empty( $size_data['original_size'] ) ) { $data['stats']['original_size'] += $size_data['original_size']; } if ( ! empty( $size_data['optimized_size'] ) ) { $data['stats']['optimized_size'] += $size_data['optimized_size']; } } // Remove disallowed sizes. if ( ! imagify_is_active_for_network() ) { $sizes = array_diff_key( $sizes, get_imagify_option( 'disallowed-sizes' ) ); } if ( ! $sizes ) { $data['stats']['percent'] = $data['stats']['original_size'] ? round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 ) : 0; update_post_meta( $this->id, '_imagify_data', $data ); $this->delete_running_status(); return; } $optimization_level = $this->get_optimization_level(); $thumbnail_path = $this->get_original_path(); $thumbnail_url = $this->get_original_url(); $attachment_path_dirname = $this->filesystem->dir_path( $thumbnail_path ); $attachment_url_dirname = $this->filesystem->dir_path( $thumbnail_url ); foreach ( $sizes as $size_key => $size_data ) { $thumbnail_path = $attachment_path_dirname . $size_data['file']; $thumbnail_url = $attachment_url_dirname . $size_data['file']; // Optimize the thumbnail size. $response = do_imagify( $thumbnail_path, array( 'backup' => false, 'optimization_level' => $optimization_level, 'context' => $this->get_context(), ) ); $data = $this->fill_data( $data, $response, $size_key ); /** This filter is documented in /inc/classes/class-imagify-attachment.php. */ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level ); } // End foreach(). $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 ); update_post_meta( $this->id, '_imagify_data', $data ); /** * Fires after re-optimizing some thumbnails of an attachment. * * @since 1.7.1 * @author Grégory Viguier * * @param int $id The attachment ID. * @param array $sizes The sizes to optimize. */ do_action( 'after_imagify_reoptimize_attachment_thumbnails', $this->id, $sizes ); $this->delete_running_status(); } /** * Process an attachment restoration from the backup file. * * @since 1.0 * @access public * * @return void */ public function restore() { // Check if the attachment extension is allowed. if ( ! $this->is_extension_supported() ) { return; } // Stop the process if there is no backup file to restore. if ( ! $this->has_backup() ) { return; } $backup_path = $this->get_backup_path(); $attachment_path = $this->get_original_path(); /** * Fires before restoring an attachment. * * @since 1.0 * * @param int $id The attachment ID */ do_action( 'before_imagify_restore_attachment', $this->id ); // Create the original image from the backup. $this->filesystem->copy( $backup_path, $attachment_path, true ); $this->filesystem->chmod_file( $attachment_path ); if ( $this->is_image() ) { if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { require_once ABSPATH . 'wp-admin/includes/image.php'; } wp_generate_attachment_metadata( $this->id, $attachment_path ); // Restore the original size in the metadata. $this->update_metadata_size(); } // Remove old optimization data. $this->delete_imagify_data(); /** * Fires after restoring an attachment. * * @since 1.0 * * @param int $id The attachment ID */ do_action( 'after_imagify_restore_attachment', $this->id ); } }