PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 158
Lasso Lite – Affiliate Link Manager & Product Displays v158
158 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 All 57 releases
← All changes | classes/class-cron.php +290 -2 148158 View file →
@@ -6,8 +6,9 @@
6 6 */
7 7
8 8 namespace LassoLite\Classes;
9 9
10 +use LassoLite\Classes\Estimate_Earning;
10 11 use LassoLite\Classes\License;
11 12 use LassoLite\Classes\Processes\Amazon;
12 13 use LassoLite\Classes\Processes\Amazon_Shortlink;
13 14 use LassoLite\Classes\Processes\Import_All;
@@ -31,8 +32,9 @@
31 32 'lasso_lite_cron_get_snippet' => 'daily',
32 33 'lasso_lite_cron_get_js_domain' => 'daily',
33 34 'lasso_lite_cron_get_info' => 'daily',
34 35 'lasso_lite_check_lite_user' => 'daily',
36 + Estimate_Earning::CRON_HOOK => 'weekly',
35 37 );
36 38
37 39 /**
38 40 * Cron constructor.
@@ -48,8 +50,9 @@
48 50 add_action( 'lasso_lite_cron_get_snippet', array( $this, 'lasso_lite_cron_get_snippet' ) );
49 51 add_action( 'lasso_lite_cron_get_js_domain', array( $this, 'lasso_lite_cron_get_js_domain' ) );
50 52 add_action( 'lasso_lite_cron_get_info', array( $this, 'lasso_lite_cron_get_info' ) );
51 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' ) );
52 55 $this->lasso_create_schedule_hook();
53 56 }
54 57
55 58 /**
@@ -62,8 +65,15 @@
62 65 'interval' => 15 * MINUTE_IN_SECONDS, // ? 15 minutes in seconds
63 66 'display' => __( '15 minutes' ),
64 67 );
65 68
69 + if ( ! isset( $schedules['weekly'] ) ) {
70 + $schedules['weekly'] = array(
71 + 'interval' => WEEK_IN_SECONDS,
72 + 'display' => __( 'Once Weekly' ),
73 + );
74 + }
75 +
66 76 return $schedules;
67 77 }
68 78
69 79 /**
@@ -129,8 +139,11 @@
129 139 return;
130 140 }
131 141
132 142 foreach ( $crons_array as $time => $cron ) {
143 + if ( ! is_array( $cron ) ) {
144 + continue;
145 + }
133 146 foreach ( $cron as $hook => $dings ) {
134 147 if ( strpos( $hook, 'lasso_lite_' ) === false ) {
135 148 continue;
136 149 }
@@ -152,18 +165,286 @@
152 165 }
153 166 }
154 167 }
155 168
169 + $load_slot = self::site_load_slot( self::site_schedule_key() );
170 +
156 171 foreach ( $crons as $cron_name => $interval ) {
157 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 +
158 188 if ( ! $next_scheduled ) {
159 - // No schedule exists - create a new one.
160 - wp_schedule_event( time(), $interval, $cron_name );
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 );
161 197 }
162 198 }
163 199 }
164 200
165 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 + $hash = abs( crc32( $key ) );
236 + // Minutes 60–1439 (hour 1–23). UTC hour 0 stays for sites that never
237 + // upgrade so 00Z is not re-occupied by hashed slots.
238 + $minute_of_day = 60 + ( $hash % 1380 );
239 +
240 + return array(
241 + 'minute_of_day' => $minute_of_day,
242 + 'hour' => (int) floor( $minute_of_day / 60 ),
243 + 'minute' => $minute_of_day % 60,
244 + );
245 + }
246 +
247 + /**
248 + * Next daily UTC timestamp at the hashed hour:minute.
249 + *
250 + * @param array $slot site_load_slot().
251 + * @param int $current_time Unix timestamp.
252 + * @return int
253 + */
254 + public static function next_daily_run_ts( $slot, $current_time ) {
255 + $hour = isset( $slot['hour'] ) ? max( 1, min( 23, (int) $slot['hour'] ) ) : 1;
256 + $minute = isset( $slot['minute'] ) ? max( 0, min( 59, (int) $slot['minute'] ) ) : 0;
257 + $today = gmmktime( $hour, $minute, 0 );
258 + if ( $today > $current_time ) {
259 + return $today;
260 + }
261 +
262 + return $today + DAY_IN_SECONDS;
263 + }
264 +
265 + /**
266 + * Next 15-minute UTC timestamp at hashed offset inside the window.
267 + *
268 + * @param array $slot site_load_slot().
269 + * @param int $current_time Unix timestamp.
270 + * @return int
271 + */
272 + public static function next_15m_run_ts( $slot, $current_time ) {
273 + $offset = isset( $slot['minute'] ) ? ( (int) $slot['minute'] % 15 ) : 0;
274 + $cur_min = (int) gmdate( 'i', $current_time );
275 + $hour = (int) gmdate( 'G', $current_time );
276 + $window = $cur_min - ( $cur_min % 15 );
277 + $candidate = gmmktime( $hour, $window + $offset, 0 );
278 + if ( $candidate <= $current_time ) {
279 + $candidate += 15 * MINUTE_IN_SECONDS;
280 + }
281 + // Do not land 15-minute events in UTC hour 0.
282 + while ( 0 === (int) gmdate( 'G', $candidate ) ) {
283 + $candidate += 15 * MINUTE_IN_SECONDS;
284 + }
285 +
286 + return $candidate;
287 + }
288 +
289 + /**
290 + * Whether a scheduled timestamp sits on the daily slot.
291 + *
292 + * @param int $timestamp Unix timestamp.
293 + * @param array $slot site_load_slot().
294 + * @return bool
295 + */
296 + public static function timestamp_matches_daily_slot( $timestamp, $slot ) {
297 + if ( 0 === (int) gmdate( 'G', $timestamp ) ) {
298 + return false;
299 + }
300 +
301 + return (int) gmdate( 'G', $timestamp ) === (int) $slot['hour']
302 + && (int) gmdate( 'i', $timestamp ) === (int) $slot['minute'];
303 + }
304 +
305 + /**
306 + * Whether a 15-minute event sits on the hashed offset.
307 + *
308 + * @param int $timestamp Unix timestamp.
309 + * @param array $slot site_load_slot().
310 + * @return bool
311 + */
312 + public static function timestamp_matches_15m_slot( $timestamp, $slot ) {
313 + if ( 0 === (int) gmdate( 'G', $timestamp ) ) {
314 + return false;
315 + }
316 +
317 + $offset = isset( $slot['minute'] ) ? ( (int) $slot['minute'] % 15 ) : 0;
318 + return ( (int) gmdate( 'i', $timestamp ) % 15 ) === $offset;
319 + }
320 +
321 + /**
322 + * 0–499ms delay so one site's URL list does not hit lasso.link as one burst.
323 + *
324 + * @param string $site_key Site URL.
325 + * @param string $item_key Request URL.
326 + * @return int
327 + */
328 + public static function request_spread_delay_ms( $site_key, $item_key ) {
329 + return abs( crc32( (string) $site_key . "\0" . (string) $item_key ) ) % 500;
330 + }
331 +
332 + /**
333 + * Whether the current request should spread lasso.link API calls.
334 + *
335 + * Cron handlers enqueue background-process work that runs via admin-ajax.php
336 + * where DOING_CRON is false; LASSO_LITE_BACKGROUND_PROCESS marks those workers.
337 + *
338 + * @return bool
339 + */
340 + public static function is_paced_background_context() {
341 + if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
342 + return true;
343 + }
344 +
345 + return defined( 'LASSO_LITE_BACKGROUND_PROCESS' ) && LASSO_LITE_BACKGROUND_PROCESS;
346 + }
347 +
348 + /**
349 + * Sleep only on cron/background API calls. Admin save stays instant.
350 + *
351 + * @param string $item_key Request URL.
352 + * @param bool $is_lasso_save Interactive save.
353 + * @return int Milliseconds slept (0 when skipped).
354 + */
355 + public static function maybe_pace_background_request( $item_key, $is_lasso_save = false ) {
356 + if ( $is_lasso_save ) {
357 + return 0;
358 + }
359 + if ( ! self::is_paced_background_context() ) {
360 + return 0;
361 + }
362 +
363 + $ms = self::request_spread_delay_ms( self::site_schedule_key(), $item_key );
364 + $ms = (int) apply_filters( 'lasso_lite_request_spread_delay_ms', $ms, $item_key );
365 + if ( $ms > 0 && $ms < 500 ) {
366 + usleep( $ms * 1000 );
367 + }
368 +
369 + return $ms;
370 + }
371 +
372 + const OPTION_BLS_LAST_TICK = 'lasso_lite_bls_last_tick';
373 +
374 + /**
375 + * Per-URL minute-of-day due slot (site + url).
376 + *
377 + * @param string $site_key Site URL.
378 + * @param string $item_key Request URL.
379 + * @return int
380 + */
381 + public static function item_due_minute( $site_key, $item_key ) {
382 + return abs( crc32( (string) $site_key . "\0" . (string) $item_key ) ) % 1440;
383 + }
384 +
385 + /**
386 + * Late = due more than an hour ago (skip the line vs current-hour items).
387 + *
388 + * @param int $due_minute Minute of day.
389 + * @param int $now Unix timestamp.
390 + * @return bool
391 + */
392 + public static function is_late_data_request( $due_minute, $now ) {
393 + $now_minute = ( (int) gmdate( 'G', $now ) * 60 ) + (int) gmdate( 'i', $now );
394 + $age = ( $now_minute - (int) $due_minute + 1440 ) % 1440;
395 + return $age > 60;
396 + }
397 +
398 + /**
399 + * Hourly due window: current hour plus late catch-up (2h, or 3h in UTC hour 1).
400 + *
401 + * 15-minute crons skip UTC hour 0, so the first tick after midnight is ~01:xx.
402 + * A 2h lookback from 01:xx starts ~23:xx and drops the 22:xx band that a 00:xx
403 + * tick used to catch (WP-Cron often misses the last evening window).
404 + *
405 + * @param string $item_key Request URL.
406 + * @param int|null $now Unix timestamp.
407 + * @param int|null $last_tick Previous tick (0 = first run).
408 + * @param int|null $due_minute Injected slot for tests.
409 + * @return bool
410 + */
411 + public static function should_send_scheduled_data_request( $item_key, $now = null, $last_tick = null, $due_minute = null ) {
412 + $now = null === $now ? time() : (int) $now;
413 + if ( null === $last_tick ) {
414 + $last_tick = (int) get_option( self::OPTION_BLS_LAST_TICK, 0 );
415 + }
416 + if ( $last_tick <= 0 ) {
417 + $last_tick = $now - HOUR_IN_SECONDS;
418 + }
419 +
420 + $lookback = ( 1 === (int) gmdate( 'G', $now ) ) ? ( 3 * HOUR_IN_SECONDS ) : ( 2 * HOUR_IN_SECONDS );
421 + $window_start = max( $last_tick - 300, $now - $lookback );
422 + if ( null === $due_minute ) {
423 + $due_minute = self::item_due_minute( self::site_schedule_key(), $item_key );
424 + }
425 +
426 + $midnight = gmmktime( 0, 0, 0, (int) gmdate( 'n', $now ), (int) gmdate( 'j', $now ), (int) gmdate( 'Y', $now ) );
427 + $due_today = $midnight + ( (int) $due_minute * 60 );
428 + if ( $due_today <= $now ) {
429 + return $due_today > $window_start;
430 + }
431 +
432 + // Due later today: still catch yesterday's occurrence inside the lookback
433 + // window (WP-Cron often fires after UTC midnight and would otherwise skip 22:00–23:59).
434 + return ( $due_today - DAY_IN_SECONDS ) > $window_start;
435 + }
436 +
437 + /**
438 + * Close this hour's window so the next tick only opens new + late-capped work.
439 + *
440 + * @param int|null $now Unix timestamp.
441 + */
442 + public static function advance_data_request_tick( $now = null ) {
443 + update_option( self::OPTION_BLS_LAST_TICK, null === $now ? time() : (int) $now, false );
444 + }
445 +
446 + /**
166 447 * Tracking support status
167 448 */
168 449 public function lasso_lite_tracking_support_status() {
169 450 $settings = Setting::get_settings();
@@ -314,6 +595,13 @@
314 595 return 200 === $status_code;
315 596 } catch ( \Exception $e ) {
316 597 return false;
317 598 }
599 + }
600 +
601 + /**
602 + * Weekly: refresh cached orphan Affiliate+ payout estimate for the weekly banner.
603 + */
604 + public function lasso_lite_weekly_estimate_earning() {
605 + Estimate_Earning::fetch_and_cache();
318 606 }
319 607 }