PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.8
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.8
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / media-optimization / class-media-batch-optimizer.php

class-media-batch-optimizer.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.8, at media-optimization/class-media-batch-optimizer.php

247 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Metasync_Media_Batch_Optimizer
4 * AJAX-driven batch optimizer for converting existing media library images.
5 * Uses browser-driven AJAX chaining for speed, with WP Cron as fallback.
6 *
7 * @package Search Atlas SEO
8 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
9 * @since 2.6.0
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 class Metasync_Media_Batch_Optimizer {
17
18 private const QUEUE_OPTION = 'metasync_batch_optimize_queue';
19 private const PROGRESS_OPTION = 'metasync_batch_optimize_progress';
20 private const CRON_HOOK = 'metasync_media_batch_optimize_cron';
21 private const BATCH_SIZE = 10;
22 private const CRON_TIME_LIMIT = 30; // seconds per cron tick
23
24 /**
25 * Start a new batch optimization run.
26 *
27 * @param array $settings Media optimization settings.
28 * @return array Progress data.
29 */
30 public static function start_batch(array $settings): array {
31 if (self::is_running()) {
32 return self::get_progress();
33 }
34
35 $ids = self::query_unoptimized_ids();
36
37 if (empty($ids)) {
38 return [
39 'total' => 0,
40 'processed' => 0,
41 'failed' => 0,
42 'status' => 'completed',
43 'started_at' => current_time('mysql'),
44 ];
45 }
46
47 update_option(self::QUEUE_OPTION, $ids, false);
48
49 $progress = [
50 'total' => count($ids),
51 'processed' => 0,
52 'failed' => 0,
53 'status' => 'running',
54 'started_at' => current_time('mysql'),
55 ];
56 update_option(self::PROGRESS_OPTION, $progress, false);
57
58 // Store settings for cron fallback to use
59 update_option('metasync_batch_optimize_settings', $settings, false);
60
61 // Schedule cron as fallback (runs if browser tab is closed)
62 if (!wp_next_scheduled(self::CRON_HOOK)) {
63 wp_schedule_event(time() + 120, 'metasync_every_2_minutes', self::CRON_HOOK);
64 }
65
66 return self::get_progress();
67 }
68
69 /**
70 * Cancel a running batch optimization.
71 */
72 public static function cancel_batch(): void {
73 $progress = self::get_progress();
74 $progress['status'] = 'cancelled';
75 update_option(self::PROGRESS_OPTION, $progress, false);
76
77 delete_option(self::QUEUE_OPTION);
78 wp_clear_scheduled_hook(self::CRON_HOOK);
79 }
80
81 /**
82 * Get current batch progress.
83 *
84 * @return array Progress data.
85 */
86 public static function get_progress(): array {
87 $default = [
88 'total' => 0,
89 'processed' => 0,
90 'failed' => 0,
91 'status' => 'idle',
92 'started_at' => '',
93 ];
94
95 return wp_parse_args(get_option(self::PROGRESS_OPTION, []), $default);
96 }
97
98 /**
99 * Check if a batch is currently running.
100 */
101 public static function is_running(): bool {
102 $progress = self::get_progress();
103 return $progress['status'] === 'running';
104 }
105
106 /**
107 * Process one batch via AJAX (browser-driven chaining).
108 * Processes BATCH_SIZE images and returns updated progress immediately.
109 *
110 * @return array Updated progress data.
111 */
112 public static function process_ajax_tick(): array {
113 $progress = self::get_progress();
114
115 if ($progress['status'] !== 'running') {
116 return $progress;
117 }
118
119 $queue = get_option(self::QUEUE_OPTION, []);
120
121 if (empty($queue)) {
122 self::complete_batch();
123 return self::get_progress();
124 }
125
126 $settings = get_option('metasync_batch_optimize_settings', []);
127 $batch = array_splice($queue, 0, self::BATCH_SIZE);
128
129 foreach ($batch as $attachment_id) {
130 // Re-check status in case cancel was triggered mid-batch
131 $current = get_option(self::PROGRESS_OPTION, []);
132 if (($current['status'] ?? '') !== 'running') {
133 break;
134 }
135
136 self::convert_single($attachment_id, $settings, $progress);
137 }
138
139 update_option(self::QUEUE_OPTION, $queue, false);
140 update_option(self::PROGRESS_OPTION, $progress, false);
141
142 if (empty($queue)) {
143 self::complete_batch();
144 return self::get_progress();
145 }
146
147 return $progress;
148 }
149
150 /**
151 * Process batch tick via WP Cron (fallback when browser tab is closed).
152 * Runs a time-limited loop to process as many images as possible within CRON_TIME_LIMIT seconds.
153 */
154 public static function process_batch_tick(): void {
155 $progress = self::get_progress();
156
157 if ($progress['status'] !== 'running') {
158 self::complete_batch();
159 return;
160 }
161
162 $queue = get_option(self::QUEUE_OPTION, []);
163
164 if (empty($queue)) {
165 self::complete_batch();
166 return;
167 }
168
169 $settings = get_option('metasync_batch_optimize_settings', []);
170 $start = time();
171
172 // Process images until time limit or queue empty
173 while (!empty($queue) && (time() - $start) < self::CRON_TIME_LIMIT) {
174 $batch = array_splice($queue, 0, self::BATCH_SIZE);
175
176 foreach ($batch as $attachment_id) {
177 self::convert_single($attachment_id, $settings, $progress);
178 }
179
180 // Save progress after each batch (in case of crash)
181 update_option(self::PROGRESS_OPTION, $progress, false);
182 }
183
184 update_option(self::QUEUE_OPTION, $queue, false);
185
186 if (empty($queue)) {
187 self::complete_batch();
188 }
189 }
190
191 /**
192 * Convert a single attachment with error handling.
193 * Catches fatal errors (e.g. memory_limit) per-image so the batch continues.
194 */
195 private static function convert_single(int $attachment_id, array $settings, array &$progress): void {
196 try {
197 $success = Metasync_Image_Converter::convert_attachment($attachment_id, $settings);
198 $progress['processed']++;
199 if (!$success) {
200 $progress['failed']++;
201 }
202 } catch (\Throwable $e) {
203 $progress['processed']++;
204 $progress['failed']++;
205 error_log('[MetaSync Media Opt] Batch conversion error for attachment ' . $attachment_id . ': ' . $e->getMessage());
206 }
207 }
208
209 /**
210 * Mark batch as completed and clean up.
211 */
212 private static function complete_batch(): void {
213 $progress = self::get_progress();
214 if ($progress['status'] === 'running') {
215 $progress['status'] = 'completed';
216 update_option(self::PROGRESS_OPTION, $progress, false);
217 }
218
219 delete_option(self::QUEUE_OPTION);
220 delete_option('metasync_batch_optimize_settings');
221 wp_clear_scheduled_hook(self::CRON_HOOK);
222 }
223
224 /**
225 * Query all JPEG/PNG attachment IDs that have not been converted.
226 *
227 * @return int[] Attachment IDs.
228 */
229 private static function query_unoptimized_ids(): array {
230 $args = [
231 'post_type' => 'attachment',
232 'post_status' => 'inherit',
233 'post_mime_type' => ['image/jpeg', 'image/png'],
234 'posts_per_page' => -1,
235 'fields' => 'ids',
236 'meta_query' => [
237 [
238 'key' => '_metasync_converted_format',
239 'compare' => 'NOT EXISTS',
240 ],
241 ],
242 ];
243
244 return get_posts($args);
245 }
246 }
247