<?php
session_start();
require __DIR__ . '/auth.php';
require __DIR__ . '/includes/admin_lib.php';
$pageTitle = 'Web Stats — Admin';

/**
 * Cloudflare Web Stats (GraphQL) - Almost Heaven Radio
 * Uses the SAME working query + parsing as OldiesAndMore,
 * but loads secrets from:
 *   C:\inetpub\Authentication\api\almostheaven.php
 */

$configPath = 'C:/inetpub/Authentication/api/almostheaven.php';

$CF_ZONE_ID   = '';
$CF_API_TOKEN = '';

if (file_exists($configPath)) {
    $cf = require $configPath;
    $CF_ZONE_ID   = trim((string)($cf['zone_id'] ?? ''));
    $CF_API_TOKEN = trim((string)($cf['api_token'] ?? ''));
}

$tokenMissing = ($CF_API_TOKEN === '' || preg_match('/\s/', $CF_API_TOKEN) || $CF_ZONE_ID === '');

function iso_utc(int $ts): string { return gmdate('Y-m-d\TH:i:s\Z', $ts); }
function fmt_int($n): string { if ($n === null || $n === '') return '—'; return number_format((float)$n, 0, '.', ','); }
function fmt_bytes($bytes): string { if ($bytes === null || $bytes === '') return '—'; $bytes = (float)$bytes; $units = ['B','KB','MB','GB','TB']; $i = 0; while ($bytes >= 1024 && $i < count($units)-1) { $bytes /= 1024; $i++; } return number_format($bytes, $i === 0 ? 0 : 2) . ' ' . $units[$i]; }
function fmt_pct($n): string { if ($n === null || $n === '') return '—'; return rtrim(rtrim(number_format((float)$n, 2, '.', ''), '0'), '.') . '%'; }
function cf_rest_get(string $url, string $token): array { $token = trim((string)$token); $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,CURLOPT_TIMEOUT => 30,CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token,'Accept: application/json','Content-Type: application/json',],]); $raw = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($raw === false) return ['success' => false, 'errors' => [['message' => 'cURL error: ' . $err]], '_http_code' => $code]; $json = json_decode($raw, true); if (!is_array($json)) return ['success' => false, 'errors' => [['message' => 'Invalid JSON. HTTP ' . $code]], '_http_code' => $code, '_raw' => substr((string)$raw, 0, 300)]; $json['_http_code'] = $code; return $json; }
function cf_gql(string $query, array $vars, string $token): array { $token = trim((string)$token); $ch = curl_init("https://api.cloudflare.com/client/v4/graphql"); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,CURLOPT_TIMEOUT => 30,CURLOPT_POST => true,CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token,'Content-Type: application/json','Accept: application/json',],CURLOPT_POSTFIELDS => json_encode(['query' => $query,'variables' => $vars,]),]); $raw = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($raw === false) return ['_http_code' => $code, '_raw' => null, 'errors' => [['message' => 'cURL error: ' . $err]]]; $json = json_decode($raw, true); if (!is_array($json)) return ['_http_code' => $code, '_raw' => substr((string)$raw, 0, 500), 'errors' => [['message' => 'Invalid JSON from GraphQL']]]; $json['_http_code'] = $code; return $json; }
function extract_http_1h_groups(array $gql): array { $groups = $gql['data']['viewer']['zones'][0]['httpRequests1hGroups'] ?? []; $series = []; foreach ($groups as $row) { $series[] = ['t' => $row['dimensions']['datetime'] ?? '','requests' => $row['sum']['requests'] ?? null,'bytes' => $row['sum']['bytes'] ?? null,'cachedRequests' => $row['sum']['cachedRequests'] ?? null,'cachedBytes' => $row['sum']['cachedBytes'] ?? null,'threats' => $row['sum']['threats'] ?? null,'pageViews' => $row['sum']['pageViews'] ?? null,'uniques' => $row['uniq']['uniques'] ?? null,]; } return $series; }
function totals_from_series(array $series): array { $tot = ['requests' => 0,'bytes' => 0,'cachedRequests' => 0,'cachedBytes' => 0,'threats' => 0,'pageViews' => 0,'uniques' => null,'cacheHitPct' => null,]; $maxUniques = null; foreach ($series as $r) { $tot['requests'] += (int)($r['requests'] ?? 0); $tot['bytes'] += (int)($r['bytes'] ?? 0); $tot['cachedRequests'] += (int)($r['cachedRequests'] ?? 0); $tot['cachedBytes'] += (int)($r['cachedBytes'] ?? 0); $tot['threats'] += (int)($r['threats'] ?? 0); $tot['pageViews'] += (int)($r['pageViews'] ?? 0); if ($r['uniques'] !== null) { $u = (int)$r['uniques']; $maxUniques = ($maxUniques === null) ? $u : max($maxUniques, $u); } } $tot['uniques'] = $maxUniques; $tot['cacheHitPct'] = ($tot['requests'] > 0) ? round(($tot['cachedRequests'] / $tot['requests']) * 100, 2) : null; return $tot; }
function sparkline(array $values, int $w=220, int $h=40): string { $vals = array_values(array_filter($values, fn($v) => $v !== null && $v !== '')); if (count($vals) < 2) return '<span class="text-muted">—</span>'; $min = min($vals); $max = max($vals); $range = ($max - $min) ?: 1; $pts = []; $n = count($vals); for ($i=0; $i<$n; $i++) { $x = ($w - 4) * ($i / ($n - 1)) + 2; $y = ($h - 4) * (1 - (($vals[$i] - $min) / $range)) + 2; $pts[] = round($x,2) . ',' . round($y,2); } return '<svg width="'.$w.'" height="'.$h.'" viewBox="0 0 '.$w.' '.$h.'" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="trend" style="color:#111;"><polyline fill="none" stroke="currentColor" stroke-width="2" points="'.implode(' ', $pts).'"/></svg>'; }

