Excludes analytics/data, vendor, support, .env, ACME tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
129 lines
4.9 KiB
PHP
129 lines
4.9 KiB
PHP
<?php
|
|
/* Version: 1.0.0 | Created: 2026-04-08 | Updated: 2026-04-08 */
|
|
/* HELP4BIS Analytics — Event collector endpoint */
|
|
|
|
header('Content-Type: application/json');
|
|
header('Access-Control-Allow-Origin: https://help4bis.com');
|
|
header('Access-Control-Allow-Methods: POST');
|
|
header('Access-Control-Allow-Headers: Content-Type');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
http_response_code(405);
|
|
echo json_encode(['error' => 'Method not allowed']);
|
|
exit;
|
|
}
|
|
|
|
// Read JSON body
|
|
$raw = file_get_contents('php://input');
|
|
$data = json_decode($raw, true);
|
|
|
|
if (!$data || empty($data['event']) || empty($data['page'])) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Invalid payload']);
|
|
exit;
|
|
}
|
|
|
|
// Open/create SQLite database
|
|
$dbPath = __DIR__ . '/data/analytics.db';
|
|
$dbDir = dirname($dbPath);
|
|
if (!is_dir($dbDir)) {
|
|
mkdir($dbDir, 0750, true);
|
|
}
|
|
|
|
try {
|
|
$db = new SQLite3($dbPath);
|
|
$db->busyTimeout(5000);
|
|
$db->exec('PRAGMA journal_mode=WAL');
|
|
$db->exec('PRAGMA synchronous=NORMAL');
|
|
|
|
// Tables and indexes are pre-created via CLI setup.
|
|
// See analytics/setup.php for schema if DB needs recreation.
|
|
|
|
// Privacy: hash the IP, don't store raw
|
|
$ipHash = hash('sha256', ($_SERVER['REMOTE_ADDR'] ?? '') . date('Y-m-d'));
|
|
$uaHash = hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? '');
|
|
$sessionId = hash('sha256', $ipHash . $uaHash);
|
|
|
|
// Extract UTM params
|
|
$utm = $data['utm'] ?? [];
|
|
|
|
// Extract extra data (duration, scroll depth, href, etc.)
|
|
$extra = [];
|
|
foreach (['seconds', 'depth', 'href', 'text', 'form_id', 'form_action'] as $key) {
|
|
if (isset($data[$key])) {
|
|
$extra[$key] = $data[$key];
|
|
}
|
|
}
|
|
|
|
// Insert event
|
|
$now = gmdate('Y-m-d H:i:s');
|
|
$stmt = $db->prepare('INSERT INTO events (event, page, title, referrer, screen, language,
|
|
utm_source, utm_medium, utm_campaign, utm_term, utm_content,
|
|
extra, ip_hash, user_agent_hash, session_id, created_at)
|
|
VALUES (:event, :page, :title, :referrer, :screen, :language,
|
|
:utm_source, :utm_medium, :utm_campaign, :utm_term, :utm_content,
|
|
:extra, :ip_hash, :ua_hash, :session_id, :created_at)');
|
|
|
|
$stmt->bindValue(':event', substr($data['event'], 0, 50), SQLITE3_TEXT);
|
|
$stmt->bindValue(':page', substr($data['page'], 0, 500), SQLITE3_TEXT);
|
|
$stmt->bindValue(':title', substr($data['title'] ?? '', 0, 200), SQLITE3_TEXT);
|
|
$stmt->bindValue(':referrer', substr($data['referrer'] ?? '', 0, 500), SQLITE3_TEXT);
|
|
$stmt->bindValue(':screen', substr($data['screen'] ?? '', 0, 20), SQLITE3_TEXT);
|
|
$stmt->bindValue(':language', substr($data['language'] ?? '', 0, 10), SQLITE3_TEXT);
|
|
$stmt->bindValue(':utm_source', substr($utm['utm_source'] ?? '', 0, 100), SQLITE3_TEXT);
|
|
$stmt->bindValue(':utm_medium', substr($utm['utm_medium'] ?? '', 0, 100), SQLITE3_TEXT);
|
|
$stmt->bindValue(':utm_campaign', substr($utm['utm_campaign'] ?? '', 0, 100), SQLITE3_TEXT);
|
|
$stmt->bindValue(':utm_term', substr($utm['utm_term'] ?? '', 0, 100), SQLITE3_TEXT);
|
|
$stmt->bindValue(':utm_content', substr($utm['utm_content'] ?? '', 0, 100), SQLITE3_TEXT);
|
|
$stmt->bindValue(':extra', !empty($extra) ? json_encode($extra) : null, SQLITE3_TEXT);
|
|
$stmt->bindValue(':ip_hash', $ipHash, SQLITE3_TEXT);
|
|
$stmt->bindValue(':ua_hash', $uaHash, SQLITE3_TEXT);
|
|
$stmt->bindValue(':session_id', $sessionId, SQLITE3_TEXT);
|
|
$stmt->bindValue(':created_at', $now, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
|
|
// Update daily stats for pageviews
|
|
if ($data['event'] === 'pageview') {
|
|
$today = date('Y-m-d');
|
|
$safePage = SQLite3::escapeString(substr($data['page'], 0, 500));
|
|
|
|
$db->exec("INSERT INTO daily_stats (date, page, pageviews, unique_visitors)
|
|
VALUES ('$today', '$safePage', 1, 1)
|
|
ON CONFLICT(date, page) DO UPDATE SET
|
|
pageviews = pageviews + 1,
|
|
unique_visitors = (
|
|
SELECT COUNT(DISTINCT session_id) FROM events
|
|
WHERE page = '$safePage' AND event = 'pageview'
|
|
AND date(created_at) = '$today'
|
|
)");
|
|
}
|
|
|
|
// Update duration in daily stats
|
|
if ($data['event'] === 'duration' && isset($data['seconds'])) {
|
|
$today = date('Y-m-d');
|
|
$safePage = SQLite3::escapeString(substr($data['page'], 0, 500));
|
|
|
|
$db->exec("UPDATE daily_stats SET
|
|
avg_duration = (
|
|
SELECT AVG(CAST(json_extract(extra, '$.seconds') AS REAL))
|
|
FROM events WHERE page = '$safePage' AND event = 'duration'
|
|
AND date(created_at) = '$today'
|
|
)
|
|
WHERE date = '$today' AND page = '$safePage'");
|
|
}
|
|
|
|
$db->close();
|
|
|
|
http_response_code(200);
|
|
echo json_encode(['ok' => true]);
|
|
|
|
} catch (Exception $e) {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'Database error']);
|
|
}
|