ushit' ) ) ); } return false; } /** * Restore PNG. * * @param int $attachment_id Attachment ID. * @param string $backup_file_path Full backup file, the result of self::get_backup_file(). * @param string $file_path File path. * * @since 3.9.10 Moved wp_update_attachment_metadata into self::restore_image() after deleting the backup file, * in order to support S3 - @see SMUSH-1141. * * @return bool|array */ private function restore_png( $attachment_id, $backup_file_path, $file_path ) { if ( empty( $attachment_id ) || empty( $backup_file_path ) || empty( $file_path ) ) { return false; } $meta = array(); // Else get the Attachment details. /** * For Full Size * 1. Get the original file path * 2. Update the attachment metadata and all other meta details * 3. Delete the JPEG * 4. And we're done * 5. Add an action after updating the URLs, that'd allow the users to perform an additional search, replace action */ if ( file_exists( $backup_file_path ) ) { $mod = WP_Smush::get_instance()->core()->mod; // Update the path details in meta and attached file, replace the image. $meta = $mod->png2jpg->update_image_path( $attachment_id, $file_path, $backup_file_path, $meta, 'full', 'restore' ); $files_to_remove = array(); // Unlink JPG after updating attached file. if ( ! empty( $meta['file'] ) && wp_basename( $backup_file_path ) === wp_basename( $meta['file'] ) ) { /** * Note, we use size key smush-png2jpg-full for PNG2JPG file to support S3 private media, * to remove converted JPG file after restoring in private folder. * * @see Smush\Core\Integrations\S3::get_object_key() */ $files_to_remove['smush-png2jpg-full'] = $file_path; } $jpg_meta = wp_get_attachment_metadata( $attachment_id ); foreach ( $jpg_meta['sizes'] as $size_key => $size_data ) { $size_path = str_replace( wp_basename( $backup_file_path ), wp_basename( $size_data['file'] ), $backup_file_path ); // Add to delete the thumbnails jpg. $files_to_remove[ $size_key ] = $size_path; } // Re-generate metadata for PNG file. $metadata = wp_generate_attachment_metadata( $attachment_id, $backup_file_path ); // Perform an action after the image URL is updated in post content. do_action( 'wp_smush_image_url_updated', $attachment_id, $file_path, $backup_file_path ); } else { Helper::logger()->backup()->warning( sprintf( 'Backup file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) ); } if ( ! empty( $metadata ) ) { // Delete jpg files, we also try to delete these files on cloud, e.g S3. Helper::delete_permanently( $files_to_remove, $attachment_id, false ); return $metadata; } else { Helper::logger()->backup()->warning( sprintf( 'Meta file [%s(%d)] is empty.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) ); } return false; } /** * Remove a specific backup key from the backup size array. * * @param int $attachment_id Attachment ID. */ private function remove_from_backup_sizes( $attachment_id ) { // Get backup sizes. $backup_sizes = $this->get_backup_sizes( $attachment_id ); // If we don't have any backup sizes list or if the particular key is not set, return. if ( empty( $backup_sizes ) || ! isset( $backup_sizes[ $this->backup_key ] ) ) { return; } unset( $backup_sizes[ $this->backup_key ] ); if ( empty( $backup_sizes ) ) { delete_post_meta( $attachment_id, '_wp_attachment_backup_sizes' ); } else { update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes ); } } /** * Get the attachments that can be restored. * * @since 3.6.0 Changed from private to public. * * @return array Array of attachments IDs. */ public function get_attachments_with_backups() { global $wpdb; $images_to_restore = $wpdb->get_col( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_wp_attachment_backup_sizes' AND (`meta_value` LIKE '%smush-full%' OR `meta_value` LIKE '%smush_png_path%')" ); return $images_to_restore; } /** * Get the number of attachments that can be restored. * * @since 3.2.2 */ public function get_image_count() { check_ajax_referer( 'smush_bulk_restore' ); // Check for permission. if ( ! Helper::is_user_allowed( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 ); } wp_send_json_success( array( 'items' => $this->get_attachments_with_backups(), ) ); } /** * Bulk restore images from the modal. * * @since 3.2.2 */ public function restore_step() { check_ajax_referer( 'smush_bulk_restore' ); // Check for permission. if ( ! Helper::is_user_allowed( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 ); } $id = filter_input( INPUT_POST, 'item', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $media_item = Media_Item_Cache::get_instance()->get( $id ); if ( ! $media_item->is_mime_type_supported() ) { wp_send_json_error( array( /* translators: %s: Error message */ 'error_msg' => sprintf( esc_html__( 'Image not restored. %s', 'wp-smushit' ), $media_item->get_errors()->get_error_message() ), ) ); } $optimizer = new Media_Item_Optimizer( $media_item ); $status = $id && $optimizer->restore(); $file_name = $media_item->get_full_or_scaled_size()->get_file_name(); wp_send_json_success( array( 'success' => $status, 'src' => ! empty( $file_name ) ? $file_name : __( 'Error getting file name', 'wp-smushit' ), 'thumb' => wp_get_attachment_image( $id ), 'link' => Helper::get_image_media_link( $id, $file_name, true ), ) ); } /** * Returns the backup path for attachment * * @param string $attachment_path Attachment path. * * @return string */ public function get_image_backup_path( $attachment_path ) { if ( empty( $attachment_path ) ) { return ''; } $path = pathinfo( $attachment_path ); if ( empty( $path['extension'] ) ) { return ''; } return trailingslashit( $path['dirname'] ) . $path['filename'] . '.bak.' . $path['extension']; } /** * Clear up all the backup files for the image while deleting the image. * * @since 3.9.6 * Note, we only call this method while deleting the image, as it will delete * .bak file and might be the original file too. * * Note, for the old version < 3.9.6 we also save all PNG files (original file and thumbnails) * when the site doesn't compress original file. * But it's not safe to remove them if the user add another image with the same PNG file name, and didn't convert it. * So we still leave them there. * * @param int $attachment_id Attachment ID. */ public function delete_backup_files( $attachment_id ) { $smush_meta = get_post_meta( $attachment_id, Smush::$smushed_meta_key, true ); if ( empty( $smush_meta ) ) { return; } // Save list files to remove. $files_to_remove = array(); $unfiltered = false; $file_path = get_attached_file( $attachment_id, false ); // We only work with the real file path, not cloud URL like S3. if ( false === strpos( $file_path, ABSPATH ) ) { $unfiltered = true; $file_path = get_attached_file( $attachment_id, true ); } // Remove from the cache. wp_cache_delete( 'images_with_backups', 'wp-smush' ); /** * We only remove the backup file from the metadata, * keep the backup file from 3rd-party. */ $backup_path = null;// Reset backup file. $backup_sizes = $this->get_backup_sizes( $attachment_id ); if ( isset( $backup_sizes[ $this->backup_key ]['file'] ) ) { $backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes[ $this->backup_key ]['file'] ), $file_path ); // Add to remove the backup file. $files_to_remove[ $this->backup_key ] = $backup_path; } // Check the backup file from resized PNG file (< 3.9.6). if ( isset( $backup_sizes['smush_png_path']['file'] ) ) { $backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes['smush_png_path']['file'] ), $file_path ); // Add to remove the backup file. $files_to_remove['smush_png_path'] = $backup_path; } if ( ! $backup_path ) { // Check for legacy original file path. It's for old version < V.2.7.0. $original_file = get_post_meta( $attachment_id, 'wp-smush-original_file', true ); if ( ! empty( $original_file ) ) { // For old version < v.2.7.0, we are saving meta['file'] or _wp_attached_file. $backup_path = Helper::original_file( $original_file ); // Add to remove the backup file. $files_to_remove[] = $backup_path; } } // Check meta for rest of the sizes. $meta = wp_get_attachment_metadata( $attachment_id, $unfiltered ); if ( empty( $meta ) || empty( $meta['sizes'] ) ) { Helper::logger()->backup()->info( sprintf( 'Empty meta sizes [%s(%d)]', $file_path, $attachment_id ) ); return; } foreach ( $meta['sizes'] as $size ) { if ( empty( $size['file'] ) ) { continue; } // Image path and backup path. $image_size_path = path_join( dirname( $file_path ), $size['file'] ); $image_backup_path = $this->get_image_backup_path( $image_size_path ); // Add to remove the backup file. $files_to_remove[] = $image_backup_path; } // We also try to delete this file on cloud, e.g. S3. Helper::delete_permanently( $files_to_remove, $attachment_id, false ); } }