index.php

<?php
declare(strict_types=1);

defined('START_TIME') || define('START_TIME', microtime(true));

require_once __DIR__ . '/includes/core.php';

loadTranslations();

if (brivaciaNeedsInstall()) {
    require_once __DIR__ . '/includes/modules/wizard.php';
    return;
}

require_once __DIR__ . '/includes/archive.php';

if (brivaciaShouldCheckYearArchive()) {
    archiveClosedYears(brivaciaDb());
    brivaciaMarkYearArchiveChecked();
}

require_once __DIR__ . '/includes/dashboard/_init.php';
require_once __DIR__ . '/includes/dashboard/counters.php';
require_once __DIR__ . '/includes/dashboard/global.php';
require_once __DIR__ . '/includes/dashboard/privacy.php';
require_once __DIR__ . '/includes/dashboard/referrers.php';
require_once __DIR__ . '/includes/dashboard/top_pages.php';
require_once __DIR__ . '/includes/modules/update.php';
require_once __DIR__ . '/includes/version.php';

/*
|--------------------------------------------------------------------------
| Graph buttons visibility
|--------------------------------------------------------------------------
|
| Keep these rules here in index.php so the dashboard stays easy to read:
| the class is added directly on each button instead of being hidden in a
| helper function.
|
*/

$hideLineGraphs = $view === 'today';
$hideCountriesGraphs = count($countries) < 2;
$hideGlobalMapGraph = !$showCountries;
$hideReferrersGraphs = count($referrers) < 2;
$hideSearchEnginesGraphs = count($searchEngines) < 2;
$hideTopPagesGraph = $topTotal < 2;

$restoreAlert = brivaciaRestoreAlert();
?>

<!doctype html>
<html data-theme="<?= h($theme) ?>" dir="<?= h(currentDir()) ?>" lang="<?= h(currentLang()) ?>">
<head>
    <title><?= h(t('title.statistics')) ?> • <?= brivacia_setting('dashboard.instance_name', 'Brivacia') ?></title>
    <meta charset="utf-8">
    <meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="<?= assetOrPlaceholder('favicon', brivaciaFaviconPlaceholder(), $currentSite) ?>">
    <link rel="stylesheet" type="text/css" href="/assets/css/style.min.css?v=<?= microtime(true) ?>">
</head>
<body>

