chore: baseline of the live help4bis.com docroot as of 2026-10-23 (cockpit #168)

Excludes analytics/data, vendor, support, .env, ACME tokens.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 06:45:34 +10:00
co-authored by Claude Sonnet 5.5
parent 84c1a01338
commit 9e6a41fc5d
86 changed files with 20852 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
<?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']);
}