<?php
/**
 * CacheLoop Advanced Cache Drop-in
 *
 * This file is loaded by WordPress extremely early (before DB connection)
 * if WP_CACHE is true. It serves the static HTML cache if it exists.
 *
 * @package CacheLoop
 */

defined( 'ABSPATH' ) || exit;

// Load runtime configuration (written by the plugin with current settings).
// This is a plain JSON data file, not executable code — decoded and turned
// into the same CACHELOOP_* constants the rest of this drop-in expects.
$cacheloop_config_file = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/cache/cacheloop/runtime-config.json' : ABSPATH . 'wp-content/cache/cacheloop/runtime-config.json';
if ( file_exists( $cacheloop_config_file ) ) {
	// A concurrent flush/settings-save can remove this exact file between the
	// file_exists() check above and here — fail soft (use defaults) instead
	// of a read warning. A warning here is worse than usual: this file runs
	// pre-bootstrap, so it would trigger "headers already sent" for the rest
	// of the request.
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents -- Pre-bootstrap context: WP_Filesystem not available yet.
	$cacheloop_config_raw = @file_get_contents( $cacheloop_config_file );
	$cacheloop_config     = is_string( $cacheloop_config_raw ) ? json_decode( $cacheloop_config_raw, true ) : null;

	if ( is_array( $cacheloop_config ) ) {
		if ( ! defined( 'CACHELOOP_VERSION' ) && isset( $cacheloop_config['version'] ) ) {
			define( 'CACHELOOP_VERSION', (string) $cacheloop_config['version'] );
		}
		if ( ! defined( 'CACHELOOP_TTL' ) && isset( $cacheloop_config['ttl'] ) ) {
			define( 'CACHELOOP_TTL', (int) $cacheloop_config['ttl'] );
		}
		if ( ! defined( 'CACHELOOP_DEVICE_SPLIT' ) && isset( $cacheloop_config['device_split'] ) ) {
			define( 'CACHELOOP_DEVICE_SPLIT', (int) $cacheloop_config['device_split'] );
		}
		if ( ! defined( 'CACHELOOP_CDN_ENABLED' ) && isset( $cacheloop_config['cdn_enabled'] ) ) {
			define( 'CACHELOOP_CDN_ENABLED', (int) $cacheloop_config['cdn_enabled'] );
		}
		if ( ! defined( 'CACHELOOP_CDN_DOMAIN' ) && isset( $cacheloop_config['cdn_domain'] ) ) {
			define( 'CACHELOOP_CDN_DOMAIN', (string) $cacheloop_config['cdn_domain'] );
		}
		if ( ! defined( 'CACHELOOP_HOME_URL' ) && isset( $cacheloop_config['home_url'] ) ) {
			define( 'CACHELOOP_HOME_URL', (string) $cacheloop_config['home_url'] );
		}
		if ( ! defined( 'CACHELOOP_BYPASS_COOKIES' ) && isset( $cacheloop_config['bypass_cookies'] ) ) {
			define( 'CACHELOOP_BYPASS_COOKIES', (string) $cacheloop_config['bypass_cookies'] );
		}
	}
}

/**
 * CacheLoop Drop-in Logic
 */
class CacheLoop_Advanced_Cache {

