PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.7.7
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.7.7
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
← All changes | modules/optimization/components/bulk-optimization.php +249 -86 1.1.01.7.7 View file →
@@ -1,143 +1,306 @@
1 1 <?php
2 2
3 3 namespace ImageOptimization\Modules\Optimization\Components;
4 4
5 -use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook;
5 +use ImageOptimization\Classes\Async_Operation\{
6 + Async_Operation,
7 + Async_Operation_Hook,
8 + Async_Operation_Queue,
9 +};
10 +
6 11 use ImageOptimization\Classes\Image\{
7 - Image,
8 12 Image_Meta,
9 - Image_Optimization_Error_Type,
10 - Image_Restore,
11 13 Image_Status
12 14 };
13 -use ImageOptimization\Classes\Logger;
14 -use ImageOptimization\Modules\Oauth\Classes\Exceptions\Quota_Exceeded_Error;
15 +
15 16 use ImageOptimization\Modules\Optimization\{
16 17 Classes\Exceptions\Bulk_Token_Expired_Error,
17 - Classes\Exceptions\Image_File_Already_Exists_Error,
18 18 Classes\Optimize_Image,
19 - Classes\Bulk_Optimization_Controller,
20 - Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error,
19 + Classes\Bulk_Optimization\Bulk_Optimization_Queue,
20 + Classes\Bulk_Optimization\Bulk_Optimization_Queue_Status,
21 + Classes\Bulk_Optimization\Bulk_Optimization_Queue_Type,
22 + Classes\Bulk_Optimization\Bulk_Optimization_Token_Manager,
21 23 };
22 24
25 +use ImageOptimization\Classes\Logger;
26 +use ImageOptimization\Classes\Utils;
27 +use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error;
23 28 use Throwable;
24 29
30 +// @codeCoverageIgnoreStart
25 31 if ( ! defined( 'ABSPATH' ) ) {
26 32 exit; // Exit if accessed directly.
27 33 }
34 +// @codeCoverageIgnoreEnd
28 35
29 36 class Bulk_Optimization {
30 - const BULK_OPTIMIZATION_BASE_SLUG = 'image-optimization-bulk-optimization';
31 - const BULK_OPTIMIZATION_CAPABILITY = 'manage_options';
37 + /** @async */
38 + public function optimize_bulk() {
39 + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );
32 40
33 - public function render_app() {
34 - ?>
35 - <!-- The hack required to wrap WP notifications -->
36 - <div class="wrap">
37 - <h1 style="display: none;" role="presentation"></h1>
38 - </div>
41 + if ( ! $queue->exists() ) {
42 + Logger::debug( 'Bulk optimization queue did not found' );
39 43
40 - <div id="image-optimization-app"></div>
41 - <?php
42 - }
44 + return;
45 + }
43 46
44 - public function register_page() {
45 - add_media_page(
46 - __( 'Bulk Optimization', 'image-optimization' ),
47 - __( 'Bulk Optimization', 'image-optimization' ),
48 - self::BULK_OPTIMIZATION_CAPABILITY,
49 - self::BULK_OPTIMIZATION_BASE_SLUG,
50 - [ $this, 'render_app' ],
51 - 7
52 - );
53 - }
47 + if ( ! $queue->has_more_images() ) {
48 + Logger::debug( 'No more pending images, deleting queue' );
49 + $queue->delete();
54 50
55 - /** @async */
56 - public function optimize_bulk( int $image_id, string $operation_id ) {
51 + return;
52 + }
53 +
54 + if ( $queue->should_refresh_token() ) {
55 + Logger::debug( 'Refreshing bulk token' );
56 +
57 + try {
58 + $token_data = Bulk_Optimization_Token_Manager::obtain_token(
59 + $queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ],
60 + $queue->get_max_batch_size()
61 + );
62 +
63 + $queue
64 + ->set_bulk_token(
65 + $token_data['token'],
66 + time() + HOUR_IN_SECONDS,
67 + $token_data['batch_size']
68 + )
69 + ->save();
70 + } catch ( Throwable $t ) {
71 + Logger::info( 'Failed to obtain a bulk token: ' . $t->getMessage() );
72 +
73 + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
74 + ( new Image_Meta( $image['id'] ) )
75 + ->set_status( Image_Status::NOT_OPTIMIZED )
76 + ->save();
77 + }
78 +
79 + $queue->delete();
80 +
81 + return;
82 + }
83 + }
84 +
85 + $image_id = $queue->get_next_image();
86 +
87 + Logger::debug( 'Processing image: ' . $image_id );
88 +
89 + if ( ! $image_id ) {
90 + Logger::debug( 'No image to process, deleting queue' );
91 + $queue->delete();
92 +
93 + return;
94 + }
95 +
96 + $queue
97 + ->set_current_image_id( $image_id )
98 + ->save();
99 +
57 100 try {
58 - $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id );
59 -
60 101 $oi = new Optimize_Image(
61 102 $image_id,
62 103 'bulk',
63 - $bulk_token
104 + $queue->get_bulk_token()
64 105 );
65 106
66 107 $oi->optimize();
67 - } catch ( Quota_Exceeded_Error $qe ) {
68 - ( new Image_Meta( $image_id ) )
69 - ->set_status( Image_Status::OPTIMIZATION_FAILED )
70 - ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED )
108 +
109 + $queue
110 + ->mark_image_completed( $image_id )
111 + ->increment_optimized_counter()
71 112 ->save();
72 - } catch ( Image_File_Already_Exists_Error $fe ) {
73 - ( new Image_Meta( $image_id ) )
74 - ->set_status( Image_Status::OPTIMIZATION_FAILED )
75 - ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS )
76 - ->save();
77 - } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) {
78 - ( new Image_Meta( $image_id ) )
79 - ->set_status( Image_Status::NOT_OPTIMIZED )
80 - ->save();
81 113
82 - Bulk_Optimization_Controller::reschedule_bulk_optimization();
114 + } catch ( Bulk_Token_Expired_Error $btee ) {
115 + $queue->mark_image_failed( $image_id )->save();
116 +
117 + Logger::info( "Token expired while optimizing image {$image_id}" );
118 + } catch ( Quota_Exceeded_Error $qee ) {
119 + $queue->mark_image_failed( $image_id )->save();
120 +
121 + Logger::info( "Quota exceeded while optimizing image {$image_id}" );
122 + } catch ( Throwable $e ) {
123 + $queue->mark_image_failed( $image_id )->save();
124 +
125 + Logger::info( "Failed to optimize image {$image_id}: " . $e->getMessage() );
126 + }
127 +
128 + $queue
129 + ->set_current_image_id( null )
130 + ->save();
131 +
132 + try {
133 + if ( $queue->has_more_images() ) {
134 + Async_Operation::create(
135 + Async_Operation_Hook::OPTIMIZE_BULK,
136 + [ 'operation_id' => $queue->get_operation_id() ],
137 + Async_Operation_Queue::OPTIMIZE
138 + );
139 +
140 + Logger::debug( 'Async operation created' );
141 + } else {
142 + Logger::debug( 'All images were optimized, deleting queue' );
143 +
144 + $queue->delete();
145 + }
83 146 } catch ( Throwable $t ) {
84 - Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() );
85 -
86 - ( new Image_Meta( $image_id ) )
87 - ->set_status( Image_Status::OPTIMIZATION_FAILED )
88 - ->set_error_type( Image_Optimization_Error_Type::GENERIC )
89 - ->save();
147 + Logger::error( 'Failed to create next async operation or delete queue: ' . $t->getMessage() );
90 148 }
91 149 }
92 150
93 151 /** @async */
94 - public function reoptimize_bulk( int $image_id, string $operation_id ) {
95 - try {
96 - $image = new Image( $image_id );
152 + public function reoptimize_bulk() {
153 + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::REOPTIMIZATION );
97 154
98 - if ( $image->can_be_restored() ) {
99 - Image_Restore::restore( $image_id, true );
155 + if ( ! $queue->exists() ) {
156 + return;
157 + }
158 +
159 + if ( ! $queue->has_more_images() ) {
160 + $queue->delete();
161 +
162 + return;
163 + }
164 +
165 + if ( $queue->should_refresh_token() ) {
166 + try {
167 + $token_data = Bulk_Optimization_Token_Manager::obtain_token(
168 + $queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ],
169 + $queue->get_max_batch_size()
170 + );
171 +
172 + $queue
173 + ->set_bulk_token(
174 + $token_data['token'],
175 + time() + HOUR_IN_SECONDS,
176 + $token_data['batch_size']
177 + )
178 + ->save();
179 + } catch ( Throwable $t ) {
180 + Logger::info( 'Failed to obtain bulk token: ' . $t->getMessage() );
181 +
182 + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
183 + ( new Image_Meta( $image['id'] ) )
184 + ->set_status( Image_Status::OPTIMIZATION_FAILED )
185 + ->save();
186 + }
187 +
188 + $queue->delete();
189 +
190 + return;
100 191 }
192 + }
101 193
102 - $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id );
194 + $image_id = $queue->get_next_image();
103 195
196 + if ( ! $image_id ) {
197 + $queue
198 + ->set_status( Bulk_Optimization_Queue_Status::COMPLETED )
199 + ->save();
200 +
201 + return;
202 + }
203 +
204 + $queue
205 + ->set_current_image_id( $image_id )
206 + ->save();
207 +
208 + try {
104 209 $oi = new Optimize_Image(
105 210 $image_id,
106 - 'bulk',
107 - $bulk_token
211 + 'bulk-reoptimize',
212 + $queue->get_bulk_token(),
213 + true
108 214 );
109 215
110 216 $oi->optimize();
111 - } catch ( Quota_Exceeded_Error $qe ) {
112 - ( new Image_Meta( $image_id ) )
113 - ->set_status( Image_Status::REOPTIMIZING_FAILED )
114 - ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED )
217 +
218 + $queue
219 + ->mark_image_completed( $image_id )
220 + ->increment_optimized_counter()
115 221 ->save();
116 - } catch ( Image_File_Already_Exists_Error $fe ) {
117 - ( new Image_Meta( $image_id ) )
118 - ->set_status( Image_Status::REOPTIMIZING_FAILED )
119 - ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS )
120 - ->save();
121 - } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) {
122 - ( new Image_Meta( $image_id ) )
123 - ->set_status( Image_Status::NOT_OPTIMIZED )
124 - ->save();
125 222
126 - Bulk_Optimization_Controller::reschedule_bulk_reoptimization();
223 + } catch ( Bulk_Token_Expired_Error $btee ) {
224 + $queue->mark_image_failed( $image_id )->save();
225 +
226 + Logger::info( "Token expired while reoptimizing image {$image_id}" );
227 + } catch ( Quota_Exceeded_Error $qee ) {
228 + $queue->mark_image_failed( $image_id )->save();
229 +
230 + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
231 + ( new Image_Meta( $image['id'] ) )
232 + ->set_status( Image_Status::OPTIMIZATION_FAILED )
233 + ->save();
234 + }
235 +
236 + $queue->delete();
237 +
238 + Logger::info( "Quota exceeded while reoptimizing image {$image_id}" );
239 +
240 + return;
241 + } catch ( Throwable $e ) {
242 + $queue->mark_image_failed( $image_id )->save();
243 +
244 + Logger::info( "Failed to reoptimize image {$image_id}: " . $e->getMessage() );
245 + }
246 +
247 + $queue
248 + ->set_current_image_id( null )
249 + ->save();
250 +
251 + try {
252 + if ( $queue->has_more_images() ) {
253 + Async_Operation::create(
254 + Async_Operation_Hook::REOPTIMIZE_BULK,
255 + [ 'operation_id' => $queue->get_operation_id() ],
256 + Async_Operation_Queue::OPTIMIZE
257 + );
258 + } else {
259 + $queue->delete();
260 + }
127 261 } catch ( Throwable $t ) {
128 - Logger::log( Logger::LEVEL_ERROR, 'Reoptimization error. Reason: ' . $t->getMessage() );
262 + Logger::error( 'Failed to create next async operation or delete queue: ' . $t->getMessage() );
263 + }
264 + }
129 265
130 - ( new Image_Meta( $image_id ) )
131 - ->set_status( Image_Status::REOPTIMIZING_FAILED )
132 - ->set_error_type( Image_Optimization_Error_Type::GENERIC )
133 - ->save();
134 - }
266 + /**
267 + * Renders the bulk optimization notice
268 + *
269 + * @return void
270 + */
271 + public function render_bulk_optimization_notice() {
272 + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );
273 + $is_in_progress = $queue->exists();
274 + ?>
275 + <div class="notice notice-info notice image-optimizer__notice image-optimizer__notice--info image-optimizer__notice--bulk-tip"
276 + style="display: <?php echo $is_in_progress ? 'block' : 'none'; ?>">
277 + <p>
278 + <b>
279 + <?php esc_html_e(
280 + 'Heads up!',
281 + 'image-optimization'
282 + ); ?>
283 + </b>
284 +
285 + <span>
286 + <?php esc_html_e(
287 + 'Bulk optimizing may take a lot of processing and server time, depending on the number of images. Your site will still work smoothly until the processing is all done, without any downtime.',
288 + 'image-optimization'
289 + ); ?>
290 + </span>
291 + </p>
292 + </div>
293 + <?php
135 294 }
136 295
137 296 public function __construct() {
138 - add_action( 'admin_menu', [ $this, 'register_page' ] );
297 + add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ] );
298 + add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ] );
139 299
140 - add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ], 10, 2 );
141 - add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ], 10, 2 );
300 + add_action( 'current_screen', function () {
301 + if ( Utils::is_bulk_optimization_page() ) {
302 + add_filter( 'admin_footer_text', [ $this, 'render_bulk_optimization_notice' ] );
303 + }
304 + } );
142 305 }
143 306 }