diff --git a/Changelog.md b/Changelog.md index c79f4db..c7cb999 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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 diff --git a/assets/statistics.css b/assets/statistics.css index 1775003..f62c838 100644 --- a/assets/statistics.css +++ b/assets/statistics.css @@ -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; +} diff --git a/assets/statistics.js b/assets/statistics.js index 7dcd638..ebd3557 100644 --- a/assets/statistics.js +++ b/assets/statistics.js @@ -314,6 +314,7 @@ $(document).on("rex:ready", function (event, container) { initPagesDomainFilter(); initLazyBlocks(); initLazyCollapses(); + initAnalysisCards(); } function initStatsTabHandling() { @@ -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) { diff --git a/boot.php b/boot.php index 15dcfaa..6440897 100644 --- a/boot.php +++ b/boot.php @@ -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', ''); @@ -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 @@ -175,8 +174,6 @@ // save visitor $visit->persistVisitor(); } - - $visit->persist(); } } diff --git a/composer.json b/composer.json index 9e76b65..32507c7 100644 --- a/composer.json +++ b/composer.json @@ -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": "*", @@ -23,6 +26,11 @@ "symfony/polyfill-php80": "*", "symfony/polyfill-php81": "*" }, + "autoload": { + "psr-4": { + "AndiLeni\\Statistics\\": "lib/" + } + }, "config": { "platform": { "php": "7.4" diff --git a/composer.lock b/composer.lock index d35ee84..87450cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,90 +4,14 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "621c6fea5cc8098f46adc636ce82d464", + "content-hash": "aa9b3fc6f9a2d32cca0119af477ff995", "packages": [ - { - "name": "composer/ca-bundle", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "b66d11b7479109ab547f9405b97205640b17d385" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", - "reference": "b66d11b7479109ab547f9405b97205640b17d385", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-12-18T12:05:55+00:00" - }, { "name": "geoip2/geoip2", "version": "v2.13.0", "source": { "type": "git", - "url": "git@github.com:maxmind/GeoIP2-php.git", + "url": "https://github.com/maxmind/GeoIP2-php.git", "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23" }, "dist": { @@ -134,27 +58,31 @@ "geolocation", "maxmind" ], + "support": { + "issues": "https://github.com/maxmind/GeoIP2-php/issues", + "source": "https://github.com/maxmind/GeoIP2-php/tree/v2.13.0" + }, "time": "2022-08-05T20:32:58+00:00" }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.116", + "version": "v1.3.11", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "97e9fe30219e60092e107651abb379a38b342921" + "reference": "484792759de89fe94ea6a192065ea7cd99f1eaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/97e9fe30219e60092e107651abb379a38b342921", - "reference": "97e9fe30219e60092e107651abb379a38b342921", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/484792759de89fe94ea6a192065ea7cd99f1eaa2", + "reference": "484792759de89fe94ea6a192065ea7cd99f1eaa2", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4" + "phpunit/phpunit": "^4.8|^5.5|^6.5|^7.5|^8.5.52|^9.4" }, "type": "library", "autoload": { @@ -184,22 +112,22 @@ ], "support": { "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.116" + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.3.11" }, - "time": "2023-07-21T15:49:49+00:00" + "time": "2026-05-10T14:08:06+00:00" }, { "name": "matomo/device-detector", - "version": "6.2.1", + "version": "6.5.1", "source": { "type": "git", "url": "https://github.com/matomo-org/device-detector.git", - "reference": "19138b0c4b9ddf4ffd8e423d6af3764b7317cb0b" + "reference": "f30457500c6be4c80c8830466f8a746724779fd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/19138b0c4b9ddf4ffd8e423d6af3764b7317cb0b", - "reference": "19138b0c4b9ddf4ffd8e423d6af3764b7317cb0b", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/f30457500c6be4c80c8830466f8a746724779fd0", + "reference": "f30457500c6be4c80c8830466f8a746724779fd0", "shasum": "" }, "require": { @@ -213,13 +141,13 @@ "matthiasmullie/scrapbook": "^1.4.7", "mayflower/mo4-coding-standard": "^v9.0.0", "phpstan/phpstan": "^1.10.44", - "phpunit/phpunit": "^8.5.8", - "psr/cache": "^1.0.1", - "psr/simple-cache": "^1.0.1", - "symfony/yaml": "^5.1.7" + "phpunit/phpunit": "^8.5.2 || ^9 || ^10 || ^11 || ^12 || ^13", + "psr/cache": "^1.0.1 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0.1 || ^2.0 || ^3.0", + "slevomat/coding-standard": "<8.16.0", + "symfony/yaml": "^5.4 || ^6.4 || ^7.4 || ^8.0" }, "suggest": { - "doctrine/cache": "Can directly be used for caching purpose", "ext-yaml": "Necessary for using the Pecl YAML parser" }, "type": "library", @@ -253,29 +181,29 @@ "forum": "https://forum.matomo.org/", "issues": "https://github.com/matomo-org/device-detector/issues", "source": "https://github.com/matomo-org/matomo", - "wiki": "https://dev.matomo.org/" + "wiki": "https://developer.matomo.org/" }, - "time": "2024-01-05T09:03:21+00:00" + "time": "2026-05-27T09:53:26+00:00" }, { "name": "matomo/network", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/matomo-org/component-network.git", - "reference": "ff654b8fc7778b80279815d06a368f7b41249501" + "reference": "49407377fb4755cb1eb987ca827951bed732374e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/component-network/zipball/ff654b8fc7778b80279815d06a368f7b41249501", - "reference": "ff654b8fc7778b80279815d06a368f7b41249501", + "url": "https://api.github.com/repos/matomo-org/component-network/zipball/49407377fb4755cb1eb987ca827951bed732374e", + "reference": "49407377fb4755cb1eb987ca827951bed732374e", "shasum": "" }, "require": { - "php": ">=5.4" + "php": ">=7.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36" + "phpunit/phpunit": "^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -289,22 +217,22 @@ ], "support": { "issues": "https://github.com/matomo-org/component-network/issues", - "source": "https://github.com/matomo-org/component-network/tree/2.0.1" + "source": "https://github.com/matomo-org/component-network/tree/2.0.3" }, - "time": "2020-10-05T06:09:39+00:00" + "time": "2026-04-17T10:12:27+00:00" }, { "name": "matomo/referrer-spam-blacklist", - "version": "4.0.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/matomo-org/referrer-spam-list.git", - "reference": "afe4c1ea107ee7a8915a0d5eb0031cf0366608a8" + "reference": "e3254bf771f6520aa9701276a74c5a19df535d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/referrer-spam-list/zipball/afe4c1ea107ee7a8915a0d5eb0031cf0366608a8", - "reference": "afe4c1ea107ee7a8915a0d5eb0031cf0366608a8", + "url": "https://api.github.com/repos/matomo-org/referrer-spam-list/zipball/e3254bf771f6520aa9701276a74c5a19df535d4a", + "reference": "e3254bf771f6520aa9701276a74c5a19df535d4a", "shasum": "" }, "replace": { @@ -318,42 +246,41 @@ "description": "Community-contributed list of referrer spammers", "support": { "issues": "https://github.com/matomo-org/referrer-spam-list/issues", - "source": "https://github.com/matomo-org/referrer-spam-list/tree/4.0.0" + "source": "https://github.com/matomo-org/referrer-spam-list/tree/4.0.5" }, - "time": "2020-08-10T19:54:07+00:00" + "time": "2026-04-15T22:44:28+00:00" }, { "name": "maxmind-db/reader", - "version": "v1.11.1", + "version": "v1.13.1", "source": { "type": "git", - "url": "git@github.com:maxmind/MaxMind-DB-Reader-php.git", - "reference": "1e66f73ffcf25e17c7a910a1317e9720a95497c7" + "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/1e66f73ffcf25e17c7a910a1317e9720a95497c7", - "reference": "1e66f73ffcf25e17c7a910a1317e9720a95497c7", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908", + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.11.1,>=2.0.0" + "ext-maxminddb": "<1.11.1 || >=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", - "php-coveralls/php-coveralls": "^2.1", "phpstan/phpstan": "*", - "phpunit/phpcov": ">=6.0.0", "phpunit/phpunit": ">=8.0.0,<10.0.0", - "squizlabs/php_codesniffer": "3.*" + "squizlabs/php_codesniffer": "4.*" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", - "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" + "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups", + "maxmind-db/reader-ext": "C extension for significantly faster IP lookups (install via PIE: pie install maxmind-db/reader-ext)" }, "type": "library", "autoload": { @@ -381,7 +308,11 @@ "geolocation", "maxmind" ], - "time": "2023-12-02T00:09:23+00:00" + "support": { + "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", + "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1" + }, + "time": "2025-11-21T22:24:26+00:00" }, { "name": "maxmind/web-service-common", @@ -439,7 +370,7 @@ "version": "0.6.3", "source": { "type": "git", - "url": "git@github.com:mustangostang/spyc.git", + "url": "https://github.com/mustangostang/spyc.git", "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" }, "dist": { @@ -482,6 +413,10 @@ "yaml", "yml" ], + "support": { + "issues": "https://github.com/mustangostang/spyc/issues", + "source": "https://github.com/mustangostang/spyc/tree/0.6.3" + }, "time": "2019-09-10T13:16:29+00:00" }, { @@ -535,16 +470,16 @@ }, { "name": "symfony/cache", - "version": "v5.4.34", + "version": "v5.4.53", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b17f28169f7a2f2c0cddf2b044d729f5b75efe5a" + "reference": "bf581474737420d5c932ae80b868e253f465ee5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b17f28169f7a2f2c0cddf2b044d729f5b75efe5a", - "reference": "b17f28169f7a2f2c0cddf2b044d729f5b75efe5a", + "url": "https://api.github.com/repos/symfony/cache/zipball/bf581474737420d5c932ae80b868e253f465ee5b", + "reference": "bf581474737420d5c932ae80b868e253f465ee5b", "shasum": "" }, "require": { @@ -573,7 +508,7 @@ "cache/integration-tests": "dev-master", "doctrine/cache": "^1.6|^2.0", "doctrine/dbal": "^2.13.1|^3|^4", - "predis/predis": "^1.1", + "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0", "symfony/config": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", @@ -612,7 +547,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.34" + "source": "https://github.com/symfony/cache/tree/v5.4.53" }, "funding": [ { @@ -623,25 +558,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-12-18T14:56:06+00:00" + "time": "2026-05-24T08:41:21+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.5.2", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/517c3a3619dadfa6952c4651767fcadffb4df65e", + "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e", "shasum": "" }, "require": { @@ -653,167 +592,17 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { "branch-alias": { "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Contracts\\Cache\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -830,7 +619,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Generic abstractions related to caching", "homepage": "https://symfony.com", "keywords": [ "abstractions", @@ -841,7 +630,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.4" }, "funding": [ { @@ -857,20 +646,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.4.32", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "fdb022f0d3d41df240c18e2eb9a117c430f06add" + "reference": "862700068db0ddfd8c5b850671e029a90246ec75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/fdb022f0d3d41df240c18e2eb9a117c430f06add", - "reference": "fdb022f0d3d41df240c18e2eb9a117c430f06add", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/862700068db0ddfd8c5b850671e029a90246ec75", + "reference": "862700068db0ddfd8c5b850671e029a90246ec75", "shasum": "" }, "require": { @@ -914,7 +703,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.32" + "source": "https://github.com/symfony/var-exporter/tree/v5.4.45" }, "funding": [ { @@ -930,19 +719,19 @@ "type": "tidelift" } ], - "time": "2023-11-16T19:33:05+00:00" + "time": "2024-09-25T14:11:13+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/fragments/analysis_cards.php b/fragments/analysis_cards.php new file mode 100644 index 0000000..724a3ea --- /dev/null +++ b/fragments/analysis_cards.php @@ -0,0 +1,47 @@ + $this->i18n('statistics_analysis_card_device_title'), + 'description' => $this->i18n('statistics_analysis_card_device_desc'), + 'icon' => 'fa-mobile', + 'target' => 'statistics_lazy_device', + ], + [ + 'title' => $this->i18n('statistics_analysis_card_extended_title'), + 'description' => $this->i18n('statistics_analysis_card_extended_desc'), + 'icon' => 'fa-line-chart', + 'target' => 'statistics_lazy_extended', + ], + [ + 'title' => $this->i18n('statistics_analysis_card_bots_title'), + 'description' => $this->i18n('statistics_analysis_card_bots_desc'), + 'icon' => 'fa-bug', + 'target' => 'statistics_lazy_bots', + ], +]; + +?> + +
+
+

i18n('statistics_analysis_heading'); ?>

+
+ +
+ +
+ +
diff --git a/fragments/overview.php b/fragments/overview.php index f1bfdd6..b720552 100644 --- a/fragments/overview.php +++ b/fragments/overview.php @@ -13,33 +13,77 @@
-

Seitenaufrufe : filtered_visits; ?>

+

i18n('statistics_overview_visits'); ?>: filtered_visits; ?>


-

Besucher : filtered_visitors; ?>

+

i18n('statistics_overview_visitors'); ?>: filtered_visitors; ?>

-
Heute
+
i18n('statistics_overview_today'); ?>
-

Seitenaufrufe : today_visits; ?>

+

i18n('statistics_overview_visits'); ?>: today_visits; ?>


-

Besucher : today_visitors; ?>

+

i18n('statistics_overview_visitors'); ?>: today_visitors; ?>

-
Insgesamt
+
i18n('statistics_overview_total'); ?>
-

Seitenaufrufe : total_visits; ?>

+

i18n('statistics_overview_visits'); ?>: total_visits; ?>


-

Besucher : total_visitors; ?>

+

i18n('statistics_overview_visitors'); ?>: total_visitors; ?>

+
+
+
+ + +
+
+
+
+
i18n('statistics_overview_week_visits'); ?>
+
+
+

visits_week; ?>

+
+
+
+
+
+
+
i18n('statistics_overview_week_visitors'); ?>
+
+
+

visitors_week; ?>

+
+
+
+
+
+
+
i18n('statistics_overview_week_top_article'); ?>
+
+
+

top_article_path_week, ENT_QUOTES); ?>

+

i18n('statistics_overview_views'); ?>: top_article_count_week; ?>

+
+
+
+
+
+
+
i18n('statistics_overview_week_pages_per_session'); ?>
+
+
+

pages_per_session_week, 2, ',', '.'); ?>