	public static function serve_cache() {
		// 1. Bypass logic
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Read-only access in pre-bootstrap context.
		$request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET';
		if ( 'GET' !== $request_method ) {
			return false;
		}

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Read-only access in pre-bootstrap context.
		$query_string = isset( $_SERVER['QUERY_STRING'] ) ? $_SERVER['QUERY_STRING'] : '';
		if ( ! empty( $query_string ) ) {
			return false;
		}

		// Bypass for logged-in users or active WooCommerce carts.
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Read-only access in pre-bootstrap context.
		$cookies = isset( $_SERVER['HTTP_COOKIE'] ) ? $_SERVER['HTTP_COOKIE'] : '';
		if ( preg_match( '/wordpress_logged_in_|wp-postpass_|woocommerce_items_in_cart/i', $cookies ) ) {
			return false;
		}

		// Generic cookie-based bypass (custom cookies set by plugins/themes).
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Read-only access in pre-bootstrap context.
		if ( ! empty( $cookies ) && defined( 'CACHELOOP_BYPASS_COOKIES' ) ) {
			$bypass_cookies = explode( ',', CACHELOOP_BYPASS_COOKIES );
			foreach ( $bypass_cookies as $cookie ) {
				$cookie = trim( $cookie );
				if ( $cookie && strpos( $cookies, $cookie ) !== false ) {
					return false;
				}
			}
		}

		// 2. Determine file path
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Read-only access in pre-bootstrap context.
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/';

		// Strip query string from URI just in case
        // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Pre-bootstrap context: wp_parse_url not available yet.
		$parsed_url = parse_url( $request_uri );
		$path       = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/';

		// Use version from runtime config; fall back to embedded default.
		$version = defined( 'CACHELOOP_VERSION' ) ? CACHELOOP_VERSION : '1.0.3';

		// Optionally include device type in hash for mobile/desktop differentiation.
		$device_suffix = '';
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Pre-bootstrap context.
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
		if ( defined( 'CACHELOOP_DEVICE_SPLIT' ) && CACHELOOP_DEVICE_SPLIT ) {
			if ( preg_match( '/Mobile|Android|iPod|iPhone|iPad|Opera Mini|IEMobile|WPDesktop/i', $user_agent ) ) {
				$device_suffix = 'mobile';
			} else {
				$device_suffix = 'desktop';
			}
		}

		$uri_hash = md5( $path . $version . $device_suffix );
		$sub_dir  = substr( $uri_hash, 0, 2 );

		$cache_dir  = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/cache/cacheloop/' : ABSPATH . 'wp-content/cache/cacheloop/';
		$cache_file = $cache_dir . $sub_dir . '/' . $uri_hash . '.html';

		// 3. Serve file if it exists and is within configured TTL.
		if ( file_exists( $cache_file ) ) {
			$ttl = defined( 'CACHELOOP_TTL' ) ? (int) CACHELOOP_TTL : 86400;
			if ( ( time() - filemtime( $cache_file ) ) < $ttl ) {
				// Prevent double-serving: if another process already handled this request.
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- internal marker this file sets itself below, not user input.
				if ( isset( $_SERVER['CACHELOOP_DROPIN_SERVED'] ) && $_SERVER['CACHELOOP_DROPIN_SERVED'] ) {
					return false;
				}

				// Send aggressive cache headers
				header( 'Cache-Control: public, max-age=3600, stale-while-revalidate=86400' );
				header( 'X-CacheLoop-Cache: HIT' );

				// Mark as served BEFORE outputting, so any parallel/bypass path sees it.
				$_SERVER['CACHELOOP_DROPIN_SERVED'] = true;

				// Record the hit for the HealthLoop bridge's stats. This is a
				// DELIBERATE DUPLICATE of CACHELOOP_Stats::record_hit()/append()
				// in includes/class-cl-stats.php — that class isn't reachable
				// from here (this file runs pre-bootstrap, before plugins load
				// and before CACHELOOP_PLUGIN_DIR exists), so the same
				// lock-free single-line append is inlined instead. If you
				// change the log file naming, the write, or the stats
				// directory in either place, update the other or the two will
				// silently drift apart.
				$cacheloop_stats_dir = $cache_dir . 'stats/';
				if ( ! is_dir( $cacheloop_stats_dir ) ) {
					// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- Pre-bootstrap context: wp_mkdir_p() not available yet.
					@mkdir( $cacheloop_stats_dir, 0755, true );
				}
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- deliberate lock-free append into wp-content/cache/; see class-cl-stats.php's docblock for the concurrency reasoning.
				@file_put_contents( $cacheloop_stats_dir . gmdate( 'Y-m-d' ) . '-hits.log', "1\n", FILE_APPEND );

				// Serve the file
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Pre-bootstrap context: WP_Filesystem not available yet.
				$raw_html = file_get_contents( $cache_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents

				// CDN URL rewriting (pre-bootstrap context: no WP functions available).
				if ( defined( 'CACHELOOP_CDN_ENABLED' ) && CACHELOOP_CDN_ENABLED && defined( 'CACHELOOP_CDN_DOMAIN' ) && ! empty( CACHELOOP_CDN_DOMAIN ) ) {
					$cdn_url = 'https://' . CACHELOOP_CDN_DOMAIN;
                    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Pre-bootstrap context: wp_unslash/sanitize_text_field not available yet; used only for string comparison below, never output.
					$home_url = defined( 'CACHELOOP_HOME_URL' ) ? CACHELOOP_HOME_URL : ( ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ? 'https' : 'http' ) . '://' . ( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) );

					// Basic attribute-level replacement.
					$raw_html = str_replace( $home_url . '/', $cdn_url . '/', $raw_html );

					// Extract host from home_url for regex.
                    // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Pre-bootstrap context: wp_parse_url not available yet.
					$parsed = parse_url( $home_url );
					$host   = isset( $parsed['host'] ) ? $parsed['host'] : '.*';

					// Inline CSS url() rewriting: url(http://host/path)
					$raw_html = preg_replace_callback(
						'#url\(\s*[\'"]?https?://' . preg_quote( $host, '#' ) . '/([^\'"\)]+)\s*[\'"]?\)#',
						function ( $matches ) use ( $cdn_url ) {
							return 'url(\'' . $cdn_url . '/' . $matches[1] . '\')';
						},
						$raw_html
					);

					// Protocol-relative url(): url(//host/path)
					$raw_html = preg_replace_callback(
						'#url\(\s*[\'"]?//' . preg_quote( $host, '#' ) . '/([^\'"\)]+)\s*[\'"]?\)#',
						function ( $matches ) use ( $cdn_url ) {
							return 'url(\'' . $cdn_url . '/' . $matches[1] . '\')';
						},
						$raw_html
					);
				}

                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached HTML from our own generation.
				echo $raw_html;
				exit; // Stop WordPress entirely!
			}
		}

		// Inform via headers that cache was missed
		header( 'X-CacheLoop-Cache: MISS' );
		return false;
	}
}

// Execute the drop-in!
CacheLoop_Advanced_Cache::serve_cache();
