Matomo-Tracking und Aufrufzaehlung absichern

This commit is contained in:
2026-08-27 12:20:56 +02:00
parent 528af07890
commit c86803bbd6
26 changed files with 1019 additions and 106 deletions
+106
View File
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/database.php';
/**
* Fester Katalog der gezaehlten oeffentlichen Seiten. Ein Request-Wert darf
* nie zum Seitenschluessel werden: So koennen insbesondere Query-Parameter,
* Token oder frei eingegebene Inhalte nicht in der Statistik landen.
*
* @return array<string, string>
*/
function app_public_page_view_labels(): array
{
return [
'landing' => 'Startseite',
'prices' => 'Preise',
'register' => 'Registrierung',
'login' => 'Login',
'privacy' => 'Datenschutz',
'imprint' => 'Impressum',
'terms' => 'AGB',
'dpa' => 'AVV',
'withdrawal_information' => 'Widerrufsbelehrung',
];
}
/**
* Erhoeht ausschliesslich einen aggregierten Tageszaehler. Es werden keine
* Request-Metadaten gelesen oder gespeichert. Schreibfehler duerfen eine
* oeffentliche Seite nicht unbenutzbar machen, etwa waehrend eines Deploys,
* bei dem die Migration wenige Sekunden nach dem Code eingespielt wird.
*/
function app_record_public_page_view(string $pageKey, ?PDO $pdo = null): void
{
// Ein explizit injiziertes PDO ist nur fuer den CLI-Regressionstest
// vorgesehen. Normale CLI-Aufrufe sollen keine Seitenansicht erzeugen.
if (($pdo === null && PHP_SAPI === 'cli') || ($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
return;
}
if (!array_key_exists($pageKey, app_public_page_view_labels())) {
throw new InvalidArgumentException('Unbekannter öffentlicher Seitenschlüssel.');
}
try {
$stmt = ($pdo ?? app_db_pdo())->prepare(
'INSERT INTO public_page_views (view_date, page_key, view_count)
VALUES (CURRENT_DATE, ?, 1)
ON DUPLICATE KEY UPDATE view_count = view_count + 1'
);
$stmt->execute([$pageKey]);
} catch (Throwable $e) {
error_log('Anonymer Seitenzähler konnte nicht aktualisiert werden: ' . $e->getMessage());
}
}
/**
* @return array{
* totals: array{today: int, last_30_days: int, all_time: int},
* pages: list<array{page_key: string, label: string, today: int, last_30_days: int, all_time: int}>
* }
*/
function app_public_page_view_summary(PDO $pdo): array
{
$totalsRow = $pdo->query(
'SELECT
COALESCE(SUM(CASE WHEN view_date = CURRENT_DATE THEN view_count ELSE 0 END), 0) AS today,
COALESCE(SUM(CASE WHEN view_date >= CURRENT_DATE - INTERVAL 29 DAY THEN view_count ELSE 0 END), 0) AS last_30_days,
COALESCE(SUM(view_count), 0) AS all_time
FROM public_page_views'
)->fetch() ?: [];
$pageRows = $pdo->query(
'SELECT
page_key,
COALESCE(SUM(CASE WHEN view_date = CURRENT_DATE THEN view_count ELSE 0 END), 0) AS today,
COALESCE(SUM(CASE WHEN view_date >= CURRENT_DATE - INTERVAL 29 DAY THEN view_count ELSE 0 END), 0) AS last_30_days,
COALESCE(SUM(view_count), 0) AS all_time
FROM public_page_views
GROUP BY page_key
ORDER BY all_time DESC, page_key ASC'
)->fetchAll();
$labels = app_public_page_view_labels();
$pages = [];
foreach ($pageRows as $row) {
$pageKey = (string)$row['page_key'];
$pages[] = [
'page_key' => $pageKey,
'label' => $labels[$pageKey] ?? $pageKey,
'today' => (int)$row['today'],
'last_30_days' => (int)$row['last_30_days'],
'all_time' => (int)$row['all_time'],
];
}
return [
'totals' => [
'today' => (int)($totalsRow['today'] ?? 0),
'last_30_days' => (int)($totalsRow['last_30_days'] ?? 0),
'all_time' => (int)($totalsRow['all_time'] ?? 0),
],
'pages' => $pages,
];
}