diff --git a/lang/de_de.lang b/lang/de_de.lang index b0614b4..61128a9 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -88,6 +88,14 @@ statistics_scroll_panel = Container statistics_scroll_none = Keines von beidem statistics_statistics_ignore_url_params = Ignoriere URL-Parameter statistics_statistics_ignore_url_params_note = URL-Parameter werden in der Statistik nicht geloggt. Z.b. www.example.com?data=1 wird als www.example.com gespeichert. +statistics_details_for = Details für: +statistics_all_domains = Alle Domains +statistics_filter_all = Alle +statistics_filter_only_200 = Nur 200er +statistics_filter_only_not_200 = Nur nicht 200er +statistics_ignore_success = Es wurden %s Einträge gelöscht. +statistics_ignore_url_future = Die URL %s wird zukünftig ignoriert. +statistics_deleted_campaign_entries = Es wurden %s Einträge der Kampagne %s gelöscht. perm_general_statistics[] = Benutzer darf das Addon "Statistiken" aufrufen. perm_options_statistics[settings] = Benutzer darf Einstellungen des Addons "Statistiken" ändern. statistics_default_datefilter_range = Standard Datumsbereich @@ -132,4 +140,20 @@ statistics_api_enable_campaigns_note = Aktiviert das tracken von API Aufrufen statistics_api_delete_api = Alle API Aufrufe löschen statistics_api_delete_api_confirm = Möchten Sie wirklich alle API Aufrufe und Kampagnen löschen? statistics_api_delete = Löschen -statistics_api_delete_confirm = Wirklich alle Einträge für diese Kampagne löschen? \ No newline at end of file +statistics_api_delete_confirm = Wirklich alle Einträge für diese Kampagne löschen? +statistics_overview_visits = Seitenaufrufe +statistics_overview_visitors = Besucher +statistics_overview_today = Heute +statistics_overview_total = Insgesamt +statistics_overview_week_visits = Besuche letzte 7 Tage +statistics_overview_week_visitors = Besucher letzte 7 Tage +statistics_overview_week_top_article = Top-Artikel letzte 7 Tage +statistics_overview_week_pages_per_session = Seiten pro Sitzung (7 Tage) +statistics_overview_views = Aufrufe +statistics_analysis_heading = Detaillierte Analysen +statistics_analysis_card_device_title = Geräte & Browser +statistics_analysis_card_device_desc = Gerätetypen, Browser, OS, Marken und Modelle +statistics_analysis_card_extended_title = Verhaltensdaten +statistics_analysis_card_extended_desc = Sitzungsdauer, Seiten pro Sitzung, Ausstiegsseiten, Länder +statistics_analysis_card_bots_title = Bots +statistics_analysis_card_bots_desc = Erkannte Crawler, Kategorien und Hersteller \ No newline at end of file diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 8ade558..52afdac 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -90,6 +90,14 @@ statistics_scroll_panel = Container statistics_scroll_none = None of both statistics_statistics_ignore_url_params = Ignore URL parameters. statistics_statistics_ignore_url_params_note = URL parameters are not logged in the statistics. E.g. www.example.com?data=1 is stored as www.example.com. +statistics_details_for = Details for: +statistics_all_domains = All domains +statistics_filter_all = All +statistics_filter_only_200 = Only 200 +statistics_filter_only_not_200 = Only non-200 +statistics_ignore_success = %s entries were deleted. +statistics_ignore_url_future = The URL %s will be ignored in the future. +statistics_deleted_campaign_entries = %s entries of campaign %s were deleted. perm_general_statistics[] = User may open the "Statistics" addon. perm_options_statistics[settings] = User may change settings of the "Statistics" addon. statistics_default_datefilter_range = Default date range. @@ -134,4 +142,20 @@ statistics_api_enable_campaigns_note = Enable tracking of API calls statistics_api_delete_api = Delete all API calls statistics_api_delete_api_confirm = Do you really want to delete all API calls and campaigns? statistics_api_delete = Delete -statistics_api_delete_confirm = Really delete all entries for this campaign? \ No newline at end of file +statistics_api_delete_confirm = Really delete all entries for this campaign? +statistics_overview_visits = Page views +statistics_overview_visitors = Visitors +statistics_overview_today = Today +statistics_overview_total = Total +statistics_overview_week_visits = Visits last 7 days +statistics_overview_week_visitors = Visitors last 7 days +statistics_overview_week_top_article = Top article last 7 days +statistics_overview_week_pages_per_session = Pages per session (7 days) +statistics_overview_views = Views +statistics_analysis_heading = Detailed analyses +statistics_analysis_card_device_title = Devices and browsers +statistics_analysis_card_device_desc = Device types, browsers, operating systems, brands and models +statistics_analysis_card_extended_title = Behavior metrics +statistics_analysis_card_extended_desc = Session duration, pages per session, exit pages, countries +statistics_analysis_card_bots_title = Bots +statistics_analysis_card_bots_desc = Detected crawlers, categories and vendors \ No newline at end of file diff --git a/lib/DateFilter.php b/lib/DateFilter.php index 8c0b4ec..de5b0ab 100644 --- a/lib/DateFilter.php +++ b/lib/DateFilter.php @@ -4,6 +4,8 @@ use rex; use rex_addon; +use rex_addon_interface; +use rex_request; use rex_sql; use rex_view; use InvalidArgumentException; @@ -22,8 +24,10 @@ class DateFilter public DateTimeImmutable $whole_time_start; + /** @var non-empty-string */ private string $table; - private rex_addon $addon; + private rex_addon_interface $addon; + private const SESSION_KEY = 'statistics_datefilter'; /** @@ -38,37 +42,62 @@ class DateFilter */ function __construct(string $date_start, string $date_end, string $table) { + if ('' === $table) { + throw new InvalidArgumentException('Table name must not be empty.'); + } + + /** @var non-empty-string $table */ $this->table = $table; $this->addon = rex_addon::get('statistics'); - if ($date_start == '') { - - // prefered date range - $date_range = $this->addon->getConfig('statistics_default_datefilter_range'); - - if ($date_range == 'last7days') { - $date = new DateTimeImmutable(); - $date = $date->modify("-7 day"); - $this->date_start = $date; - } elseif ($date_range == 'last30days') { - $date = new DateTimeImmutable(); - $date = $date->modify("-30 day"); - $this->date_start = $date; - } elseif ($date_range == 'thisYear') { - $date = new DateTimeImmutable(); - $date = $date->modify("-365 day"); - $this->date_start = $date; - } else { - $this->date_start = $this->getMinDateFromTable(); - } - // design decision, uncomment this line to default show only timespan where data was collected - // $this->date_end = $this->getMaxDateFromTable(); + $hasRequestDateRange = '' !== $date_start && '' !== $date_end; - $this->date_end = new DateTimeImmutable(); - // $this->date_end->modify('+1 day'); - } else { + if ($hasRequestDateRange) { $this->date_start = new DateTimeImmutable($date_start); $this->date_end = new DateTimeImmutable($date_end); + + rex_request::setSession(self::SESSION_KEY, [ + 'date_start' => $this->date_start->format('Y-m-d'), + 'date_end' => $this->date_end->format('Y-m-d'), + ]); + } else { + $sessionDateRange = rex_request::session(self::SESSION_KEY); + $hasSessionDateRange = is_array($sessionDateRange) + && isset($sessionDateRange['date_start'], $sessionDateRange['date_end']) + && is_string($sessionDateRange['date_start']) + && is_string($sessionDateRange['date_end']) + && '' !== $sessionDateRange['date_start'] + && '' !== $sessionDateRange['date_end']; + + if ($hasSessionDateRange) { + $this->date_start = new DateTimeImmutable($sessionDateRange['date_start']); + $this->date_end = new DateTimeImmutable($sessionDateRange['date_end']); + } else { + + // prefered date range + $date_range = $this->addon->getConfig('statistics_default_datefilter_range'); + + if ($date_range === 'last7days') { + $date = new DateTimeImmutable(); + $date = $date->modify('-7 day'); + $this->date_start = $date; + } elseif ($date_range === 'last30days') { + $date = new DateTimeImmutable(); + $date = $date->modify('-30 day'); + $this->date_start = $date; + } elseif ($date_range === 'thisYear') { + $date = new DateTimeImmutable(); + $date = $date->modify('-365 day'); + $this->date_start = $date; + } else { + $this->date_start = $this->getMinDateFromTable(); + } + // design decision, uncomment this line to default show only timespan where data was collected + // $this->date_end = $this->getMaxDateFromTable(); + + $this->date_end = new DateTimeImmutable(); + // $this->date_end->modify('+1 day'); + } } // set total time range to use in datefilter fragment with javascript @@ -90,16 +119,18 @@ function __construct(string $date_start, string $date_end, string $table) private function getMinDateFromTable(): DateTimeImmutable { $sql = rex_sql::factory(); - $min_date = $sql->setQuery('SELECT MIN(date) AS "date" from ' . rex::getTable($this->table)); - $min_date = $min_date->getValue('date'); + $result = $sql->setQuery('SELECT MIN(date) AS "date" from ' . rex::getTable($this->table)); + $minDateRaw = $result->getValue('date'); - if ($min_date === null) { - $min_date = new DateTimeImmutable("now"); - } else { - $min_date = DateTimeImmutable::createFromFormat('Y-m-d', $min_date); + if (!is_string($minDateRaw) || '' === $minDateRaw) { + return new DateTimeImmutable('now'); } + $minDate = DateTimeImmutable::createFromFormat('Y-m-d', $minDateRaw); + if (false === $minDate) { + return new DateTimeImmutable('now'); + } - return $min_date; + return $minDate; } } diff --git a/lib/StatsDashboard.php b/lib/StatsDashboard.php index 2981d2c..8c47945 100644 --- a/lib/StatsDashboard.php +++ b/lib/StatsDashboard.php @@ -34,10 +34,24 @@ public static function renderOverview(DateFilter $filterDateHelper, array $overv $fragmentOverview->setVar('today_visitors', $overviewData['visitors_today']); $fragmentOverview->setVar('total_visits', $overviewData['visits_total']); $fragmentOverview->setVar('total_visitors', $overviewData['visitors_total']); + $fragmentOverview->setVar('visits_week', $overviewData['visits_week']); + $fragmentOverview->setVar('visitors_week', $overviewData['visitors_week']); + $fragmentOverview->setVar('top_article_path_week', $overviewData['top_article_path_week']); + $fragmentOverview->setVar('top_article_count_week', $overviewData['top_article_count_week']); + $fragmentOverview->setVar('pages_per_session_week', $overviewData['pages_per_session_week']); return $fragmentOverview->parse('overview.php'); } + public static function renderAnalysisCards(DateFilter $filterDateHelper): string + { + $fragment = new rex_fragment(); + $fragment->setVar('date_start', $filterDateHelper->date_start->format('Y-m-d')); + $fragment->setVar('date_end', $filterDateHelper->date_end->format('Y-m-d')); + + return $fragment->parse('analysis_cards.php'); + } + public static function renderMainChartSection(DateFilter $filterDateHelper): string { return StatsMainChartSection::render($filterDateHelper); @@ -83,9 +97,9 @@ public static function renderTableLanguageConfigScript(): string } /** - * @return array|null + * @return array */ - private static function getTableLanguage(): ?array + private static function getTableLanguage(): array { $language = (string) rex::getProperty('lang'); $isGerman = 'de_de' === $language; diff --git a/lib/Summary.php b/lib/Summary.php index accae7b..46465cf 100644 --- a/lib/Summary.php +++ b/lib/Summary.php @@ -29,7 +29,7 @@ public function __construct(DateFilter $filter_date_helper) /** * * - * @return array + * @return array * @throws InvalidArgumentException * @throws rex_sql_exception */ @@ -37,6 +37,8 @@ public function getSummaryData(): array { $sql = rex_sql::factory(); $today = date('Y-m-d'); + $weekStart = date('Y-m-d', strtotime('-6 day')); + $weekEnd = $today; $visits = $sql->getArray( 'SELECT ' @@ -67,13 +69,76 @@ public function getSummaryData(): array $visitsRow = $visits[0] ?? ['total' => 0, 'today' => 0, 'filtered' => 0]; $visitorsRow = $visitors[0] ?? ['total' => 0, 'today' => 0, 'filtered' => 0]; + $visitsWeek = $sql->getArray( + 'SELECT IFNULL(SUM(count), 0) AS total ' + . 'FROM ' . rex::getTable('pagestats_visits_per_day') + . ' WHERE date BETWEEN :start AND :end', + [ + 'start' => $weekStart, + 'end' => $weekEnd, + ] + ); + + $visitorsWeek = $sql->getArray( + 'SELECT IFNULL(SUM(count), 0) AS total ' + . 'FROM ' . rex::getTable('pagestats_visitors_per_day') + . ' WHERE date BETWEEN :start AND :end', + [ + 'start' => $weekStart, + 'end' => $weekEnd, + ] + ); + + $topArticle = $sql->getArray( + 'SELECT url, SUM(count) AS total ' + . 'FROM ' . rex::getTable('pagestats_visits_per_url') + . ' WHERE date BETWEEN :start AND :end ' + . 'GROUP BY url ORDER BY total DESC LIMIT 1', + [ + 'start' => $weekStart, + 'end' => $weekEnd, + ] + ); + + $weekVisits = (int) ($visitsWeek[0]['total'] ?? 0); + $weekVisitors = (int) ($visitorsWeek[0]['total'] ?? 0); + $weekPagesPerSession = $weekVisitors > 0 ? round($weekVisits / $weekVisitors, 2) : 0.0; + + $topArticleUrl = (string) ($topArticle[0]['url'] ?? ''); + $topArticleCount = (int) ($topArticle[0]['total'] ?? 0); + $topArticlePath = $this->extractPathFromTrackedUrl($topArticleUrl); + return [ - 'visits_datefilter' => $visitsRow['filtered'], - 'visitors_datefilter' => $visitorsRow['filtered'], - 'visits_today' => $visitsRow['today'], - 'visitors_today' => $visitorsRow['today'], - 'visits_total' => $visitsRow['total'], - 'visitors_total' => $visitorsRow['total'], + 'visits_datefilter' => (int) $visitsRow['filtered'], + 'visitors_datefilter' => (int) $visitorsRow['filtered'], + 'visits_today' => (int) $visitsRow['today'], + 'visitors_today' => (int) $visitorsRow['today'], + 'visits_total' => (int) $visitsRow['total'], + 'visitors_total' => (int) $visitorsRow['total'], + 'visits_week' => $weekVisits, + 'visitors_week' => $weekVisitors, + 'top_article_path_week' => $topArticlePath, + 'top_article_count_week' => $topArticleCount, + 'pages_per_session_week' => $weekPagesPerSession, ]; } + + private function extractPathFromTrackedUrl(string $url): string + { + if ('' === $url) { + return '/'; + } + + $firstSlash = strpos($url, '/'); + if (false === $firstSlash) { + return '/'; + } + + $path = substr($url, $firstSlash); + if ('' === $path) { + return '/'; + } + + return $path; + } } diff --git a/lib/Visit.php b/lib/Visit.php index e0f12ea..22688fd 100644 --- a/lib/Visit.php +++ b/lib/Visit.php @@ -526,6 +526,16 @@ public function isBot(): bool return $this->DeviceDetector->isBot(); } + public function setUrl(string $url): void + { + $this->url = $url; + } + + public function getUrl(): string + { + return $this->url; + } + /** * diff --git a/package.yml b/package.yml index 93547c4..3cba338 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: statistics -version: 3.2.0 +version: 3.2.3 author: Andreas Lenhardt supportpage: https://github.com/AndiLeni/statistics diff --git a/pages/events.php b/pages/events.php index 7fc2b37..ffa65d6 100644 --- a/pages/events.php +++ b/pages/events.php @@ -19,14 +19,14 @@ echo StatsSubpageRenderer::renderFilter($current_backend_page, $filter_date_helper); -if ($request_name != '' && $delete_entry === true) { +if ($request_name !== '' && $delete_entry === true) { $sql = rex_sql::factory(); $sql->setQuery('delete from ' . rex::getTable('pagestats_api') . ' where name = :name', ['name' => $request_name]); - echo rex_view::success('Es wurden ' . $sql->getRows() . ' Einträge der Kampagne ' . $request_name . ' gelöscht.'); + echo rex_view::success(sprintf($addon->i18n('statistics_deleted_campaign_entries'), (string) $sql->getRows(), htmlspecialchars($request_name, ENT_QUOTES))); } // details section -if ($request_name != '' && !$delete_entry) { +if ($request_name !== '' && !$delete_entry) { // details section for single campaign $pagedetails = new EventDetails($request_name, $filter_date_helper); @@ -36,7 +36,7 @@ $content = '
'; echo StatsSubpageRenderer::renderInfoSection( - 'Details für:', + $addon->i18n('statistics_details_for'), $request_name, $content . StatsChartConfig::renderScript('chart_details', StatsChartConfig::buildTimelineOption($sum_data['labels'], $sum_data['values'])) ); diff --git a/pages/media.php b/pages/media.php index 680957b..0950f2b 100644 --- a/pages/media.php +++ b/pages/media.php @@ -18,14 +18,14 @@ $filter_date_helper = new DateFilter($request_date_start, $request_date_end, 'pagestats_media'); echo StatsSubpageRenderer::renderFilter($current_backend_page, $filter_date_helper); -if ($request_url != '' && $delete_entry === true) { +if ($request_url !== '' && $delete_entry === true) { $sql = rex_sql::factory(); $sql->setQuery('delete from ' . rex::getTable('pagestats_media') . ' where url = :url', ['url' => $request_url]); - echo rex_view::success('Es wurden ' . $sql->getRows() . ' Einträge der Kampagne ' . $request_url . ' gelöscht.'); + echo rex_view::success(sprintf($addon->i18n('statistics_deleted_campaign_entries'), (string) $sql->getRows(), htmlspecialchars($request_url, ENT_QUOTES))); } // details section -if ($request_url != '' && !$delete_entry) { +if ($request_url !== '' && !$delete_entry) { // details section for single campaign $pagedetails = new MediaDetails($request_url, $filter_date_helper); @@ -34,7 +34,7 @@ $content = '
'; echo StatsSubpageRenderer::renderInfoSection( - 'Details für:', + $addon->i18n('statistics_details_for'), $request_url, $content . StatsChartConfig::renderScript('chart_details', StatsChartConfig::buildTimelineOption($sum_data['labels'], $sum_data['values'])) ); diff --git a/pages/pages.php b/pages/pages.php index f72ef2d..e8c6c6c 100644 --- a/pages/pages.php +++ b/pages/pages.php @@ -29,14 +29,18 @@ // check if request is for ignoring a url // if yes, add url to addon settings and delete all database entries of this url -if ($request_url != '' && $ignore_page === true) { +if ($request_url !== '' && $ignore_page === true) { $rows = $pages_helper->ignorePage($request_url); - echo rex_view::success('Es wurden ' . $rows . ' Einträge gelöscht. Die Url ' . $request_url . ' wird zukünftig ignoriert.'); + echo rex_view::success( + sprintf($addon->i18n('statistics_ignore_success'), (string) $rows) + . ' ' + . sprintf($addon->i18n('statistics_ignore_url_future'), htmlspecialchars($request_url, ENT_QUOTES)) + ); } // details for one url requested -if ($request_url != '' && !$ignore_page) { +if ($request_url !== '' && !$ignore_page) { // details section for single page $pagedetails = new PageDetails($request_url, $filter_date_helper); @@ -47,7 +51,7 @@ $content .= StatsChartConfig::renderScript('chart_details', StatsChartConfig::buildTimelineOption($sum_data['labels'], $sum_data['values'])); $content .= $pagedetails->getList(); - echo StatsSubpageRenderer::renderInfoSection('Details für:', $request_url, $content); + echo StatsSubpageRenderer::renderInfoSection($addon->i18n('statistics_details_for'), $request_url, $content); } @@ -56,7 +60,7 @@ $domains = $sql->getArray('SELECT distinct domain FROM ' . rex::getTable('pagestats_visits_per_day')); $domain_select = '