<header class="toolbar">
    <h1 data-tooltip="<?= h(t('ui.back.home')) ?>"><a href="/"><img src="<?= assetOrPlaceholder('logo', brivaciaLogoPlaceholder(), $currentSite) ?>" alt="<?= h($siteTitle) ?>"><?= h($siteTitle) ?> <?= h(t('title.statistics')) ?></a></h1>

    <div class="toolbar-filters">
        <form class="date-form" method="get">
            <button aria-label="<?= h(t('ui.show.calendar')) ?>" data-period-type="<?= h($view) ?>" data-period-value="<?= h($periodInputValue) ?>" data-tooltip="<?= h(t('ui.show.calendar')) ?>" data-tooltip-placement="left" data-week-label="<?= h(t('form.week')) ?>" tabindex="0" type="button"><span data-live-date><?= h($periodLabel) ?></span><small data-current-time></small></button>
            <input name="view" type="hidden" value="<?= h($view) ?>">
            <?php if ($view !== 'all'): ?><input data-ios-type="<?= h($periodIosInputType ?? $periodInputType) ?>"  data-ios-value="<?= h($periodIosInputValue ?? $periodInputValue) ?>" id="period" name="<?= h($periodInputName) ?>" type="<?= h($periodInputType) ?>" value="<?= h($periodInputValue) ?>" onchange="this.form.submit()"><?php endif; ?>
            <small class="ios-time" data-current-time></small>
        </form>

        <nav class="view-tabs">
            <?php $viewLabels = ['today' => t('form.today'), 'week'  => t('form.week'), 'month' => t('form.month'), 'year'  => t('form.year'), 'all'   => t('form.all'),]; ?>
            <?php foreach ($viewLabels as $key => $label): ?><a class="<?= $view === $key ? 'active' : '' ?>" href="<?= h(dashboardViewUrl($key, $day, $week, $month, $year, ['site' => $currentSite])) ?>"><?= h($label) ?></a><?php endforeach; ?>
        </nav>

        <form method="get" class="view-select">
            <select name="view" onchange="this.form.submit()">
                <?php foreach ($viewLabels as $key => $label): ?>
                    <option value="<?= h($key) ?>" <?= $view === $key ? 'selected' : '' ?>>
                        <?= h($label) ?>
                    </option>
                <?php endforeach; ?>
            </select>
            <?php if ($view === 'today'): ?>
                <input type="hidden" name="day" value="<?= h($day) ?>">
            <?php elseif ($view === 'week'): ?>
                <input type="hidden" name="week" value="<?= h($week) ?>">
            <?php elseif ($view === 'month'): ?>
                <input type="hidden" name="month" value="<?= h($month) ?>">
            <?php elseif ($view === 'year'): ?>
                <input type="hidden" name="year" value="<?= h($year) ?>">
            <?php endif; ?>
            <?php if ($currentSite !== ''): ?>
                <input type="hidden" name="site" value="<?= h($currentSite) ?>">
            <?php endif; ?>
        </form>
    </div>

    <div class="toolbar-actions">
        <button data-privacy-open data-tooltip="<?= h(t('privacy.title', ['instance' => brivacia_setting('dashboard.instance_name', 'Brivacia')])) ?>" type="button"><?= icon('privacy') ?> <?= h(t('privacy.button')) ?></button>

        <?php if (count(brivacia_sites()) > 1): ?>
        <div class="sites-menu" data-dropdown>
            <button data-dropdown-toggle data-tooltip="<?= h(t('ui.sites')) ?>" data-tooltip-placement="left" type="button"><?= icon('activity') ?> <?= h($currentSite === '' ? t('ui.sites.all') : $currentSite) ?> </button>

            <div class="site-dropdown" data-dropdown-menu hidden>
                <?php if ($currentSite !== ''): ?>
                    <a href="<?= h(dashboardViewUrl($view, $day, $week, $month, $year, ['site' => ''])) ?>"><?= h(t('ui.sites.all')) ?></a>
                <?php endif; ?>

                <?php foreach (array_keys(brivacia_sites()) as $siteCode): ?>
                    <?php if ($siteCode === $currentSite) continue; ?>

                    <a href="<?= h(dashboardViewUrl($view, $day, $week, $month, $year, ['site' => $siteCode])) ?>"><?= h($siteCode) ?></a>
                <?php endforeach; ?>
            </div>
        </div>
        <?php endif; ?>

        <?php if (brivaciaUpdateAvailable()): ?>
        <div class="notification-menu" data-dropdown>
            <button aria-label="<?= h(t('update.available')) ?>" data-dropdown-toggle id="updates" title="<?= h(t('update.available')) ?>" type="button">
                <?= icon('bell') ?>
                <span></span>
            </button>

            <div class="notification-dropdown" data-dropdown-submenu hidden>
                <div id="update-available">
                    <h3><?= h(t('update.available')) ?></h3>

                    <p><strong>Brivacia v<?= h(brivaciaLatestVersion()) ?></strong></p>

                    <p><?= h(t('update.description')) ?></p>

                    <button data-update-cleanup="<?= h(t('update.cleanup')) ?>" data-update-download="<?= h(t('update.download')) ?>" data-update-extract="<?= h(t('update.extract')) ?>" data-update-failed="<?= h(t('update.failed')) ?>" data-update-installing="<?= h(t('update.installing')) ?>" data-update-reload="<?= h(t('update.reload')) ?>" data-update-starting="<?= h(t('update.starting')) ?>" data-update-verify="<?= h(t('update.verify')) ?>" id="update-install" type="button">
                        <?= icon('export') ?>
                        <?= h(t('update.install')) ?>
                    </button>
                </div>

                <div id="update-progress" hidden>
                    <h3><?= h(t('update.running')) ?></h3>

                    <p id="update-progress-text"><?= h(t('update.starting')) ?></p>

                    <small><?= h(t('update.wait')) ?></small>
                </div>
            </div>
        </div>
        <?php endif; ?>

        <div class="menu" data-dropdown>
            <button data-dropdown-toggle data-tooltip="<?= h(t('ui.menu')) ?>" data-tooltip-placement="left" type="button"><?= icon('menu') ?></button>

            <div class="menu-dropdown" data-dropdown-menu hidden>
                <button data-modal-open="pixel-modal" data-tooltip="<?= h(t('ui.pixel.open', ['instance' => brivacia_setting('dashboard.instance_name', 'Brivacia')])) ?>" data-tooltip-placement="left" type="button"><?= icon('code') ?> <?= h(t('ui.pixel')) ?></button>

                <div class="export-menu" data-dropdown>
                    <button data-dropdown-toggle type="button"><?= icon('export') ?> <?= h(t('ui.export')) ?></button>

                    <?php $exportParams = ltrim(dashboardViewUrl($view, $day, $week, $month, $year, ['site' => $currentSite]), '?'); ?>
                    <div class="export-dropdown" data-dropdown-submenu hidden>
                        <a href="/api/export.php?format=csv&<?= h($exportParams) ?>"><?= icon('csv') ?> CSV</a>
                        <a href="/api/export.php?format=json&<?= h($exportParams) ?>"><?= icon('json') ?> JSON</a>
                        <a href="/api/export.php?format=txt&<?= h($exportParams) ?>"><?= icon('text') ?> TXT</a>
                    </div>
                </div>

                <div class="import-menu" data-dropdown>
                    <button data-dropdown-toggle type="button"><?= icon('import') ?> <?= h(t('ui.import')) ?></button>

                    <div class="import-dropdown" data-dropdown-submenu hidden>
                        <button data-modal-open="import-modal" data-import-provider="adobe_analytics" data-tooltip="<?= h(t('import.provider.unavailable')) ?>" data-tooltip-placement="left" disabled type="button"><?= icon('adobe_analytics') ?> Adobe Analytics</button>
                        <button data-modal-open="import-modal" data-import-provider="google_analytics" data-tooltip="<?= h(t('import.provider.unavailable')) ?>" data-tooltip-placement="left" disabled type="button"><?= icon('google_analytics') ?> Google Analytics</button>
                        <button data-modal-open="import-modal" data-import-provider="matomo_db" type="button"><?= icon('matomo') ?> matomo</button>
                        <button data-modal-open="import-modal" data-import-provider="plausible_db" data-tooltip="<?= h(t('import.provider.unavailable')) ?>" data-tooltip-placement="left" disabled type="button"><?= icon('plausible') ?> Plausible</button>
                        <button data-modal-open="import-modal" data-import-provider="umami_db" data-tooltip="<?= h(t('import.provider.unavailable')) ?>" data-tooltip-placement="left" disabled type="button"><?= icon('umami') ?> Umami</button>
                    </div>
                </div>

                <button data-modal-open="settings-modal" type="button"><?= icon('settings') ?> <?= h(t('ui.settings')) ?></button>
            </div>
        </div>
    </div>
