##############################################################################
#  LumenCache — Global Nginx Template  (v1.0.2)
#  Light & Composition University
#
#  PURPOSE
#  ───────
#  Apply this block ONCE to the master Nginx server template in your
#  control panel (WHM "Apache & nginx Settings" → Nginx template, or
#  Plesk "Additional nginx directives" on the global server vhost template).
#
#  After this one-time change, every WordPress site on the server gains
#  Redis-backed full-page caching with zero per-site configuration.
#
#  PREREQUISITES
#  ─────────────
#  1. nginx must be compiled with ngx_http_redis module, OR use OpenResty
#     (which ships lua-resty-redis).
#     • Standard nginx: install nginx-mod-http-redis (RHEL/CentOS) or
#                       libnginx-mod-http-redis2 (Debian/Ubuntu).
#     • OpenResty: use the content_by_lua_block variant at the bottom.
#  2. Redis is running on 127.0.0.1:6379.
#  3. The LumenCache WordPress plugin is installed and activated on each site.
#
#  HOW IT WORKS
#  ────────────
#  1. Nginx receives a GET request.
#  2. The $redis_key variable is assembled from: scheme + method + host + URI.
#     This exactly mirrors the key the WordPress plugin writes on cache miss.
#  3. Nginx attempts to fetch the value from Redis (redis_pass directive).
#  4. On Redis HIT  → response served directly from memory; PHP never runs.
#  5. On Redis MISS → request falls through to @fallback → PHP-FPM.
#     The LumenCache plugin's shutdown hook writes the response to Redis.
#  6. If $lumen_state is set (active-session cookie present)
#     → request is routed to @fallback, bypassing Redis entirely.
#
#  CACHE KEY FORMAT
#  ────────────────
#  Prefix:   cache:   (matches LUMENCACHE_REDIS_PREFIX default)
#  Key body: {scheme}{method}{host}{request_uri}
#  Example:  cache:httpsGETexample.com/shop/
#
#  This key is constructed identically by Nginx ($redis_key below)
#  and the PHP plugin (Redis_Connector::build_nginx_key()).
##############################################################################


# ── 1. Session-cookie bypass map ─────────────────────────────────────────────
#
# Place this map{} block in your nginx.conf http{} context.
# It sets $lumen_state to "1" when a recognised dynamic-session cookie is
# present, routing the request around the cache entirely.

# (Paste into http { } context)
#
# BUG FIX (v1.6.6): this reference block was out of sync with the live
# generated map (Server_Config::nginx_map_block()) in two ways:
#   1. It bypassed on wp_woocommerce_session_, which WooCommerce sets for
#      EVERY visitor on first load — that would defeat caching for 100% of
#      anonymous WooCommerce traffic. Removed.
#   2. It was missing "memberpress_", which the PHP-side MemberPress driver
#      also treats as a valid session-cookie prefix alongside "mepr_"; a site
#      using that prefix would have Nginx serve a cached page to a logged-in
#      member. Added, along with the value-checked cart conditions the live
#      code uses so a stale cart cookie with value "0" doesn't force a bypass.
#
# map $http_cookie $lumen_state {
#     default                             "";
#     "~*sys_active_state=true"           1;   # LumenCache active-session cookie
#     "~*wordpress_logged_in_"            1;   # WordPress authentication
#     "~*woocommerce_items_in_cart=[1-9]" 1;   # WooCommerce cart has items
#     "~*woocommerce_cart_hash=."         1;   # WooCommerce cart hash present
#     "~*learndash_"                      1;   # LearnDash quiz/lesson cookie
#     "~*llms_"                           1;   # LifterLMS student cookie
#     "~*mepr_"                           1;   # MemberPress member cookie
#     "~*memberpress_"                    1;   # MemberPress member cookie (alt prefix)
# }


# ── 2. Primary location: try Redis first ─────────────────────────────────────

    location / {

        # Use HTTP 418 as a clean internal signal to the @fallback named location.
        # This avoids the fragile "if + proxy_pass + break" pattern.
        error_page 418 = @fallback;

        # Bypass Redis for authenticated or active-session users.
        if ($lumen_state = 1) {
            return 418;
        }

        # Only cache GET requests; POST/PUT/DELETE always go to PHP.
        if ($request_method != GET) {
            return 418;
        }

        # Build the Redis key. The "cache:" prefix matches LUMENCACHE_REDIS_PREFIX.
        set $redis_key "cache:$scheme$request_method$host$request_uri";

        # Attempt Redis lookup via ngx_http_redis module.
        redis_pass  127.0.0.1:6379;

        # Serve Redis content as HTML. Without this, some browsers receive
        # the response without a Content-Type header and render it incorrectly.
        default_type text/html;

        # Fallthrough to PHP on Redis miss (404) or Redis errors (502/504).
        error_page 404 502 504 = @fallback;

        # Expose cache-hit status in response headers (useful for QA).
        add_header X-Cache-Status "HIT";
        add_header X-Cache-Key    $redis_key;
    }


