* * @param array $meta Attachment metadata. * @param int $id Attachment ID. * * @return mixed */ public function smush_image( $meta, $id ) { // We need to check if this call originated from Gutenberg and allow only media. if ( Helper::is_non_rest_media() ) { // If not - return image metadata. return $this->no_smushit( $id, null, $meta ); } $upload_attachment = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_SPECIAL_CHARS ); $is_upload_attachment = 'upload-attachment' === $upload_attachment || isset( $_POST['post_id'] ); // Our async task runs when action is upload-attachment and post_id found. So do not run on these conditions. if ( $is_upload_attachment && defined( 'WP_SMUSH_ASYNC' ) && WP_SMUSH_ASYNC ) { return $meta; } // Is doing wp_generate_attachment_metadata. $generating_metadata = doing_filter( 'wp_generate_attachment_metadata' ); if ( $generating_metadata && ! $this->should_auto_smush( $id ) ) { // TODO: the following commented out line is here for historic reference only in case we need it again but hopefully we won't. Remove it once the version 3.13 has been out for a while. The reason why we are removing it is that it causes the test test__global_stats_updated_on_restore to fail. // Remove stats and update cache. //WP_Smush::get_instance()->core()->remove_stats( $id ); return $meta; } /** * Smush image. * * @since 3.9.6 * * @param int $id Attachment ID. * @param array $meta Image metadata (passed by reference). * @param WP_Error $errors WP_Error (passed by reference). */ $this->smushit( $id, $meta, $errors ); return $meta; } /** * Smush single images * * @param int $attachment_id Attachment ID. * @param bool $return Return/echo the stats. * * @return array|string|void */ public function smush_single( $attachment_id, $return = false ) { /** * If the smushing option is already set, return the status. * * @since 3.9.6 * If it's not in ajax we are already handled it inside self::smushit(). */ if ( ! $return && $attachment_id > 0 && ( get_transient( 'smush-in-progress-' . $attachment_id ) || get_transient( 'wp-smush-restore-' . $attachment_id ) ) ) { // Get the button status. $status = WP_Smush::get_instance()->library()->generate_markup( $attachment_id ); wp_send_json_success( $status ); } // Get the image metadata from $_POST. $original_meta = ! empty( $_POST['metadata'] ) ? Helper::format_meta_from_post( $_POST['metadata'] ) : ''; /** * Smush image. * * @since 3.9.6 * * @param int $attachment_id Attachment ID. * @param array $original_meta Image metadata (passed by reference). * @param WP_Error $errors WP_Error (passed by reference). */ $this->smushit( $attachment_id, $original_meta, $errors ); // Send JSON response if we are not supposed to return the results. if ( $errors && is_wp_error( $errors ) && $errors->has_errors() ) { if ( $return ) { return array( 'error' => $errors->get_error_message() ); } // Prepare data for ajax. $error_code = $errors->get_error_code(); $error_data = $errors->get_error_data(); $status = array( 'error' => $error_code, 'error_msg' => Helper::filter_error( $errors->get_error_message( $error_code ), $attachment_id ), 'html_stats' => WP_Smush::get_instance()->library()->generate_markup( $attachment_id ), 'show_warning' => (int) $this->show_warning(), ); // Add error data (file_name) to status. if ( $error_data && is_array( $error_data ) ) { $status = array_merge( $error_data, $status ); } // Send data. wp_send_json_error( $status ); } $this->update_resmush_list( $attachment_id ); Core::add_to_smushed_list( $attachment_id ); // Get the button status later after update resmushed list. $status = WP_Smush::get_instance()->library()->generate_markup( $attachment_id ); if ( $return ) { return $status; } wp_send_json_success( $status ); } /** * If auto smush is set to true or not, default is true * * @return int|bool */ public function is_auto_smush_enabled() { $auto_smush = $this->settings->get( 'auto' ); // Keep the auto smush on by default. if ( ! isset( $auto_smush ) ) { $auto_smush = 1; } return $auto_smush; } /** * Deletes all the backup files when an attachment is deleted * Update resmush List * Update Super Smush image count * * @param int $image_id Attachment ID. * * @return bool|void */ public function delete_images( $image_id ) { // Update the savings cache. WP_Smush::get_instance()->core()->get_savings( 'resize' ); // Update the savings cache. WP_Smush::get_instance()->core()->get_savings( 'pngjpg' ); // If no image id provided. if ( empty( $image_id ) ) { return false; } // Check and Update resmush list. $resmush_list = get_option( 'wp-smush-resmush-list' ); if ( $resmush_list ) { $this->update_resmush_list( $image_id ); } /** Delete Backups */ // Check if we have any smush data for image. WP_Smush::get_instance()->core()->mod->backup->delete_backup_files( $image_id ); /** * Delete webp. * * Run WebP::delete_images always even when the module is deactivated. * * @since 3.8.0 */ //WP_Smush::get_instance()->core()->mod->webp->delete_images( $image_id, false ); } /** * Calculate saving percentage for a given size stats * * @param object $stats Stats object. * * @return float|int */ private function calculate_percentage_from_stats( $stats ) { if ( empty( $stats ) || ! isset( $stats->size_before, $stats->size_after ) ) { return 0; } $savings = $stats->size_before - $stats->size_after; if ( $savings > 0 ) { $percentage = ( $savings / $stats->size_before ) * 100; return $percentage > 0 ? round( $percentage, 2 ) : $percentage; } return 0; } /** * Perform the resize operation for the image * * @param int $attachment_id Attachment ID. * @param array $meta Attachment meta. * * @return mixed */ public function resize_image( $attachment_id, $meta ) { if ( empty( $attachment_id ) || empty( $meta ) ) { return $meta; } return WP_Smush::get_instance()->core()->mod->resize->auto_resize( $attachment_id, $meta ); } /** * Send a smush request for the attachment * * @param int $id Attachment ID. */ public function wp_smush_handle_async( $id ) { // If we don't have image id or auto Smush is disabled, return. if ( empty( $id ) || ! $this->should_auto_smush( $id ) ) { return; } // Try to use smushit. $this->smush_single( $id, true ); } /** * Send a smush request for the attachment * * @param int $id Attachment ID. * @param array $post_data Post data. */ public function wp_smush_handle_editor_async( $id, $post_data ) { // If we don't have image id, or the smush is already in progress for the image, return. if ( empty( $id ) || get_transient( 'smush-in-progress-' . $id ) || get_transient( 'wp-smush-restore-' . $id ) ) { return; } // If auto Smush is disabled. if ( ! $this->should_auto_smush( $id ) ) { return; } /** * Filter: wp_smush_image * * Whether to smush the given attachment id or not * * @param bool $skip Whether to Smush image or not. * @param int $id Attachment ID of the image being processed. */ if ( ! apply_filters( 'wp_smush_image', true, $id ) ) { return; } // If filepath is not set or file doesn't exist. if ( ! isset( $post_data['filepath'] ) || ! file_exists( $post_data['filepath'] ) ) { return; } $res = $this->do_smushit( $post_data['filepath'] ); if ( is_wp_error( $res ) || empty( $res['success'] ) || ! $res['success'] ) { // Logged the error inside do_smushit. return; } // Update stats if it's the full size image. Return if it's not the full image size. if ( get_attached_file( $post_data['postid'] ) !== $post_data['filepath'] ) { return; } // Get the existing Stats. $smush_stats = get_post_meta( $post_data['postid'], self::$smushed_meta_key, true ); $stats_full = ! empty( $smush_stats['sizes'] ) && ! empty( $smush_stats['sizes']['full'] ) ? $smush_stats['sizes']['full'] : ''; if ( empty( $stats_full ) ) { return; } // store the original image size. $stats_full->size_before = ( ! empty( $stats_full->size_before ) && $stats_full->size_before > $res['data']->before_size ) ? $stats_full->size_before : $res['data']->before_size; $stats_full->size_after = $res['data']->after_size; // Update compression percent and bytes saved for each size. $stats_full->bytes = $stats_full->size_before - $stats_full->size_after; $stats_full->percent = $this->calculate_percentage_from_stats( $stats_full ); $smush_stats['sizes']['full'] = $stats_full; // Update stats. update_post_meta( $post_data['postid'], self::$smushed_meta_key, $smush_stats ); } /** * Make sure we treat the scaled image as an attachment size, rather than the original uploaded image. * * @since 3.9.1 * * @param array $meta Attachment meta data. * @param int $attachment_id Attachment ID. * * @return array */ public function add_scaled_to_meta( $meta, $attachment_id ) { // If the image is not a scaled version - do nothing. if ( false === strpos( $meta['file'], '-scaled.' ) || ! isset( $meta['original_image'] ) || isset( $meta['sizes']['wp_scaled'] ) ) { return $meta; } $meta['sizes']['wp_scaled'] = array( 'file' => basename( $meta['file'] ), 'width' => $meta['width'], 'height' => $meta['height'], 'mime-type' => get_post_mime_type( $attachment_id ), ); return $meta; } /** * @param $file_path * @param $image * * @return string */ public function put_webp_image_file( $file_path, $image ) { $file_path = WP_Smush::get_instance()->core()->mod->webp->get_webp_file_path( $file_path, true ); file_put_contents( $file_path, $image ); return $file_path; } /** * @param $file_path * @param $image * * @return void */ public function put_smushed_image_file( $file_path, $image ) { $temp_file = $file_path . '.tmp'; // Add the file as tmp. file_put_contents( $temp_file, $image ); // Replace the file. $success = rename( $temp_file, $file_path ); // If temp file still exists, unlink it. if ( file_exists( $temp_file ) ) { unlink( $temp_file ); } // If file renaming failed. if ( ! $success ) { copy( $temp_file, $file_path ); unlink( $temp_file ); } } /** * @param $file_path * * @return int */ public function get_file_permissions( $file_path ) { clearstatcache(); $perms = fileperms( $file_path ) & 0777; // Some servers are having issue with file permission, this should fix it. if ( empty( $perms ) ) { // Source: WordPress Core. $stat = stat( dirname( $file_path ) ); $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits. } return $perms; } private function save_smushed_image_file( $file_path, $convert_to_webp, $image ) { $pre = apply_filters( 'wp_smush_pre_image_write', false, $file_path, $convert_to_webp, $image ); if ( $pre !== false ) { Helper::logger()->notice( 'Another plugin/theme short circuited the image write operation using the wp_smush_pre_image_write filter.' ); return; } // Backup the old permissions $permissions = $this->get_file_permissions( $file_path ); // Save the new file if ( $convert_to_webp ) { $file_path = $this->put_webp_image_file( $file_path, $image ); } else { $this->put_smushed_image_file( $file_path, $image ); } // Restore the old permissions chmod( $file_path, $permissions ); } /** * @param $convert_to_webp * * @return string[] */ private function get_api_request_headers( $convert_to_webp ) { $headers = array( 'accept' => 'application/json', // The API returns JSON. 'content-type' => 'application/binary', // Set content type to binary. 'lossy' => $this->settings->get( 'lossy' ) ? 'true' : 'false', 'exif' => $this->settings->get( 'strip_exif' ) ? 'false' : 'true', ); if ( $convert_to_webp ) { $headers['webp'] = 'true'; } // Check if premium member, add API key. $api_key = Helper::get_wpmudev_apikey(); if ( ! empty( $api_key ) && WP_Smush::is_pro() ) { $headers['apikey'] = $api_key; } return $headers; } /** * @return string */ private function get_api_url() { return defined( 'WP_SMUSH_API_HTTP' ) ? WP_SMUSH_API_HTTP : WP_SMUSH_API; } /** * @param $file_path * @param $convert_to_webp * * @return array */ private function get_api_request_args( $file_path, $convert_to_webp ) { return array( 'headers' => $this->get_api_request_headers( $convert_to_webp ), 'body' => file_get_contents( $file_path ), 'timeout' => WP_SMUSH_TIMEOUT, 'user-agent' => WP_SMUSH_UA, ); } /** * @return Request_Multiple */ public function get_request_multiple() { return $this->request_multiple; } /** * @param Request_Multiple $request_multiple */ public function set_request_multiple( $request_multiple ) { $this->request_multiple = $request_multiple; } private function do_smushit_optimization( $file_path, $convert_to_webp, $retries ) { $errors = $this->validate_file( $file_path ); if ( count( $errors->get_error_messages() ) ) { return $errors; } $smusher = $convert_to_webp ? new Webp_Converter() : new Smusher(); $smusher->set_retry_attempts( $retries ); $data = $smusher->smush_file( $file_path ); if ( $data ) { return array( 'success' => true, 'data' => $data, ); } else { return $smusher->get_errors(); } } private function run_optimizer( $attachment_id ) { $in_progress_error = new WP_Error( 'in_progress', 'Smush already in progress' ); if ( $this->prevent_infinite_loop ) { return $this->no_smushit( $attachment_id, $in_progress_error, $in_progress_error ); } $this->prevent_infinite_loop = true; $media_item = Media_Item_Cache::get_instance()->get( $attachment_id ); $optimizer = new Media_Item_Optimizer( $media_item ); $optimized = $optimizer->optimize(); $this->prevent_infinite_loop = false; if ( $optimized ) { return $media_item->get_wp_metadata(); } else { if ( $media_item->has_errors() ) { return $media_item->get_errors(); } return $optimizer->get_errors(); } } public function should_auto_smush( $attachment_id ) { // TODO: We already verified in restoring status but better to disable auto smush while restoring. if ( ! $this->settings->is_automatic_compression_active() ) { return false; } $media_item = Media_Item_Cache::get_instance()->get( $attachment_id ); if ( ! $media_item->is_valid() ) { return false; } /** * Skip auto smush filter. * * @param bool $skip_auto_smush Whether to skip auto smush or not. */ $skip_auto_smush = apply_filters( 'wp_smush_should_skip_auto_smush', false, $attachment_id ); // We don't want very large files to be auto smushed. $skip_auto_smush = $skip_auto_smush || $media_item->is_large(); if ( $skip_auto_smush ) { return false; } return true; } }