PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 155
Lasso Lite – Affiliate Link Manager & Product Displays v155
157 155 156 154 153 152 151 150 149 148 trunk 0.9.9 104 105 106 107 108 109 110 111 112 113 114 115 116 All 56 releases
simple-urls / classes / class-cron.php

class-cron.php in Lasso Lite – Affiliate Link Manager & Product Displays 155, at classes/class-cron.php

588 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Declare class Config
4 *
5 * @package Config
6 */
7
8 namespace LassoLite\Classes;
9
10 use LassoLite\Classes\Estimate_Earning;
11 use LassoLite\Classes\License;
12 use LassoLite\Classes\Processes\Amazon;
13 use LassoLite\Classes\Processes\Amazon_Shortlink;
14 use LassoLite\Classes\Processes\Import_All;
15 use LassoLite\Classes\Processes\Revert_All;
16 use LassoLite\Classes\Setting;
17 use LassoLite\Classes\Enum;
18 use LassoLite\Admin\Constant;
19
20 /**
21 * Config
22 */
23 class Cron {
24
25 const CRONS = array(
26 'lasso_lite_amazon_shortlink' => 'lasso_lite_15_minutes',
27 'lasso_lite_update_amazon' => 'lasso_lite_15_minutes',
28 'lasso_lite_import_all' => 'lasso_lite_15_minutes',
29 'lasso_lite_revert_all' => 'lasso_lite_15_minutes',
30 'lasso_lite_tracking_support_status' => 'daily',
31 'lasso_lite_update_license_status' => 'daily',
32 'lasso_lite_cron_get_snippet' => 'daily',
33 'lasso_lite_cron_get_js_domain' => 'daily',
34 'lasso_lite_cron_get_info' => 'daily',
35 'lasso_lite_check_lite_user' => 'daily',
36 Estimate_Earning::CRON_HOOK => 'weekly',
37 );
38
39 /**
40 * Cron constructor.
41 */
42 public function register_hooks() {
43 add_filter( 'cron_schedules', array( $this, 'add_lasso_cron' ) );
44 add_action( 'lasso_lite_tracking_support_status', array( $this, 'lasso_lite_tracking_support_status' ) );
45 add_action( 'lasso_lite_import_all', array( $this, 'lasso_import_all' ) );
46 add_action( 'lasso_lite_revert_all', array( $this, 'lasso_revert_all' ) );
47 add_action( 'lasso_lite_update_amazon', array( $this, 'lasso_lite_update_amazon' ) );
48 add_action( 'lasso_lite_amazon_shortlink', array( $this, 'lasso_lite_amazon_shortlink' ) );
49 add_action( 'lasso_lite_update_license_status', array( $this, 'lasso_lite_update_license_status' ) );
50 add_action( 'lasso_lite_cron_get_snippet', array( $this, 'lasso_lite_cron_get_snippet' ) );
51 add_action( 'lasso_lite_cron_get_js_domain', array( $this, 'lasso_lite_cron_get_js_domain' ) );
52 add_action( 'lasso_lite_cron_get_info', array( $this, 'lasso_lite_cron_get_info' ) );
53 add_action( 'lasso_lite_check_lite_user', array( $this, 'lasso_lite_check_lite_user' ) );
54 add_action( Estimate_Earning::CRON_HOOK, array( $this, 'lasso_lite_weekly_estimate_earning' ) );
55 $this->lasso_create_schedule_hook();
56 }
57
58 /**
59 * Add a custom cron to WordPress
60 *
61 * @param array $schedules An array of non-default cron schedules. Default empty.
62 */
63 public function add_lasso_cron( $schedules ) {
64 $schedules['lasso_lite_15_minutes'] = array(
65 'interval' => 15 * MINUTE_IN_SECONDS, // ? 15 minutes in seconds
66 'display' => __( '15 minutes' ),
67 );
68
69 if ( ! isset( $schedules['weekly'] ) ) {
70 $schedules['weekly'] = array(
71 'interval' => WEEK_IN_SECONDS,
72 'display' => __( 'Once Weekly' ),
73 );
74 }
75
76 return $schedules;
77 }
78
79 /**
80 * Upgrade the stored cron option structure to version 2 (hash-keyed args).
81 *
82 * Mirrors WordPress core cron array shape so iteration in this class matches
83 * `wp_next_scheduled` / `wp_schedule_event` expectations.
84 *
85 * @param array $cron Cron info array from lasso_get_stored_cron_array().
86 * @return array Upgraded cron info array.
87 */
88 private function lasso_upgrade_stored_cron_array( $cron ) {
89 if ( isset( $cron['version'] ) && 2 === $cron['version'] ) {
90 return $cron;
91 }
92
93 $new_cron = array();
94
95 foreach ( (array) $cron as $timestamp => $hooks ) {
96 foreach ( (array) $hooks as $hook => $args ) {
97 $key = md5( serialize( $args['args'] ) );
98
99 $new_cron[ $timestamp ][ $hook ][ $key ] = $args;
100 }
101 }
102
103 $new_cron['version'] = 2;
104
105 update_option( 'cron', $new_cron );
106
107 return $new_cron;
108 }
109
110 /**
111 * Load the `cron` option as an array, upgrading legacy shape when needed.
112 *
113 * @return array Cron events (no `version` key in the returned array).
114 */
115 private function lasso_get_stored_cron_array() {
116 $cron = get_option( 'cron' );
117 if ( ! is_array( $cron ) ) {
118 return array();
119 }
120
121 if ( ! isset( $cron['version'] ) ) {
122 $cron = $this->lasso_upgrade_stored_cron_array( $cron );
123 }
124
125 unset( $cron['version'] );
126
127 return $cron;
128 }
129
130 /**
131 * Create hook for the new cron
132 */
133 public function lasso_create_schedule_hook() {
134 $crons = self::CRONS;
135 $events = array();
136 $crons_array = $this->lasso_get_stored_cron_array();
137
138 if ( ! is_array( $crons_array ) ) {
139 return;
140 }
141
142 foreach ( $crons_array as $time => $cron ) {
143 if ( ! is_array( $cron ) ) {
144 continue;
145 }
146 foreach ( $cron as $hook => $dings ) {
147 if ( strpos( $hook, 'lasso_lite_' ) === false ) {
148 continue;
149 }
150
151 foreach ( $dings as $sig => $data ) {
152 $interval = $data['interval'] ?? HOUR_IN_SECONDS;
153
154 // ? get the cron that is less than the existing one
155 if ( isset( $events[ $hook ] ) && $interval >= $events[ $hook ]->interval ) {
156 continue;
157 }
158
159 $events[ $hook ] = (object) array(
160 'hook' => $hook,
161 'time' => $time, // ? UTC
162 'schedule' => $data['schedule'],
163 'interval' => $interval,
164 );
165 }
166 }
167 }
168
169 $load_slot = self::site_load_slot( self::site_schedule_key() );
170
171 foreach ( $crons as $cron_name => $interval ) {
172 $next_scheduled = wp_next_scheduled( $cron_name );
173 $is_daily = ( 'daily' === $interval );
174 $is_15m = ( 'lasso_lite_15_minutes' === $interval );
175 $is_weekly = ( 'weekly' === $interval );
176
177 if ( $next_scheduled && $is_daily && ! self::timestamp_matches_daily_slot( $next_scheduled, $load_slot ) ) {
178 wp_clear_scheduled_hook( $cron_name );
179 $next_scheduled = false;
180 } elseif ( $next_scheduled && $is_15m && ! self::timestamp_matches_15m_slot( $next_scheduled, $load_slot ) ) {
181 wp_clear_scheduled_hook( $cron_name );
182 $next_scheduled = false;
183 } elseif ( $next_scheduled && $is_weekly && ! self::timestamp_matches_daily_slot( $next_scheduled, $load_slot ) ) {
184 wp_clear_scheduled_hook( $cron_name );
185 $next_scheduled = false;
186 }
187
188 if ( ! $next_scheduled ) {
189 $current_time = time();
190 $next_run_time = $current_time;
191 if ( $is_daily || $is_weekly ) {
192 $next_run_time = self::next_daily_run_ts( $load_slot, $current_time );
193 } elseif ( $is_15m ) {
194 $next_run_time = self::next_15m_run_ts( $load_slot, $current_time );
195 }
196 wp_schedule_event( $next_run_time, $interval, $cron_name );
197 }
198 }
199 }
200
201 /**
202 * Stable key for load-slot hashing. Prefer site URL.
203 *
204 * @return string
205 */
206 public static function site_schedule_key() {
207 $url = '';
208 if ( function_exists( 'home_url' ) ) {
209 $url = (string) home_url();
210 }
211 if ( function_exists( 'untrailingslashit' ) ) {
212 $url = untrailingslashit( strtolower( trim( $url ) ) );
213 } else {
214 $url = strtolower( trim( $url ) );
215 }
216 if ( '' !== $url ) {
217 return $url;
218 }
219
220 return 'lasso-missing-site-key';
221 }
222
223 /**
224 * Deterministic minute-of-day slot from a unique site key.
225 *
226 * @param string $site_key home_url.
227 * @return array
228 */
229 public static function site_load_slot( $site_key ) {
230 $key = (string) $site_key;
231 if ( '' === $key ) {
232 $key = 'lasso-missing-site-key';
233 }
234
235 $minute_of_day = abs( crc32( $key ) ) % 1440;
236
237 return array(
238 'minute_of_day' => $minute_of_day,
239 'hour' => (int) floor( $minute_of_day / 60 ),
240 'minute' => $minute_of_day % 60,
241 );
242 }
243
244 /**
245 * Next daily UTC timestamp at the hashed hour:minute.
246 *
247 * @param array $slot site_load_slot().
248 * @param int $current_time Unix timestamp.
249 * @return int
250 */
251 public static function next_daily_run_ts( $slot, $current_time ) {
252 $hour = isset( $slot['hour'] ) ? max( 0, min( 23, (int) $slot['hour'] ) ) : 0;
253 $minute = isset( $slot['minute'] ) ? max( 0, min( 59, (int) $slot['minute'] ) ) : 0;
254 $today = gmmktime( $hour, $minute, 0 );
255 if ( $today > $current_time ) {
256 return $today;
257 }
258
259 return $today + DAY_IN_SECONDS;
260 }
261
262 /**
263 * Next 15-minute UTC timestamp at hashed offset inside the window.
264 *
265 * @param array $slot site_load_slot().
266 * @param int $current_time Unix timestamp.
267 * @return int
268 */
269 public static function next_15m_run_ts( $slot, $current_time ) {
270 $offset = isset( $slot['minute'] ) ? ( (int) $slot['minute'] % 15 ) : 0;
271 $cur_min = (int) gmdate( 'i', $current_time );
272 $hour = (int) gmdate( 'G', $current_time );
273 $window = $cur_min - ( $cur_min % 15 );
274 $candidate = gmmktime( $hour, $window + $offset, 0 );
275 if ( $candidate > $current_time ) {
276 return $candidate;
277 }
278
279 return $candidate + ( 15 * MINUTE_IN_SECONDS );
280 }
281
282 /**
283 * Whether a scheduled timestamp sits on the daily slot.
284 *
285 * @param int $timestamp Unix timestamp.
286 * @param array $slot site_load_slot().
287 * @return bool
288 */
289 public static function timestamp_matches_daily_slot( $timestamp, $slot ) {
290 return (int) gmdate( 'G', $timestamp ) === (int) $slot['hour']
291 && (int) gmdate( 'i', $timestamp ) === (int) $slot['minute'];
292 }
293
294 /**
295 * Whether a 15-minute event sits on the hashed offset.
296 *
297 * @param int $timestamp Unix timestamp.
298 * @param array $slot site_load_slot().
299 * @return bool
300 */
301 public static function timestamp_matches_15m_slot( $timestamp, $slot ) {
302 $offset = isset( $slot['minute'] ) ? ( (int) $slot['minute'] % 15 ) : 0;
303 return ( (int) gmdate( 'i', $timestamp ) % 15 ) === $offset;
304 }
305
306 /**
307 * 0–499ms delay so one site's URL list does not hit lasso.link as one burst.
308 *
309 * @param string $site_key Site URL.
310 * @param string $item_key Request URL.
311 * @return int
312 */
313 public static function request_spread_delay_ms( $site_key, $item_key ) {
314 return abs( crc32( (string) $site_key . "\0" . (string) $item_key ) ) % 500;
315 }
316
317 /**
318 * Whether the current request should spread lasso.link API calls.
319 *
320 * Cron handlers enqueue background-process work that runs via admin-ajax.php
321 * where DOING_CRON is false; LASSO_LITE_BACKGROUND_PROCESS marks those workers.
322 *
323 * @return bool
324 */
325 public static function is_paced_background_context() {
326 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
327 return true;
328 }
329
330 return defined( 'LASSO_LITE_BACKGROUND_PROCESS' ) && LASSO_LITE_BACKGROUND_PROCESS;
331 }
332
333 /**
334 * Sleep only on cron/background API calls. Admin save stays instant.
335 *
336 * @param string $item_key Request URL.
337 * @param bool $is_lasso_save Interactive save.
338 * @return int Milliseconds slept (0 when skipped).
339 */
340 public static function maybe_pace_background_request( $item_key, $is_lasso_save = false ) {
341 if ( $is_lasso_save ) {
342 return 0;
343 }
344 if ( ! self::is_paced_background_context() ) {
345 return 0;
346 }
347
348 $ms = self::request_spread_delay_ms( self::site_schedule_key(), $item_key );
349 $ms = (int) apply_filters( 'lasso_lite_request_spread_delay_ms', $ms, $item_key );
350 if ( $ms > 0 && $ms < 500 ) {
351 usleep( $ms * 1000 );
352 }
353
354 return $ms;
355 }
356
357 const OPTION_BLS_LAST_TICK = 'lasso_lite_bls_last_tick';
358
359 /**
360 * Per-URL minute-of-day due slot (site + url).
361 *
362 * @param string $site_key Site URL.
363 * @param string $item_key Request URL.
364 * @return int
365 */
366 public static function item_due_minute( $site_key, $item_key ) {
367 return abs( crc32( (string) $site_key . "\0" . (string) $item_key ) ) % 1440;
368 }
369
370 /**
371 * Late = due more than an hour ago (skip the line vs current-hour items).
372 *
373 * @param int $due_minute Minute of day.
374 * @param int $now Unix timestamp.
375 * @return bool
376 */
377 public static function is_late_data_request( $due_minute, $now ) {
378 $now_minute = ( (int) gmdate( 'G', $now ) * 60 ) + (int) gmdate( 'i', $now );
379 $age = ( $now_minute - (int) $due_minute + 1440 ) % 1440;
380 return $age > 60;
381 }
382
383 /**
384 * Hourly due window: current hour plus up to 2h of late catch-up.
385 *
386 * @param string $item_key Request URL.
387 * @param int|null $now Unix timestamp.
388 * @param int|null $last_tick Previous tick (0 = first run).
389 * @param int|null $due_minute Injected slot for tests.
390 * @return bool
391 */
392 public static function should_send_scheduled_data_request( $item_key, $now = null, $last_tick = null, $due_minute = null ) {
393 $now = null === $now ? time() : (int) $now;
394 if ( null === $last_tick ) {
395 $last_tick = (int) get_option( self::OPTION_BLS_LAST_TICK, 0 );
396 }
397 if ( $last_tick <= 0 ) {
398 $last_tick = $now - HOUR_IN_SECONDS;
399 }
400
401 $window_start = max( $last_tick - 300, $now - ( 2 * HOUR_IN_SECONDS ) );
402 if ( null === $due_minute ) {
403 $due_minute = self::item_due_minute( self::site_schedule_key(), $item_key );
404 }
405
406 $midnight = gmmktime( 0, 0, 0, (int) gmdate( 'n', $now ), (int) gmdate( 'j', $now ), (int) gmdate( 'Y', $now ) );
407 $due_today = $midnight + ( (int) $due_minute * 60 );
408 if ( $due_today <= $now ) {
409 return $due_today > $window_start;
410 }
411
412 // Due later today: still catch yesterday's occurrence inside the 2h window
413 // (WP-Cron often fires after UTC midnight and would otherwise skip 22:00–23:59).
414 return ( $due_today - DAY_IN_SECONDS ) > $window_start;
415 }
416
417 /**
418 * Close this hour's window so the next tick only opens new + late-capped work.
419 *
420 * @param int|null $now Unix timestamp.
421 */
422 public static function advance_data_request_tick( $now = null ) {
423 update_option( self::OPTION_BLS_LAST_TICK, null === $now ? time() : (int) $now, false );
424 }
425
426 /**
427 * Tracking support status
428 */
429 public function lasso_lite_tracking_support_status() {
430 $settings = Setting::get_settings();
431 if ( boolval( $settings[ Enum::SUPPORT_ENABLED ] ) ) {
432 Setting::save_support( false );
433 }
434 }
435
436 /**
437 * Import all
438 */
439 public function lasso_import_all() {
440 $allow_import_all = get_option( Import_All::OPTION, '0' );
441 if ( 1 === intval( $allow_import_all ) ) {
442 $lasso_import_all = new Import_All();
443 $lasso_import_all->import();
444 }
445 }
446
447 /**
448 * Revert all
449 */
450 public function lasso_revert_all() {
451 $allow_revert_all = get_option( Revert_All::OPTION, '0' );
452 if ( 1 === intval( $allow_revert_all ) ) {
453 $lasso_import_all = new Revert_All();
454 $lasso_import_all->revert();
455 }
456 }
457
458 /**
459 * Revert all
460 */
461 public function lasso_lite_update_amazon() {
462 $settings = Setting::get_settings();
463 if ( boolval( $settings['amazon_pricing_daily'] ) ) {
464 $lasso_amazon = new Amazon();
465 $lasso_amazon->run();
466 }
467 }
468
469 /**
470 * Revert all
471 */
472 public function lasso_lite_amazon_shortlink() {
473 $settings = Setting::get_settings();
474 if ( boolval( $settings['amazon_pricing_daily'] ) ) {
475 $lasso_amazon = new Amazon_Shortlink();
476 $lasso_amazon->run();
477 }
478 }
479
480 /**
481 * Update license status.
482 */
483 public function lasso_lite_update_license_status() {
484 License::check_user_license();
485 }
486
487 /**
488 * Daily update snippet: Fetch snippet performance and write to connect-snippet.min.js
489 */
490 public function lasso_lite_cron_get_snippet() {
491 try {
492 $url = Constant::LASSO_LINK . '/api/snippet/performance?ver=' . time();
493 $res = Helper::send_request( 'get', $url );
494
495 $status_code = isset( $res['status_code'] ) ? intval( $res['status_code'] ) : 0;
496 $body = isset( $res['response'] ) ? ( $res['response']->content ?? '' ) : '';
497 $body_str = is_string( $body ) ? $body : wp_json_encode( $body );
498
499 if ( 200 === $status_code && ! empty( $body_str ) && strpos( $body_str, 'LASSO_REDIRECT_AMAZON_URL,' ) !== false ) {
500 $file_path = LASSO_CONNECT_SNIPPET_FILE_LITE;
501 $result = file_put_contents( $file_path, (string) $body_str );
502 if ( false === $result ) {
503 return false;
504 }
505 }
506 return true;
507 } catch ( \Exception $e ) {
508 return false;
509 }
510 }
511
512 /**
513 * Fetches the JS domain URL from the remote API and updates the 'js_domain' option if valid.
514 *
515 * @return string Returns an empty string. Returns early on exception or after processing.
516 * @throws \Exception This method catches all exceptions internally and does not throw.
517 */
518 public function lasso_lite_cron_get_js_domain() {
519 try {
520 $url = Constant::LASSO_LINK . '/api/js-domain?ver=' . time();
521 $res = Helper::send_request( 'get', $url );
522 $status_code = intval( $res['status_code'] ?? 500 );
523 $response = $res['response'] ?? '';
524
525 if ( 200 === $status_code && $response ) {
526 // API returns JSON object that contains a URL to the JS file
527 $file_url = $response->url ?? '';
528 // Only return the URL. Do not fetch or write files here.
529 if ( ! empty( $file_url ) && Helper::validate_url( $file_url ) ) {
530 Helper::update_option( 'js_domain', $file_url );
531 }
532
533 $full_file_url = $response->full_url ?? '';
534 if ( ! empty( $full_file_url ) && Helper::validate_url( $full_file_url ) ) {
535 Helper::update_option( 'full_js_domain', $full_file_url );
536 }
537 }
538 } catch ( \Exception $e ) {
539 return '';
540 }
541
542 return '';
543 }
544
545 /**
546 * Get info
547 */
548 public function lasso_lite_cron_get_info() {
549 try {
550 License::lasso_getinfo(['license_key']);
551 } catch ( \Exception $e ) {
552 return false;
553 }
554
555 return true;
556 }
557
558 /**
559 * Daily: request lite user record for this site's admin email.
560 *
561 * @return bool True when the HTTP request completes with 200.
562 */
563 public function lasso_lite_check_lite_user() {
564 try {
565 $admin_email = get_option( 'admin_email' );
566 if ( empty( $admin_email ) || ! is_email( $admin_email ) ) {
567 return false;
568 }
569
570 $url = Constant::LASSO_LINK . '/plugin/lite/users/' . rawurlencode( $admin_email );
571 $headers = Helper::get_headers();
572 $res = Helper::send_request( 'get', $url, array(), $headers );
573
574 $status_code = isset( $res['status_code'] ) ? intval( $res['status_code'] ) : 0;
575 return 200 === $status_code;
576 } catch ( \Exception $e ) {
577 return false;
578 }
579 }
580
581 /**
582 * Weekly: refresh cached orphan Affiliate+ payout estimate for the weekly banner.
583 */
584 public function lasso_lite_weekly_estimate_earning() {
585 Estimate_Earning::fetch_and_cache();
586 }
587 }
588