<?php
/**
 * First-party bot logger.
 *
 * .htaccess rewrites bot-UA page requests here (mode=serve) and blocked
 * SEO-scraper requests here (ao_mode=block). Logs one Apache combined-format
 * line to ../logs/bot_access_YYYY-MM.log — a sibling of public_html, chosen
 * because the deploy wipes public_html on every run (dangerous-clean-slate),
 * and because a path outside the web root is unreachable by URL. Then serves
 * the exact static file the bot asked for — content, status, and headers
 * mirror direct LiteSpeed serving. Humans never hit this script.
 *
 * Fetch logs:   node .agent/scripts/fetch_access_logs.cjs   (repo root)
 * Analyze:      node .agent/scripts/analyze_access_logs.cjs <log>
 */

error_reporting(0);

$docroot = __DIR__;
$method  = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$uri     = $_SERVER['REQUEST_URI'] ?? '/';
$path    = parse_url($uri, PHP_URL_PATH);
if ($path === false || $path === null || $path === '') {
    $path = '/';
}
$blocked = (($_GET['ao_mode'] ?? '') === 'block');

// --- resolve what would be served, mirroring LiteSpeed static behavior ------
$status = 200;
$file   = null;
$redirect = null;

$clean = str_replace(chr(0), '', urldecode($path));
if ($blocked) {
    $status = 403;
} elseif (!in_array($method, ['GET', 'HEAD'], true)) {
    $status = 405;
} elseif (strpos($clean, '..') !== false || strpos($clean, '/private') === 0 || $clean === '/ao_bot_logger.php') {
    $status = 404;
} else {
    $candidate = $docroot . $clean;
    if (substr($clean, -1) === '/') {
        $candidate .= 'index.html';
    }
    $real = realpath($candidate);
    if ($real !== false && strpos($real, $docroot) === 0 && is_file($real)) {
        $file = $real;
    } elseif (substr($clean, -1) !== '/' && is_dir($docroot . $clean)) {
        // mimic mod_dir DirectorySlash: /about-us -> /about-us/
        $redirect = $clean . '/';
        $status = 301;
    } else {
        $status = 404;
    }
}

// --- log (never let logging failures affect the response) -------------------
$ip  = $_SERVER['REMOTE_ADDR'] ?? '-';
$ua  = $_SERVER['HTTP_USER_AGENT'] ?? '-';
$ref = $_SERVER['HTTP_REFERER'] ?? '-';
$bytes = ($file && $method !== 'HEAD') ? (filesize($file) ?: 0) : 0;
$line = sprintf(
    "%s - - [%s] \"%s %s HTTP/1.1\" %d %d \"%s\" \"%s\"\n",
    $ip,
    gmdate('d/M/Y:H:i:s +0000'),
    $method,
    str_replace(['"', "\n", "\r"], '', $uri),
    $status,
    $bytes,
    str_replace(['"', "\n", "\r"], '', $ref),
    str_replace(['"', "\n", "\r"], '', $ua)
);
// Log OUTSIDE the document root. deploy.yml runs FTP-Deploy-Action with
// dangerous-clean-slate, which wipes public_html on every scheduled run — a
// log inside it can never survive one deploy interval. A sibling of
// public_html survives, and is not web-reachable at all (stronger than
// relying on private/.htaccess deny-all). Falls back to the legacy path if
// the sibling is not writable, so a permissions problem degrades rather than
// silently ending crawler visibility.
$logName = 'bot_access_' . gmdate('Y-m') . '.log';
$logDir  = dirname($docroot) . '/logs';
if (!is_dir($logDir)) {
    @mkdir($logDir, 0750, true);
}
$logFile = (is_dir($logDir) && is_writable($logDir))
    ? $logDir . '/' . $logName
    : $docroot . '/private/' . $logName;
// Monitoring probes are not crawler traffic: UptimeRobot's UA matches the
// .htaccess bot pattern (contains "robot") and was polluting the crawler log
// at ~288 hits/day, which would swamp the weekly digest's crawler mix
// (2026-07-21). Serve it normally, log nothing.
$isMonitorProbe = (stripos($ua, 'uptimerobot') !== false);
if (!$isMonitorProbe) {
    @file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
}

// --- respond -----------------------------------------------------------------
if ($blocked) {
    http_response_code(403);
    header('Content-Type: text/plain; charset=utf-8');
    echo "403 Forbidden\n";
    exit;
}
if ($status === 405) {
    http_response_code(405);
    header('Allow: GET, HEAD');
    exit;
}
if ($redirect !== null) {
    http_response_code(301);
    header('Location: https://atlasoptima.com' . $redirect);
    exit;
}
if ($status === 404 || $file === null) {
    http_response_code(404);
    $notFound = $docroot . '/404.html';
    header('Content-Type: text/html; charset=utf-8');
    if (is_file($notFound) && $method !== 'HEAD') {
        readfile($notFound);
    }
    exit;
}

$types = [
    'html' => 'text/html; charset=utf-8',
    'htm'  => 'text/html; charset=utf-8',
    'txt'  => 'text/plain; charset=utf-8',
    'xml'  => 'application/xml; charset=utf-8',
    'json' => 'application/json; charset=utf-8',
    'svg'  => 'image/svg+xml',
];
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
header('Content-Type: ' . ($types[$ext] ?? 'application/octet-stream'));
// mirror the .htaccess cache policy for html/xml/txt/json
header('Cache-Control: public, max-age=300, must-revalidate');
header('Content-Length: ' . (filesize($file) ?: 0));
if ($method !== 'HEAD') {
    readfile($file);
}