$now = time();
$until = iso_utc($now);
$since24 = iso_utc($now - 86400);
$since72 = iso_utc($now - 86400 * 3);
$GQL = <<<'GQL'
query($zoneTag: String!, $since: DateTime!, $until: DateTime!) {
  viewer {
    zones(filter: { zoneTag: $zoneTag }) {
      httpRequests1hGroups(limit: 1000 filter: { datetime_geq: $since, datetime_leq: $until }) {
        dimensions { datetime }
        sum { requests bytes cachedRequests cachedBytes threats pageViews }
        uniq { uniques }
      }
    }
  }
}
GQL;
$verify = $tokenMissing ? ['success'=>false] : cf_rest_get('https://api.cloudflare.com/client/v4/user/tokens/verify', $CF_API_TOKEN);
$gql24 = (!$tokenMissing && !empty($verify['success'])) ? cf_gql($GQL, ['zoneTag' => $CF_ZONE_ID, 'since' => $since24, 'until' => $until], $CF_API_TOKEN) : [];
$gql72 = (!$tokenMissing && !empty($verify['success'])) ? cf_gql($GQL, ['zoneTag' => $CF_ZONE_ID, 'since' => $since72, 'until' => $until], $CF_API_TOKEN) : [];
$series24 = extract_http_1h_groups($gql24);
$series72 = extract_http_1h_groups($gql72);
$tot24 = totals_from_series($series24);
$tot72 = totals_from_series($series72);
include __DIR__ . '/includes/admin_header.php';
?>
<section class="content-card">
  <h1 class="admin-title">📈 Web Stats</h1>
  <p style="opacity:.85;">Almost Heaven admin shell now matches the rest of the site. This page reads Cloudflare analytics for the Almost Heaven zone and shows the same key metrics the public/admin workflow expects.</p>
  <?php if ($tokenMissing): ?>
    <div class="content-card" style="text-align:left;border-color:rgba(255,120,120,.55);">Cloudflare credentials were not loaded from <code><?= smurf_h($configPath) ?></code>. Add the zone ID and API token there to populate stats.</div>
  <?php elseif (empty($verify['success'])): ?>
    <div class="content-card" style="text-align:left;border-color:rgba(255,120,120,.55);">Token verification failed. Check the API token and zone ID in <code><?= smurf_h($configPath) ?></code>.</div>
  <?php endif; ?>
  <div class="admin-grid-2" style="margin-top:16px;">
    <div class="content-card" style="text-align:left;">
      <h3 style="margin-top:0;">Last 24 Hours</h3>
      <table class="admin-table"><tbody>
        <tr><th>Total requests</th><td><?= fmt_int($tot24['requests']) ?></td></tr>
        <tr><th>Unique visitors</th><td><?= fmt_int($tot24['uniques']) ?></td></tr>
        <tr><th>Bandwidth</th><td><?= fmt_bytes($tot24['bytes']) ?></td></tr>
        <tr><th>Cache hit ratio</th><td><?= fmt_pct($tot24['cacheHitPct']) ?></td></tr>
        <tr><th>Threats</th><td><?= fmt_int($tot24['threats']) ?></td></tr>
        <tr><th>Page views</th><td><?= fmt_int($tot24['pageViews']) ?></td></tr>
      </tbody></table>
      <div style="margin-top:10px;"><strong>Trend:</strong> <?= sparkline(array_column($series24, 'requests')) ?></div>
    </div>
    <div class="content-card" style="text-align:left;">
      <h3 style="margin-top:0;">Last 72 Hours</h3>
      <table class="admin-table"><tbody>
        <tr><th>Total requests</th><td><?= fmt_int($tot72['requests']) ?></td></tr>
        <tr><th>Unique visitors</th><td><?= fmt_int($tot72['uniques']) ?></td></tr>
        <tr><th>Bandwidth</th><td><?= fmt_bytes($tot72['bytes']) ?></td></tr>
        <tr><th>Cache hit ratio</th><td><?= fmt_pct($tot72['cacheHitPct']) ?></td></tr>
        <tr><th>Threats</th><td><?= fmt_int($tot72['threats']) ?></td></tr>
        <tr><th>Page views</th><td><?= fmt_int($tot72['pageViews']) ?></td></tr>
      </tbody></table>
      <div style="margin-top:10px;"><strong>Trend:</strong> <?= sparkline(array_column($series72, 'requests')) ?></div>
    </div>
  </div>
</section>
<?php include __DIR__ . '/includes/admin_footer.php'; ?>
