| 1 | <?php |
|---|
| 2 | /** |
|---|
| 3 | * The cron task class. |
|---|
| 4 | * |
|---|
| 5 | * @since 1.1.3 |
|---|
| 6 | * @package LiteSpeed |
|---|
| 7 | */ |
|---|
| 8 | |
|---|
| 9 | namespace LiteSpeed; |
|---|
| 10 | |
|---|
| 11 | defined( 'WPINC' ) || exit(); |
|---|
| 12 | |
|---|
| 13 | /** |
|---|
| 14 | * Schedules and runs LiteSpeed Cache background tasks. |
|---|
| 15 | */ |
|---|
| 16 | class Task extends Root { |
|---|
| 17 | |
|---|
| 18 | /** |
|---|
| 19 | * Tag for debug logs. |
|---|
| 20 | * |
|---|
| 21 | * @var string |
|---|
| 22 | */ |
|---|
| 23 | const LOG_TAG = '⏰'; |
|---|
| 24 | |
|---|
| 25 | /** |
|---|
| 26 | * Map of option id => cron hook registration. |
|---|
| 27 | * |
|---|
| 28 | * @var array<string,array{name:string,hook:callable|string}> |
|---|
| 29 | */ |
|---|
| 30 | private static $_triggers = [ |
|---|
| 31 | Base::O_IMG_OPTM_CRON => [ |
|---|
| 32 | 'name' => 'litespeed_task_imgoptm_pull', |
|---|
| 33 | 'hook' => 'LiteSpeed\Img_Optm::start_async_cron', |
|---|
| 34 | ], // always fetch immediately |
|---|
| 35 | Base::O_OPTM_CSS_ASYNC => [ |
|---|
| 36 | 'name' => 'litespeed_task_ccss', |
|---|
| 37 | 'hook' => 'LiteSpeed\CSS::cron_ccss', |
|---|
| 38 | ], |
|---|
| 39 | Base::O_OPTM_UCSS => [ |
|---|
| 40 | 'name' => 'litespeed_task_ucss', |
|---|
| 41 | 'hook' => 'LiteSpeed\UCSS::cron', |
|---|
| 42 | ], |
|---|
| 43 | Base::O_MEDIA_VPI_CRON => [ |
|---|
| 44 | 'name' => 'litespeed_task_vpi', |
|---|
| 45 | 'hook' => 'LiteSpeed\VPI::cron', |
|---|
| 46 | ], |
|---|
| 47 | Base::O_MEDIA_PLACEHOLDER_RESP_ASYNC => [ |
|---|
| 48 | 'name' => 'litespeed_task_lqip', |
|---|
| 49 | 'hook' => 'LiteSpeed\Placeholder::cron', |
|---|
| 50 | ], |
|---|
| 51 | Base::O_DISCUSS_AVATAR_CRON => [ |
|---|
| 52 | 'name' => 'litespeed_task_avatar', |
|---|
| 53 | 'hook' => 'LiteSpeed\Avatar::cron', |
|---|
| 54 | ], |
|---|
| 55 | Base::O_IMG_OPTM_AUTO => [ |
|---|
| 56 | 'name' => 'litespeed_task_imgoptm_req', |
|---|
| 57 | 'hook' => 'LiteSpeed\Img_Optm::cron_auto_request', |
|---|
| 58 | ], |
|---|
| 59 | Base::O_GUEST => [ |
|---|
| 60 | 'name' => 'litespeed_task_guest_sync', |
|---|
| 61 | 'hook' => 'LiteSpeed\Guest::cron', |
|---|
| 62 | ], // Daily sync Guest Mode IP/UA lists |
|---|
| 63 | Base::O_CRAWLER => [ |
|---|
| 64 | 'name' => 'litespeed_task_crawler', |
|---|
| 65 | 'hook' => 'LiteSpeed\Crawler::start_async_cron', |
|---|
| 66 | ], // Set crawler to last one to use above results |
|---|
| 67 | ]; |
|---|
| 68 | |
|---|
| 69 | /** |
|---|
| 70 | * Options allowed to run for guest optimization. |
|---|
| 71 | * |
|---|
| 72 | * @var array<int,string> |
|---|
| 73 | */ |
|---|
| 74 | private static $_guest_options = [ Base::O_OPTM_CSS_ASYNC, Base::O_OPTM_UCSS, Base::O_MEDIA_VPI ]; |
|---|
| 75 | |
|---|
| 76 | /** |
|---|
| 77 | * Schedule id for crawler. |
|---|
| 78 | * |
|---|
| 79 | * @var string |
|---|
| 80 | */ |
|---|
| 81 | const FILTER_CRAWLER = 'litespeed_crawl_filter'; |
|---|
| 82 | |
|---|
| 83 | /** |
|---|
| 84 | * Schedule id for general tasks. |
|---|
| 85 | * |
|---|
| 86 | * @var string |
|---|
| 87 | */ |
|---|
| 88 | const FILTER = 'litespeed_filter'; |
|---|
| 89 | |
|---|
| 90 | /** |
|---|
| 91 | * Keep all tasks in cron. |
|---|
| 92 | * |
|---|
| 93 | * @since 3.0 |
|---|
| 94 | * @access public |
|---|
| 95 | * @return void |
|---|
| 96 | */ |
|---|
| 97 | public function init() { |
|---|
| 98 | self::debug2( 'Init' ); |
|---|
| 99 | add_filter( 'cron_schedules', [ $this, 'lscache_cron_filter' ] ); |
|---|
| 100 | |
|---|
| 101 | $guest_optm = $this->conf( Base::O_GUEST ) && $this->conf( Base::O_GUEST_OPTM ); |
|---|
| 102 | |
|---|
| 103 | foreach ( self::$_triggers as $id => $trigger ) { |
|---|
| 104 | if ( Base::O_IMG_OPTM_CRON === $id ) { |
|---|
| 105 | if ( ! Img_Optm::need_pull() ) { |
|---|
| 106 | continue; |
|---|
| 107 | } |
|---|
| 108 | } elseif ( ! $this->conf( $id ) ) { |
|---|
| 109 | if ( ! $guest_optm || ! in_array( $id, self::$_guest_options, true ) ) { |
|---|
| 110 | continue; |
|---|
| 111 | } |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | // Special check for crawler. |
|---|
| 115 | if ( Base::O_CRAWLER === $id ) { |
|---|
| 116 | if ( ! Router::can_crawl() ) { |
|---|
| 117 | continue; |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | add_filter( 'cron_schedules', [ $this, 'lscache_cron_filter_crawler' ] ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | if ( ! wp_next_scheduled( $trigger['name'] ) ) { |
|---|
| 124 | self::debug( 'Cron hook register [name] ' . $trigger['name'] ); |
|---|
| 125 | |
|---|
| 126 | // Determine schedule: crawler uses its own, guest uses daily, others use 15min |
|---|
| 127 | if ( Base::O_CRAWLER === $id ) { |
|---|
| 128 | $schedule = self::FILTER_CRAWLER; |
|---|
| 129 | } elseif ( Base::O_GUEST === $id ) { |
|---|
| 130 | $schedule = 'daily'; |
|---|
| 131 | } else { |
|---|
| 132 | $schedule = self::FILTER; |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | wp_schedule_event( time(), $schedule, $trigger['name'] ); |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | add_action( $trigger['name'], $trigger['hook'] ); |
|---|
| 139 | } |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | /** |
|---|
| 143 | * Handle all async noabort requests. |
|---|
| 144 | * |
|---|
| 145 | * @since 5.5 |
|---|
| 146 | * @return void |
|---|
| 147 | */ |
|---|
| 148 | public static function async_litespeed_handler() { |
|---|
| 149 | $hash_data = self::get_option( 'async_call-hash', [] ); |
|---|
| 150 | if ( ! $hash_data || ! is_array( $hash_data ) || empty( $hash_data['hash'] ) || empty( $hash_data['ts'] ) ) { |
|---|
| 151 | self::debug( 'async_litespeed_handler no hash data', $hash_data ); |
|---|
| 152 | return; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | $nonce = isset( $_GET['nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
|---|
| 156 | if ( 120 < time() - (int) $hash_data['ts'] || '' === $nonce || $nonce !== $hash_data['hash'] ) { |
|---|
| 157 | self::debug( 'async_litespeed_handler nonce mismatch' ); |
|---|
| 158 | return; |
|---|
| 159 | } |
|---|
| 160 | self::delete_option( 'async_call-hash' ); |
|---|
| 161 | |
|---|
| 162 | $type = Router::verify_type(); |
|---|
| 163 | self::debug( 'type=' . $type ); |
|---|
| 164 | |
|---|
| 165 | // Don't lock up other requests while processing. |
|---|
| 166 | session_write_close(); |
|---|
| 167 | |
|---|
| 168 | switch ( $type ) { |
|---|
| 169 | case 'crawler': |
|---|
| 170 | Crawler::async_handler(); |
|---|
| 171 | break; |
|---|
| 172 | case 'crawler_force': |
|---|
| 173 | Crawler::async_handler( true ); |
|---|
| 174 | break; |
|---|
| 175 | case 'imgoptm': |
|---|
| 176 | Img_Optm::async_handler(); |
|---|
| 177 | break; |
|---|
| 178 | case 'imgoptm_force': |
|---|
| 179 | Img_Optm::async_handler( true ); |
|---|
| 180 | break; |
|---|
| 181 | default: |
|---|
| 182 | break; |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | /** |
|---|
| 187 | * Async caller wrapper func. |
|---|
| 188 | * |
|---|
| 189 | * @since 5.5 |
|---|
| 190 | * |
|---|
| 191 | * @param string $type Async operation type. |
|---|
| 192 | * @return void |
|---|
| 193 | */ |
|---|
| 194 | public static function async_call( $type ) { |
|---|
| 195 | $hash = Str::rrand( 32 ); |
|---|
| 196 | self::update_option( |
|---|
| 197 | 'async_call-hash', |
|---|
| 198 | [ |
|---|
| 199 | 'hash' => $hash, |
|---|
| 200 | 'ts' => time(), |
|---|
| 201 | ] |
|---|
| 202 | ); |
|---|
| 203 | |
|---|
| 204 | $args = [ |
|---|
| 205 | 'timeout' => 0.01, |
|---|
| 206 | 'blocking' => false, |
|---|
| 207 | 'sslverify' => false, |
|---|
| 208 | // 'cookies' => $_COOKIE, |
|---|
| 209 | ]; |
|---|
| 210 | |
|---|
| 211 | $qs = [ |
|---|
| 212 | 'action' => 'async_litespeed', |
|---|
| 213 | 'nonce' => $hash, |
|---|
| 214 | Router::TYPE => $type, |
|---|
| 215 | ]; |
|---|
| 216 | |
|---|
| 217 | $url = add_query_arg( $qs, admin_url( 'admin-ajax.php' ) ); |
|---|
| 218 | self::debug( 'async call to ' . $url ); |
|---|
| 219 | wp_safe_remote_post( esc_url_raw( $url ), $args ); |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | /** |
|---|
| 223 | * Clean all potential existing crons. |
|---|
| 224 | * |
|---|
| 225 | * @since 3.0 |
|---|
| 226 | * @access public |
|---|
| 227 | * @return void |
|---|
| 228 | */ |
|---|
| 229 | public static function destroy() { |
|---|
| 230 | Utility::compatibility(); |
|---|
| 231 | array_map( 'wp_clear_scheduled_hook', array_column( self::$_triggers, 'name' ) ); |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | /** |
|---|
| 235 | * Try to clean the crons if disabled. |
|---|
| 236 | * |
|---|
| 237 | * @since 3.0 |
|---|
| 238 | * @access public |
|---|
| 239 | * |
|---|
| 240 | * @param string $id Option id of cron trigger. |
|---|
| 241 | * @return void |
|---|
| 242 | */ |
|---|
| 243 | public function try_clean( $id ) { |
|---|
| 244 | if ( $id && ! empty( self::$_triggers[ $id ] ) ) { |
|---|
| 245 | if ( ! $this->conf( $id ) || ( Base::O_CRAWLER === $id && ! Router::can_crawl() ) ) { |
|---|
| 246 | self::debug( 'Cron clear [id] ' . $id . ' [hook] ' . self::$_triggers[ $id ]['name'] ); |
|---|
| 247 | wp_clear_scheduled_hook( self::$_triggers[ $id ]['name'] ); |
|---|
| 248 | } |
|---|
| 249 | return; |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | self::debug( '❌ Unknown cron [id] ' . $id ); |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | /** |
|---|
| 256 | * Register cron interval for general tasks. |
|---|
| 257 | * |
|---|
| 258 | * @since 1.6.1 |
|---|
| 259 | * @access public |
|---|
| 260 | * |
|---|
| 261 | * @param array $schedules Existing schedules. |
|---|
| 262 | * @return array |
|---|
| 263 | */ |
|---|
| 264 | public function lscache_cron_filter( $schedules ) { |
|---|
| 265 | if ( ! array_key_exists( self::FILTER, $schedules ) ) { |
|---|
| 266 | $schedules[ self::FILTER ] = [ |
|---|
| 267 | 'interval' => 900, |
|---|
| 268 | 'display' => __( 'Every 15 Minutes', 'litespeed-cache' ), |
|---|
| 269 | ]; |
|---|
| 270 | } |
|---|
| 271 | return $schedules; |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | /** |
|---|
| 275 | * Register cron interval for crawler. |
|---|
| 276 | * |
|---|
| 277 | * @since 1.1.0 |
|---|
| 278 | * @access public |
|---|
| 279 | * |
|---|
| 280 | * @param array $schedules Existing schedules. |
|---|
| 281 | * @return array |
|---|
| 282 | */ |
|---|
| 283 | public function lscache_cron_filter_crawler( $schedules ) { |
|---|
| 284 | $crawler_run_interval = defined( 'LITESPEED_CRAWLER_RUN_INTERVAL' ) ? (int) constant( 'LITESPEED_CRAWLER_RUN_INTERVAL' ) : 600; |
|---|
| 285 | |
|---|
| 286 | if ( ! array_key_exists( self::FILTER_CRAWLER, $schedules ) ) { |
|---|
| 287 | $schedules[ self::FILTER_CRAWLER ] = [ |
|---|
| 288 | 'interval' => $crawler_run_interval, |
|---|
| 289 | 'display' => __( 'LiteSpeed Crawler Cron', 'litespeed-cache' ), |
|---|
| 290 | ]; |
|---|
| 291 | } |
|---|
| 292 | return $schedules; |
|---|
| 293 | } |
|---|
| 294 | } |
|---|