<?php
/**
 * BaseCloud Boost — Advanced Cache Drop-in
 *
 * Intercepts page requests and serves pre-cached HTML files, bypassing
 * WordPress entirely for maximum performance.
 *
 * Cache variants per URL:
 *   index.html      — plain HTML
 *   index.html.gz   — GZIP compressed
 *   index.html.br   — Brotli compressed
 *   index-mobile.html (+ .gz / .br) — separate mobile cache
 *
 * Features:
 *   - ETag / If-None-Match support for zero-byte 304 responses
 *   - Stale-While-Revalidate Cache-Control directive
 *   - Cloudflare HTTP_CF_VISITOR HTTPS detection
 *   - Expanded tracking-only query param bypass list
 *
 * @package BaseCloud\Boost
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

// ─── Fail-safe wrapper ───────────────────────────────────────────────────────
// This drop-in runs on EVERY request, before WordPress has finished booting, so
// any uncaught error here would 500 the entire site (front-end and admin). Wrap
// the whole routine: on ANY error/exception, abandon caching and let WordPress
// render the request normally. A cache layer must never be able to take a site
// down. (Returning from inside the try bails to wp-settings.php exactly as the
// inline early-return bailouts below do.)
try {

// ─── Minimal ABSPATH definition (needed before WP loads) ─────────────────────

if ( ! defined( 'WP_CONTENT_DIR' ) ) {
	// Derive WP_CONTENT_DIR from this file's location:
	// advanced-cache.php lives at WP_CONTENT_DIR/advanced-cache.php.
	define( 'WP_CONTENT_DIR', dirname( __FILE__ ) );
}

// ─── Early bailouts ───────────────────────────────────────────────────────────

// POST/PUT/DELETE requests are never cached.
if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] !== 'GET' ) {
	return;
}

// Skip AJAX and CLI requests.
if (
	( defined( 'DOING_AJAX' ) && DOING_AJAX ) ||
	( defined( 'WP_CLI' ) && WP_CLI )
) {
	return;
}

// Logged-in users and password-protected posts bypass cache.
if ( ! empty( $_COOKIE ) ) {
	foreach ( $_COOKIE as $name => $value ) {
		if (
			strpos( $name, 'wordpress_logged_in' ) === 0 ||
			strpos( $name, 'wp-postpass_' ) === 0 ||
			$name === 'bcboost-nocache'
		) {
			return;
		}
	}

	// WooCommerce: bypass for active cart / session cookies.
	foreach ( $_COOKIE as $name => $value ) {
		if (
			strpos( $name, 'woocommerce_items_in_cart' ) !== false ||
			strpos( $name, 'woocommerce_cart_hash' ) === 0 ||
			strpos( $name, 'woocommerce_session_' ) === 0
		) {
			return;
		}
	}
}

// ─── Load Plugin Options (no DB hit) ─────────────────────────────────────────

$BCBOOST_cache_dir  = WP_CONTENT_DIR . '/cache/basecloud-boost/';
$BCBOOST_options    = [];
$BCBOOST_opts_file  = $BCBOOST_cache_dir . 'bcboost-options.json';

if ( file_exists( $BCBOOST_opts_file ) ) {
	$BCBOOST_json = file_get_contents( $BCBOOST_opts_file );
	if ( false !== $BCBOOST_json ) {
		$BCBOOST_options = (array) json_decode( $BCBOOST_json, true );
	}
}

// Bail if caching is explicitly disabled.
if ( isset( $BCBOOST_options['cache_enabled'] ) && ! $BCBOOST_options['cache_enabled'] ) {
	return;
}

$BCBOOST_lifetime = (int) ( $BCBOOST_options['cache_lifetime'] ?? 86400 );

// ─── Custom cookie exclusions ─────────────────────────────────────────────────

if ( ! empty( $BCBOOST_options['exclude_cookies'] ) && ! empty( $_COOKIE ) ) {
	$BCBOOST_cookie_excludes = array_filter( array_map( 'trim', explode( "\n", $BCBOOST_options['exclude_cookies'] ) ) );
	foreach ( $BCBOOST_cookie_excludes as $BCBOOST_cookie_name ) {
		if ( '' !== $BCBOOST_cookie_name && isset( $_COOKIE[ $BCBOOST_cookie_name ] ) ) {
			return;
		}
	}
}

// Skip query strings (unless explicitly allowed or only tracking-only params present).
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in.
if ( ! empty( $_SERVER['QUERY_STRING'] ) && empty( $BCBOOST_options['cache_query_strings'] ) ) {
	// These params are appended by ad/analytics platforms and do not change page content.
	// The cache path already strips the query string, so we can safely serve cached HTML.
	$BCBOOST_tracking_params = [
		// Google Analytics / Ads.
		'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
		'gclid', 'gbraid', 'wbraid', 'gad_source',
		// Meta / Facebook.
		'fbclid', 'fb_source', 'fb_ref',
		// Microsoft Ads.
		'msclkid',
		// Mailchimp.
		'mc_cid', 'mc_eid',
		// Other.
		'referrer', '_ga', '_gl', 'twclid', 'ttclid', 'li_fat_id', 'igshid',
	];
	// Sanitize QUERY_STRING: allow only printable ASCII.
	// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; preg_replace provides equivalent sanitization.
	parse_str( preg_replace( '/[^\x20-\x7E]/', '', (string) ( $_SERVER['QUERY_STRING'] ?? '' ) ), $BCBOOST_qs_parsed );
	if ( ! empty( array_diff_key( $BCBOOST_qs_parsed, array_flip( $BCBOOST_tracking_params ) ) ) ) {
		// Non-tracking params present — bypass cache entirely.
		return;
	}
	// Only tracking params — path is already stripped above; fall through to serve cache.
}

// ─── Build Cache File Path ────────────────────────────────────────────────────

// Detect HTTPS: check direct SSL, reverse-proxy headers, and Cloudflare JSON.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; comparison against fixed strings is safe.
$BCBOOST_is_https = ( ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' )
	|| ( ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' )
	|| ( ! empty( $_SERVER['HTTP_CF_VISITOR'] ) && strpos( $_SERVER['HTTP_CF_VISITOR'], '"https"' ) !== false )
	|| ( ! empty( $_SERVER['HTTP_X_FORWARDED_SSL'] ) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on' )
	|| ( isset( $_SERVER['SERVER_PORT'] ) && (int) $_SERVER['SERVER_PORT'] === 443 );

$BCBOOST_scheme = $BCBOOST_is_https ? 'https' : 'http';

// Sanitize HTTP_HOST: strip port suffix and allow only alphanumeric, dots, hyphens, colons.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; preg_replace provides equivalent sanitization.
$BCBOOST_host = strtolower( preg_replace( '/:[0-9]+$/', '', preg_replace( '/[^a-zA-Z0-9.\-:]/', '', (string) ( $_SERVER['HTTP_HOST'] ?? '' ) ) ) );
// Sanitize REQUEST_URI: allow only safe URL characters and strip query string.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; preg_replace provides equivalent sanitization.
$BCBOOST_path = rtrim( strtok( preg_replace( '/[^a-zA-Z0-9\/_\-.]/', '', (string) ( $_SERVER['REQUEST_URI'] ?? '/' ) ), '?' ), '/' ) ?: '/';

if ( empty( $BCBOOST_host ) ) {
	return;
}

// ─── URL Exclusion Checks ─────────────────────────────────────────────────────

// Always-excluded paths.
$BCBOOST_always_excluded = [
	'/wp-admin',
	'/wp-login.php',
	'/wp-cron.php',
	'/xmlrpc.php',
	'/feed',
	'/sitemap',
	'/checkout',
	'/cart',
	'/my-account',
];

foreach ( $BCBOOST_always_excluded as $excl ) {
	if ( strpos( $BCBOOST_path, $excl ) === 0 ) {
		return;
	}
}

// Custom exclusion patterns from settings.
if ( ! empty( $BCBOOST_options['exclude_urls'] ) ) {
	$BCBOOST_custom_excl = array_filter( array_map( 'trim', explode( "\n", $BCBOOST_options['exclude_urls'] ) ) );
	foreach ( $BCBOOST_custom_excl as $pattern ) {
		if ( '' !== $pattern && ( false !== strpos( $BCBOOST_path, $pattern ) || fnmatch( $pattern, $BCBOOST_path ) ) ) {
			return;
		}
	}
}

// ─── Resolve Cache File (variant-aware) ───────────────────────────────────────
// The cache stores one file per visitor "variant" so geolocation / currency
// switching keeps working from cache. The stem is built identically to
// includes/Core/Cache_Context.php::file_stem() — KEEP THE TWO IN SYNC.

$BCBOOST_stem = 'index';

// Mobile-separate caching.
if ( ! empty( $BCBOOST_options['cache_mobile_separate'] ) ) {
	// Sanitize HTTP_USER_AGENT: allow only printable ASCII and limit length.
	// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; preg_replace and substr provide equivalent sanitization.
	$BCBOOST_ua = substr( preg_replace( '/[^\x20-\x7E]/', '', (string) ( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ), 0, 512 );
	if ( preg_match( '/Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi|webOS/i', $BCBOOST_ua ) ) {
		$BCBOOST_stem .= '-mobile';
	}
}

// ── BCBOOST VARIANCE (mirror of Cache_Context::variant_segment) ──────────────
$BCBOOST_vparts = array();

// Country variance.
if ( ! empty( $BCBOOST_options['cache_vary_by_country'] ) ) {
	$BCBOOST_country = '';
	$BCBOOST_geo_keys = array(
		'HTTP_CF_IPCOUNTRY', 'HTTP_CLOUDFRONT_VIEWER_COUNTRY', 'GEOIP_COUNTRY_CODE',
		'MM_COUNTRY_CODE', 'HTTP_X_COUNTRY_CODE', 'HTTP_X_GEO_COUNTRY',
	);
	foreach ( $BCBOOST_geo_keys as $BCBOOST_gk ) {
		if ( ! empty( $_SERVER[ $BCBOOST_gk ] ) ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- preg_replace sanitizes; WP functions unavailable in drop-in.
			$BCBOOST_c = strtoupper( preg_replace( '/[^A-Za-z]/', '', (string) $_SERVER[ $BCBOOST_gk ] ) );
			if ( strlen( $BCBOOST_c ) >= 2 ) {
				$BCBOOST_c = substr( $BCBOOST_c, 0, 2 );
				if ( 'XX' !== $BCBOOST_c && 'T1' !== $BCBOOST_c ) {
					$BCBOOST_country = $BCBOOST_c;
					break;
				}
			}
		}
	}
	if ( '' === $BCBOOST_country && ! empty( $_COOKIE['bcboost_country'] ) ) {
		$BCBOOST_c = strtoupper( preg_replace( '/[^A-Za-z]/', '', (string) $_COOKIE['bcboost_country'] ) );
		if ( strlen( $BCBOOST_c ) >= 2 ) {
			$BCBOOST_c = substr( $BCBOOST_c, 0, 2 );
			if ( 'XX' !== $BCBOOST_c && 'T1' !== $BCBOOST_c ) {
				$BCBOOST_country = $BCBOOST_c;
			}
		}
	}
	if ( '' === $BCBOOST_country ) {
		// Country unknown at the edge → let WordPress render it fresh (and set the
		// bcboost_country cookie) rather than risk serving the wrong country.
		header( 'X-BaseCloud-Boost: BYPASS-GEO' );
		return;
	}
	$BCBOOST_vparts[] = 'c=' . $BCBOOST_country;
}

// Cookie variance (currency switchers, etc.).
$BCBOOST_vary_cookies = array(
	'aelia_cs_selected_currency', 'aelia_customer_country', 'woocommerce_currency',
	'woocs_current_currency', 'wmc_current_currency', 'wcml_currency', 'wcpbc_country',
);
if ( ! empty( $BCBOOST_options['vary_cookies'] ) ) {
	foreach ( explode( "\n", (string) $BCBOOST_options['vary_cookies'] ) as $BCBOOST_vc ) {
		$BCBOOST_vc = trim( $BCBOOST_vc );
		if ( '' !== $BCBOOST_vc && ! in_array( $BCBOOST_vc, $BCBOOST_vary_cookies, true ) ) {
			$BCBOOST_vary_cookies[] = $BCBOOST_vc;
		}
	}
}
foreach ( $BCBOOST_vary_cookies as $BCBOOST_vc ) {
	if ( isset( $_COOKIE[ $BCBOOST_vc ] ) && '' !== (string) $_COOKIE[ $BCBOOST_vc ] ) {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- value only feeds an md5 cache key.
		$BCBOOST_vparts[] = 'k:' . $BCBOOST_vc . '=' . (string) $_COOKIE[ $BCBOOST_vc ];
	}
}

if ( ! empty( $BCBOOST_vparts ) ) {
	$BCBOOST_stem .= '--' . substr( md5( implode( '&', $BCBOOST_vparts ) ), 0, 12 );
}

$BCBOOST_base_file = $BCBOOST_cache_dir . $BCBOOST_scheme . '/' . $BCBOOST_host . $BCBOOST_path . '/' . $BCBOOST_stem;
$BCBOOST_html_file = $BCBOOST_base_file . '.html';

// ─── Serve Cache ──────────────────────────────────────────────────────────────

// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress functions not available in drop-in; used only for string matching.
$BCBOOST_accept = (string) ( $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '' );

if ( strpos( $BCBOOST_accept, 'br' ) !== false && file_exists( $BCBOOST_html_file . '.br' ) ) {
	$BCBOOST_serve = $BCBOOST_html_file . '.br';
	$BCBOOST_enc   = 'br';
} elseif ( strpos( $BCBOOST_accept, 'gzip' ) !== false && file_exists( $BCBOOST_html_file . '.gz' ) ) {
	$BCBOOST_serve = $BCBOOST_html_file . '.gz';
	$BCBOOST_enc   = 'gzip';
} elseif ( file_exists( $BCBOOST_html_file ) ) {
	$BCBOOST_serve = $BCBOOST_html_file;
	$BCBOOST_enc   = null;
} else {
	// Cache MISS — let WordPress handle the request normally.
	header( 'X-BaseCloud-Boost: MISS' );
	return;
}

// Check expiry.
if ( ( time() - (int) filemtime( $BCBOOST_serve ) ) > $BCBOOST_lifetime ) {
	header( 'X-BaseCloud-Boost: EXPIRED' );
	return;
}

// Late DONOTCACHEPAGE check (set by themes/plugins).
if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) {
	header( 'X-BaseCloud-Boost: BYPASS' );
	return;
}

// ─── Output ───────────────────────────────────────────────────────────────────

$BCBOOST_mtime = (int) filemtime( $BCBOOST_serve );
$BCBOOST_age   = time() - $BCBOOST_mtime;
$BCBOOST_etag  = '"' . dechex( $BCBOOST_mtime ) . '-' . dechex( filesize( $BCBOOST_serve ) ) . '"';

header( 'Content-Type: text/html; charset=UTF-8' );
header( 'X-BaseCloud-Boost: HIT' );
header( 'X-BCBoost-Age: ' . $BCBOOST_age );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $BCBOOST_mtime ) . ' GMT' );
header( 'ETag: ' . $BCBOOST_etag );

// ─── ETag / 304 Not Modified ─────────────────────────────────────────────────
// Check ETag first (more reliable than If-Modified-Since with CDNs).
if ( ! empty( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {
	$BCBOOST_client_etag = trim( $_SERVER['HTTP_IF_NONE_MATCH'] );
	if ( $BCBOOST_client_etag === $BCBOOST_etag || $BCBOOST_client_etag === '*' ) {
		header( 'HTTP/1.1 304 Not Modified' );
		exit;
	}
} elseif ( ! empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
	$BCBOOST_ims = strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
	if ( $BCBOOST_ims && $BCBOOST_ims >= $BCBOOST_mtime ) {
		header( 'HTTP/1.1 304 Not Modified' );
		exit;
	}
}

// ─── Cache-Control ────────────────────────────────────────────────────────────
// HTML is always revalidated by the browser (max-age=0) so a cache purge or
// content edit is reflected immediately — the ETag above makes that a cheap 304
// when nothing changed. A CDN may still cache at the edge via s-maxage; the
// plugin purges the edge on change, so the edge never serves stale content.
//
// EXCEPTION: this response is a per-visitor variant (country/cookie). A shared
// proxy/CDN that keys only on the URL would collapse every variant into one and
// serve e.g. one country's prices to everyone — the very bug variants fix. So a
// variant response is marked private and is never edge-cached.
$BCBOOST_is_variant = ! empty( $BCBOOST_vparts );

if ( $BCBOOST_is_variant ) {
	header( 'Cache-Control: private, max-age=0, must-revalidate' );
} elseif ( ! empty( $BCBOOST_options['cdn_enabled'] ) ) {
	$BCBOOST_edge_ttl = (int) ( $BCBOOST_options['cdn_edge_ttl'] ?? 86400 );
	$BCBOOST_cc       = 'public, max-age=0, s-maxage=' . $BCBOOST_edge_ttl . ', must-revalidate';
	if ( ! empty( $BCBOOST_options['stale_while_revalidate'] ) ) {
		$BCBOOST_cc .= ', stale-while-revalidate=' . $BCBOOST_edge_ttl;
	}
	header( 'Cache-Control: ' . $BCBOOST_cc );
} else {
	header( 'Cache-Control: public, max-age=0, must-revalidate' );
}
header( 'Vary: Accept-Encoding' );

if ( $BCBOOST_enc ) {
	header( 'Content-Encoding: ' . $BCBOOST_enc );
	header( 'Vary: Accept-Encoding' );
}

// ─── Track HIT (non-blocking, approximate) ───────────────────────────────────
// Uses a read-modify-write with a tmp+rename to avoid flock(), which fails on
// NFS-backed volumes (CloudWays) with "Inappropriate ioctl for device".

$BCBOOST_sf  = $BCBOOST_cache_dir . 'bcboost-stats.json';
$BCBOOST_raw = @file_get_contents( $BCBOOST_sf );
$BCBOOST_sd  = $BCBOOST_raw !== false ? @json_decode( $BCBOOST_raw, true ) : [];
if ( ! is_array( $BCBOOST_sd ) ) {
	$BCBOOST_sd = [];
}
$BCBOOST_sd['hits'] = ( (int) ( $BCBOOST_sd['hits'] ?? 0 ) ) + 1;
$BCBOOST_tmp = $BCBOOST_sf . '.tmp.' . getmypid();
if ( @file_put_contents( $BCBOOST_tmp, json_encode( $BCBOOST_sd ) ) !== false ) {
	@rename( $BCBOOST_tmp, $BCBOOST_sf );
} else {
	@unlink( $BCBOOST_tmp );
}

readfile( $BCBOOST_serve );
exit;

// phpcs:ignore Squiz.Commenting.InlineComment -- closes the fail-safe wrapper opened above.
} catch ( \Throwable $BCBOOST_dropin_error ) {
	// Never let the page-cache drop-in fatal a site — fall back to normal WordPress.
	return;
}
