Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## [3.2.3] - 07.06.2026

- Dashboard-Overview erweitert um KPI-Leiste für die letzten 7 Tage (Besuche, Besucher, Top-Artikel, Seiten pro Sitzung)
- Neue klickbare Analyse-Karten auf der Statistik-Startseite, die vorhandene Lazy-Blöcke gezielt laden und anspringen
- Restliche harte Labels aus `fragments/overview.php` in Sprachdateien ausgelagert

## [3.2.2] - 07.06.2026

- 404/Non-200-Aufrufe werden bei aktivierter Option "Nur 200er Aufrufe erfassen" nicht mehr als Seitenaufrufe gespeichert ([#121](https://github.com/FriendsOfREDAXO/statistics/issues/121))
- search_it-Indexierungsaufrufe werden wieder zuverlässig ignoriert, auch wenn URL-Parameter aus Statistik-URLs entfernt werden ([#114](https://github.com/FriendsOfREDAXO/statistics/issues/114))

## [3.2.1] - 07.06.2026

- Datumsbereich bleibt beim Wechsel zwischen Statistik-Tabs erhalten ([#88](https://github.com/FriendsOfREDAXO/statistics/issues/88))
- Weitere harte UI-Texte in Sprachdateien ausgelagert ([#109](https://github.com/FriendsOfREDAXO/statistics/issues/109))

## [3.2.0] - 29.03.2026

- Performance improvements with lazy loading
Expand Down
60 changes: 60 additions & 0 deletions assets/statistics.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,63 @@
#rex-page-statistics-stats.modal-open {
overflow: visible;
}

.statistics_week_kpis {
margin-top: 10px;
}

.statistics-kpi-panel .panel-body {
min-height: 86px;
}

.statistics_cards_row {
margin-bottom: 14px;
}

.statistics_cards_heading {
margin-top: 6px;
margin-bottom: 12px;
}

.statistics-analysis-card {
display: flex;
align-items: center;
width: 100%;
border: 1px solid #d7dde3;
border-radius: 4px;
background: #ffffff;
padding: 12px;
margin-bottom: 12px;
text-align: left;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.statistics-analysis-card:hover,
.statistics-analysis-card:focus {
border-color: #5ca2de;
box-shadow: 0 0 0 2px rgba(92, 162, 222, 0.15);
outline: none;
}

.statistics-analysis-card__icon {
width: 40px;
min-width: 40px;
text-align: center;
color: #5ca2de;
font-size: 24px;
}

.statistics-analysis-card__content {
display: flex;
flex-direction: column;
gap: 2px;
}

.statistics-analysis-card__title {
font-weight: 600;
}

.statistics-analysis-card__desc {
color: #66717d;
font-size: 12px;
}
30 changes: 30 additions & 0 deletions assets/statistics.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ $(document).on("rex:ready", function (event, container) {
initPagesDomainFilter();
initLazyBlocks();
initLazyCollapses();
initAnalysisCards();
}

function initStatsTabHandling() {
Expand Down Expand Up @@ -470,6 +471,35 @@ $(document).on("rex:ready", function (event, container) {
});
}

function initAnalysisCards() {
var cards = document.querySelectorAll('[data-statistics-focus-lazy]');
if (!cards.length) {
return;
}

cards.forEach(function (card) {
if (card.dataset.statisticsBound === 'true') {
return;
}

card.dataset.statisticsBound = 'true';
card.addEventListener('click', function () {
var targetId = card.getAttribute('data-statistics-focus-lazy');
if (!targetId) {
return;
}

var target = document.getElementById(targetId);
if (!target) {
return;
}

loadLazyBlock(target);
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
}

function initLazyBlocks() {
var blocks = document.querySelectorAll('[data-statistics-lazy-block]');
if (!blocks.length) {
Expand Down
43 changes: 20 additions & 23 deletions boot.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,33 +91,16 @@
$domain = 'undefined';
}

// page url
// page url (raw URL including parameters for ignore checks)
$url = $domain . rex::getRequest()->getRequestUri();

// request response code
$response_code = rex_response::getStatus();


if (rex::getRequest()->getRequestUri() != "/favicon.ico") {

if ($response_code == rex_response::HTTP_OK || !$addon->getConfig("statistics_rec_onlyok", false)) {
// visitduration, number pages visited, last visited page
$sql = rex_sql::factory();
$sql->setQuery("INSERT INTO " . rex::getTable('pagestats_sessionstats') . " (token, lastpage, lastvisit, visitduration, pagecount) VALUES (:token, :lastpage, NOW(), 0, 1) ON DUPLICATE KEY UPDATE lastpage = VALUES(lastpage), visitduration = visitduration + (NOW() - lastvisit), lastvisit = NOW(), pagecount = pagecount + 1", [":token" => $token, ":lastpage" => $url]);
}
}


// get ip from visitor, set to 0.0.0.0 when ip can not be determined
$clientAddress = rex::getRequest()->getClientIp();
$clientAddress = $clientAddress ? $clientAddress : '0.0.0.0';


// optionally ignore url parameters
if ($addon->getConfig('statistics_ignore_url_params')) {
$url = Visit::removeUrlParameters($url);
}

// user agent
$userAgent = rex_server('HTTP_USER_AGENT', 'string', '');

Expand Down Expand Up @@ -151,10 +134,26 @@

if ($visit->shouldSaveVisit() && !$visit->DeviceDetector->isLibrary()) {

// visits_per_url and pagestats_urlstatus must also be updated if response_code is != 200
$visit->updateVisitsPerUrl();
$recordOnlyOk = (bool) $addon->getConfig('statistics_rec_onlyok', false);
$shouldRecordResponse = !$recordOnlyOk || $response_code === rex_response::HTTP_OK;

if ($shouldRecordResponse) {
// optionally ignore url parameters after ignore checks were done on the raw URL
if ((bool) $addon->getConfig('statistics_ignore_url_params')) {
$visit->setUrl(Visit::removeUrlParameters($visit->getUrl()));
}

// visitduration, number pages visited, last visited page
if (rex::getRequest()->getRequestUri() !== '/favicon.ico') {
$sql = rex_sql::factory();
$sql->setQuery(
'INSERT INTO ' . rex::getTable('pagestats_sessionstats') . ' (token, lastpage, lastvisit, visitduration, pagecount) VALUES (:token, :lastpage, NOW(), 0, 1) ON DUPLICATE KEY UPDATE lastpage = VALUES(lastpage), visitduration = visitduration + (NOW() - lastvisit), lastvisit = NOW(), pagecount = pagecount + 1',
[':token' => $token, ':lastpage' => $visit->getUrl()]
);
}

$visit->updateVisitsPerUrl();

if ($response_code == rex_response::HTTP_OK || !$addon->getConfig("statistics_rec_onlyok", false)) {
// visitor is human
// check hash with save_visit, if true then save visit

Expand All @@ -175,8 +174,6 @@
// save visitor
$visit->persistVisitor();
}


$visit->persist();
}
}
Expand Down
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
"matomo/network": "^2.0"
},
"replace": {
"composer/ca-bundle": "*",
"psr/log": "*",
"psr/container": "*",
"psr/http-message": "*",
"ankitpokhrel/tus-php": "*",
"symfony/deprecation-contracts": "*",
"symfony/service-contracts": "*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-intl-grapheme": "*",
Expand All @@ -23,6 +26,11 @@
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*"
},
"autoload": {
"psr-4": {
"AndiLeni\\Statistics\\": "lib/"
}
},
"config": {
"platform": {
"php": "7.4"
Expand Down
Loading