PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
← All changes | classes/Bulk/Bulk.php +169 -1 2.3.1 → trunk View file →
@@ -20,8 +20,9 @@
20 20 public function init() {
21 21 add_action( 'imagify_optimize_media', [ $this, 'optimize_media' ], 10, 3 );
22 22 add_action( 'imagify_convert_next_gen', [ $this, 'generate_nextgen_versions' ], 10, 2 ); // @phpstan-ignore-line
23 23 add_action( 'wp_ajax_imagify_bulk_optimize', [ $this, 'bulk_optimize_callback' ] );
24 + add_action( 'wp_ajax_imagify_bulk_stop', [ $this, 'bulk_stop_callback' ] );
24 25 add_action( 'wp_ajax_imagify_missing_nextgen_generation', [ $this, 'missing_nextgen_callback' ] );
25 26 add_action( 'wp_ajax_imagify_get_folder_type_data', [ $this, 'get_folder_type_data_callback' ] );
26 27 add_action( 'wp_ajax_imagify_bulk_info_seen', [ $this, 'bulk_info_seen_callback' ] );
27 28 add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] );
@@ -27,11 +28,40 @@
27 28 add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] );
28 29 add_action( 'imagify_after_optimize', [ $this, 'check_optimization_status' ], 10, 2 );
29 30 add_action( 'imagify_deactivation', [ $this, 'delete_transients_data' ] );
30 31 add_action( 'update_option_imagify_settings', [ $this, 'maybe_generate_missing_nextgen' ], 10, 2 );
32 + add_action( 'imagify_upgrade', [ $this, 'maybe_clear_stale_bulk_run' ], 10, 2 );
31 33 }
32 34
33 35 /**
36 + * Clear any bulk optimization progress left running from before 2.3.2, on upgrade.
37 + *
38 + * Queue items enqueued by a bulk run before 2.3.2 don't carry the `bulk` flag used by
39 + * self::check_optimization_status() to tell bulk completions from manual/auto ones (see
40 + * that method). If a bulk run straddles the upgrade, its remaining items complete without
41 + * that flag and are never counted, so the run's `remaining` counter never reaches zero and
42 + * the bulk page is stuck showing it as still running. Clearing the running transients lets
43 + * a fresh bulk run start clean instead.
44 + *
45 + * @since 2.3.2
46 + *
47 + * @param string $network_version Previous version stored on the network.
48 + * @param string $site_version Previous version stored on site level.
49 + *
50 + * @return void
51 + */
52 + public function maybe_clear_stale_bulk_run( $network_version, $site_version ) {
53 + if ( version_compare( $site_version, '2.3.2' ) >= 0 ) {
54 + return;
55 + }
56 +
57 + delete_transient( 'imagify_custom-folders_optimize_running' );
58 + delete_transient( 'imagify_wp_optimize_running' );
59 + delete_transient( 'imagify_bulk_optimization_result' );
60 + delete_transient( 'imagify_bulk_optimization_complete' );
61 + }
62 +
63 + /**
34 64 * Delete transients data on deactivation
35 65 *
36 66 * @return void
37 67 */
@@ -61,8 +91,15 @@
61 91 ) {
62 92 return;
63 93 }
64 94
95 + if ( empty( $item['data']['bulk'] ) ) {
96 + // This completion was not enqueued by Bulk::run_optimize() (e.g. a manual click or
97 + // an auto-optimize-on-upload that happens to complete while a bulk job is active):
98 + // do not let it affect the bulk job's progress counters.
99 + return;
100 + }
101 +
65 102 $data = $process->get_data();
66 103
67 104 if ( ! $data ) {
68 105 return;
@@ -241,8 +278,112 @@
241 278 ];
242 279 }
243 280
244 281 /**
282 + * Stops a running bulk process for the given contexts.
283 + *
284 + * Cancels every media still waiting in the queue and clears the progress data. Media already
285 + * sent to the Imagify API cannot be cancelled: the one being processed when the stop is
286 + * requested still completes.
287 + *
288 + * @since 2.3
289 + *
290 + * @param array $contexts An array of contexts (WP/Custom folders).
291 + *
292 + * @return array {
293 + * @type bool $success Whether a running process was found.
294 + * @type string $message Status message.
295 + * @type int $cancelled Number of queued media actually cancelled.
296 + * }
297 + */
298 + public function run_stop( array $contexts ): array {
299 + $cancelled = 0;
300 + $was_running = false;
301 +
302 + foreach ( $contexts as $context ) {
303 + if ( false !== get_transient( "imagify_{$context}_optimize_running" ) ) {
304 + $was_running = true;
305 + }
306 +
307 + $cancelled += $this->cancel_pending_actions( 'imagify_optimize_media', "imagify-{$context}-optimize-media" );
308 + $cancelled += $this->cancel_pending_actions( 'imagify_convert_next_gen', "imagify-{$context}-convert-nextgen" );
309 +
310 + delete_transient( "imagify_{$context}_optimize_running" );
311 + }
312 +
313 + delete_transient( 'imagify_bulk_optimization_result' );
314 + delete_transient( 'imagify_missing_next_gen_total' );
315 +
316 + if ( ! $was_running && 0 === $cancelled ) {
317 + return [
318 + 'success' => false,
319 + 'message' => 'not-running',
320 + 'cancelled' => 0,
321 + ];
322 + }
323 +
324 + /**
325 + * Fires after a bulk process has been manually stopped.
326 + *
327 + * @since 2.3
328 + *
329 + * @param array $contexts The contexts that were stopped.
330 + * @param int $cancelled Number of queued media actually cancelled.
331 + */
332 + do_action( 'imagify_bulk_stopped', $contexts, $cancelled );
333 +
334 + return [
335 + 'success' => true,
336 + 'message' => 'success',
337 + 'cancelled' => $cancelled,
338 + ];
339 + }
340 +
341 + /**
342 + * Cancel all the pending actions for a given hook and group.
343 + *
344 + * Actions already running are left untouched: Action Scheduler cannot interrupt them.
345 + *
346 + * The pending actions are counted before being cancelled, so the caller reports what was
347 + * really removed from the queue. The `remaining` value of the progress transient cannot be
348 + * used for that: it is only decremented by the `imagify_after_optimize` hook, which never
349 + * fires when an optimization bails out early (unsupported media, already optimized, locked
350 + * process) or when the request dies, so it drifts above the real queue size.
351 + *
352 + * @since 2.3
353 + *
354 + * @param string $hook The action hook.
355 + * @param string $group The Action Scheduler group.
356 + *
357 + * @return int Number of pending actions that were cancelled.
358 + */
359 + private function cancel_pending_actions( string $hook, string $group ): int {
360 + if ( ! function_exists( 'as_unschedule_all_actions' ) || ! function_exists( 'as_get_scheduled_actions' ) ) {
361 + return 0;
362 + }
363 +
364 + try {
365 + $pending = as_get_scheduled_actions(
366 + [
367 + 'hook' => $hook,
368 + 'group' => $group,
369 + // ActionScheduler_Store::STATUS_PENDING, without depending on the class being loaded.
370 + 'status' => 'pending',
371 + 'per_page' => -1,
372 + 'orderby' => 'none',
373 + ],
374 + 'ids'
375 + );
376 +
377 + as_unschedule_all_actions( $hook, [], $group );
378 + } catch ( Exception $exception ) {
379 + return 0;
380 + }
381 +
382 + return is_array( $pending ) ? count( $pending ) : 0;
383 + }
384 +
385 + /**
245 386 * Runs the bulk restore for a given context.
246 387 *
247 388 * Restores all optimized media to their original state synchronously.
248 389 * Does not consume API quota since restore is a local file operation.
@@ -451,9 +592,9 @@
451 592 return $result;
452 593 }
453 594 }
454 595
455 - return $process->optimize( $level );
596 + return $process->optimize( $level, [ 'bulk' => true ] );
456 597 }
457 598
458 599 /**
459 600 * Generate next-gen images if they are missing.
@@ -558,8 +699,35 @@
558 699 wp_send_json_error( [ 'message' => $data['message'] ] );
559 700 }
560 701
561 702 wp_send_json_success( [ 'total' => $data['message'] ] );
703 + }
704 +
705 + /**
706 + * Stop the running bulk optimization
707 + *
708 + * @since 2.3
709 + *
710 + * @return void
711 + */
712 + public function bulk_stop_callback() {
713 + imagify_check_nonce( 'imagify-bulk-optimize' );
714 +
715 + $contexts = $this->get_contexts();
716 +
717 + foreach ( $contexts as $context ) {
718 + if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
719 + imagify_die();
720 + }
721 + }
722 +
723 + $data = $this->run_stop( $contexts );
724 +
725 + if ( false === $data['success'] ) {
726 + wp_send_json_error( [ 'message' => $data['message'] ] );
727 + }
728 +
729 + wp_send_json_success( [ 'cancelled' => $data['cancelled'] ] );
562 730 }
563 731
564 732 /**
565 733 * Launch the missing Next-gen versions generation