From cece44c73583738849df9ba85e6a99ef9dbd211a Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Fri, 7 Feb 2020 10:27:07 +0100 Subject: [PATCH 01/16] Add first basic tracking for page views --- Classes/Domain/Model/Pageview.php | 91 ++++++++++++++++++++ Classes/Domain/Pageview/Factory.php | 46 ++++++++++ Classes/Domain/Pageview/FromRequest.php | 30 +++++++ Classes/Domain/Repository/Pageview.php | 53 ++++++++++++ Classes/Middleware/Pageview.php | 51 +++++++++++ Configuration/RequestMiddlewares.php | 15 ++++ Configuration/Services.yaml | 20 +++++ Configuration/TCA/tx_tracking_pageview.php | 71 +++++++++++++++ Resources/Private/Language/locallang_tca.xlf | 29 +++++++ composer.json | 26 ++++++ ext_emconf.php | 21 +++++ ext_localconf.php | 3 + ext_tables.sql | 4 + 13 files changed, 460 insertions(+) create mode 100644 Classes/Domain/Model/Pageview.php create mode 100644 Classes/Domain/Pageview/Factory.php create mode 100644 Classes/Domain/Pageview/FromRequest.php create mode 100644 Classes/Domain/Repository/Pageview.php create mode 100644 Classes/Middleware/Pageview.php create mode 100644 Configuration/RequestMiddlewares.php create mode 100644 Configuration/Services.yaml create mode 100644 Configuration/TCA/tx_tracking_pageview.php create mode 100644 Resources/Private/Language/locallang_tca.xlf create mode 100644 composer.json create mode 100644 ext_emconf.php create mode 100644 ext_localconf.php create mode 100644 ext_tables.sql diff --git a/Classes/Domain/Model/Pageview.php b/Classes/Domain/Model/Pageview.php new file mode 100644 index 0000000..6f39b83 --- /dev/null +++ b/Classes/Domain/Model/Pageview.php @@ -0,0 +1,91 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +class Pageview +{ + /** + * @var int + */ + private $pageUid; + + /** + * @var SiteLanguage + */ + private $language; + + /** + * @var \DateTimeImmutable + */ + private $crdate; + + /** + * @var int + */ + private $pageType; + + /** + * @var string + */ + private $url; + + public function __construct( + int $pageUid, + SiteLanguage $language, + \DateTimeImmutable $crdate, + int $pageType, + string $url + ) { + $this->pageUid = $pageUid; + $this->language = $language; + $this->crdate = $crdate; + $this->pageType = $pageType; + $this->url = $url; + } + + public function getPageUid(): int + { + return $this->pageUid; + } + + public function getLanguage(): SiteLanguage + { + return $this->language; + } + + public function getCrdate(): \DateTimeImmutable + { + return $this->crdate; + } + + public function getPageType(): int + { + return $this->pageType; + } + + public function getUrl(): string + { + return $this->url; + } +} diff --git a/Classes/Domain/Pageview/Factory.php b/Classes/Domain/Pageview/Factory.php new file mode 100644 index 0000000..cd5faea --- /dev/null +++ b/Classes/Domain/Pageview/Factory.php @@ -0,0 +1,46 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Routing\PageArguments; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +class Factory implements FromRequest +{ + public static function fromRequest(ServerRequestInterface $request): Pageview + { + return new PageView( + static::getRouting($request)->getPageId(), + $request->getAttribute('language'), + new \DateTimeImmutable(), + static::getRouting($request)->getPageType(), + (string) $request->getUri() + ); + } + + private static function getRouting(ServerRequestInterface $request): PageArguments + { + return $request->getAttribute('routing'); + } +} diff --git a/Classes/Domain/Pageview/FromRequest.php b/Classes/Domain/Pageview/FromRequest.php new file mode 100644 index 0000000..de4967e --- /dev/null +++ b/Classes/Domain/Pageview/FromRequest.php @@ -0,0 +1,30 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview; +use Psr\Http\Message\ServerRequestInterface; + +interface FromRequest +{ + public static function fromRequest(ServerRequestInterface $request): Pageview; +} diff --git a/Classes/Domain/Repository/Pageview.php b/Classes/Domain/Repository/Pageview.php new file mode 100644 index 0000000..b6b7793 --- /dev/null +++ b/Classes/Domain/Repository/Pageview.php @@ -0,0 +1,53 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview as Model; +use TYPO3\CMS\Core\Database\Connection; + +class Pageview +{ + /** + * @var Connection + */ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + public function add(Model $pageview) + { + $this->connection->insert( + 'tx_tracking_pageview', + [ + 'pid' => $pageview->getPageUid(), + 'crdate' => $pageview->getCrdate()->format('U'), + 'tstamp' => $pageview->getCrdate()->format('U'), + 'type' => $pageview->getPageType(), + 'sys_language_uid' => $pageview->getLanguage()->getLanguageId(), + 'url' => $pageview->getUrl(), + ] + ); + } +} diff --git a/Classes/Middleware/Pageview.php b/Classes/Middleware/Pageview.php new file mode 100644 index 0000000..d251d90 --- /dev/null +++ b/Classes/Middleware/Pageview.php @@ -0,0 +1,51 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Pageview\Factory; +use DanielSiepmann\Tracking\Domain\Repository\Pageview as Repository; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +class Pageview implements MiddlewareInterface +{ + /** + * @var Repository + */ + private $repository; + + public function __construct(Repository $repository) + { + $this->repository = $repository; + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + $this->repository->add(Factory::fromRequest($request)); + + return $handler->handle($request); + } +} diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..a0d00ff --- /dev/null +++ b/Configuration/RequestMiddlewares.php @@ -0,0 +1,15 @@ + [ + 'tracking-pageview' => [ + 'target' => \DanielSiepmann\Tracking\Middleware\Pageview::class, + 'before' => [ + 'typo3/cms-frontend/content-length-headers', + ], + 'after' => [ + 'typo3/cms-frontend/shortcut-and-mountpoint-redirect', + ], + ], + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..80bf2ef --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,20 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + DanielSiepmann\Tracking\: + resource: '../Classes/*' + + DanielSiepmann\DI\DatabaseConnection\Pageview: + factory: + - '@TYPO3\CMS\Core\Database\ConnectionPool' + - 'getConnectionForTable' + arguments: + - 'tx_tracking_pageview' + + DanielSiepmann\Tracking\Domain\Repository\Pageview: + public: true + arguments: + - '@DanielSiepmann\DI\DatabaseConnection\Pageview' diff --git a/Configuration/TCA/tx_tracking_pageview.php b/Configuration/TCA/tx_tracking_pageview.php new file mode 100644 index 0000000..686b416 --- /dev/null +++ b/Configuration/TCA/tx_tracking_pageview.php @@ -0,0 +1,71 @@ + [ + 'label' => 'url', + 'label_alt' => 'crdate', + 'label_alt_force' => true, + 'sortby' => 'crdate', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'languageField' => 'sys_language_uid', + 'title' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview', + 'searchFields' => 'uid, url' + ], + 'interface' => [ + 'always_description' => 0, + 'showRecordFieldList' => 'url, type, sys_language_uid, crdate, tstamp, crdate, cruser_id' + ], + 'types' => [ + '0' => [ + 'showitem' => 'sys_language_uid, pid, url, type, crdate', + ], + ], + 'columns' => [ + 'pid' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.pid', + 'config' => [ + 'type' => 'select', + 'readOnly' => true, + 'renderType' => 'selectSingle', + 'foreign_table' => 'pages', + ], + ], + 'crdate' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.crdate', + 'config' => [ + 'type' => 'input', + 'eval' => 'datetime', + ], + ], + 'sys_language_uid' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.sys_language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'items' => [ + ['LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.sys_language.0', 0], + ], + 'readOnly' => true, + ] + ], + 'type' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.type', + 'config' => [ + 'type' => 'input', + 'readOnly' => true, + 'eval' => 'int', + ], + ], + 'url' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.url', + 'config' => [ + 'readOnly' => true, + 'type' => 'input', + 'size' => 50, + 'max' => 255, + ], + ], + ], +]; diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf new file mode 100644 index 0000000..69269df --- /dev/null +++ b/Resources/Private/Language/locallang_tca.xlf @@ -0,0 +1,29 @@ + + + +
+ + + Pageview + + + Page + + + URL + + + System language + + + Default system language + + + Date + Time + + + Pagetype + + + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..08d996a --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "danielsiepmann/tracking", + "description": "Tracking for TYPO3", + "type": "typo3-cms-extension", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Daniel Siepmann", + "email": "coding@daniel-siepmann.de" + } + ], + "autoload": { + "psr-4": { + "DanielSiepmann\\Tracking\\": "Classes/" + } + }, + "require": { + "php": "^7.3.0", + "typo3/cms-core": "10.x-dev" + }, + "extra": { + "typo3/cms": { + "extension-key": "tracking" + } + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..3ae44bf --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,21 @@ + 'Tracking', + 'description' => 'Tracks page visits in TYPO3.', + 'category' => 'fe', + 'state' => 'stable', + 'uploadfolder' => 0, + 'createDirs' => '', + 'clearCacheOnLoad' => 0, + 'author' => 'Daniel Siepmann', + 'author_email' => 'coding@daniel-siepmann.de', + 'author_company' => '', + 'version' => '1.0.0', + 'constraints' => [ + 'depends' => [ + 'core' => '', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..cbdb66c --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,3 @@ + Date: Thu, 13 Feb 2020 12:26:19 +0100 Subject: [PATCH 02/16] Add user agent to tracking To enable fine grained filtering, and detect further bots / crawler to block. --- Classes/Domain/Model/Pageview.php | 14 +++++++++++++- Classes/Domain/Pageview/Factory.php | 3 ++- Classes/Domain/Repository/Pageview.php | 1 + Configuration/TCA/tx_tracking_pageview.php | 11 +++++++++-- Resources/Private/Language/locallang_tca.xlf | 3 +++ ext_tables.sql | 1 + 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Classes/Domain/Model/Pageview.php b/Classes/Domain/Model/Pageview.php index 6f39b83..83404d4 100644 --- a/Classes/Domain/Model/Pageview.php +++ b/Classes/Domain/Model/Pageview.php @@ -50,18 +50,25 @@ class Pageview */ private $url; + /** + * @var string + */ + private $userAgent; + public function __construct( int $pageUid, SiteLanguage $language, \DateTimeImmutable $crdate, int $pageType, - string $url + string $url, + string $userAgent ) { $this->pageUid = $pageUid; $this->language = $language; $this->crdate = $crdate; $this->pageType = $pageType; $this->url = $url; + $this->userAgent = $userAgent; } public function getPageUid(): int @@ -88,4 +95,9 @@ class Pageview { return $this->url; } + + public function getUserAgent(): string + { + return $this->userAgent; + } } diff --git a/Classes/Domain/Pageview/Factory.php b/Classes/Domain/Pageview/Factory.php index cd5faea..f6a1ef3 100644 --- a/Classes/Domain/Pageview/Factory.php +++ b/Classes/Domain/Pageview/Factory.php @@ -35,7 +35,8 @@ class Factory implements FromRequest $request->getAttribute('language'), new \DateTimeImmutable(), static::getRouting($request)->getPageType(), - (string) $request->getUri() + (string) $request->getUri(), + $request->getHeader('User-Agent')[0] ?? '' ); } diff --git a/Classes/Domain/Repository/Pageview.php b/Classes/Domain/Repository/Pageview.php index b6b7793..069d2ff 100644 --- a/Classes/Domain/Repository/Pageview.php +++ b/Classes/Domain/Repository/Pageview.php @@ -47,6 +47,7 @@ class Pageview 'type' => $pageview->getPageType(), 'sys_language_uid' => $pageview->getLanguage()->getLanguageId(), 'url' => $pageview->getUrl(), + 'user_agent' => $pageview->getUserAgent(), ] ); } diff --git a/Configuration/TCA/tx_tracking_pageview.php b/Configuration/TCA/tx_tracking_pageview.php index 686b416..5ecc63b 100644 --- a/Configuration/TCA/tx_tracking_pageview.php +++ b/Configuration/TCA/tx_tracking_pageview.php @@ -14,11 +14,11 @@ return [ ], 'interface' => [ 'always_description' => 0, - 'showRecordFieldList' => 'url, type, sys_language_uid, crdate, tstamp, crdate, cruser_id' + 'showRecordFieldList' => 'url, user_agent, type, sys_language_uid, crdate, tstamp, crdate, cruser_id' ], 'types' => [ '0' => [ - 'showitem' => 'sys_language_uid, pid, url, type, crdate', + 'showitem' => 'sys_language_uid, pid, url, user_agent, type, crdate', ], ], 'columns' => [ @@ -50,6 +50,13 @@ return [ 'readOnly' => true, ] ], + 'user_agent' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.user_agent', + 'config' => [ + 'type' => 'input', + 'readOnly' => true, + ], + ], 'type' => [ 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.type', 'config' => [ diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf index 69269df..03f191e 100644 --- a/Resources/Private/Language/locallang_tca.xlf +++ b/Resources/Private/Language/locallang_tca.xlf @@ -24,6 +24,9 @@ Pagetype + + User agent + diff --git a/ext_tables.sql b/ext_tables.sql index 866fccd..dc0b8fb 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -1,4 +1,5 @@ CREATE TABLE tx_tracking_pageview ( url text, + user_agent text, type int(11) unsigned DEFAULT '0' NOT NULL, ); From 6a2cd9bc50606ad3ba9c8a87bf7d7d4349c9a814 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Thu, 13 Feb 2020 12:26:43 +0100 Subject: [PATCH 03/16] Add rules to prevent tracking of certain requests --- Classes/Middleware/Pageview.php | 30 ++++++++++++++++++++++++++++-- Configuration/Services.yaml | 20 ++++++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Classes/Middleware/Pageview.php b/Classes/Middleware/Pageview.php index d251d90..276ae07 100644 --- a/Classes/Middleware/Pageview.php +++ b/Classes/Middleware/Pageview.php @@ -27,6 +27,8 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use TYPO3\CMS\Core\Context\Context; class Pageview implements MiddlewareInterface { @@ -35,17 +37,41 @@ class Pageview implements MiddlewareInterface */ private $repository; - public function __construct(Repository $repository) + /** + * @var Context + */ + private $context; + + /** + * @var string + */ + private $rule = ''; + + public function __construct(Repository $repository, Context $context, string $rule) { $this->repository = $repository; + $this->context = $context; + $this->rule = $rule; } public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { - $this->repository->add(Factory::fromRequest($request)); + if ($this->shouldTrack($request, $this->context)) { + $this->repository->add(Factory::fromRequest($request)); + } return $handler->handle($request); } + + private function shouldTrack( + ServerRequestInterface $request, + Context $context + ): bool { + return (bool) (new ExpressionLanguage())->evaluate($this->rule, [ + 'request' => $request, + 'context' => $context, + ]); + } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 80bf2ef..5d94c1c 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -8,13 +8,21 @@ services: resource: '../Classes/*' DanielSiepmann\DI\DatabaseConnection\Pageview: - factory: - - '@TYPO3\CMS\Core\Database\ConnectionPool' - - 'getConnectionForTable' - arguments: - - 'tx_tracking_pageview' + factory: + - '@TYPO3\CMS\Core\Database\ConnectionPool' + - 'getConnectionForTable' + arguments: + - 'tx_tracking_pageview' DanielSiepmann\Tracking\Domain\Repository\Pageview: public: true arguments: - - '@DanielSiepmann\DI\DatabaseConnection\Pageview' + - '@DanielSiepmann\DI\DatabaseConnection\Pageview' + + DanielSiepmann\Tracking\Middleware\Pageview: + public: true + arguments: + $rule: > + not (context.getAspect("backend.user").isLoggedIn()) + and not (request.getHeader("User-Agent")[0] matches "/^Wget/") + and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot/") From 005140d6f1b9aa8425f7c9d2aa4c3641b03bee96 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 19 Feb 2020 09:19:26 +0100 Subject: [PATCH 04/16] Expand tracking blacklist --- Configuration/Services.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 5d94c1c..184d2d6 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -24,5 +24,5 @@ services: arguments: $rule: > not (context.getAspect("backend.user").isLoggedIn()) - and not (request.getHeader("User-Agent")[0] matches "/^Wget/") - and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot/") + and not (request.getHeader("User-Agent")[0] matches "/^Wget|TYPO3|TYPO3 linkvalidator/") + and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot|NextCloud-News|Feedly|XING FeedReader|CCBot/") From b1a7226ed86262196427947f54f566aef3cbb299 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Sun, 23 Feb 2020 22:41:41 +0100 Subject: [PATCH 05/16] Add tracking widgets --- Classes/Dashboard/Widgets/PageViewsBar.php | 102 ++++++++++++++++++ .../Widgets/PageViewsPerPageDoughnut.php | 99 +++++++++++++++++ .../Backend/DashboardWidgetGroups.php | 6 ++ Configuration/Services.yaml | 18 ++++ Resources/Private/Language/locallang.xlf | 26 +++++ 5 files changed, 251 insertions(+) create mode 100644 Classes/Dashboard/Widgets/PageViewsBar.php create mode 100644 Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php create mode 100644 Configuration/Backend/DashboardWidgetGroups.php create mode 100644 Resources/Private/Language/locallang.xlf diff --git a/Classes/Dashboard/Widgets/PageViewsBar.php b/Classes/Dashboard/Widgets/PageViewsBar.php new file mode 100644 index 0000000..af05804 --- /dev/null +++ b/Classes/Dashboard/Widgets/PageViewsBar.php @@ -0,0 +1,102 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget; + +class PageViewsBar extends AbstractBarChartWidget +{ + protected $title = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsBar.title'; + + protected $description = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsBar.description'; + + protected $width = 2; + + protected $height = 4; + + protected function prepareChartData(): void + { + list($labels, $data) = $this->calculateDataForLastDays(31); + + $this->chartData = [ + 'labels' => $labels, + 'datasets' => [ + [ + 'label' => $this->getLanguageService()->sL( + 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:widgets.pageViewsBar.chart.dataSet.0' + ), + 'backgroundColor' => $this->chartColors[0], + 'border' => 0, + 'data' => $data + ] + ] + ]; + } + + protected function getPageViewsInPeriod(int $start, int $end): int + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_tracking_pageview'); + + return (int)$queryBuilder + ->count('*') + ->from('tx_tracking_pageview') + ->where( + $queryBuilder->expr()->gte('tstamp', $start), + $queryBuilder->expr()->lte('tstamp', $end), + $queryBuilder->expr()->notIn( + 'tx_tracking_pageview.pid', + $queryBuilder->createNamedParameter([ + 1, + 11, + 38, + ], Connection::PARAM_INT_ARRAY) + ) + ) + ->execute() + ->fetchColumn(); + } + + protected function calculateDataForLastDays(int $days): array + { + $labels = []; + $data = []; + + $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d'; + + for ($daysBefore = $days; $daysBefore > 0; $daysBefore--) { + $labels[] = date($format, strtotime('-' . $daysBefore . ' day')); + $startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00'); + $endPeriod = strtotime('-' . $daysBefore . ' day 23:59:59'); + + $data[] = $this->getPageViewsInPeriod($startPeriod, $endPeriod); + } + + return [ + $labels, + $data, + ]; + } +} diff --git a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php new file mode 100644 index 0000000..a3d5e84 --- /dev/null +++ b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php @@ -0,0 +1,99 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Doctrine\DBAL\ParameterType; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget; + +class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget +{ + protected $title = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsPerPageDoughnut.title'; + + protected $description = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsPerPageDoughnut.description'; + + protected function prepareChartData(): void + { + list($labels, $data) = $this->getPageViewsPerPage(31); + + $this->chartData = [ + 'labels' => $labels, + 'datasets' => [ + [ + 'backgroundColor' => $this->chartColors, + 'data' => $data, + ] + ], + ]; + } + + private function getPageViewsPerPage(int $period): array + { + $labels = []; + $data = []; + $queryBuilder = $this->getQueryBuilder(); + + $result = $queryBuilder + ->selectLiteral('count(tx_tracking_pageview.pid) as total') + ->addSelect('pages.title', 'pages.uid') + ->from('tx_tracking_pageview') + ->leftJoin( + 'tx_tracking_pageview', + 'pages', + 'pages', + $queryBuilder->expr()->eq('tx_tracking_pageview.pid', $queryBuilder->quoteIdentifier('pages.uid')) + ) + ->where( + $queryBuilder->expr()->notIn( + 'tx_tracking_pageview.pid', + $queryBuilder->createNamedParameter([ + 1, + 11, + 38, + ], Connection::PARAM_INT_ARRAY) + ) + ) + ->groupBy('tx_tracking_pageview.pid') + ->orderBy('total', 'desc') + ->setMaxResults(6) // Because 6 colors are defined + ->execute() + ->fetchAll(); + + foreach ($result as $row) { + $labels[] = $row['title'] . ' [' . $row['uid'] . ']'; + $data[] = $row['total']; + } + + return [ + $labels, + $data, + ]; + } + + private function getQueryBuilder(): QueryBuilder + { + return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_tracking_pageview'); + } +} diff --git a/Configuration/Backend/DashboardWidgetGroups.php b/Configuration/Backend/DashboardWidgetGroups.php new file mode 100644 index 0000000..eaf82ca --- /dev/null +++ b/Configuration/Backend/DashboardWidgetGroups.php @@ -0,0 +1,6 @@ + [ + 'title' => 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widget.group.tracking', + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 184d2d6..cf497d9 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -26,3 +26,21 @@ services: not (context.getAspect("backend.user").isLoggedIn()) and not (request.getHeader("User-Agent")[0] matches "/^Wget|TYPO3|TYPO3 linkvalidator/") and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot|NextCloud-News|Feedly|XING FeedReader|CCBot/") + + # Dashboard Widgets + + DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar: + class: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar + arguments: [pageViewsBar] + tags: + - name: dashboard.widget + identifier: pageViewsBar + widgetGroups: tracking + + DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut: + class: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut + arguments: [pageViewsPerPageDoughnut] + tags: + - name: dashboard.widget + identifier: pageViewsPerPageDoughnut + widgetGroups: tracking diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..b1d2f3b --- /dev/null +++ b/Resources/Private/Language/locallang.xlf @@ -0,0 +1,26 @@ + + + +
+ + + Tracking + + + Page Views / Day + + + Displays total page views per day. + + + Total Page Views + + + Page Views / Page + + + Displays total page views per page. + + + + From 2e718d41c96ff14fe08af3084894d21545cc77e6 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 24 Feb 2020 13:56:51 +0100 Subject: [PATCH 06/16] Take current day into account for page views per day --- Classes/Dashboard/Widgets/PageViewsBar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Dashboard/Widgets/PageViewsBar.php b/Classes/Dashboard/Widgets/PageViewsBar.php index af05804..1755b89 100644 --- a/Classes/Dashboard/Widgets/PageViewsBar.php +++ b/Classes/Dashboard/Widgets/PageViewsBar.php @@ -86,7 +86,7 @@ class PageViewsBar extends AbstractBarChartWidget $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d'; - for ($daysBefore = $days; $daysBefore > 0; $daysBefore--) { + for ($daysBefore = $days; $daysBefore >= 0; $daysBefore--) { $labels[] = date($format, strtotime('-' . $daysBefore . ' day')); $startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00'); $endPeriod = strtotime('-' . $daysBefore . ' day 23:59:59'); From 2105b79b8abd4737e80cd1c1836fcc372e729919 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 24 Feb 2020 13:57:06 +0100 Subject: [PATCH 07/16] Ignore further bots --- Configuration/Services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index cf497d9..af77272 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -25,7 +25,7 @@ services: $rule: > not (context.getAspect("backend.user").isLoggedIn()) and not (request.getHeader("User-Agent")[0] matches "/^Wget|TYPO3|TYPO3 linkvalidator/") - and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot|NextCloud-News|Feedly|XING FeedReader|CCBot/") + and not (request.getHeader("User-Agent")[0] matches "/Googlebot|Bingbot|bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Sogou|Exabot|NextCloud-News|Feedly|XING FeedReader|CCBot|SemrushBot|SEOkicks|Twitterbot|Seekport Crawler|SemanticScholarBot|curl/") # Dashboard Widgets From ab6c7d2967c9177d2a51acfe4a3a33b0a02c7a33 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Mon, 24 Feb 2020 14:49:31 +0100 Subject: [PATCH 08/16] Use DI instead of unnecessary code --- Classes/Dashboard/Widgets/PageViewsBar.php | 28 +++++++++----- .../Widgets/PageViewsPerPageDoughnut.php | 30 +++++++++------ Configuration/Services.yaml | 38 +++++++++++++------ readme.rst | 23 +++++++++++ 4 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 readme.rst diff --git a/Classes/Dashboard/Widgets/PageViewsBar.php b/Classes/Dashboard/Widgets/PageViewsBar.php index 1755b89..b98ac75 100644 --- a/Classes/Dashboard/Widgets/PageViewsBar.php +++ b/Classes/Dashboard/Widgets/PageViewsBar.php @@ -22,8 +22,7 @@ namespace DanielSiepmann\Tracking\Dashboard\Widgets; */ use TYPO3\CMS\Core\Database\Connection; -use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget; class PageViewsBar extends AbstractBarChartWidget @@ -36,6 +35,18 @@ class PageViewsBar extends AbstractBarChartWidget protected $height = 4; + /** + * @var QueryBuilder + */ + protected $queryBuilder; + + public function __construct(string $identifier, QueryBuilder $queryBuilder) + { + parent::__construct($identifier); + $this->queryBuilder = $queryBuilder; + $this->identifier = $identifier; + } + protected function prepareChartData(): void { list($labels, $data) = $this->calculateDataForLastDays(31); @@ -57,18 +68,15 @@ class PageViewsBar extends AbstractBarChartWidget protected function getPageViewsInPeriod(int $start, int $end): int { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('tx_tracking_pageview'); - - return (int)$queryBuilder + return (int)$this->queryBuilder ->count('*') ->from('tx_tracking_pageview') ->where( - $queryBuilder->expr()->gte('tstamp', $start), - $queryBuilder->expr()->lte('tstamp', $end), - $queryBuilder->expr()->notIn( + $this->queryBuilder->expr()->gte('tstamp', $start), + $this->queryBuilder->expr()->lte('tstamp', $end), + $this->queryBuilder->expr()->notIn( 'tx_tracking_pageview.pid', - $queryBuilder->createNamedParameter([ + $this->queryBuilder->createNamedParameter([ 1, 11, 38, diff --git a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php index a3d5e84..54776e3 100644 --- a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php +++ b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php @@ -23,9 +23,7 @@ namespace DanielSiepmann\Tracking\Dashboard\Widgets; use Doctrine\DBAL\ParameterType; use TYPO3\CMS\Core\Database\Connection; -use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget; class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget @@ -34,6 +32,17 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget protected $description = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsPerPageDoughnut.description'; + /** + * @var QueryBuilder + */ + protected $queryBuilder; + + public function __construct(string $identifier, QueryBuilder $queryBuilder) + { + parent::__construct($identifier); + $this->queryBuilder = $queryBuilder; + } + protected function prepareChartData(): void { list($labels, $data) = $this->getPageViewsPerPage(31); @@ -53,9 +62,8 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget { $labels = []; $data = []; - $queryBuilder = $this->getQueryBuilder(); - $result = $queryBuilder + $result = $this->queryBuilder ->selectLiteral('count(tx_tracking_pageview.pid) as total') ->addSelect('pages.title', 'pages.uid') ->from('tx_tracking_pageview') @@ -63,12 +71,15 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget 'tx_tracking_pageview', 'pages', 'pages', - $queryBuilder->expr()->eq('tx_tracking_pageview.pid', $queryBuilder->quoteIdentifier('pages.uid')) + $this->queryBuilder->expr()->eq( + 'tx_tracking_pageview.pid', + $this->queryBuilder->quoteIdentifier('pages.uid') + ) ) ->where( - $queryBuilder->expr()->notIn( + $this->queryBuilder->expr()->notIn( 'tx_tracking_pageview.pid', - $queryBuilder->createNamedParameter([ + $this->queryBuilder->createNamedParameter([ 1, 11, 38, @@ -91,9 +102,4 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget $data, ]; } - - private function getQueryBuilder(): QueryBuilder - { - return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_tracking_pageview'); - } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index af77272..0d66960 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -7,17 +7,27 @@ services: DanielSiepmann\Tracking\: resource: '../Classes/*' - DanielSiepmann\DI\DatabaseConnection\Pageview: + # Virtual services + + DanielSiepmann\Tracking\DI\DatabaseConnection\Pageview: factory: - '@TYPO3\CMS\Core\Database\ConnectionPool' - 'getConnectionForTable' arguments: - 'tx_tracking_pageview' + DanielSiepmann\Tracking\DI\QueryBuilder\PageView: + factory: + - '@TYPO3\CMS\Core\Database\ConnectionPool' + - 'getQueryBuilderForTable' + arguments: + - 'tx_tracking_pageview' + + # Existing classes DanielSiepmann\Tracking\Domain\Repository\Pageview: public: true arguments: - - '@DanielSiepmann\DI\DatabaseConnection\Pageview' + - '@DanielSiepmann\Tracking\DI\DatabaseConnection\Pageview' DanielSiepmann\Tracking\Middleware\Pageview: public: true @@ -30,17 +40,21 @@ services: # Dashboard Widgets DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar: - class: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar - arguments: [pageViewsBar] + class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar' + arguments: + - 'pageViewsBar' + - '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' tags: - - name: dashboard.widget - identifier: pageViewsBar - widgetGroups: tracking + - name: 'dashboard.widget' + identifier: 'pageViewsBar' + widgetGroups: 'tracking' DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut: - class: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut - arguments: [pageViewsPerPageDoughnut] + class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut' + arguments: + - 'pageViewsPerPageDoughnut' + - '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' tags: - - name: dashboard.widget - identifier: pageViewsPerPageDoughnut - widgetGroups: tracking + - name: 'dashboard.widget' + identifier: 'pageViewsPerPageDoughnut' + widgetGroups: 'tracking' diff --git a/readme.rst b/readme.rst new file mode 100644 index 0000000..a30f73b --- /dev/null +++ b/readme.rst @@ -0,0 +1,23 @@ +Todos +===== + +#. Add command that will iterate over all DB entries and remove ones matching the black list rule. + E.g. if rule is adjusted in meanwhile. + +#. Add further widgets. + + #. Top 404 requests (Collect them to show them). + + #. Grouped by user agents. + +#. Move bot detection to another rule. + + #. Keep indexing those requests, but mark them as bot and separate them in widgets. + + #. Provide an overview of crawls as widgets. E.g. to allow fine grained robots.txt. + +#. Add information to Admin Panel. + +#. Add operating System + + #. Another Symfony Expression which returns the OS ("Ubuntu", "Macintosh", "Android", "iPhone", "Windows") From b8cde34d608c87be73dfe8d0eac1219fcf7584aa Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 14:59:00 +0100 Subject: [PATCH 09/16] Update to 10.3 release --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 08d996a..5ec06b3 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require": { "php": "^7.3.0", - "typo3/cms-core": "10.x-dev" + "typo3/cms-core": "^10.3.0" }, "extra": { "typo3/cms": { From 654d52cede781e443806e0a9dd78200655aebb9c Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 18:54:27 +0100 Subject: [PATCH 10/16] Configure TCA to show newest page views first --- Configuration/TCA/tx_tracking_pageview.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/TCA/tx_tracking_pageview.php b/Configuration/TCA/tx_tracking_pageview.php index 5ecc63b..8efcf63 100644 --- a/Configuration/TCA/tx_tracking_pageview.php +++ b/Configuration/TCA/tx_tracking_pageview.php @@ -4,7 +4,7 @@ return [ 'label' => 'url', 'label_alt' => 'crdate', 'label_alt_force' => true, - 'sortby' => 'crdate', + 'sortby' => 'crdate DESC', 'tstamp' => 'tstamp', 'crdate' => 'crdate', 'cruser_id' => 'cruser_id', From 73ec4641ff363aca7f8e468b684b4ded049ee6df Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 19:58:46 +0100 Subject: [PATCH 11/16] Prepare usage as standalone composer package --- .gitignore | 2 ++ composer.json | 25 ++++++++++++++++++++++++- ext_emconf.php | 8 +++++--- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e951a59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.Build/ +/composer.lock diff --git a/composer.json b/composer.json index 5ec06b3..5cc9086 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,35 @@ } }, "require": { + "doctrine/dbal": "^2.10", "php": "^7.3.0", + "psr/http-message": "^1.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "symfony/expression-language": "^5.0", "typo3/cms-core": "^10.3.0" }, + "suggest": { + "typo3/cms-dashboard": "To make use of provided TYPO3 widgets" + }, + "config": { + "vendor-dir": ".Build/vendor", + "bin-dir": ".Build/bin" + }, "extra": { "typo3/cms": { - "extension-key": "tracking" + "cms-package-dir": "{$vendor-dir}/typo3/cms", + "extension-key": "tracking", + "web-dir": ".Build/web" + }, + "branch-alias": { + "dev-develop": "1.0.x-dev" } + }, + "scripts": { + "post-autoload-dump": [ + "mkdir -p .Build/web/typo3conf/ext/", + "[ -L .Build/web/typo3conf/ext/tracking ] || ln -snvf ../../../../. .Build/web/typo3conf/ext/tracking" + ] } } diff --git a/ext_emconf.php b/ext_emconf.php index 3ae44bf..3c53263 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -3,19 +3,21 @@ $EM_CONF[$_EXTKEY] = [ 'title' => 'Tracking', 'description' => 'Tracks page visits in TYPO3.', 'category' => 'fe', - 'state' => 'stable', + 'state' => 'alpha', 'uploadfolder' => 0, 'createDirs' => '', 'clearCacheOnLoad' => 0, 'author' => 'Daniel Siepmann', 'author_email' => 'coding@daniel-siepmann.de', 'author_company' => '', - 'version' => '1.0.0', + 'version' => '0.1.0', 'constraints' => [ 'depends' => [ 'core' => '', ], 'conflicts' => [], - 'suggests' => [], + 'suggests' => [ + 'dashboard' => '', + ], ], ]; From 50dcb619ea40868c22cfbb1a7aa5a3decd8a4a93 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 20:06:38 +0100 Subject: [PATCH 12/16] Add PSR-12 --- .gitignore | 1 + Classes/Dashboard/Widgets/PageViewsBar.php | 5 ++-- .../Widgets/PageViewsPerPageDoughnut.php | 5 ++-- Classes/Extension.php | 29 +++++++++++++++++++ composer.json | 7 ++--- phpcs.xml.dist | 15 ++++++++++ 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 Classes/Extension.php create mode 100644 phpcs.xml.dist diff --git a/.gitignore b/.gitignore index e951a59..51313d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.Build/ /composer.lock +/vendor/ diff --git a/Classes/Dashboard/Widgets/PageViewsBar.php b/Classes/Dashboard/Widgets/PageViewsBar.php index b98ac75..63ec302 100644 --- a/Classes/Dashboard/Widgets/PageViewsBar.php +++ b/Classes/Dashboard/Widgets/PageViewsBar.php @@ -21,15 +21,16 @@ namespace DanielSiepmann\Tracking\Dashboard\Widgets; * 02110-1301, USA. */ +use DanielSiepmann\Tracking\Extension; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget; class PageViewsBar extends AbstractBarChartWidget { - protected $title = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsBar.title'; + protected $title = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsBar.title'; - protected $description = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsBar.description'; + protected $description = Extension::LANGUAGE_PATH . ':trackingdashboard.widgets.pageViewsBar.description'; protected $width = 2; diff --git a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php index 54776e3..30eb1d2 100644 --- a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php +++ b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php @@ -21,6 +21,7 @@ namespace DanielSiepmann\Tracking\Dashboard\Widgets; * 02110-1301, USA. */ +use DanielSiepmann\Tracking\Extension; use Doctrine\DBAL\ParameterType; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Query\QueryBuilder; @@ -28,9 +29,9 @@ use TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget; class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget { - protected $title = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsPerPageDoughnut.title'; + protected $title = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsPerPageDoughnut.title'; - protected $description = 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.pageViewsPerPageDoughnut.description'; + protected $description = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsPerPageDoughnut.description'; /** * @var QueryBuilder diff --git a/Classes/Extension.php b/Classes/Extension.php new file mode 100644 index 0000000..1716c56 --- /dev/null +++ b/Classes/Extension.php @@ -0,0 +1,29 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +final class Extension +{ + public const EXT_KEY = 'tracking'; + + public const LANGUAGE_PATH = 'LLL:EXT:' . self::EXT_KEY . 'tracking/Resources/Private/Language/locallang.xlf'; +} diff --git a/composer.json b/composer.json index 5cc9086..ee05abe 100644 --- a/composer.json +++ b/composer.json @@ -26,10 +26,6 @@ "suggest": { "typo3/cms-dashboard": "To make use of provided TYPO3 widgets" }, - "config": { - "vendor-dir": ".Build/vendor", - "bin-dir": ".Build/bin" - }, "extra": { "typo3/cms": { "cms-package-dir": "{$vendor-dir}/typo3/cms", @@ -45,5 +41,8 @@ "mkdir -p .Build/web/typo3conf/ext/", "[ -L .Build/web/typo3conf/ext/tracking ] || ln -snvf ../../../../. .Build/web/typo3conf/ext/tracking" ] + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5" } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..0fe8bf8 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,15 @@ + + + This project coding standard + + Classes/ + + + + + + + + + + From d7c07aee79de0a5aac71c06a3ed6df368a1ef0fa Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 20:11:06 +0100 Subject: [PATCH 13/16] Add GitHub workflows --- .github/workflows/ci.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..313284a --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,16 @@ +name: CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Test CGL + run: ./vendor/bin/phpcs From 9452e66a517c53c28e4bac5af8e95c238548f385 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 21:19:18 +0100 Subject: [PATCH 14/16] Add Unittests --- .github/workflows/ci.yaml | 3 + Tests/Unit/Domain/Model/PageviewTest.php | 169 +++++++++++++++ Tests/Unit/Domain/Pageview/FactoryTest.php | 196 ++++++++++++++++++ Tests/Unit/Domain/Repository/PageviewTest.php | 72 +++++++ Tests/Unit/Middleware/PageviewTest.php | 95 +++++++++ composer.json | 8 +- phpcs.xml.dist | 1 + phpunit.xml.dist | 26 +++ 8 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 Tests/Unit/Domain/Model/PageviewTest.php create mode 100644 Tests/Unit/Domain/Pageview/FactoryTest.php create mode 100644 Tests/Unit/Domain/Repository/PageviewTest.php create mode 100644 Tests/Unit/Middleware/PageviewTest.php create mode 100644 phpunit.xml.dist diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 313284a..d23e48b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,3 +14,6 @@ jobs: - name: Test CGL run: ./vendor/bin/phpcs + + - name: Execute PHPUnit Tests + run: ./vendor/bin/phpunit diff --git a/Tests/Unit/Domain/Model/PageviewTest.php b/Tests/Unit/Domain/Model/PageviewTest.php new file mode 100644 index 0000000..87f5b1c --- /dev/null +++ b/Tests/Unit/Domain/Model/PageviewTest.php @@ -0,0 +1,169 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview; +use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +/** + * @covers DanielSiepmann\Tracking\Domain\Model\Pageview + */ +class PageviewTest extends TestCase +{ + /** + * @test + */ + public function canBeCreated() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 0, + '', + '' + ); + + static::assertInstanceOf(Pageview::class, $subject); + } + + /** + * @test + */ + public function returnsPageUid() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 500, + $language->reveal(), + new \DateTimeImmutable(), + 0, + '', + '' + ); + + static::assertSame(500, $subject->getPageUid()); + } + + /** + * @test + */ + public function returnsLanguage() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 0, + '', + '' + ); + + static::assertSame($language->reveal(), $subject->getLanguage()); + } + + /** + * @test + */ + public function returnsCrdate() + { + $language = $this->prophesize(SiteLanguage::class); + $crdate = new \DateTimeImmutable(); + + $subject = new Pageview( + 0, + $language->reveal(), + $crdate, + 0, + '', + '' + ); + + static::assertSame($crdate, $subject->getCrdate()); + } + + /** + * @test + */ + public function returnsPageType() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 999, + '', + '' + ); + + static::assertSame(999, $subject->getPageType()); + } + + /** + * @test + */ + public function returnsUrl() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 0, + 'https://example.com/path.html', + '' + ); + + static::assertSame('https://example.com/path.html', $subject->getUrl()); + } + + /** + * @test + */ + public function returnsUserAgent() + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 0, + '', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0' + ); + + static::assertSame( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0', + $subject->getUserAgent() + ); + } +} diff --git a/Tests/Unit/Domain/Pageview/FactoryTest.php b/Tests/Unit/Domain/Pageview/FactoryTest.php new file mode 100644 index 0000000..0c97d35 --- /dev/null +++ b/Tests/Unit/Domain/Pageview/FactoryTest.php @@ -0,0 +1,196 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview; +use DanielSiepmann\Tracking\Domain\Pageview\Factory; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Routing\PageArguments; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +/** + * @covers DanielSiepmann\Tracking\Domain\Pageview\Factory + */ +class FactoryTest extends TestCase +{ + /** + * @test + */ + public function returnsPageview() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertInstanceOf(Pageview::class, $result); + } + + /** + * @test + */ + public function returnedPageviewContainsUserAgent() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0' + ]); + + $result = Factory::fromRequest($request->reveal()); + static::assertSame( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0', + $result->getUserAgent() + ); + } + + /** + * @test + */ + public function returnedPageviewContainsUri() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn('https://example.com/path?query=params&some=more#anchor'); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertSame( + 'https://example.com/path?query=params&some=more#anchor', + $result->getUrl() + ); + } + + /** + * @test + */ + public function returnedPageviewContainsPageType() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(50); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertSame( + 50, + $result->getPageType() + ); + } + + /** + * @test + */ + public function returnedPageviewContainsDateTime() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertInstanceOf(\DateTimeImmutable::class, $result->getCrdate()); + } + + /** + * @test + */ + public function returnedPageviewContainsLanguage() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertInstanceOf(SiteLanguage::class, $result->getLanguage()); + } + + /** + * @test + */ + public function returnedPageviewContainsPageId() + { + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $result = Factory::fromRequest($request->reveal()); + static::assertSame( + 10, + $result->getPageUid() + ); + } +} diff --git a/Tests/Unit/Domain/Repository/PageviewTest.php b/Tests/Unit/Domain/Repository/PageviewTest.php new file mode 100644 index 0000000..bd07858 --- /dev/null +++ b/Tests/Unit/Domain/Repository/PageviewTest.php @@ -0,0 +1,72 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview as Model; +use DanielSiepmann\Tracking\Domain\Repository\Pageview; +use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +/** + * @covers DanielSiepmann\Tracking\Domain\Repository\Pageview + */ +class PageviewTest extends TestCase +{ + /** + * @test + */ + public function modelCanBeAdded() + { + $connection = $this->prophesize(Connection::class); + + $dateTime = $this->prophesize(\DateTimeImmutable::class); + $dateTime->format('U')->willReturn(1582660189); + + $language = $this->prophesize(SiteLanguage::class); + $language->getLanguageId()->willReturn(2); + + $model = $this->prophesize(Model::class); + $model->getPageUid()->willReturn(10); + $model->getCrdate()->willReturn($dateTime->reveal()); + $model->getPageType()->willReturn(999); + $model->getLanguage()->willReturn($language->reveal()); + $model->getUrl()->willReturn('https://example.com/path.html'); + $model->getUserAgent()->willReturn('Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/74.0'); + + $connection->insert( + 'tx_tracking_pageview', + [ + 'pid' => 10, + 'crdate' => 1582660189, + 'tstamp' => 1582660189, + 'type' => 999, + 'sys_language_uid' => 2, + 'url' => 'https://example.com/path.html', + 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/74.0', + ] + )->willReturn(1)->shouldBeCalledTimes(1); + + $subject = new Pageview($connection->reveal()); + $subject->add($model->reveal()); + } +} diff --git a/Tests/Unit/Middleware/PageviewTest.php b/Tests/Unit/Middleware/PageviewTest.php new file mode 100644 index 0000000..5f88677 --- /dev/null +++ b/Tests/Unit/Middleware/PageviewTest.php @@ -0,0 +1,95 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use DanielSiepmann\Tracking\Domain\Model\Pageview as Model; +use DanielSiepmann\Tracking\Domain\Repository\Pageview as Repository; +use DanielSiepmann\Tracking\Middleware\Pageview; +use PHPUnit\Framework\TestCase; +use Prophecy\Argument; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Routing\PageArguments; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; + +/** + * @covers DanielSiepmann\Tracking\Middleware\Pageview + */ +class PageviewTest extends TestCase +{ + /** + * @test + */ + public function doesNotAddBlacklistedRequest() + { + $repository = $this->prophesize(Repository::class); + $context = $this->prophesize(Context::class); + $rule = 'false'; + + $request = $this->prophesize(ServerRequestInterface::class); + $response = $this->prophesize(ResponseInterface::class); + $handler = $this->prophesize(RequestHandlerInterface::class); + + $handler->handle($request->reveal())->willReturn($response->reveal()); + $repository->add()->shouldNotBeCalled(); + + $subject = new Pageview($repository->reveal(), $context->reveal(), $rule); + $result = $subject->process($request->reveal(), $handler->reveal()); + + static::assertInstanceOf(ResponseInterface::class, $result); + } + + /** + * @test + */ + public function addsPageviewToRepository() + { + $repository = $this->prophesize(Repository::class); + $context = $this->prophesize(Context::class); + $rule = 'true'; + + $routing = $this->prophesize(PageArguments::class); + $routing->getPageId()->willReturn(10); + $routing->getPageType()->willReturn(0); + + $language = $this->prophesize(SiteLanguage::class); + + $request = $this->prophesize(ServerRequestInterface::class); + $request->getAttribute('routing')->willReturn($routing->reveal()); + $request->getAttribute('language')->willReturn($language->reveal()); + $request->getUri()->willReturn(''); + $request->getHeader('User-Agent')->willReturn([]); + + $response = $this->prophesize(ResponseInterface::class); + $handler = $this->prophesize(RequestHandlerInterface::class); + + $handler->handle($request->reveal())->willReturn($response->reveal()); + $repository->add(Argument::type(Model::class))->shouldBeCalledtimes(1); + + $subject = new Pageview($repository->reveal(), $context->reveal(), $rule); + $result = $subject->process($request->reveal(), $handler->reveal()); + + static::assertInstanceOf(ResponseInterface::class, $result); + } +} diff --git a/composer.json b/composer.json index ee05abe..32ef533 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,11 @@ "DanielSiepmann\\Tracking\\": "Classes/" } }, + "autoload-dev": { + "psr-4": { + "DanielSiepmann\\Tracking\\Tests\\": "Tests/" + } + }, "require": { "doctrine/dbal": "^2.10", "php": "^7.3.0", @@ -43,6 +48,7 @@ ] }, "require-dev": { - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^3.5", + "phpunit/phpunit": "^9.0" } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 0fe8bf8..936068c 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -3,6 +3,7 @@ This project coding standard Classes/ + Tests/ diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..4ae4c2d --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + Tests/Unit/ + + + + + + Classes + + + From 548ad0b07c4e7defb7fef3a509b334045bb1f289 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 22:33:03 +0100 Subject: [PATCH 15/16] Make widgets configurable via DI As no configuration API is provided, we use Services.yaml for now. --- Classes/Dashboard/Widgets/PageViewsBar.php | 46 +++++++++++------- .../Widgets/PageViewsPerPageDoughnut.php | 43 +++++++++++------ Classes/Dashboard/Widgets/SettingsFactory.php | 48 +++++++++++++++++++ Classes/Extension.php | 2 +- Configuration/Services.yaml | 25 ++++++++-- composer.json | 3 +- 6 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 Classes/Dashboard/Widgets/SettingsFactory.php diff --git a/Classes/Dashboard/Widgets/PageViewsBar.php b/Classes/Dashboard/Widgets/PageViewsBar.php index 63ec302..c713a33 100644 --- a/Classes/Dashboard/Widgets/PageViewsBar.php +++ b/Classes/Dashboard/Widgets/PageViewsBar.php @@ -24,6 +24,7 @@ namespace DanielSiepmann\Tracking\Dashboard\Widgets; use DanielSiepmann\Tracking\Extension; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Dashboard\Widgets\AbstractBarChartWidget; class PageViewsBar extends AbstractBarChartWidget @@ -41,16 +42,25 @@ class PageViewsBar extends AbstractBarChartWidget */ protected $queryBuilder; - public function __construct(string $identifier, QueryBuilder $queryBuilder) - { + /** + * @var \ArrayObject + */ + private $settings; + + public function __construct( + string $identifier, + QueryBuilder $queryBuilder, + \ArrayObject $settings + ) { parent::__construct($identifier); + $this->queryBuilder = $queryBuilder; - $this->identifier = $identifier; + $this->settings = $settings; } protected function prepareChartData(): void { - list($labels, $data) = $this->calculateDataForLastDays(31); + list($labels, $data) = $this->calculateDataForLastDays((int) $this->settings['periodInDays']); $this->chartData = [ 'labels' => $labels, @@ -69,21 +79,25 @@ class PageViewsBar extends AbstractBarChartWidget protected function getPageViewsInPeriod(int $start, int $end): int { + $constraints = [ + $this->queryBuilder->expr()->gte('crdate', $start), + $this->queryBuilder->expr()->lte('crdate', $end), + ]; + + if (count($this->settings['blackListedPages'])) { + $constraints[] = $this->queryBuilder->expr()->notIn( + 'tx_tracking_pageview.pid', + $this->queryBuilder->createNamedParameter( + $this->settings['blackListedPages'], + Connection::PARAM_INT_ARRAY + ) + ); + } + return (int)$this->queryBuilder ->count('*') ->from('tx_tracking_pageview') - ->where( - $this->queryBuilder->expr()->gte('tstamp', $start), - $this->queryBuilder->expr()->lte('tstamp', $end), - $this->queryBuilder->expr()->notIn( - 'tx_tracking_pageview.pid', - $this->queryBuilder->createNamedParameter([ - 1, - 11, - 38, - ], Connection::PARAM_INT_ARRAY) - ) - ) + ->where(... $constraints) ->execute() ->fetchColumn(); } diff --git a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php index 30eb1d2..7b59f68 100644 --- a/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php +++ b/Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php @@ -38,15 +38,25 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget */ protected $queryBuilder; - public function __construct(string $identifier, QueryBuilder $queryBuilder) - { + /** + * @var \ArrayObject + */ + private $settings; + + public function __construct( + string $identifier, + QueryBuilder $queryBuilder, + \ArrayObject $settings + ) { parent::__construct($identifier); + $this->queryBuilder = $queryBuilder; + $this->settings = $settings; } protected function prepareChartData(): void { - list($labels, $data) = $this->getPageViewsPerPage(31); + list($labels, $data) = $this->getPageViewsPerPage((int) $this->settings['periodInDays']); $this->chartData = [ 'labels' => $labels, @@ -59,11 +69,25 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget ]; } - private function getPageViewsPerPage(int $period): array + private function getPageViewsPerPage(int $days): array { $labels = []; $data = []; + $constraints = [ + $this->queryBuilder->expr()->gte('tx_tracking_pageview.crdate', strtotime('-' . $days . ' day 0:00:00')), + $this->queryBuilder->expr()->lte('tx_tracking_pageview.crdate', time()), + ]; + if (count($this->settings['blackListedPages'])) { + $constraints[] = $this->queryBuilder->expr()->notIn( + 'tx_tracking_pageview.pid', + $this->queryBuilder->createNamedParameter( + $this->settings['blackListedPages'], + Connection::PARAM_INT_ARRAY + ) + ); + } + $result = $this->queryBuilder ->selectLiteral('count(tx_tracking_pageview.pid) as total') ->addSelect('pages.title', 'pages.uid') @@ -77,16 +101,7 @@ class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget $this->queryBuilder->quoteIdentifier('pages.uid') ) ) - ->where( - $this->queryBuilder->expr()->notIn( - 'tx_tracking_pageview.pid', - $this->queryBuilder->createNamedParameter([ - 1, - 11, - 38, - ], Connection::PARAM_INT_ARRAY) - ) - ) + ->where(... $constraints) ->groupBy('tx_tracking_pageview.pid') ->orderBy('total', 'desc') ->setMaxResults(6) // Because 6 colors are defined diff --git a/Classes/Dashboard/Widgets/SettingsFactory.php b/Classes/Dashboard/Widgets/SettingsFactory.php new file mode 100644 index 0000000..65aac9d --- /dev/null +++ b/Classes/Dashboard/Widgets/SettingsFactory.php @@ -0,0 +1,48 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +class SettingsFactory +{ + private $defaults = [ + 'pageViewsBar' => [ + 'periodInDays' => 31, + 'blackListedPages' => [], + ], + 'pageViewsPerPageDoughnut' => [ + 'periodInDays' => 31, + 'blackListedPages' => [], + 'maxResults' => 6, + ], + ]; + + public function fromArray(string $widgetIdentifier, array $settings): \ArrayObject + { + $settingsToUse = $this->defaults[$widgetIdentifier] ?? []; + + ArrayUtility::mergeRecursiveWithOverrule($settingsToUse, $settings); + + return new \ArrayObject($settingsToUse); + } +} diff --git a/Classes/Extension.php b/Classes/Extension.php index 1716c56..227fa52 100644 --- a/Classes/Extension.php +++ b/Classes/Extension.php @@ -25,5 +25,5 @@ final class Extension { public const EXT_KEY = 'tracking'; - public const LANGUAGE_PATH = 'LLL:EXT:' . self::EXT_KEY . 'tracking/Resources/Private/Language/locallang.xlf'; + public const LANGUAGE_PATH = 'LLL:EXT:' . self::EXT_KEY . '/Resources/Private/Language/locallang.xlf'; } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 0d66960..245218d 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -9,6 +9,21 @@ services: # Virtual services + DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsBar: + factory: + - '@DanielSiepmann\Tracking\Dashboard\Widgets\SettingsFactory' + - 'fromArray' + arguments: + $widgetIdentifier: 'pageViewsBar' + $settings: [] + DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsPerPageDoughnut: + factory: + - '@DanielSiepmann\Tracking\Dashboard\Widgets\SettingsFactory' + - 'fromArray' + arguments: + $widgetIdentifier: 'pageViewsPerPageDoughnut' + $settings: [] + DanielSiepmann\Tracking\DI\DatabaseConnection\Pageview: factory: - '@TYPO3\CMS\Core\Database\ConnectionPool' @@ -42,8 +57,9 @@ services: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar: class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar' arguments: - - 'pageViewsBar' - - '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' + $identifier: 'pageViewsBar' + $queryBuilder: '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' + $settings: '@DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsBar' tags: - name: 'dashboard.widget' identifier: 'pageViewsBar' @@ -52,8 +68,9 @@ services: DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut: class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut' arguments: - - 'pageViewsPerPageDoughnut' - - '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' + $identifier: 'pageViewsPerPageDoughnut' + $queryBuilder: '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView' + $settings: '@DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsPerPageDoughnut' tags: - name: 'dashboard.widget' identifier: 'pageViewsPerPageDoughnut' diff --git a/composer.json b/composer.json index 32ef533..a4e6c54 100644 --- a/composer.json +++ b/composer.json @@ -49,6 +49,7 @@ }, "require-dev": { "squizlabs/php_codesniffer": "^3.5", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.0", + "typo3/cms-dashboard": "^10.3.0" } } From c5e788e31d5e941ebcdf7b8a8a1bb5316393e88c Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 25 Feb 2020 22:33:41 +0100 Subject: [PATCH 16/16] Add some about information --- readme.rst | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/readme.rst b/readme.rst index a30f73b..ada3a07 100644 --- a/readme.rst +++ b/readme.rst @@ -1,3 +1,39 @@ +About +===== + +This extension should only demonstrate technical features of TYPO3. +It is not intended for use in production systems. + +The following features should be demonstrated: + +PSR-4 Autoloading Standard + Use `composer.json` to provide autoloading information. + Classes will be loaded when needed. No need for require statements. + +PSR-12 Extended Coding Style Guide + Current stable Coding Style Guide, applied via Coding Sniffer. + +PSR-7 HTTP Message Interface + Also known as Request Response, used to create tracking information from incoming + request. + +PSR-11 Container Interface + Also known as Dependency Injection. + Used to resolve external dependencies, e.g. foreign classes. + Existing TYPO3 factories are used to build `QueryBuilder` instances. + Also DI is "misused" to provide configuration for dashboard widgets + and tracking blacklists. + +PSR-15 HTTP Handlers + Also known as middlewares. + Used to hook into processing to create tracking information. + +PSR-14 Event Dispatcher + Not in use yet. + +EXT:dashboard + Used to visualize collected tracking information. + Todos =====