dia_Item $media_item * * @return bool */ private function is_media_item_valid( $media_item ) { $invalid = ! $media_item || empty( $media_item->get_wp_metadata() ); if ( $invalid ) { $this->log_error( 'Media item is not valid.' ); } return ! $invalid; } public function disable_s3_get_attached_file_filters() { // Make sure smush always gets local paths $this->disable_stream_wrapper_file(); // S3 auto downloads an image when get_attached_file is called, we want to disable this, because we will explicitly download all media item sizes. $this->disable_s3_auto_download(); // Reset media items, so they have to fetch the new values $this->media_item_cache->reset_all(); } public function enable_back_s3_get_attached_file_filters() { $this->enable_back_stream_wrapper_file(); $this->enable_back_s3_auto_download(); $this->media_item_cache->reset_all(); } private function disable_stream_wrapper_file() { add_filter( 'as3cf_get_attached_file', array( $this, 'return_local_file_path' ), self::AS3CF_GET_ATTACHED_FILE_PRIORITY, // Our callback needs to run before the s3 callback get_stream_wrapper_file 2 ); } private function enable_back_stream_wrapper_file() { remove_filter( 'as3cf_get_attached_file', array( $this, 'return_local_file_path', ), self::AS3CF_GET_ATTACHED_FILE_PRIORITY ); } public function return_local_file_path( $url, $file_path ) { return $file_path; } /** * @param callable $callback * * @return void */ private function before_smush( $callback, $priority ) { add_action( 'wp_smush_before_smush_file', $callback, $priority ); } private function before_smush_attempt( $callback, $priority ) { add_action( 'wp_smush_before_smush_attempt', $callback, $priority ); } /** * @param callable $callback * * @return void */ private function after_smush( $callback, $priority ) { add_action( 'wp_smush_after_smush_file', $callback, $priority ); } /** * @param $attachment_id * * @return void */ public function trigger_update_attachment_metadata( $attachment_id ) { $media_item = $this->media_item_cache->get( $attachment_id ); if ( ! $this->is_media_item_valid( $media_item ) ) { return; } wp_update_attachment_metadata( $attachment_id, $media_item->get_wp_metadata() ); } /** * @param $size * @param $key * @param $metadata * @param $media_item Media_Item * * @return S3_Media_Item_Size */ public function initialize_s3_size( $size, $key, $metadata, $media_item ) { return new S3_Media_Item_Size( $key, $media_item->get_id(), $media_item->get_dir(), $media_item->get_base_url(), $metadata ); } /** * @param $attachment_id * * @return Media_Library_Item|null */ private function get_s3_media_item( $attachment_id ) { return $this->wp_offload_media->is_attachment_served_by_provider( $attachment_id, true ); } /** * @return void */ private function support_s3_image_optimization() { /** * Prevent frequent offloading attempts */ // During the optimization we might call wp_update_attachment_metadata multiple times. Prevent any offload attempts while smushing is in progress. $this->before_smush( array( $this, 'disable_s3_update_attachment' ), 10 ); /** * Ensure smush has access to local files during optimization */ // Download any of the sizes that don't exist locally $this->before_smush( array( $this, 'download_all_sizes' ), 20 ); /** * Delete remote version before uploading optimized */ // When all optimizations are completed, the new files will be uploaded. // Note that this is especially important for Png2Jpg optimization for getting rid of the old files from the servers. The new files are nothing like the old ones. add_action( 'wp_smush_png_jpg_converted', array( $this, 'delete_old_png_files_after_convert' ), 10, 4 ); /** * Trigger offloading after smush is done */ // Turn offloading back on $this->after_smush( array( $this, 'enable_back_s3_update_attachment' ), 20 ); // Trigger offloading $this->after_smush( array( $this, 'trigger_update_attachment_metadata' ), 30 ); /** * Delay offloading on new media upload when auto smush is on */ $auto_smush_on = $this->settings->get( 'auto' ); if ( $auto_smush_on ) { /** * We need to prevent {@see Media_Library::wp_update_attachment_metadata()} from getting called */ // New media upload triggers wp_update_attachment_metadata which triggers offloading. Make sure offloading is postponed until smush is done. add_filter( 'add_attachment', array( $this, 'disable_s3_update_attachment' ) ); $priority = 100; // This has to be higher than other methods attached to this hook because $media_item->is_skipped() depends on those other methods $this->after_attachment_upload( array( $this, 'offload_if_media_item_not_optimizable' ), $priority ); $this->before_smush_attempt( array( $this, 'offload_if_media_item_not_optimizable' ), $priority ); } } /** * @return void */ private function support_s3_backup_and_restore() { /** * Disable remote file filters during the restore process */ $this->before_restore_attempt( array( $this, 'disable_s3_get_attached_file_filters' ), 10 ); /** * Ensure smush has access to local files during restoration */ $this->before_restore( array( $this, 'download_backup_file' ), 10 ); /** * Disable offloading */ $this->before_restore( array( $this, 'disable_s3_update_attachment' ), 20 ); /** * Delete remote version before uploading restored */ // When the restoration is completed, the new files will be uploaded. Again, this is especially important for Png2Jpg add_action( 'wp_smush_after_restore_png_jpg', array( $this, 'delete_old_jpg_files_after_restore' ), 10, 2 ); /** * Trigger offloading after restore is done */ $this->after_restore( array( $this, 'enable_back_s3_update_attachment' ), 20 ); $this->after_restore( array( $this, 'enable_back_s3_get_attached_file_filters' ), 30 ); $this->after_restore( function ( $restored, $backup_file_path, $attachment_id ) { if ( $restored ) { $this->wp_offload_media->delete_remote_files( $backup_file_path, $attachment_id ); $this->trigger_update_attachment_metadata( $attachment_id ); } }, 40 ); } private function log_error( $error ) { $this->logger->error( "Smush S3 Integration: $error" ); } private function is_media_item_optimizable( Media_Item $media_item ) { return ! $this->is_media_item_not_optimizable( $media_item ); } /** * @param Media_Item $media_item * * @return bool */ private function is_media_item_not_optimizable( Media_Item $media_item ) { return ! $media_item->is_valid() || $media_item->has_errors() || $media_item->is_skipped(); } public function offload_if_media_item_not_optimizable( $attachment_id ) { $media_item = $this->media_item_cache->get( $attachment_id ); if ( $this->is_media_item_not_optimizable( $media_item ) ) { // If there is an error we want the image to be offloaded explicitly $this->enable_back_s3_update_attachment(); $this->trigger_update_attachment_metadata( $attachment_id ); } // We have already added hooks for enabling back and triggering offloading after successful smush. } private function after_attachment_upload( $callback, $priority ) { add_action( 'wp_smush_after_attachment_upload', $callback, $priority ); } public function delete_old_png_files_after_convert( $attachment_id, $image_metadata, $media_item_stats, $png_file_paths ) { if ( empty( $png_file_paths ) ) { return; } $this->after_smush( function () use ( $png_file_paths, $attachment_id ) { $this->wp_offload_media->delete_remote_files( $png_file_paths, $attachment_id ); }, 50 ); } public function delete_old_jpg_files_after_restore( $media_item, $jpg_file_paths ) { if ( empty( $jpg_file_paths ) ) { return; } $attachment_id = $media_item->get_id(); $this->after_restore( function ( $restored ) use ( $jpg_file_paths, $attachment_id ) { if ( $restored ) { $this->wp_offload_media->delete_remote_files( $jpg_file_paths, $attachment_id ); } }, 50 ); } }