PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.0.0
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.0.0
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
image-optimization / modules / optimization / classes / optimize-image.php

optimize-image.php in Image Optimizer – Compress Images and Convert to WebP or AVIF 1.0.0, at modules/optimization/classes/optimize-image.php

253 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImageOptimizer\Modules\Optimization\Classes;
4
5 use ImageOptimizer\Classes\File_Utils;
6 use ImageOptimizer\Classes\Image\{
7 Exceptions\Invalid_Image_Exception,
8 Image,
9 Image_Backup,
10 Image_Meta,
11 Image_Status,
12 WP_Image_Meta
13 };
14 use ImageOptimizer\Classes\Utils;
15 use ImageOptimizer\Modules\Oauth\Components\Connect;
16 use ImageOptimizer\Modules\Optimization\Classes\Exceptions\Image_Optimization_Error;
17 use ImageOptimizer\Modules\Settings\Classes\Settings;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // Exit if accessed directly.
21 }
22
23 require_once ABSPATH . 'wp-admin/includes/file.php';
24 WP_Filesystem();
25
26 /**
27 * The class is responsible for the optimization logic itself. It gets an image file, runs
28 * backup process if needed, sends a file to the service, stores the result and updates image meta.
29 *
30 * This class is used by manual, bulk and on-upload optimization flows.
31 */
32 class Optimize_Image {
33 private const IMAGE_OPTIMIZE_ENDPOINT = 'image/optimize';
34
35 protected ?Image $image;
36 protected WP_Image_Meta $wp_meta;
37 protected string $initiator;
38 protected ?string $bulk_token;
39 private string $current_image_path;
40 private string $current_image_size;
41 private bool $convert_to_webp;
42
43 /**
44 * @throws Image_Optimization_Error
45 */
46 public function optimize(): void {
47 $sizes_enabled = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME );
48 $sizes_exist = $this->wp_meta->get_size_keys();
49
50 foreach ( $sizes_exist as $size_exist ) {
51 // If some image sizes optimization is disabled in settings, we check if the current one is still enabled
52 if (
53 'all' !== $sizes_enabled &&
54 Image::SIZE_FULL !== $size_exist &&
55 ! in_array( $size_exist, $sizes_enabled, true )
56 ) {
57 continue;
58 }
59
60 $image_meta = new Image_Meta( $this->image->get_id() );
61
62 // If the current size was already optimized -- ignore it.
63 if ( in_array( $size_exist, $image_meta->get_optimized_sizes(), true ) ) {
64 continue;
65 }
66
67 if ( ! file_exists( $this->image->get_file_path( $size_exist ) ) ) {
68 throw new Image_Optimization_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimizer' ) );
69 }
70
71 $this->current_image_size = $size_exist;
72 $this->current_image_path = $this->image->get_file_path( $size_exist );
73
74 $this->optimize_current_size();
75
76 $this->current_image_size = '';
77 $this->current_image_path = '';
78 }
79
80 $this->mark_as_optimized();
81 }
82
83 /**
84 * @throws Image_Optimization_Error
85 */
86 private function optimize_current_size(): void {
87 $response = $this->send_file();
88
89 if ( is_wp_error( $response ) ) {
90 throw new Image_Optimization_Error( $response->get_error_message() );
91 }
92
93 $original_size = (int) $response->originalSize; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
94 $optimized_size = (int) $response->optimizedSize; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
95 $optimized_image_binary = base64_decode( $response->image, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
96
97 // Sometimes the BE returns images bigger than original ones. In that case, we don't want to replace the original.
98 if ( $original_size > $optimized_size ) {
99 $this->replace_image_file( $optimized_image_binary );
100 $this->update_attachment_meta( $optimized_size );
101 } else {
102 $this->update_attachment_meta( $original_size );
103 }
104
105 // This should only be updated after meta
106 $this->update_attachment_post();
107 }
108
109 private function send_file() {
110 $connect_status = Connect::check_connect_status();
111 $headers = [
112 'access_token' => $connect_status->access_token ?? '',
113 ];
114
115 if ( $this->bulk_token ) {
116 $headers['x-elementor-bulk-token'] = $this->bulk_token;
117 }
118
119 $optimization_options = [
120 'compression_level' => Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ),
121 'convert_to_webp' => $this->convert_to_webp,
122 'strip_exif' => Settings::get( Settings::STRIP_EXIF_METADATA_OPTION_NAME ),
123 ];
124
125 if ( Settings::get( Settings::RESIZE_LARGER_IMAGES_OPTION_NAME ) ) {
126 $optimization_options['resize'] = Settings::get( Settings::RESIZE_LARGER_IMAGES_SIZE_OPTION_NAME );
127 }
128
129 return Utils::get_api_client()->make_request(
130 'POST',
131 self::IMAGE_OPTIMIZE_ENDPOINT,
132 [
133 'initiator' => $this->initiator,
134 'image_url' => $this->image->get_url( $this->current_image_size ),
135 'attachment_id' => $this->image->get_id(),
136 'attachment_parent_id' => Image::SIZE_FULL === $this->current_image_size ? 0 : $this->image->get_id(),
137 'image_optimization_settings' => base64_encode( wp_json_encode( $optimization_options ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
138 ],
139 $headers,
140 $this->current_image_path,
141 'image'
142 );
143 }
144
145 private function replace_image_file( string $file_data ): void {
146 global $wp_filesystem;
147
148 $path = $this->current_image_path;
149
150 // If we have backups disabled, we want to make sure we can download and save new file before we
151 // remove an existing one.
152 $tmp_path = File_Utils::replace_extension( $path, 'tmp' );
153
154 $wp_filesystem->put_contents( $tmp_path, $file_data );
155
156 // The original file doesn't exist after any of these operations.
157 if ( Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME ) ) {
158 Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path );
159 } else {
160 $wp_filesystem->delete( $path, false, 'f' );
161 }
162
163 if ( $this->convert_to_webp ) {
164 $path = File_Utils::replace_extension( $tmp_path, 'webp' );
165 }
166
167 $wp_filesystem->move( $tmp_path, $path, true );
168
169 if ( Image::SIZE_FULL === $this->current_image_size ) {
170 // Drop WP caches
171 update_attached_file( $this->image->get_id(), $path );
172 }
173
174 // Updating to the correct value
175 $this->current_image_path = $path;
176 }
177
178 /**
179 * Updates attachment records in the `wp_posts` table.
180 *
181 * @return void
182 */
183 private function update_attachment_post() {
184 $update_query = [];
185
186 if ( $this->convert_to_webp ) {
187 $attachment_object = $this->image->get_attachment_object();
188
189 $update_query['guid'] = File_Utils::replace_extension( $attachment_object->guid, 'webp' );
190 $update_query['post_mime_type'] = 'image/webp';
191 }
192
193 $update_query['post_modified'] = current_time( 'mysql' );
194 $update_query['post_modified_gmt'] = current_time( 'mysql', true );
195
196 $this->image->update_attachment( $update_query );
197 }
198
199 /**
200 * Updates attachment records in the `wp_postmeta` table.
201 *
202 * @param int $optimized_size
203 *
204 * @return void
205 */
206 private function update_attachment_meta( int $optimized_size ) {
207 $meta = new Image_Meta( $this->image->get_id() );
208
209 list($width, $height) = getimagesize( $this->current_image_path );
210
211 $meta
212 ->set_compression_level( Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ) )
213 ->add_optimized_size( $this->current_image_size )
214 ->add_original_data( $this->current_image_size, $this->wp_meta->get_size_data( $this->current_image_size ) );
215
216 $this->wp_meta
217 ->set_width( $this->current_image_size, $width )
218 ->set_height( $this->current_image_size, $height )
219 ->set_file_size( $this->current_image_size, $optimized_size );
220
221 if ( $this->convert_to_webp ) {
222 $this->wp_meta
223 ->set_file_path( $this->current_image_size, $this->current_image_path )
224 ->set_mime_type( $this->current_image_size, 'image/webp' );
225 }
226
227 $meta->save();
228 $this->wp_meta->save();
229 }
230
231 /**
232 * Changes image status after all image sizes were optimized.
233 *
234 * @return void
235 */
236 private function mark_as_optimized() {
237 ( new Image_Meta( $this->image->get_id() ) )
238 ->set_status( Image_Status::OPTIMIZED )
239 ->save();
240 }
241
242 /**
243 * @throws Invalid_Image_Exception
244 */
245 public function __construct( int $image_id, string $initiator, ?string $bulk_token = null ) {
246 $this->image = new Image( $image_id );
247 $this->wp_meta = new WP_Image_Meta( $image_id, $this->image );
248 $this->initiator = $initiator;
249 $this->bulk_token = $bulk_token;
250 $this->convert_to_webp = Settings::get( Settings::CONVERT_TO_WEBP_OPTION_NAME );
251 }
252 }
253