PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.2.3
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.2.3
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / classes / background-task-manager.php

background-task-manager.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.2.3, at inc/classes/background-task-manager.php

346 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MasterAddons\Inc\Classes;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 /**
10 * Background Task Manager
11 * Wraps Action Scheduler (preferred) with WP-Cron fallback for scheduling
12 * recurring, single, and async tasks. Provides retry logic, logging,
13 * and image optimization queue support.
14 */
15 class Background_Task_Manager
16 {
17 private static $instance = null;
18
19 /** Action Scheduler group for all MA actions */
20 const AS_GROUP = 'jltma_background';
21
22 /** Option key for task log */
23 const LOG_OPTION = 'jltma_background_task_log';
24
25 public function __construct()
26 {
27 add_action('init', [$this, 'init'], 20);
28 }
29
30 public function init()
31 {
32 // Image optimization background handler
33 add_action('jltma_optimize_image_background', [$this, 'handle_image_optimization'], 10, 1);
34
35 // Admin AJAX status endpoint
36 add_action('wp_ajax_jltma_get_background_task_status', [$this, 'ajax_get_status']);
37 }
38
39 /**
40 * Check if Action Scheduler is available
41 */
42 public function is_action_scheduler_available()
43 {
44 return function_exists('as_schedule_recurring_action');
45 }
46
47 /**
48 * Schedule a recurring action
49 *
50 * @param string $hook Action hook name
51 * @param int $interval Interval in seconds
52 * @param array $args Optional arguments
53 * @param bool $unique Whether to skip if already scheduled
54 */
55 public function schedule_recurring($hook, $interval, $args = [], $unique = true)
56 {
57 if ($this->is_action_scheduler_available()) {
58 if ($unique && as_has_scheduled_action($hook, $args, self::AS_GROUP)) {
59 return;
60 }
61 as_schedule_recurring_action(time(), $interval, $hook, $args, self::AS_GROUP);
62 } else {
63 // WP-Cron fallback
64 if ($unique && wp_next_scheduled($hook, $args)) {
65 return;
66 }
67 $recurrence = $this->interval_to_recurrence($interval);
68 wp_schedule_event(time(), $recurrence, $hook, $args);
69 }
70 }
71
72 /**
73 * Schedule a single (one-time) action
74 *
75 * @param string $hook Action hook name
76 * @param array $args Optional arguments
77 * @param int $delay Delay in seconds from now (default 0)
78 */
79 public function schedule_single($hook, $args = [], $delay = 0)
80 {
81 $timestamp = time() + $delay;
82
83 if ($this->is_action_scheduler_available()) {
84 as_schedule_single_action($timestamp, $hook, $args, self::AS_GROUP);
85 } else {
86 wp_schedule_single_event($timestamp, $hook, $args);
87 }
88 }
89
90 /**
91 * Enqueue an async action (runs ASAP in background)
92 *
93 * @param string $hook Action hook name
94 * @param array $args Optional arguments
95 */
96 public function enqueue_async($hook, $args = [])
97 {
98 if ($this->is_action_scheduler_available()) {
99 as_enqueue_async_action($hook, $args, self::AS_GROUP);
100 } else {
101 // WP-Cron fallback: schedule 1 second from now
102 wp_schedule_single_event(time() + 1, $hook, $args);
103 }
104 }
105
106 /**
107 * Unschedule all instances of an action
108 *
109 * @param string $hook Action hook name
110 * @param array $args Optional arguments (must match scheduled args)
111 */
112 public function unschedule($hook, $args = [])
113 {
114 if ($this->is_action_scheduler_available()) {
115 as_unschedule_all_actions($hook, $args, self::AS_GROUP);
116 }
117
118 // Always clear from WP-Cron too (in case of migration)
119 $timestamp = wp_next_scheduled($hook, $args);
120 while ($timestamp) {
121 wp_unschedule_event($timestamp, $hook, $args);
122 $timestamp = wp_next_scheduled($hook, $args);
123 }
124 }
125
126 /**
127 * Execute a callback with exponential backoff retry
128 *
129 * @param callable $callback The function to execute
130 * @param int $max_retries Maximum retry attempts
131 * @param string $task_name Human-readable task name for logging
132 * @return mixed Result of the callback on success
133 * @throws \Exception Re-throws after all retries exhausted
134 */
135 public function execute_with_retry($callback, $max_retries = 3, $task_name = '')
136 {
137 $attempt = 0;
138 $last_error = null;
139
140 while ($attempt < $max_retries) {
141 try {
142 $result = call_user_func($callback);
143 if ($task_name) {
144 $this->log_success($task_name);
145 }
146 return $result;
147 } catch (\Exception $e) {
148 $last_error = $e;
149 $attempt++;
150
151 if ($attempt < $max_retries) {
152 // Exponential backoff: 2s, 4s, 8s...
153 $wait = pow(2, $attempt);
154 sleep($wait);
155 }
156 }
157 }
158
159 if ($task_name && $last_error) {
160 $this->log_failure($task_name, $last_error->getMessage());
161 }
162
163 throw $last_error;
164 }
165
166 /**
167 * Log a successful task execution
168 */
169 public function log_success($task)
170 {
171 $log = get_option(self::LOG_OPTION, []);
172 $log[$task] = array_merge($log[$task] ?? [], [
173 'last_success' => current_time('mysql'),
174 'last_error' => null,
175 ]);
176 update_option(self::LOG_OPTION, $log, false);
177 }
178
179 /**
180 * Log a failed task execution
181 */
182 public function log_failure($task, $error)
183 {
184 $log = get_option(self::LOG_OPTION, []);
185 $log[$task] = array_merge($log[$task] ?? [], [
186 'last_failure' => current_time('mysql'),
187 'last_error' => $error,
188 ]);
189 update_option(self::LOG_OPTION, $log, false);
190 }
191
192 /**
193 * Get status for a single task
194 */
195 public function get_task_status($task)
196 {
197 $log = get_option(self::LOG_OPTION, []);
198 return $log[$task] ?? null;
199 }
200
201 /**
202 * Get status for all tracked tasks
203 */
204 public function get_all_statuses()
205 {
206 return get_option(self::LOG_OPTION, []);
207 }
208
209 /**
210 * Queue a single image for background optimization
211 *
212 * @param int $attachment_id WordPress attachment ID
213 */
214 public function queue_image_optimization($attachment_id)
215 {
216 $this->enqueue_async('jltma_optimize_image_background', [$attachment_id]);
217 }
218
219 /**
220 * Queue multiple images for background optimization
221 *
222 * @param array $attachment_ids Array of attachment IDs
223 */
224 public function queue_bulk_optimization($attachment_ids)
225 {
226 foreach ($attachment_ids as $id) {
227 $this->enqueue_async('jltma_optimize_image_background', [(int) $id]);
228 }
229 }
230
231 /**
232 * Handle background image optimization (action callback)
233 *
234 * @param int $attachment_id
235 */
236 public function handle_image_optimization($attachment_id)
237 {
238 $attachment_id = absint($attachment_id);
239 if (!$attachment_id || !wp_attachment_is_image($attachment_id)) {
240 return;
241 }
242
243 // Check if already optimized
244 $existing = get_post_meta($attachment_id, '_jltma_optimization_data', true);
245 if (!empty($existing) && isset($existing['percentage'])) {
246 return;
247 }
248
249 // Delegate to the Image Optimizer if available
250 $optimizer_class = '\\MasterAddons\\Pro\\Admin\\Image_Optimizer\\Image_Optimizer';
251 if (class_exists($optimizer_class)) {
252 $optimizer = $optimizer_class::get_instance();
253 if (method_exists($optimizer, 'process_background_optimization')) {
254 $optimizer->process_background_optimization($attachment_id);
255 }
256 }
257 }
258
259 /**
260 * Add custom cron intervals
261 *
262 * @param array $schedules Existing WP-Cron schedules
263 * @return array Modified schedules
264 */
265 public function add_cron_intervals($schedules)
266 {
267 if (!isset($schedules['jltma_six_hourly'])) {
268 $schedules['jltma_six_hourly'] = [
269 'interval' => 6 * HOUR_IN_SECONDS,
270 'display' => __('Every 6 Hours (MA)', 'master-addons'),
271 ];
272 }
273 return $schedules;
274 }
275
276 /**
277 * Map seconds to WP-Cron recurrence name
278 *
279 * @param int $seconds Interval in seconds
280 * @return string WP-Cron recurrence identifier
281 */
282 public function interval_to_recurrence($seconds)
283 {
284 if ($seconds <= 6 * HOUR_IN_SECONDS) {
285 return 'jltma_six_hourly';
286 }
287 if ($seconds <= 12 * HOUR_IN_SECONDS) {
288 return 'twicedaily';
289 }
290 return 'daily';
291 }
292
293 /**
294 * AJAX handler: return background task statuses
295 */
296 public function ajax_get_status()
297 {
298 if (!current_user_can('manage_options')) {
299 wp_send_json_error(['message' => __('Permission denied', 'master-addons')]);
300 }
301
302 check_ajax_referer('jltma_background_task_status', '_wpnonce', true);
303
304 $statuses = $this->get_all_statuses();
305 $result = [];
306
307 foreach ($statuses as $task => $info) {
308 $entry = [
309 'last_success' => $info['last_success'] ?? null,
310 'last_failure' => $info['last_failure'] ?? null,
311 'last_error' => $info['last_error'] ?? null,
312 'next_run' => null,
313 'queued_count' => 0,
314 ];
315
316 // Try to get next scheduled run
317 if ($this->is_action_scheduler_available()) {
318 $next = as_next_scheduled_action($task, [], self::AS_GROUP);
319 if ($next) {
320 $entry['next_run'] = gmdate('Y-m-d H:i:s', $next);
321 }
322 } else {
323 $next = wp_next_scheduled($task);
324 if ($next) {
325 $entry['next_run'] = gmdate('Y-m-d H:i:s', $next);
326 }
327 }
328
329 $result[$task] = $entry;
330 }
331
332 wp_send_json_success($result);
333 }
334
335 /**
336 * Get singleton instance
337 */
338 public static function get_instance()
339 {
340 if (self::$instance === null) {
341 self::$instance = new self();
342 }
343 return self::$instance;
344 }
345 }
346