</header>

<?php if ($restoreAlert): ?>
<div class="restore-alert" data-restore-alert>
    <div>
        <strong><?= h(t('ui.restore.detected')) ?></strong>

        <?php $time = DateTime::createFromFormat('Y-m-d H:i:s', $restoreAlert['time']); ?>

        <p class="restore-alert-time"><?= h($time?->format('d/m/Y H:i') ?? '') ?></p>

        <p><?= h(t('ui.restore.description')) ?></p>
        <small><?= h(t('ui.restore.backup')) ?> <?= h((string)($restoreAlert['backup'] ?? '')) ?></small>
    </div>

    <button aria-label="<?= h(t('ui.close')) ?>" data-restore-alert-close type="button">
        <?= icon('close') ?>
    </button>
</div>
<?php endif; ?>

<div class="cards">
    <div class="card">
        <span><?= h(t('metric.unique.visitors')) ?><button aria-label="<?= h(t('ui.show.graph.line')) ?>" <?= $hideLineGraphs ? 'class="hidden"' : '' ?> data-graph="unique_visitors" data-tooltip="<?= h(t('ui.show.graph.line')) ?>" tabindex="0" type="button"><?= icon('chart-line') ?></button></span>
        <strong><?= h($today['unique_visitors'] ?? 0) ?></strong>
    </div>
    <div class="card">
        <span><?= h(t('metric.visits')) ?><button aria-label="<?= h(t('ui.show.graph.line')) ?>" <?= $hideLineGraphs ? 'class="hidden"' : '' ?> data-graph="visits" data-tooltip="<?= h(t('ui.show.graph.line')) ?>" tabindex="0" type="button"><?= icon('chart-line') ?></button></span>
        <strong><?= h($today['visits'] ?? 0) ?></strong>
    </div>
    <div class="card">
        <span><?= h(t('metric.page.views')) ?><button aria-label="<?= h(t('ui.show.graph.line')) ?>" <?= $hideLineGraphs ? 'class="hidden"' : '' ?> data-graph="page_views" data-tooltip="<?= h(t('ui.show.graph.line')) ?>" tabindex="0" type="button"><?= icon('chart-line') ?></button></span>
        <strong><?= h($today['pageviews'] ?? 0) ?></strong>
    </div>
    <div class="card">
        <span><?= h(t('metric.bots')) ?><button aria-label="<?= h(t('ui.bots.info')) ?>" data-tooltip="<?= h(t('ui.bots.info')) ?>" tabindex="0" type="button"><?= icon('info') ?></button></span>
        <strong><?= h($today['bots'] ?? 0) ?></strong>
    </div>
    <div class="card"><span><?= h(t('metric.average.visitors.per.day')) ?></span><strong><?= h($avgDay['avg_unique'] ?? 0) ?></strong></div>
    <div class="card"><span><?= h(t('metric.average.visits.per.day')) ?></span><strong><?= h($avgDay['avg_visits'] ?? 0) ?></strong></div>
