get_all_settings(); if ( empty( $settings['browser_cache_enabled'] ) && empty( $settings['page_cache_headers'] ) ) { return; } add_action( 'send_headers', array( $this, 'send_html_headers' ), 1 ); } /** * Whether public shared cache headers are allowed for this request. * * @return bool */ public static function public_headers_allowed() { $raw_uri = isset( $_SERVER['REQUEST_URI'] ) && is_string( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/'; $uri = sanitize_text_field( $raw_uri ); if ( '/' !== substr( $uri, 0, 1 ) || strlen( $uri ) > 8192 ) { $uri = '/'; } $password = false; if ( \QWSpeedCache\Cache\Status::query_ready() && function_exists( 'is_singular' ) && is_singular() ) { $post = get_post(); $password = ( $post && ! empty( $post->post_password ) ); } $settings = array(); if ( class_exists( '\QWSpeedCache\Plugin' ) ) { $settings = \QWSpeedCache\Plugin::instance()->get_all_settings(); } return SafetyPolicy::request_is_cacheable( array( 'method' => isset( $_SERVER['REQUEST_METHOD'] ) && is_string( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET', 'uri' => $uri, 'cookies' => isset( $_COOKIE ) && is_array( $_COOKIE ) ? $_COOKIE : array(), 'is_admin' => function_exists( 'is_admin' ) && is_admin(), 'is_logged_in' => function_exists( 'is_user_logged_in' ) && is_user_logged_in(), 'is_preview' => ( \QWSpeedCache\Cache\Status::query_ready() && function_exists( 'is_preview' ) && is_preview() ) || ( function_exists( 'is_customize_preview' ) && is_customize_preview() ), 'is_feed' => \QWSpeedCache\Cache\Status::query_ready() && function_exists( 'is_feed' ) && is_feed(), 'doing_ajax' => defined( 'DOING_AJAX' ) && DOING_AJAX, 'rest_request' => defined( 'REST_REQUEST' ) && REST_REQUEST, 'donotcachepage' => defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE, 'has_post_password' => $password, 'is_multisite_signup' => function_exists( 'is_multisite' ) && is_multisite() && function_exists( 'is_signup_page' ) && is_signup_page(), ) ); } /** * Apply Cache-Control / Vary for front-end HTML (not admin/ajax/rest/sensitive). * * @return string Cache-Control value applied (empty if none). */ public function send_html_headers() { return self::apply_html_cache_control(); } /** * Compute and send HTML Cache-Control (testable). * * @param array|null $settings Settings override. * @param array|null $context Optional request context (doing_ajax, uri) for tests — does not define constants. * @return string Cache-Control value. */ public static function apply_html_cache_control( $settings = null, $context = null ) { $send = ! headers_sent(); $ctx_ajax = is_array( $context ) && ! empty( $context['doing_ajax'] ); $env_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX; if ( $ctx_ajax || $env_ajax ) { $cc = 'private, no-store, no-cache, must-revalidate'; if ( $send ) { header( 'Cache-Control: ' . $cc ); } return $cc; } if ( is_array( $context ) && ! empty( $context['uri'] ) ) { $prev = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : null; $_SERVER['REQUEST_URI'] = (string) $context['uri']; $allowed = self::public_headers_allowed(); if ( null === $prev ) { unset( $_SERVER['REQUEST_URI'] ); } else { $_SERVER['REQUEST_URI'] = $prev; } if ( ! $allowed ) { $cc = 'private, no-store, no-cache, must-revalidate'; if ( $send ) { header( 'Cache-Control: ' . $cc ); header( 'Vary: Cookie' ); } return $cc; } } elseif ( ! self::public_headers_allowed() ) { $cc = 'private, no-store, no-cache, must-revalidate'; if ( $send ) { header( 'Cache-Control: ' . $cc ); header( 'Vary: Cookie' ); } return $cc; } if ( null === $settings ) { $settings = \QWSpeedCache\Plugin::instance()->get_all_settings(); } if ( empty( $settings['page_cache_headers'] ) ) { return ''; } $max = (int) ( $settings['browser_cache_html'] ?? 0 ); $max = max( 0, min( 86400, $max ) ); if ( $max > 0 ) { $cc = 'public, max-age=' . $max; } else { $cc = 'public, max-age=0, s-maxage=3600'; } if ( $send ) { header( 'Cache-Control: ' . $cc ); header( 'Vary: ' . self::vary_header_value( $settings ) ); } return $cc; } /** * Headers for a served page-cache HIT (called from Engine). * * @param array $settings Plugin settings. * @param int $age Age of cache file in seconds. */ /** * Vary header value; includes User-Agent when mobile cache is separate. * * @param array $settings Settings. * @return string */ public static function vary_header_value( array $settings ) { $parts = array( 'Accept-Encoding', 'Cookie' ); if ( ! empty( $settings['cache_mobile'] ) ) { $parts[] = 'User-Agent'; } return implode( ', ', $parts ); } /** * Emit cache status + optional reason (N-05). * * @param string $status HIT|MISS|BYPASS|HIT-EARLY. * @param string $reason Machine reason code. */ public static function send_status_headers( $status, $reason = '' ) { if ( headers_sent() ) { return; } $status = strtoupper( (string) $status ); $allowed = array( 'HIT', 'MISS', 'BYPASS', 'HIT-EARLY', 'STALE' ); if ( ! in_array( $status, $allowed, true ) ) { $status = 'MISS'; } header( 'X-QWSC-Cache-Status: ' . $status ); $reason = preg_replace( '/[^a-z0-9_\-]/i', '', (string) $reason ); if ( '' !== $reason ) { header( 'X-QWSC-Reason: ' . $reason ); } } public static function send_cache_hit_headers( array $settings, $age = 0, $stale = false ) { $age = max( 0, (int) $age ); header( 'X-QWSC-Cache: ' . ( $stale ? 'STALE' : 'HIT' ) ); header( 'Age: ' . $age ); // Premium cache-tag headers are emitted by the separately licensed Pro add-on. do_action( 'qwsc_cache_hit_headers', $settings, $age, (bool) $stale ); $ttl_h = class_exists( '\QWSpeedCache\Cache\Ttl' ) ? (int) \QWSpeedCache\Cache\Ttl::hours_for_request( $settings ) : 10; $max_age = max( 0, $ttl_h * ( defined( '\HOUR_IN_SECONDS' ) ? \HOUR_IN_SECONDS : 3600 ) - $age ); if ( ! empty( $settings['browser_cache_enabled'] ) || ! empty( $settings['page_cache_headers'] ) ) { if ( $stale ) { $cc = 'public, max-age=0, s-maxage=0'; } elseif ( $max_age > 0 ) { $cc = 'public, max-age=' . $max_age; } else { $cc = 'public, max-age=0, s-maxage=3600'; } // Pro may add its own stale-serving directives; Free owns the safe default. $cc = apply_filters( 'qwsc_cache_hit_cache_control', $cc, $settings, (bool) $stale, $max_age ); header( 'Cache-Control: ' . $cc ); } } }