| Server IP : 213.255.246.8 / Your IP : 216.73.217.138 Web Server : Apache System : Linux dublin.stapolin.com 5.14.0-362.18.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Jan 29 07:05:48 EST 2024 x86_64 User : stapolin ( 1019) PHP Version : 8.4.24 Disable Function : exec,passthru,shell_exec,system MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/stapolin/public_html/tracking.stapolin.com/admin/ |
Upload File : |
<?php
declare(strict_types=1);
require_once __DIR__ . '/../includes/bootstrap.php';
$currentUser = require_login();
$sessionId = (int) ($_GET['id'] ?? 0);
$selectedWebsiteId = selected_website_id();
$session = db_fetch_one(
'SELECT s.*, w.name AS website_name, ep.path AS entry_path, xp.path AS exit_path
FROM sessions s
JOIN websites w ON w.id = s.website_id
LEFT JOIN pages ep ON ep.id = s.entry_page_id
LEFT JOIN pages xp ON xp.id = s.exit_page_id
WHERE s.id = ?' . ($selectedWebsiteId > 0 ? ' AND s.website_id = ?' : ''),
$selectedWebsiteId > 0 ? 'ii' : 'i',
$selectedWebsiteId > 0 ? [$sessionId, $selectedWebsiteId] : [$sessionId]
);
if ($session === null) {
http_response_code(404);
exit('Session not found.');
}
$pageViews = db_fetch_all(
'SELECT "page_view" AS item_type, pv.entered_at AS occurred_at, pv.title, pv.url, pv.time_on_page_seconds,
pv.max_scroll_depth, p.path, NULL AS event_type, NULL AS category, NULL AS label, NULL AS numeric_value
FROM page_views pv
JOIN pages p ON p.id = pv.page_id
WHERE pv.session_id = ?',
'i',
[$sessionId]
);
$events = db_fetch_all(
'SELECT "event" AS item_type, e.occurred_at, NULL AS title, NULL AS url, NULL AS time_on_page_seconds,
NULL AS max_scroll_depth, p.path, e.event_type, e.category, e.label, e.numeric_value
FROM events e
LEFT JOIN pages p ON p.id = e.page_id
WHERE e.session_id = ?',
'i',
[$sessionId]
);
$timeline = array_merge($pageViews, $events);
usort($timeline, static fn (array $a, array $b): int => strcmp((string) $a['occurred_at'], (string) $b['occurred_at']));
$pathNodes = [];
foreach ($pageViews as $view) {
$pathNodes[] = [
'path' => (string) $view['path'],
'title' => (string) ($view['title'] ?: $view['path']),
'time' => (string) $view['occurred_at'],
'duration' => seconds_to_duration((float) $view['time_on_page_seconds']),
];
}
$pageTitle = 'Session Journey';
require APP_ROOT . '/templates/admin_header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">Session Journey</h1>
<p class="text-muted mb-0"><?= e($session['website_name']) ?> · <code><?= e(substr((string) $session['public_id'], 0, 18)) ?></code></p>
</div>
<a class="btn btn-outline-secondary" href="<?= e(app_url('admin/sessions.php')) ?>">Back to sessions</a>
</div>
<div class="row g-3 mb-4">
<?php foreach ([
'Started' => $session['started_at'],
'Last activity' => $session['last_activity_at'],
'Pages' => (int) $session['page_views'],
'Events' => (int) $session['total_events'],
'Device' => ucfirst((string) $session['device_category']),
'Browser' => trim((string) $session['browser'] . ' ' . (string) $session['operating_system']),
'Entry' => $session['entry_path'] ?? '-',
'Exit' => $session['exit_path'] ?? '-',
'Mode' => $session['consent_mode'],
] as $label => $value): ?>
<div class="col-sm-6 col-xl-3">
<div class="card metric-card"><div class="card-body"><div class="text-muted small"><?= e($label) ?></div><div class="h5 mb-0"><?= e((string) $value) ?></div></div></div>
</div>
<?php endforeach; ?>
</div>
<div class="row g-4">
<section class="col-xl-5">
<div class="card">
<div class="card-header bg-white">Visual Path</div>
<div class="card-body">
<?php if ($pathNodes === []): ?>
<p class="text-muted mb-0">No page views recorded for this session.</p>
<?php endif; ?>
<div class="journey-path">
<?php foreach ($pathNodes as $index => $node): ?>
<div class="journey-node">
<div class="fw-semibold"><?= e($node['title']) ?></div>
<code><?= e($node['path']) ?></code>
<div class="text-muted small"><?= e($node['time']) ?> · <?= e($node['duration']) ?></div>
</div>
<?php if ($index < count($pathNodes) - 1): ?><div class="journey-connector">↓</div><?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
</section>
<section class="col-xl-7">
<div class="card">
<div class="card-header bg-white">Timeline</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead><tr><th>Time</th><th>Type</th><th>Page or event</th><th>Details</th><th class="text-end">Time</th><th class="text-end">Scroll</th></tr></thead>
<tbody>
<?php if ($timeline === []): ?><tr><td colspan="6" class="text-center text-muted py-5">No timeline items found.</td></tr><?php endif; ?>
<?php foreach ($timeline as $item): ?>
<tr>
<td><?= e($item['occurred_at']) ?></td>
<td><?= e($item['item_type']) ?></td>
<td>
<?php if ($item['item_type'] === 'page_view'): ?>
<div><?= e($item['title'] ?: $item['path']) ?></div><code><?= e($item['path']) ?></code>
<?php else: ?>
<div><?= e($item['event_type']) ?></div><code><?= e($item['path'] ?? '-') ?></code>
<?php endif; ?>
</td>
<td><?= e(trim((string) ($item['category'] ?? '') . ' ' . (string) ($item['label'] ?? ''))) ?></td>
<td class="text-end"><?= e(seconds_to_duration((float) $item['time_on_page_seconds'])) ?></td>
<td class="text-end"><?= $item['max_scroll_depth'] !== null ? (int) $item['max_scroll_depth'] . '%' : '-' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</section>
</div>
<?php require APP_ROOT . '/templates/admin_footer.php'; ?>