</div>

<div class="trends">
    <?php if (brivacia_setting('trends.visitors', true)  && $view !== 'all'): ?>
        <div class="card" data-tooltip="<?= h(t('metric.change.visitors.tooltip')) ?>">
            <span><?= h(t('metric.change.visitors')) ?></span>

            <?php if ($visitorsChange !== null): ?>
                <strong class="<?= $visitorsChange >= 0 ? 'success' : 'danger' ?>">
                    <?= $visitorsChange > 0 ? '+' : '' ?><?= h($visitorsChange) ?>%
                </strong>
                <small><?= h($visitsChangeLabel) ?></small>
            <?php else: ?>
                <strong class="trend-new-day"><?= icon('moon') ?><?= icon('sun') ?></strong>
                <small><?= h(t('metric.change.not.enough.data')) ?></small>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <?php if (brivacia_setting('trends.visits', true) && $view !== 'all'): ?>
        <div class="card" data-tooltip="<?= h(t('metric.change.visits.tooltip')) ?>">
            <span><?= h(t('metric.change.visits')) ?></span>

            <?php if ($visitsChange !== null): ?>
                <strong class="<?= $visitsChange >= 0 ? 'success' : 'danger' ?>">
                    <?= $visitsChange > 0 ? '+' : '' ?><?= h($visitsChange) ?>%
                </strong>
                <small><?= h($visitsChangeLabel) ?></small>
            <?php else: ?>
                <strong class="trend-new-day"><?= icon('moon') ?><?= icon('sun') ?></strong>
                <small><?= h(t('metric.change.not.enough.data')) ?></small>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <?php if (brivacia_setting('trends.pageviews', true) && $view !== 'all'): ?>
        <div class="card" data-tooltip="<?= h(t('metric.change.pageviews.tooltip')) ?>">
            <span><?= h(t('metric.change.pageviews')) ?></span>

            <?php if ($pageviewsChange !== null): ?>
                <strong class="<?= $pageviewsChange >= 0 ? 'success' : 'danger' ?>">
                    <?= $pageviewsChange > 0 ? '+' : '' ?><?= h($pageviewsChange) ?>%
                </strong>
                <small><?= h($visitsChangeLabel) ?></small>
            <?php else: ?>
                <strong class="trend-new-day"><?= icon('moon') ?><?= icon('sun') ?></strong>
                <small><?= h(t('metric.change.not.enough.data')) ?></small>
            <?php endif; ?>
        </div>
    <?php endif; ?>
