From 44ca6a2d3cd3d75e1265dedb5a6d96d3dfb9d39e Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 1 Apr 2020 21:04:32 +0200 Subject: [PATCH] Add operating system to page view record Extract operating system from user agent and store it in database record. Another widget is added which displays the page views per operating system. An command is provided which will update existing data. --- Classes/Command/UpdateDataCommand.php | 65 +++++++ .../Provider/PageviewsPerOperatingSystem.php | 111 ++++++++++++ .../Dashboard/Provider/PageviewsPerPage.php | 2 +- Classes/Domain/Model/Pageview.php | 44 ++++- Classes/Domain/Pageview/Factory.php | 25 ++- Classes/Domain/Repository/Pageview.php | 72 ++++++-- Configuration/Backend/DashboardWidgets.yaml | 31 +++- Configuration/Services.yaml | 5 + Configuration/TCA/tx_tracking_pageview.php | 9 +- Resources/Private/Language/locallang.xlf | 6 + Resources/Private/Language/locallang_tca.xlf | 3 + Tests/Unit/Domain/Model/PageviewTest.php | 115 +++++++++++++ Tests/Unit/Domain/Pageview/FactoryTest.php | 37 +++- Tests/Unit/Domain/Repository/PageviewTest.php | 158 +++++++++++++++++- ext_tables.sql | 1 + readme.rst | 5 +- 16 files changed, 665 insertions(+), 24 deletions(-) create mode 100644 Classes/Command/UpdateDataCommand.php create mode 100644 Classes/Dashboard/Provider/PageviewsPerOperatingSystem.php diff --git a/Classes/Command/UpdateDataCommand.php b/Classes/Command/UpdateDataCommand.php new file mode 100644 index 0000000..158861b --- /dev/null +++ b/Classes/Command/UpdateDataCommand.php @@ -0,0 +1,65 @@ + + * + * 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\Repository\Pageview; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class UpdateDataCommand extends Command +{ + /** + * @var Pageview + */ + private $repository; + + public function __construct(Pageview $repository) + { + $this->repository = $repository; + + parent::__construct(); + } + + protected function configure(): void + { + $this->setDescription('Updates existing data.'); + $this->setHelp('In case some more data can be extracted of the existing data.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->progressStart($this->repository->countAll()); + + foreach ($this->repository->findAll() as $pageView) { + $this->repository->update($pageView); + $io->progressAdvance(); + } + + $io->progressFinish(); + + return 0; + } +} diff --git a/Classes/Dashboard/Provider/PageviewsPerOperatingSystem.php b/Classes/Dashboard/Provider/PageviewsPerOperatingSystem.php new file mode 100644 index 0000000..2507de8 --- /dev/null +++ b/Classes/Dashboard/Provider/PageviewsPerOperatingSystem.php @@ -0,0 +1,111 @@ + + * + * 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\Query\QueryBuilder; +use TYPO3\CMS\Dashboard\WidgetApi; +use TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface; + +class PageviewsPerOperatingSystem implements ChartDataProviderInterface +{ + /** + * @var QueryBuilder + */ + private $queryBuilder; + + /** + * @var int + */ + private $days; + + /** + * @var int + */ + private $maxResults; + + public function __construct( + QueryBuilder $queryBuilder, + int $days = 31, + int $maxResults = 6 + ) { + $this->queryBuilder = $queryBuilder; + $this->days = $days; + $this->maxResults = $maxResults; + } + + public function getChartData(): array + { + list($labels, $data) = $this->getPageViewsPerPage(); + + return [ + 'labels' => $labels, + 'datasets' => [ + [ + 'backgroundColor' => WidgetApi::getDefaultChartColors(), + 'data' => $data, + ] + ], + ]; + } + + private function getPageViewsPerPage(): array + { + $labels = []; + $data = []; + + $constraints = [ + $this->queryBuilder->expr()->gte( + 'tx_tracking_pageview.crdate', + strtotime('-' . $this->days . ' day 0:00:00') + ), + $this->queryBuilder->expr()->lte( + 'tx_tracking_pageview.crdate', + time() + ), + $this->queryBuilder->expr()->neq( + 'tx_tracking_pageview.operating_system', + $this->queryBuilder->createNamedParameter('') + ), + ]; + + $result = $this->queryBuilder + ->selectLiteral('count(operating_system) as total') + ->addSelect('operating_system') + ->from('tx_tracking_pageview') + ->where(... $constraints) + ->groupBy('tx_tracking_pageview.operating_system') + ->orderBy('total', 'desc') + ->setMaxResults($this->maxResults) + ->execute() + ->fetchAll(); + + foreach ($result as $row) { + $labels[] = mb_strimwidth($row['operating_system'], 0, 50, '…'); + $data[] = $row['total']; + } + + return [ + $labels, + $data, + ]; + } +} diff --git a/Classes/Dashboard/Provider/PageviewsPerPage.php b/Classes/Dashboard/Provider/PageviewsPerPage.php index ba2fda1..8f08e5a 100644 --- a/Classes/Dashboard/Provider/PageviewsPerPage.php +++ b/Classes/Dashboard/Provider/PageviewsPerPage.php @@ -123,7 +123,7 @@ class PageviewsPerPage implements ChartDataProviderInterface ->fetchAll(); foreach ($result as $row) { - $labels[] = mb_strimwidth($row['title'], 0, 50, '…') . ' [' . $row['uid'] . ']'; + $labels[] = mb_strimwidth($row['title'], 0, 25, '…'); $data[] = $row['total']; } diff --git a/Classes/Domain/Model/Pageview.php b/Classes/Domain/Model/Pageview.php index 83404d4..8c760bd 100644 --- a/Classes/Domain/Model/Pageview.php +++ b/Classes/Domain/Model/Pageview.php @@ -25,6 +25,11 @@ use TYPO3\CMS\Core\Site\Entity\SiteLanguage; class Pageview { + /** + * @var int + */ + private $uid = 0; + /** * @var int */ @@ -61,8 +66,10 @@ class Pageview \DateTimeImmutable $crdate, int $pageType, string $url, - string $userAgent + string $userAgent, + int $uid = 0 ) { + $this->uid = $uid; $this->pageUid = $pageUid; $this->language = $language; $this->crdate = $crdate; @@ -71,6 +78,11 @@ class Pageview $this->userAgent = $userAgent; } + public function getUid(): int + { + return $this->uid; + } + public function getPageUid(): int { return $this->pageUid; @@ -100,4 +112,34 @@ class Pageview { return $this->userAgent; } + + public function getOperatingSystem(): string + { + if (mb_stripos($this->userAgent, 'Android') !== false) { + return 'Android'; + } + if (mb_stripos($this->userAgent, 'Windows') !== false) { + return 'Windows'; + } + if (mb_stripos($this->userAgent, 'Linux') !== false) { + return 'Linux'; + } + if (mb_stripos($this->userAgent, 'Macintosh') !== false) { + return 'Macintosh'; + } + if (mb_stripos($this->userAgent, 'CrOS') !== false) { + return 'Google Chrome OS'; + } + if (mb_stripos($this->userAgent, 'OpenBSD') !== false) { + return 'OpenBSD'; + } + if ( + mb_stripos($this->userAgent, 'iPad') !== false + || mb_stripos($this->userAgent, 'iphone') !== false + ) { + return 'iOS'; + } + + return ''; + } } diff --git a/Classes/Domain/Pageview/Factory.php b/Classes/Domain/Pageview/Factory.php index 7555dac..d1e3fbb 100644 --- a/Classes/Domain/Pageview/Factory.php +++ b/Classes/Domain/Pageview/Factory.php @@ -24,10 +24,20 @@ namespace DanielSiepmann\Tracking\Domain\Pageview; use DanielSiepmann\Tracking\Domain\Model\Pageview; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Routing\PageArguments; -use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Site\SiteFinder; class Factory implements FromRequest { + /** + * @var SiteFinder + */ + private $siteFinder; + + public function __construct(SiteFinder $siteFinder) + { + $this->siteFinder = $siteFinder; + } + public static function fromRequest(ServerRequestInterface $request): Pageview { return new PageView( @@ -40,6 +50,19 @@ class Factory implements FromRequest ); } + public function fromDbRow(array $dbRow): Pageview + { + return new PageView( + $dbRow['pid'], + $this->siteFinder->getSiteByPageId($dbRow['pid'])->getLanguageById($dbRow['sys_language_uid']), + new \DateTimeImmutable('@' . $dbRow['crdate']), + $dbRow['type'], + $dbRow['url'], + $dbRow['user_agent'], + $dbRow['uid'] + ); + } + private static function getRouting(ServerRequestInterface $request): PageArguments { return $request->getAttribute('routing'); diff --git a/Classes/Domain/Repository/Pageview.php b/Classes/Domain/Repository/Pageview.php index 97af4cc..6a50733 100644 --- a/Classes/Domain/Repository/Pageview.php +++ b/Classes/Domain/Repository/Pageview.php @@ -22,6 +22,8 @@ namespace DanielSiepmann\Tracking\Domain\Repository; */ use DanielSiepmann\Tracking\Domain\Model\Pageview as Model; +use DanielSiepmann\Tracking\Domain\Pageview\Factory; +use Doctrine\DBAL\Driver\Statement; use TYPO3\CMS\Core\Database\Connection; class Pageview @@ -31,24 +33,72 @@ class Pageview */ private $connection; - public function __construct(Connection $connection) - { + /** + * @var Factory + */ + private $factory; + + public function __construct( + Connection $connection, + Factory $factory + ) { $this->connection = $connection; + $this->factory = $factory; + } + + public function countAll(): int + { + return $this->connection->createQueryBuilder() + ->count('uid') + ->from('tx_tracking_pageview') + ->execute() + ->fetchColumn(); + } + + public function findAll(): \Generator + { + $queryBuilder = $this->connection->createQueryBuilder(); + $pageViews = $queryBuilder->select('*')->from('tx_tracking_pageview')->execute(); + + if ($pageViews instanceof Statement) { + while ($pageView = $pageViews->fetch()) { + yield $this->factory->fromDbRow($pageView); + } + } + } + + public function update(Model $pageview): void + { + if ($pageview->getUid() === 0) { + throw new \InvalidArgumentException('Can not update pageview if uid is 0.', 1585770573); + } + + $this->connection->update( + 'tx_tracking_pageview', + $this->getFieldsFromModel($pageview), + ['uid' => $pageview->getUid()] + ); } public function add(Model $pageview): void { $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(), - 'user_agent' => $pageview->getUserAgent(), - ] + $this->getFieldsFromModel($pageview) ); } + + private function getFieldsFromModel(Model $pageview): array + { + return [ + '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(), + 'user_agent' => $pageview->getUserAgent(), + 'operating_system' => $pageview->getOperatingSystem(), + ]; + } } diff --git a/Configuration/Backend/DashboardWidgets.yaml b/Configuration/Backend/DashboardWidgets.yaml index 8c85103..da66647 100644 --- a/Configuration/Backend/DashboardWidgets.yaml +++ b/Configuration/Backend/DashboardWidgets.yaml @@ -1,7 +1,7 @@ services: _defaults: - autowire: true - autoconfigure: true + autowire: false + autoconfigure: false public: false DanielSiepmann\Tracking\Dashboard\: @@ -11,7 +11,7 @@ services: arguments: $queryBuilder: '@querybuilder.tx_tracking_pageview' - dashboard.widget.danielsiepmann_tracking.pageViewsPerDay: + dashboard.widget.danielsiepmann.tracking.pageViewsPerDay: class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget' arguments: $view: '@dashboard.views.widget' @@ -31,7 +31,7 @@ services: arguments: $queryBuilder: '@querybuilder.tx_tracking_pageview' - dashboard.widget.danielsiepmann_tracking.pageViewsPerPage: + dashboard.widget.danielsiepmann.tracking.pageViewsPerPage: class: 'TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget' arguments: $view: '@dashboard.views.widget' @@ -51,7 +51,7 @@ services: arguments: $queryBuilder: '@querybuilder.tx_tracking_pageview' - dashboard.widget.danielsiepmann_tracking.newestPageviews: + dashboard.widget.danielsiepmann.tracking.newestPageviews: class: 'TYPO3\CMS\Dashboard\Widgets\ListWidget' arguments: $view: '@dashboard.views.widget' @@ -65,3 +65,24 @@ services: description: 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.newestPageviewsList.description' height: 'medium' width: 'small' + + DanielSiepmann\Tracking\Dashboard\Provider\PageviewsPerOperatingSystem: + class: 'DanielSiepmann\Tracking\Dashboard\Provider\PageviewsPerOperatingSystem' + arguments: + $queryBuilder: '@querybuilder.tx_tracking_pageview' + + dashboard.widget.danielsiepmann.tracking.operatingSystems: + class: 'TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget' + arguments: + $view: '@dashboard.views.widget' + $dataProvider: '@DanielSiepmann\Tracking\Dashboard\Provider\PageviewsPerOperatingSystem' + tags: + - name: 'dashboard.widget' + identifier: 'operatingSystemsDoughnut' + groupNames: 'tracking' + iconIdentifier: 'content-widget-chart-pie' + title: 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.operatingSystemsDoughnut.title' + description: 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widgets.operatingSystemsDoughnut.description' + additionalCssClasses: 'dashboard-item--chart' + height: 'medium' + width: 'small' diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index ae6fdbc..63937f9 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -41,3 +41,8 @@ services: and not (request.getHeader("User-Agent")[0] matches "/Wget|curl|Go-http-client/") 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|ia_archiver|PaperLiBot|TrendsmapResolver|AhrefsBot|Nuzzel/") and not (request.getHeader("User-Agent")[0] matches "/mattermost|Slackbot|WhatsApp/") + + DanielSiepmann\Tracking\Command\UpdateDataCommand: + tags: + - name: 'console.command' + command: 'tracking:updatedata' diff --git a/Configuration/TCA/tx_tracking_pageview.php b/Configuration/TCA/tx_tracking_pageview.php index 436616e..47d4b87 100644 --- a/Configuration/TCA/tx_tracking_pageview.php +++ b/Configuration/TCA/tx_tracking_pageview.php @@ -15,7 +15,7 @@ return [ ], 'types' => [ '0' => [ - 'showitem' => 'sys_language_uid, pid, url, user_agent, type, crdate', + 'showitem' => 'sys_language_uid, pid, url, user_agent, operating_system, type, crdate', ], ], 'columns' => [ @@ -54,6 +54,13 @@ return [ 'readOnly' => true, ], ], + 'operating_system' => [ + 'label' => 'LLL:EXT:tracking/Resources/Private/Language/locallang_tca.xlf:table.pageview.operating_system', + '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.xlf b/Resources/Private/Language/locallang.xlf index af57387..476b371 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -21,6 +21,12 @@ Displays total page views per page. + + Page Views / Operating System + + + Displays total page views per operating system. + Newest Page Views diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf index 03f191e..68f515f 100644 --- a/Resources/Private/Language/locallang_tca.xlf +++ b/Resources/Private/Language/locallang_tca.xlf @@ -27,6 +27,9 @@ User agent + + Operating System + diff --git a/Tests/Unit/Domain/Model/PageviewTest.php b/Tests/Unit/Domain/Model/PageviewTest.php index 16cbc94..5c9f066 100644 --- a/Tests/Unit/Domain/Model/PageviewTest.php +++ b/Tests/Unit/Domain/Model/PageviewTest.php @@ -169,4 +169,119 @@ class PageviewTest extends TestCase $subject->getUserAgent() ); } + + /** + * @test + */ + public function returnsZeroAsDefaultUid(): void + { + $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( + 0, + $subject->getUid() + ); + } + + /** + * @test + */ + public function returnsSetAsUid(): void + { + $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', + 10 + ); + + static::assertSame( + 10, + $subject->getUid() + ); + } + + /** + * @test + * @dataProvider possibleUserStringWithOperatingSystems + * @testdox Operating system $expectedOperatingSystem is extracted from UserAgent string: $userAgent + */ + public function returnsOperatingSystem(string $userAgent, string $expectedOperatingSystem): void + { + $language = $this->prophesize(SiteLanguage::class); + + $subject = new Pageview( + 0, + $language->reveal(), + new \DateTimeImmutable(), + 0, + '', + $userAgent + ); + + static::assertSame( + $expectedOperatingSystem, + $subject->getOperatingSystem() + ); + } + + public function possibleUserStringWithOperatingSystems(): array + { + return [ + [ + 'userAgent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + 'expectedOperatingSystem' => 'Linux', + ], + [ + 'userAgent' => 'Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3003 Build/PKQ1.181203.001)', + 'expectedOperatingSystem' => 'Android', + ], + [ + 'userAgent' => 'Apache-HttpClient/4.5.2 (Java/1.8.0_151)', + 'expectedOperatingSystem' => '', + ], + [ + 'userAgent' => 'AwarioSmartBot/1.0 (+https://awario.com/bots.html; bots@awario.com)', + 'expectedOperatingSystem' => '', + ], + [ + 'userAgent' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', + 'expectedOperatingSystem' => 'Windows', + ], + [ + 'userAgent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:73.0) Gecko/20100101 Firefox/73.0', + 'expectedOperatingSystem' => 'Macintosh', + ], + [ + 'userAgent' => 'Mozilla/5.0 (X11; CrOS x86_64 12607.82.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.123 Safari/537.36', + 'expectedOperatingSystem' => 'Google Chrome OS', + ], + [ + 'userAgent' => 'Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.8.1.4) Gecko/20070704 Firefox/52.0', + 'expectedOperatingSystem' => 'OpenBSD', + ], + [ + 'userAgent' => 'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/80.0.3987.95 Mobile/15E148 Safari/604.1', + 'expectedOperatingSystem' => 'iOS', + ], + [ + 'userAgent' => 'Mozilla/5.0 (iPhone; CPU OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/22.0 Mobile/15E148 Safari/605.1.15', + 'expectedOperatingSystem' => 'iOS', + ], + ]; + } } diff --git a/Tests/Unit/Domain/Pageview/FactoryTest.php b/Tests/Unit/Domain/Pageview/FactoryTest.php index fcf32fc..25b11f9 100644 --- a/Tests/Unit/Domain/Pageview/FactoryTest.php +++ b/Tests/Unit/Domain/Pageview/FactoryTest.php @@ -28,7 +28,9 @@ use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophet; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Routing\PageArguments; +use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Site\SiteFinder; /** * @covers DanielSiepmann\Tracking\Domain\Pageview\Factory @@ -40,7 +42,7 @@ class FactoryTest extends TestCase /** * @test */ - public function returnsPageview(): void + public function returnsPageviewFromRequest(): void { $routing = $this->prophesize(PageArguments::class); $routing->getPageId()->willReturn(10); @@ -197,4 +199,37 @@ class FactoryTest extends TestCase $result->getPageUid() ); } + + /** + * @test + */ + public function returnsPageviewFromDbRow(): void + { + $siteLanguage = $this->prophesize(SiteLanguage::class); + $site = $this->prophesize(Site::class); + $site->getLanguageById(0)->willReturn($siteLanguage->reveal()); + $siteFinder = $this->prophesize(SiteFinder::class); + $siteFinder->getSiteByPageId(2)->willReturn($site->reveal()); + + $subject = new Factory($siteFinder->reveal()); + + $result = $subject->fromDbRow([ + 'uid' => 1, + 'pid' => 2, + 'sys_language_uid' => 0, + 'crdate' => 1533906435, + 'type' => 0, + 'url' => 'https://example.com/path', + 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + ]); + + static::assertInstanceOf(Pageview::class, $result); + static::assertSame(1, $result->getUid()); + static::assertSame(2, $result->getPageUid()); + static::assertSame($siteLanguage->reveal(), $result->getLanguage()); + static::assertSame('1533906435', $result->getCrdate()->format('U')); + static::assertSame(0, $result->getPageType()); + static::assertSame('https://example.com/path', $result->getUrl()); + static::assertSame('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', $result->getUserAgent()); + } } diff --git a/Tests/Unit/Domain/Repository/PageviewTest.php b/Tests/Unit/Domain/Repository/PageviewTest.php index 7f85984..392982b 100644 --- a/Tests/Unit/Domain/Repository/PageviewTest.php +++ b/Tests/Unit/Domain/Repository/PageviewTest.php @@ -22,10 +22,13 @@ namespace DanielSiepmann\Tracking\Tests\Unit\Domain\Repository; */ use DanielSiepmann\Tracking\Domain\Model\Pageview as Model; +use DanielSiepmann\Tracking\Domain\Pageview\Factory; use DanielSiepmann\Tracking\Domain\Repository\Pageview; +use Doctrine\DBAL\Statement; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; /** @@ -41,6 +44,7 @@ class PageviewTest extends TestCase public function modelCanBeAdded(): void { $connection = $this->prophesize(Connection::class); + $factory = $this->prophesize(Factory::class); $dateTime = $this->prophesize(\DateTimeImmutable::class); $dateTime->format('U')->willReturn(1582660189); @@ -55,6 +59,7 @@ class PageviewTest extends TestCase $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'); + $model->getOperatingSystem()->willReturn('Linux'); $connection->insert( 'tx_tracking_pageview', @@ -66,10 +71,161 @@ class PageviewTest extends TestCase 'sys_language_uid' => 2, 'url' => 'https://example.com/path.html', 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/74.0', + 'operating_system' => 'Linux', ] )->willReturn(1)->shouldBeCalledTimes(1); - $subject = new Pageview($connection->reveal()); + $subject = new Pageview($connection->reveal(), $factory->reveal()); $subject->add($model->reveal()); } + + /** + * @test + */ + public function throwsExceptionIfModelToUodateHasNoUid(): void + { + $connection = $this->prophesize(Connection::class); + $factory = $this->prophesize(Factory::class); + + $model = $this->prophesize(Model::class); + $model->getUid()->willReturn(0); + + $subject = new Pageview($connection->reveal(), $factory->reveal()); + $this->expectExceptionMessage('Can not update pageview if uid is 0.'); + $subject->update($model->reveal()); + } + + /** + * @test + */ + public function modelCanBeUpdated(): void + { + $connection = $this->prophesize(Connection::class); + $factory = $this->prophesize(Factory::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->getUid()->willReturn(1); + $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'); + $model->getOperatingSystem()->willReturn('Linux'); + + $connection->update( + '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', + 'operating_system' => 'Linux', + ], + [ + 'uid' => 1 + ] + )->willReturn(1)->shouldBeCalledTimes(1); + + $subject = new Pageview($connection->reveal(), $factory->reveal()); + $subject->update($model->reveal()); + } + + /** + * @test + */ + public function returnsACountOfAllModels(): void + { + $statement = $this->prophesize(Statement::class); + $statement->fetchColumn()->willReturn(10); + + $queryBuilder = $this->prophesize(QueryBuilder::class); + $queryBuilder->count('uid')->willReturn($queryBuilder->reveal()); + $queryBuilder->from('tx_tracking_pageview')->willReturn($queryBuilder->reveal()); + $queryBuilder->execute()->willReturn($statement->reveal()); + + $connection = $this->prophesize(Connection::class); + $connection->createQueryBuilder()->willReturn($queryBuilder->reveal()); + + $factory = $this->prophesize(Factory::class); + + $subject = new Pageview($connection->reveal(), $factory->reveal()); + static::assertSame(10, $subject->countAll()); + } + + /** + * @test + */ + public function returnsAllModells(): void + { + $statement = $this->prophesize(Statement::class); + $statement->fetch()->willReturn( + [ + 'pid' => '10', + 'crdate' => '1595948372', + 'type' => '0', + 'sys_language_uid' => '0', + 'url' => 'https://example.com/path/file.html', + 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + ], + [ + 'pid' => '9', + 'crdate' => '1595948376', + 'type' => '0', + 'sys_language_uid' => '0', + 'url' => 'https://example.com/path/file.html', + 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + ], + false + ); + + $queryBuilder = $this->prophesize(QueryBuilder::class); + $queryBuilder->select('*')->willReturn($queryBuilder->reveal()); + $queryBuilder->from('tx_tracking_pageview')->willReturn($queryBuilder->reveal()); + $queryBuilder->execute()->willReturn($statement->reveal()); + + $connection = $this->prophesize(Connection::class); + $connection->createQueryBuilder()->willReturn($queryBuilder->reveal()); + + $model1 = $this->prophesize(Model::class); + $model1->getPageUid()->willReturn(10); + $model2 = $this->prophesize(Model::class); + $model2->getPageUid()->willReturn(9); + + $factory = $this->prophesize(Factory::class); + $factory->fromDbRow([ + 'pid' => '10', + 'crdate' => '1595948372', + 'type' => '0', + 'sys_language_uid' => '0', + 'url' => 'https://example.com/path/file.html', + 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + ])->willReturn($model1->reveal()); + $factory->fromDbRow([ + 'pid' => '9', + 'crdate' => '1595948376', + 'type' => '0', + 'sys_language_uid' => '0', + 'url' => 'https://example.com/path/file.html', + 'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36', + ])->willReturn($model2->reveal()); + + $subject = new Pageview($connection->reveal(), $factory->reveal()); + static::assertCount(2, $subject->findAll()); + + $pageUid = 10; + foreach ($subject->findAll() as $model) { + static::assertSame($pageUid, $model->getPageUid()); + --$pageUid; + } + } } diff --git a/ext_tables.sql b/ext_tables.sql index dc0b8fb..471859d 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -1,5 +1,6 @@ CREATE TABLE tx_tracking_pageview ( url text, user_agent text, + operating_system varchar(255) DEFAULT '' NOT NULL, type int(11) unsigned DEFAULT '0' NOT NULL, ); diff --git a/readme.rst b/readme.rst index d795eb6..e2835b3 100644 --- a/readme.rst +++ b/readme.rst @@ -45,9 +45,9 @@ Todos #. Add referrer if available. -#. Add operating System +#. Add device type phone, tablet, desktop? - #. Another Symfony Expression which returns the OS ("Ubuntu", "Macintosh", "Android", "iPhone", "Windows") +#. Add operating system version? #. Add further widgets. @@ -62,6 +62,7 @@ Todos #. Provide an overview of crawls as widgets. E.g. to allow fine grained robots.txt. #. Add information to Admin Panel. + #. 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.