# ── 3. Fallback: PHP backend ──────────────────────────────────────────────────
#
# cPanel / WHM (Nginx in front of Apache) — use proxy_pass:

    location @fallback {

        # Pass to your existing PHP/Apache upstream.
        # Replace "php_backend" with your actual upstream name
        # (e.g. "127.0.0.1:8080" for Apache on an alternate port, or a
        # named upstream block from your upstream{} directive).
        proxy_pass http://php_backend;

        # Standard proxy headers.
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;

        # Cloudflare real-IP passthrough (harmless when not behind Cloudflare).
        proxy_set_header CF-Connecting-IP  $http_cf_connecting_ip;

        # Increase timeouts for long-running WooCommerce / LMS operations.
        proxy_connect_timeout   60s;
        proxy_send_timeout      120s;
        proxy_read_timeout      120s;

        add_header X-Cache-Status "MISS";
    }

    # ── Alternative: direct FastCGI (Nginx → PHP-FPM without Apache) ──────────
    # Uncomment this block and comment out the proxy_pass @fallback above
    # if your stack is Nginx → PHP-FPM directly (no Apache layer).
    #
    # location @fallback {
    #     include         fastcgi_params;
    #     fastcgi_pass    unix:/run/php/php8.3-fpm.sock;   # adjust to your PHP version
    #     fastcgi_index   index.php;
    #     fastcgi_param   SCRIPT_FILENAME $document_root/index.php;
    #
    #     fastcgi_param   HTTP_X_REAL_IP        $http_cf_connecting_ip;
    #     fastcgi_param   HTTP_X_FORWARDED_FOR  $proxy_add_x_forwarded_for;
    #     fastcgi_param   HTTP_X_FORWARDED_PROTO $scheme;
    #
    #     fastcgi_connect_timeout  60s;
    #     fastcgi_send_timeout     120s;
    #     fastcgi_read_timeout     120s;
    #     fastcgi_buffers          16 16k;
    #     fastcgi_buffer_size      32k;
    #
    #     add_header X-Cache-Status "MISS";
    # }


# ── 4. Static assets — never hit Redis or PHP ─────────────────────────────────

    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|otf|eot|mp4|webm|pdf|zip)$ {
        expires     max;
        log_not_found off;
        access_log  off;
        add_header  Cache-Control "public, immutable";
    }


# ── 5. WordPress-specific bypass rules ───────────────────────────────────────
#
# These paths must always reach PHP directly.

    location ~* ^/(wp-admin|wp-login\.php|wp-cron\.php|wp-json|xmlrpc\.php) {
        proxy_pass http://php_backend;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

##############################################################################
#  OPENRESTY / LUA VARIANT
#  ─────────────────────────
#  If you are using OpenResty and prefer lua-resty-redis over ngx_http_redis,
#  replace the location / block above with the following.
#  The map{} block and all other locations stay the same.
#
#  location / {
#      error_page 418 = @fallback;
#
#      if ($lumen_state = 1) { return 418; }
#      if ($request_method != GET) { return 418; }
#
#      content_by_lua_block {
#          local redis = require "resty.redis"
#          local red   = redis:new()
#          red:set_timeouts(250, 0, 250)
#
#          local ok, err = red:connect("127.0.0.1", 6379)
#          if not ok then
#              ngx.exec("@fallback")
#              return
#          end
#
#          local scheme = ngx.var.scheme
#          local method = ngx.var.request_method
#          local host   = ngx.var.host
#          local uri    = ngx.var.request_uri
#          local raw    = scheme .. method .. host .. uri
#
#          -- Armour Layer I: Cryptographic Key Salting
#          -- Set salt to your LUMENCACHE_CACHE_SALT value, or leave as "" to disable.
#          -- Must match the value defined in wp-config.php.
#          local salt   = ""   -- e.g. "your-long-random-string"
#
#          local key
#          if salt ~= "" then
#              local sha256 = require "resty.sha256"
#              local str    = require "resty.string"
#              local d      = sha256:new()
#              d:update(salt .. raw)
#              key = "cache:" .. str.to_hex(d:final())
#          else
#              key = "cache:" .. raw
#          end
#
#          local res, err = red:get(key)
#          red:set_keepalive(10000, 100)
#
#          if not res or res == ngx.null then
#              ngx.exec("@fallback")
#              return
#          end
#
#          ngx.header["X-Cache-Status"] = "HIT"
#          ngx.header["Content-Type"]   = "text/html; charset=utf-8"
#          ngx.say(res)
#      }
#  }
##############################################################################