</div>

<div class="summary-row <?= !$showCountries ? 'summary-row--no-countries' : '' ?>">
    <section>
        <h2>
            <?= h(t('section.global.total')) ?>
            <button aria-label="<?= h(t('ui.show.graph.map')) ?>" <?= $hideGlobalMapGraph ? 'class="hidden"' : '' ?> data-graph="global_map" data-tooltip="<?= h(t('ui.show.graph.map')) ?>" tabindex="0" type="button"><?= icon('chart-map') ?></button>
            <button aria-label="<?= h(t('ui.expand')) ?>" data-modal-open="global-modal" data-tooltip="<?= h(t('ui.expand')) ?>" tabindex="0" type="button"><?= icon('expand') ?></button>
        </h2>
        <table>
            <tbody>
                <tr>
                    <td><?= icon('visitor') ?> <?= h(t('metric.unique.visitors')) ?></td>
                    <td><?= h($globalTotal['unique_visitors']) ?></td>
                </tr>
                <tr>
                    <td><?= icon('visits') ?> <?= h(t('metric.visits')) ?></td>
                    <td><?= h($globalTotal['visits']) ?></td>
                </tr>
                <tr>
                    <td><?= icon('page') ?> <?= h(t('metric.page.views')) ?></td>
                    <td><?= h($globalTotal['pageviews']) ?></td>
                </tr>
                <tr>
                    <td><?= icon('bot') ?> <?= h(t('metric.bots')) ?></td>
                    <td><?= h($globalTotal['bots'] ?? 0) ?></td>
                </tr>
                <?php if ($showCountries): ?>
                <tr>
                    <td><?= icon('countries') ?> <?= h(t('section.countries')) ?></td>
                    <td><?= h($globalTotal['countries']) ?></td>
                </tr>
                <?php endif; ?>

                <tr>
                    <td><?= icon('search') ?> <?= h(t('section.search.engines')) ?></td>
                    <td><?= h($globalTotal['search_engines'] ?? 0) ?></td>
                </tr>

                <tr>
                    <td><?= icon('referrals') ?> <?= h(t('section.referrers')) ?></td>
                    <td><?= h($globalTotal['referrers']) ?></td>
                </tr>
            </tbody>
        </table>
    </section>

    <?php if ($showCountries): ?>
    <section>
        <h2>
            <?= h(t('section.countries')) ?>
            <button aria-label="<?= h(t('ui.show.graph.bar')) ?>" <?= $hideCountriesGraphs ? 'class="hidden"' : '' ?> data-graph="countries_bar" data-tooltip="<?= h(t('ui.show.graph.bar')) ?>" tabindex="0" type="button"><?= icon('chart-bar') ?></button>
            <button aria-label="<?= h(t('ui.show.graph.pie')) ?>" <?= $hideCountriesGraphs ? 'class="hidden"' : '' ?> data-graph="countries_pie" data-tooltip="<?= h(t('ui.show.graph.pie')) ?>" tabindex="0" type="button"><?= icon('chart-pie') ?></button>
            <button aria-label="<?= h(t('ui.expand')) ?>" data-modal-open="countries-modal" data-tooltip="<?= h(t('ui.expand')) ?>" tabindex="0" type="button"><?= icon('expand') ?></button>
        </h2>
        <table>
            <tbody>
            <?php foreach ($countries as $row): ?>
                <?php $cc = strtolower((string)$row['country']); ?>
                <tr>
                    <td>
                        <?= countryFlag($cc) ?> <span data-country="<?= h(strtoupper($cc)) ?>"><?= h(countryName($cc)) ?></span>
                    </td>
                    <td><?= h($row['views']) ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </section>
    <?php endif; ?>

    <section>
        <h2>
            <?= h(t('section.search.engines')) ?>
            <button aria-label="<?= h(t('ui.show.graph.bar')) ?>" <?= $hideSearchEnginesGraphs ? 'class="hidden"' : '' ?> data-graph="search_engines_bar" data-tooltip="<?= h(t('ui.show.graph.bar')) ?>" tabindex="0" type="button"><?= icon('chart-bar') ?></button>
            <button aria-label="<?= h(t('ui.show.graph.pie')) ?>" <?= $hideSearchEnginesGraphs ? 'class="hidden"' : '' ?> data-graph="search_engines_pie" data-tooltip="<?= h(t('ui.show.graph.pie')) ?>" tabindex="0" type="button"><?= icon('chart-pie') ?></button>
            <button aria-label="<?= h(t('ui.expand')) ?>" data-modal-open="search-engines-modal" data-tooltip="<?= h(t('ui.expand')) ?>" tabindex="0" type="button"><?= icon('expand') ?></button>
        </h2>
        <table>
            <tbody>
            <?php foreach ($searchEngines as $row): ?>
                <tr>
                    <td>
                        <?php if (($row['referrer'] ?? '') === BRIVACIA_BLOCKED): ?>
                            <?= icon('blocked') ?>
                        <?php else: ?>
                            <?= referrerIconHtml((string)$row['referrer']) ?>
                        <?php endif; ?>
                        <?= h((string)$row['label']) ?>
                    </td>
                    <td><?= h($row['views']) ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </section>

    <section>
        <h2>
            <?= h(t('section.referrers')) ?>
            <button aria-label="<?= h(t('ui.show.graph.bar')) ?>" <?= $hideReferrersGraphs ? 'class="hidden"' : '' ?> data-graph="referrers_bar" data-tooltip="<?= h(t('ui.show.graph.bar')) ?>" tabindex="0" type="button"><?= icon('chart-bar') ?></button>
            <button aria-label="<?= h(t('ui.show.graph.pie')) ?>" <?= $hideReferrersGraphs ? 'class="hidden"' : '' ?> data-graph="referrers_pie" data-tooltip="<?= h(t('ui.show.graph.pie')) ?>" tabindex="0" type="button"><?= icon('chart-pie') ?></button>
            <button aria-label="<?= h(t('ui.expand')) ?>" data-modal-open="referrers-modal" data-tooltip="<?= h(t('ui.expand')) ?>" tabindex="0" type="button"><?= icon('expand') ?></button>
        </h2>
        <table>
            <tbody>
            <?php foreach ($referrers as $row): ?>
                <tr>
                    <td>
                        <?php if (($row['referrer'] ?? '') === BRIVACIA_BLOCKED): ?>
                            <?= icon('blocked') ?>
                        <?php else: ?>
                            <?= referrerIconHtml((string)$row['referrer']) ?>
                        <?php endif; ?>
                        <?= h((string)$row['label']) ?>
                    </td>
                    <td><?= h($row['views']) ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    </section>
