PluginProbe ʕ •ᴥ•ʔ
LiteSpeed Cache / 7.6.1
LiteSpeed Cache v7.6.1
trunk 1.0.15 1.9.1.1 2.9.9.2 3.6.4 4.6 5.7.0.1 6.5.4 7.0.0.1 7.0.1 7.1 7.2 7.3 7.3.0.1 7.4 7.5 7.5.0.1 7.6 7.6.1 7.6.2 7.7 7.8 7.8.0.1 7.8.1
litespeed-cache / src / core.cls.php
litespeed-cache / src Last commit date
cdn 7 months ago data_structure 7 months ago activation.cls.php 7 months ago admin-display.cls.php 7 months ago admin-settings.cls.php 7 months ago admin.cls.php 7 months ago api.cls.php 7 months ago avatar.cls.php 7 months ago base.cls.php 7 months ago cdn.cls.php 7 months ago cloud.cls.php 7 months ago conf.cls.php 7 months ago control.cls.php 7 months ago core.cls.php 7 months ago crawler-map.cls.php 7 months ago crawler.cls.php 7 months ago css.cls.php 7 months ago data.cls.php 7 months ago data.upgrade.func.php 7 months ago db-optm.cls.php 7 months ago debug2.cls.php 7 months ago doc.cls.php 7 months ago error.cls.php 7 months ago esi.cls.php 7 months ago file.cls.php 7 months ago gui.cls.php 7 months ago health.cls.php 7 months ago htaccess.cls.php 7 months ago img-optm.cls.php 7 months ago import.cls.php 7 months ago import.preset.cls.php 7 months ago lang.cls.php 7 months ago localization.cls.php 7 months ago media.cls.php 7 months ago metabox.cls.php 7 months ago object-cache-wp.cls.php 7 months ago object-cache.cls.php 7 months ago object.lib.php 7 months ago optimize.cls.php 7 months ago optimizer.cls.php 7 months ago placeholder.cls.php 7 months ago purge.cls.php 7 months ago report.cls.php 7 months ago rest.cls.php 7 months ago root.cls.php 7 months ago router.cls.php 7 months ago str.cls.php 7 months ago tag.cls.php 7 months ago task.cls.php 7 months ago tool.cls.php 7 months ago ucss.cls.php 7 months ago utility.cls.php 7 months ago vary.cls.php 7 months ago vpi.cls.php 7 months ago
core.cls.php
739 lines
1 <?php
2 /**
3 * The core plugin class.
4 *
5 * This is the main class for the LiteSpeed Cache plugin, responsible for initializing
6 * the plugin's core functionality, registering hooks, and handling cache-related operations.
7 *
8 * Note: Core doesn't allow $this->cls( 'Core' )
9 *
10 * @since 1.0.0
11 * @package LiteSpeed
12 */
13
14 namespace LiteSpeed;
15
16 defined( 'WPINC' ) || exit();
17
18 /**
19 * Class Core
20 *
21 * @since 1.0.0
22 */
23 class Core extends Root {
24
25 const NAME = 'LiteSpeed Cache';
26 const PLUGIN_NAME = 'litespeed-cache';
27 const PLUGIN_FILE = 'litespeed-cache/litespeed-cache.php';
28 const VER = LSCWP_V;
29
30 const ACTION_DISMISS = 'dismiss';
31 const ACTION_PURGE_BY = 'PURGE_BY';
32 const ACTION_PURGE_EMPTYCACHE = 'PURGE_EMPTYCACHE';
33 const ACTION_QS_PURGE = 'PURGE';
34 const ACTION_QS_PURGE_SINGLE = 'PURGESINGLE'; // This will be same as `ACTION_QS_PURGE` (purge single URL only)
35 const ACTION_QS_SHOW_HEADERS = 'SHOWHEADERS';
36 const ACTION_QS_PURGE_ALL = 'purge_all';
37 const ACTION_QS_PURGE_EMPTYCACHE = 'empty_all';
38 const ACTION_QS_NOCACHE = 'NOCACHE';
39
40 const HEADER_DEBUG = 'X-LiteSpeed-Debug';
41
42 /**
43 * Whether to show debug headers.
44 *
45 * @var bool
46 * @since 1.0.0
47 */
48 protected static $debug_show_header = false;
49
50 /**
51 * Footer comment buffer.
52 *
53 * @var string
54 * @since 1.0.0
55 */
56 private $footer_comment = '';
57
58 /**
59 * Define the core functionality of the plugin.
60 *
61 * Set the plugin name and the plugin version that can be used throughout the plugin.
62 * Load the dependencies, define the locale, and set the hooks for the admin area and
63 * the public-facing side of the site.
64 *
65 * @since 1.0.0
66 */
67 public function __construct() {
68 ! defined( 'LSCWP_TS_0' ) && define( 'LSCWP_TS_0', microtime( true ) );
69 $this->cls( 'Conf' )->init();
70
71 /**
72 * Load API hooks
73 *
74 * @since 3.0
75 */
76 $this->cls( 'API' )->init();
77
78 if ( defined( 'LITESPEED_ON' ) ) {
79 // Load third party detection if lscache enabled.
80 include_once LSCWP_DIR . 'thirdparty/entry.inc.php';
81 }
82
83
84 if ( $this->conf( Base::O_DEBUG_DISABLE_ALL ) || Debug2::is_tmp_disable() ) {
85 ! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true );
86 }
87
88 /**
89 * Register plugin activate/deactivate/uninstall hooks
90 * NOTE: this can't be moved under after_setup_theme, otherwise activation will be bypassed
91 *
92 * @since 2.7.1 Disabled admin&CLI check to make frontend able to enable cache too
93 */
94 $plugin_file = LSCWP_DIR . 'litespeed-cache.php';
95 register_activation_hook( $plugin_file, array( __NAMESPACE__ . '\Activation', 'register_activation' ) );
96 register_deactivation_hook( $plugin_file, array( __NAMESPACE__ . '\Activation', 'register_deactivation' ) );
97 register_uninstall_hook( $plugin_file, __NAMESPACE__ . '\Activation::uninstall_litespeed_cache' );
98
99 if ( defined( 'LITESPEED_ON' ) ) {
100 // Register purge_all actions
101 $purge_all_events = $this->conf( Base::O_PURGE_HOOK_ALL );
102
103 // Purge all on upgrade
104 if ( $this->conf( Base::O_PURGE_ON_UPGRADE ) ) {
105 $purge_all_events[] = 'automatic_updates_complete';
106 $purge_all_events[] = 'upgrader_process_complete';
107 $purge_all_events[] = 'admin_action_do-plugin-upgrade';
108 }
109 foreach ( $purge_all_events as $event ) {
110 // Don't allow hook to update_option because purge_all will cause infinite loop of update_option
111 if ( in_array( $event, array( 'update_option' ), true ) ) {
112 continue;
113 }
114 add_action( $event, __NAMESPACE__ . '\Purge::purge_all' );
115 }
116
117 // Add headers to site health check for full page cache
118 // @since 5.4
119 add_filter( 'site_status_page_cache_supported_cache_headers', function ( $cache_headers ) {
120 $is_cache_hit = function ( $header_value ) {
121 return false !== strpos( strtolower( $header_value ), 'hit' );
122 };
123 $cache_headers['x-litespeed-cache'] = $is_cache_hit;
124 $cache_headers['x-lsadc-cache'] = $is_cache_hit;
125 $cache_headers['x-qc-cache'] = $is_cache_hit;
126 return $cache_headers;
127 } );
128 }
129
130 add_action( 'after_setup_theme', array( $this, 'init' ) );
131
132 // Check if there is a purge request in queue
133 if ( ! defined( 'LITESPEED_CLI' ) ) {
134 $purge_queue = Purge::get_option( Purge::DB_QUEUE );
135 if ( $purge_queue && '-1' !== $purge_queue ) {
136 $this->http_header( $purge_queue );
137 Debug2::debug( '[Core] Purge Queue found&sent: ' . $purge_queue );
138 }
139 if ( '-1' !== $purge_queue ) {
140 Purge::update_option( Purge::DB_QUEUE, '-1' ); // Use -1 to bypass purge while still enable db update as WP's update_option will check value===false to bypass update
141 }
142
143 $purge_queue = Purge::get_option( Purge::DB_QUEUE2 );
144 if ( $purge_queue && '-1' !== $purge_queue ) {
145 $this->http_header( $purge_queue );
146 Debug2::debug( '[Core] Purge2 Queue found&sent: ' . $purge_queue );
147 }
148 if ( '-1' !== $purge_queue ) {
149 Purge::update_option( Purge::DB_QUEUE2, '-1' );
150 }
151 }
152
153 /**
154 * Hook internal REST
155 *
156 * @since 2.9.4
157 */
158 $this->cls( 'REST' );
159
160 /**
161 * Hook wpnonce function
162 *
163 * Note: ESI nonce won't be available until hook after_setup_theme ESI init due to Guest Mode concern
164 *
165 * @since 4.1
166 */
167 if ( $this->cls( 'Router' )->esi_enabled() && ! function_exists( 'wp_create_nonce' ) ) {
168 Debug2::debug( '[ESI] Overwrite wp_create_nonce()' );
169 litespeed_define_nonce_func();
170 }
171 }
172
173 /**
174 * The plugin initializer.
175 *
176 * This function checks if the cache is enabled and ready to use, then determines what actions need to be set up based on the type of user and page accessed. Output is buffered if the cache is enabled.
177 *
178 * NOTE: WP user doesn't init yet
179 *
180 * @since 1.0.0
181 */
182 public function init() {
183 /**
184 * Added hook before init
185 * 3rd party preload hooks will be fired here too (e.g. Divi disable all in edit mode)
186 *
187 * @since 1.6.6
188 * @since 2.6 Added filter to all config values in Conf
189 */
190 do_action( 'litespeed_init' );
191 add_action( 'wp_ajax_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler' );
192 add_action( 'wp_ajax_nopriv_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler' );
193
194 // In `after_setup_theme`, before `init` hook
195 $this->cls( 'Activation' )->auto_update();
196
197 if ( is_admin() && ! wp_doing_ajax() ) {
198 $this->cls( 'Admin' );
199 }
200
201 if ( defined( 'LITESPEED_DISABLE_ALL' ) && LITESPEED_DISABLE_ALL ) {
202 Debug2::debug( '[Core] Bypassed due to debug disable all setting' );
203 return;
204 }
205
206 do_action( 'litespeed_initing' );
207
208 ob_start( array( $this, 'send_headers_force' ) );
209 add_action( 'shutdown', array( $this, 'send_headers' ), 0 );
210 add_action( 'wp_footer', array( $this, 'footer_hook' ) );
211
212 /**
213 * Check if is non-optimization simulator
214 *
215 * @since 2.9
216 */
217 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
218 if ( ! empty( $_GET[ Router::ACTION ] ) && 'before_optm' === $_GET[ Router::ACTION ] && ! apply_filters( 'litespeed_qs_forbidden', false ) ) {
219 Debug2::debug( '[Core] ⛑️ bypass_optm due to QS CTRL' );
220 ! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
221 }
222
223 /**
224 * Register vary filter
225 *
226 * @since 1.6.2
227 */
228 $this->cls( 'Control' )->init();
229
230 // Init Purge hooks
231 $this->cls( 'Purge' )->init();
232
233 $this->cls( 'Tag' )->init();
234
235 // Load hooks that may be related to users
236 add_action( 'init', array( $this, 'after_user_init' ), 5 );
237
238 // Load 3rd party hooks
239 add_action( 'wp_loaded', array( $this, 'load_thirdparty' ), 2 );
240 }
241
242 /**
243 * Run hooks after user init
244 *
245 * @since 2.9.8
246 */
247 public function after_user_init() {
248 $this->cls( 'Router' )->is_role_simulation();
249
250 // Detect if is Guest mode or not
251 $this->cls( 'Vary' )->after_user_init();
252
253 // Register attachment delete hook
254 $this->cls( 'Media' )->after_user_init();
255
256 /**
257 * Preload ESI functionality for ESI request URI recovery
258 *
259 * @since 1.8.1
260 * @since 4.0 ESI init needs to be after Guest mode detection to bypass ESI if is under Guest mode
261 */
262 $this->cls( 'ESI' )->init();
263
264 if ( ! is_admin() && ! defined( 'LITESPEED_GUEST_OPTM' ) ) {
265 $result = $this->cls( 'Conf' )->in_optm_exc_roles();
266 if ( $result ) {
267 Debug2::debug( '[Core] ⛑️ bypass_optm: hit Role Excludes setting: ' . $result );
268 ! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
269 }
270 }
271
272 // Heartbeat control
273 $this->cls( 'Tool' )->heartbeat();
274
275 if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
276 // Check missing static files
277 $this->cls( 'Router' )->serve_static();
278
279 $this->cls( 'Media' )->init();
280
281 $this->cls( 'Placeholder' )->init();
282
283 $this->cls( 'Router' )->can_optm() && $this->cls( 'Optimize' )->init();
284
285 $this->cls( 'Localization' )->init();
286
287 // Hook CDN for attachments
288 $this->cls( 'CDN' )->init();
289
290 // Load cron tasks
291 $this->cls( 'Task' )->init();
292 }
293
294 // Load litespeed actions
295 $action = Router::get_action();
296 if ( $action ) {
297 $this->proceed_action( $action );
298 }
299
300 // Load frontend GUI
301 if ( ! is_admin() ) {
302 $this->cls( 'GUI' )->init();
303 }
304 }
305
306 /**
307 * Run frontend actions
308 *
309 * @since 1.1.0
310 * @param string $action The action to proceed.
311 */
312 public function proceed_action( $action ) {
313 $msg = false;
314 // Handle actions
315 switch ( $action ) {
316 case self::ACTION_QS_SHOW_HEADERS:
317 self::$debug_show_header = true;
318 break;
319
320 case self::ACTION_QS_PURGE:
321 case self::ACTION_QS_PURGE_SINGLE:
322 Purge::set_purge_single();
323 break;
324
325 case self::ACTION_QS_PURGE_ALL:
326 Purge::purge_all();
327 break;
328
329 case self::ACTION_PURGE_EMPTYCACHE:
330 case self::ACTION_QS_PURGE_EMPTYCACHE:
331 define( 'LSWCP_EMPTYCACHE', true ); // Clear all sites caches
332 Purge::purge_all();
333 $msg = __( 'Notified LiteSpeed Web Server to purge everything.', 'litespeed-cache' );
334 break;
335
336 case self::ACTION_PURGE_BY:
337 $this->cls( 'Purge' )->purge_list();
338 $msg = __( 'Notified LiteSpeed Web Server to purge the list.', 'litespeed-cache' );
339 break;
340
341 case self::ACTION_DISMISS:
342 GUI::dismiss();
343 break;
344
345 default:
346 $msg = $this->cls( 'Router' )->handler( $action );
347 break;
348 }
349 if ( $msg && ! Router::is_ajax() ) {
350 Admin_Display::add_notice( Admin_Display::NOTICE_GREEN, $msg );
351 Admin::redirect();
352 return;
353 }
354
355 if ( Router::is_ajax() ) {
356 exit();
357 }
358 }
359
360 /**
361 * Callback used to call the detect third party action.
362 *
363 * The detect action is used by third party plugin integration classes to determine if they should add the rest of their hooks.
364 *
365 * @since 1.0.5
366 */
367 public function load_thirdparty() {
368 do_action( 'litespeed_load_thirdparty' );
369 }
370
371 /**
372 * Mark wp_footer called
373 *
374 * @since 1.3
375 */
376 public function footer_hook() {
377 Debug2::debug( '[Core] Footer hook called' );
378 if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
379 define( 'LITESPEED_FOOTER_CALLED', true );
380 }
381 }
382
383 /**
384 * Trigger comment info display hook
385 *
386 * @since 1.3
387 * @param string|null $buffer The buffer to check.
388 * @return void
389 */
390 private function check_is_html( $buffer = null ) {
391 if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
392 Debug2::debug2( '[Core] CHK html bypass: miss footer const' );
393 return;
394 }
395
396 if ( wp_doing_ajax() ) {
397 Debug2::debug2( '[Core] CHK html bypass: doing ajax' );
398 return;
399 }
400
401 if ( wp_doing_cron() ) {
402 Debug2::debug2( '[Core] CHK html bypass: doing cron' );
403 return;
404 }
405
406 if ( empty( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
407 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
408 Debug2::debug2( '[Core] CHK html bypass: not get method ' . wp_unslash( $_SERVER['REQUEST_METHOD'] ) );
409 return;
410 }
411
412 if ( null === $buffer ) {
413 $buffer = ob_get_contents();
414 }
415
416 // Double check to make sure it is an HTML file
417 if ( strlen( $buffer ) > 300 ) {
418 $buffer = substr( $buffer, 0, 300 );
419 }
420 if ( false !== strstr( $buffer, '<!--' ) ) {
421 $buffer = preg_replace( '/<!--.*?-->/s', '', $buffer );
422 }
423 $buffer = trim( $buffer );
424
425 $buffer = File::remove_zero_space( $buffer );
426
427 $is_html = 0 === stripos( $buffer, '<html' ) || 0 === stripos( $buffer, '<!DOCTYPE' );
428
429 if ( ! $is_html ) {
430 Debug2::debug( '[Core] Footer check failed: ' . ob_get_level() . '-' . substr( $buffer, 0, 100 ) );
431 return;
432 }
433
434 Debug2::debug( '[Core] Footer check passed' );
435
436 if ( ! defined( 'LITESPEED_IS_HTML' ) ) {
437 define( 'LITESPEED_IS_HTML', true );
438 }
439 }
440
441 /**
442 * For compatibility with plugins that have 'Bad' logic that forced all buffer output even if it is NOT their buffer.
443 *
444 * Usually this is called after send_headers() if following original WP process
445 *
446 * @since 1.1.5
447 * @param string $buffer The buffer to process.
448 * @return string The processed buffer.
449 */
450 public function send_headers_force( $buffer ) {
451 $this->check_is_html( $buffer );
452
453 // Hook to modify buffer before
454 $buffer = apply_filters( 'litespeed_buffer_before', $buffer );
455
456 /**
457 * Media: Image lazyload && WebP
458 * GUI: Clean wrapper mainly for ESI block NOTE: this needs to be before optimizer to avoid wrapper being removed
459 * Optimize
460 * CDN
461 */
462 if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
463 Debug2::debug( '[Core] run hook litespeed_buffer_finalize' );
464 $buffer = apply_filters( 'litespeed_buffer_finalize', $buffer );
465 }
466
467 /**
468 * Replace ESI preserved list
469 *
470 * @since 3.3 Replace this in the end to avoid `Inline JS Defer` or other Page Optm features encoded ESI tags wrongly, which caused LSWS can't recognize ESI
471 */
472 $buffer = $this->cls( 'ESI' )->finalize( $buffer );
473
474 $this->send_headers( true );
475
476 // Log ESI nonce buffer empty issue
477 if ( defined( 'LSCACHE_IS_ESI' ) && 0 === strlen( $buffer ) && ! empty( $_SERVER['REQUEST_URI'] ) ) {
478 // Log ref for debug purpose
479 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.PHP.DevelopmentFunctions.error_log_error_log
480 error_log( 'ESI buffer empty ' . wp_unslash( $_SERVER['REQUEST_URI'] ) );
481 }
482
483 // Init comment info
484 $running_info_showing = defined( 'LITESPEED_IS_HTML' ) || defined( 'LSCACHE_IS_ESI' );
485 if ( defined( 'LSCACHE_ESI_SILENCE' ) ) {
486 $running_info_showing = false;
487 Debug2::debug( '[Core] ESI silence' );
488 }
489 /**
490 * Silence comment for JSON request
491 *
492 * @since 2.9.3
493 */
494 if ( REST::cls()->is_rest() || Router::is_ajax() ) {
495 $running_info_showing = false;
496 Debug2::debug( '[Core] Silence Comment due to REST/AJAX' );
497 }
498 $running_info_showing = apply_filters( 'litespeed_comment', $running_info_showing );
499 if ( $running_info_showing && $this->footer_comment ) {
500 $buffer .= $this->footer_comment;
501 }
502
503 /**
504 * If ESI request is JSON, give the content JSON format
505 *
506 * @since 2.9.3
507 * @since 2.9.4 ESI request could be from internal REST call, so moved json_encode out of this condition
508 */
509 if ( defined( 'LSCACHE_IS_ESI' ) ) {
510 Debug2::debug( '[Core] ESI Start 👇' );
511 if ( strlen( $buffer ) > 500 ) {
512 Debug2::debug( trim( substr( $buffer, 0, 500 ) ) . '.....' );
513 } else {
514 Debug2::debug( $buffer );
515 }
516 Debug2::debug( '[Core] ESI End 👆' );
517 }
518
519 if ( apply_filters( 'litespeed_is_json', false ) ) {
520 if ( null === \json_decode( $buffer, true ) ) {
521 Debug2::debug( '[Core] Buffer converting to JSON' );
522 $buffer = wp_json_encode( $buffer );
523 $buffer = trim( $buffer, '"' );
524 } else {
525 Debug2::debug( '[Core] JSON Buffer' );
526 }
527 }
528
529 // Hook to modify buffer after
530 $buffer = apply_filters( 'litespeed_buffer_after', $buffer );
531
532 Debug2::ended();
533
534 return $buffer;
535 }
536
537 /**
538 * Sends the headers out at the end of processing the request.
539 *
540 * This will send out all LiteSpeed Cache related response headers needed for the post.
541 *
542 * @since 1.0.5
543 * @param bool $is_forced If the header is sent following our normal finalizing logic.
544 */
545 public function send_headers( $is_forced = false ) {
546 // Make sure header output only runs once
547 if ( defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) {
548 return;
549 }
550 define( 'LITESPEED_DID_' . __FUNCTION__, true );
551
552 // Avoid PHP warning for headers sent out already
553 if ( headers_sent() ) {
554 self::debug( '❌ !!! Err: Header sent out already' );
555 return;
556 }
557
558 $this->check_is_html();
559
560 // Cache control output needs to be done first, as some varies are added in 3rd party hook `litespeed_api_control`.
561 $this->cls( 'Control' )->finalize();
562
563 $vary_header = $this->cls( 'Vary' )->finalize();
564
565 // If not cacheable but Admin QS is `purge` or `purgesingle`, `tag` still needs to be generated
566 $tag_header = $this->cls( 'Tag' )->output();
567 if ( ! $tag_header && Control::is_cacheable() ) {
568 Control::set_nocache( 'empty tag header' );
569 }
570
571 // `Purge` output needs to be after `tag` output as Admin QS may need to send `tag` header
572 $purge_header = Purge::output();
573
574 // Generate `control` header in the end in case control status is changed by other headers
575 $control_header = $this->cls( 'Control' )->output();
576
577 // Give one more break to avoid Firefox crash
578 if ( ! defined( 'LSCACHE_IS_ESI' ) ) {
579 $this->footer_comment .= "\n";
580 }
581
582 $cache_support = 'supported';
583 if ( defined( 'LITESPEED_ON' ) ) {
584 $cache_support = Control::is_cacheable() ? 'cached' : 'uncached';
585 }
586
587 $this->comment(
588 sprintf(
589 '%1$s %2$s by LiteSpeed Cache %4$s on %3$s',
590 defined( 'LSCACHE_IS_ESI' ) ? 'Block' : 'Page',
591 $cache_support,
592 gmdate( 'Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET ),
593 self::VER
594 )
595 );
596
597 // Send Control header
598 if ( defined( 'LITESPEED_ON' ) && $control_header ) {
599 $this->http_header( $control_header );
600 if ( ! Control::is_cacheable() && !is_admin() ) {
601 $ori_wp_header = wp_get_nocache_headers();
602 if ( isset( $ori_wp_header['Cache-Control'] ) ) {
603 $this->http_header( 'Cache-Control: ' . $ori_wp_header['Cache-Control'] ); // @ref: https://github.com/litespeedtech/lscache_wp/issues/889
604 }
605 }
606 if ( defined( 'LSCWP_LOG' ) ) {
607 $this->comment( $control_header );
608 }
609 }
610
611 // Send PURGE header (Always send regardless of cache setting disabled/enabled)
612 if ( defined( 'LITESPEED_ON' ) && $purge_header ) {
613 $this->http_header( $purge_header );
614 Debug2::log_purge( $purge_header );
615
616 if ( defined( 'LSCWP_LOG' ) ) {
617 $this->comment( $purge_header );
618 }
619 }
620
621 // Send Vary header
622 if ( defined( 'LITESPEED_ON' ) && $vary_header ) {
623 $this->http_header( $vary_header );
624 if ( defined( 'LSCWP_LOG' ) ) {
625 $this->comment( $vary_header );
626 }
627 }
628
629 if ( defined( 'LITESPEED_ON' ) && defined( 'LSCWP_LOG' ) ) {
630 $vary = $this->cls( 'Vary' )->finalize_full_varies();
631 if ( $vary ) {
632 $this->comment( 'Full varies: ' . $vary );
633 }
634 }
635
636 // Admin QS show header action
637 if ( self::$debug_show_header ) {
638 $debug_header = self::HEADER_DEBUG . ': ';
639 if ( $control_header ) {
640 $debug_header .= $control_header . '; ';
641 }
642 if ( $purge_header ) {
643 $debug_header .= $purge_header . '; ';
644 }
645 if ( $tag_header ) {
646 $debug_header .= $tag_header . '; ';
647 }
648 if ( $vary_header ) {
649 $debug_header .= $vary_header . '; ';
650 }
651 $this->http_header( $debug_header );
652 } elseif ( defined( 'LITESPEED_ON' ) && Control::is_cacheable() && $tag_header ) {
653 $this->http_header( $tag_header );
654 if ( defined( 'LSCWP_LOG' ) ) {
655 $this->comment( $tag_header );
656 }
657 }
658
659 // Object cache comment
660 if ( defined( 'LSCWP_LOG' ) && defined( 'LSCWP_OBJECT_CACHE' ) && method_exists( 'WP_Object_Cache', 'debug' ) ) {
661 $this->comment( 'Object Cache ' . \WP_Object_Cache::get_instance()->debug() );
662 }
663
664 if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
665 $this->comment( 'Guest Mode' );
666 }
667
668 if ( ! empty( $this->footer_comment ) ) {
669 self::debug( "[footer comment]\n" . trim( $this->footer_comment ) );
670 }
671
672 if ( $is_forced ) {
673 Debug2::debug( '--forced--' );
674 }
675
676 // If CLI and contains Purge Header, issue an HTTP request to Purge
677 if ( defined( 'LITESPEED_CLI' ) ) {
678 $purge_queue = Purge::get_option( Purge::DB_QUEUE );
679 if ( ! $purge_queue || '-1' === $purge_queue ) {
680 $purge_queue = Purge::get_option( Purge::DB_QUEUE2 );
681 }
682 if ( $purge_queue && '-1' !== $purge_queue ) {
683 self::debug( '[Core] Purge Queue found, issue an HTTP request to purge: ' . $purge_queue );
684 // Kick off HTTP request
685 $url = admin_url( 'admin-ajax.php' );
686 $resp = wp_safe_remote_get( $url );
687 if ( is_wp_error( $resp ) ) {
688 $error_message = $resp->get_error_message();
689 self::debug( '[URL]' . $url );
690 self::debug( 'failed to request: ' . $error_message );
691 } else {
692 self::debug( 'HTTP request response: ' . $resp['body'] );
693 }
694 }
695 }
696 }
697
698 /**
699 * Append one HTML comment
700 *
701 * @since 5.5
702 * @param string $data The comment data.
703 */
704 public static function comment( $data ) {
705 self::cls()->append_comment( $data );
706 }
707
708 /**
709 * Append one HTML comment
710 *
711 * @since 5.5
712 * @param string $data The comment data.
713 */
714 private function append_comment( $data ) {
715 $this->footer_comment .= "\n<!-- " . htmlspecialchars( $data ) . ' -->';
716 }
717
718 /**
719 * Send HTTP header
720 *
721 * @since 5.3
722 * @param string $header The header to send.
723 */
724 private function http_header( $header ) {
725 if ( defined( 'LITESPEED_CLI' ) ) {
726 return;
727 }
728
729 if ( ! headers_sent() ) {
730 header( $header );
731 }
732
733 if ( ! defined( 'LSCWP_LOG' ) ) {
734 return;
735 }
736 Debug2::debug( '💰 ' . $header );
737 }
738 }
739