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.