</div>

<section class="top-pages">
    <section>
        <div class="section-header">
            <h2><?= h(t('section.top.pages')) ?> <button aria-label="<?= h(t('ui.show.graph.bar')) ?>" <?= $hideTopPagesGraph ? 'class="hidden"' : '' ?> data-graph="top_pages" data-tooltip="<?= h(t('ui.show.graph.bar')) ?>" tabindex="0" type="button"><?= icon('chart-bar') ?></button></h2>
            <form method="get" class="per-page">
                <?php if ($view === 'today'): ?>
                    <input name="day" type="hidden" value="<?= h($day) ?>">
                <?php elseif ($view === 'week'): ?>
                    <input name="week" type="hidden" value="<?= h($week) ?>">
                <?php elseif ($view === 'month'): ?>
                    <input name="month" type="hidden" value="<?= h($month) ?>">
                <?php elseif ($view === 'year'): ?>
                    <input name="year" type="hidden" value="<?= h($year) ?>">
                <?php endif; ?>

                <input name="tp" type="hidden"value="1">
                <input name="view" type="hidden" value="<?= h($view) ?>">

                <label>
                    <?= h(t('form.per.page')) ?>
                    <select name="per" onchange="this.form.submit()">
                        <?php foreach ([20, 50, 100, 500, 1000] as $n): ?>
                            <option value="<?= $n ?>" <?= $topPerPage === $n ? 'selected' : '' ?>>
                                <?= $n ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </label>
            </form>
        </div>
        <table>
            <thead>
                <tr>
                    <th><?= h(t('table.site')) ?></th>
                    <th><?= h(t('table.page')) ?></th>
                    <th><?= h(t('table.views')) ?></th>
                </tr>
            </thead>
            <tbody>
            <?php foreach ($topPages as $row): ?>
                <tr>
                    <td class="site"><?= h((string)$row['site']) ?></td>
                    <td>
                        <?= externalLink(
                            pageUrl(
                                (string)$row['site'],
                                (string)$row['page_key'],
                                (string)($row['url'] ?? '')
                            ),
                            cleanPageTitle(
                                (string)($row['title'] ?: $row['page_key']),
                                (string)$row['site'],
                                (string)$row['page_key'],
                                (string)($row['url'] ?? '')
                            ),
                            brivacia_setting('dashboard.show_external_icon_in_top_pages', true)
                        ) ?>
                    </td>
                    <td><?= h($row['views']) ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
        <?php include __DIR__ . '/includes/modules/pagination.php'; ?>
    </section>
</section>

<?php require __DIR__ . '/includes/modules/modals.php'; ?>

<footer>
    <small>
        <span><?= h(t('footer.powered')) ?><img src="<?= brivaciaLogoIcon() ?>" alt="" aria-hidden="true"><?= externalLink('https://code.breat.fr/b/brivacia/docs/changelog', 'Brivacia ' . brivacia_version(), true, t('ui.show.changelog'), 'top') ?><span aria-hidden="true" class="desktop-only"> • </span><br class="mobile-only"> <?= h(t('footer.created')) ?><img src="<?= brivaciaBreatfrLogoIcon() ?>" alt="" aria-hidden="true"><?= externalLink('https://breat.fr','breat.fr') ?></span>
        <span><button data-modal-open="about-modal" type="button"><?= h(t('footer.about')) ?></button> • <?= externalLink('https://code.breat.fr/b/brivacia/docs/faq',h(t('wizard.help.link'))) ?></span>
        <span><?= h(t('footer.support')) ?><br class="mobile-only"> <?= h(t('footer.via')) ?> <?= icon('ko-fi') ?> <?= externalLink('https://ko-fi.com/breatfr','Ko-fi') ?> <?= h(t('footer.or')) ?> <?= icon('paypal') ?> <?= externalLink('https://paypal.me/breat','PayPal') ?></span>
    </small>
</footer>

<!-- Dashboard auto-refresh --><script>window.BRIVACIA_AUTO_REFRESH_SECONDS = <?= max(0, (int) brivacia_setting('dashboard.auto_refresh', 1) * 60) ?>;</script><!-- Dashboard auto-refresh -->
<!-- Main Script --><script defer src="/assets/js/main.min.js?v=<?= microtime(true) ?>"></script><!-- Main Script -->
<!-- Restore alert --><script>
document.querySelector('[data-restore-alert-close]')?.addEventListener('click', async () => {
    await fetch('/api/restore_alert.php', { method: 'POST' });
    document.querySelector('[data-restore-alert]')?.remove();
});
</script><!-- Restore alert -->
<?php if (defined('START_TIME')) {
    echo "\n<!--\n";
    echo "=== BRIVACIA DEBUG ===\n\n";
    echo "Generated in " . round((microtime(true) - START_TIME) * 1000, 3) . " ms\n";
    echo "Request ID : " . ($_SERVER['HTTP_X_REQUEST_ID'] ?? 'none') . "\n";
    echo "Timestamp  : " . date('Y-m-d H:i:s') . "\n\n";
    echo "===================\n";
    echo "-->\n";
} ?>
</body>
</html>