Merge pull request #1 from DanielSiepmann/develop

Develop
This commit is contained in:
Daniel Siepmann 2020-02-25 22:37:26 +01:00 committed by GitHub
commit 64b0261694
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1609 additions and 0 deletions

19
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,19 @@
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
- name: Execute PHPUnit Tests
run: ./vendor/bin/phpunit

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/.Build/
/composer.lock
/vendor/

View file

@ -0,0 +1,125 @@
<?php
namespace DanielSiepmann\Tracking\Dashboard\Widgets;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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\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
{
protected $title = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsBar.title';
protected $description = Extension::LANGUAGE_PATH . ':trackingdashboard.widgets.pageViewsBar.description';
protected $width = 2;
protected $height = 4;
/**
* @var QueryBuilder
*/
protected $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->calculateDataForLastDays((int) $this->settings['periodInDays']);
$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
{
$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(... $constraints)
->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,
];
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace DanielSiepmann\Tracking\Dashboard\Widgets;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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\Extension;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Dashboard\Widgets\AbstractDoughnutChartWidget;
class PageViewsPerPageDoughnut extends AbstractDoughnutChartWidget
{
protected $title = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsPerPageDoughnut.title';
protected $description = Extension::LANGUAGE_PATH . ':dashboard.widgets.pageViewsPerPageDoughnut.description';
/**
* @var QueryBuilder
*/
protected $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((int) $this->settings['periodInDays']);
$this->chartData = [
'labels' => $labels,
'datasets' => [
[
'backgroundColor' => $this->chartColors,
'data' => $data,
]
],
];
}
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')
->from('tx_tracking_pageview')
->leftJoin(
'tx_tracking_pageview',
'pages',
'pages',
$this->queryBuilder->expr()->eq(
'tx_tracking_pageview.pid',
$this->queryBuilder->quoteIdentifier('pages.uid')
)
)
->where(... $constraints)
->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,
];
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace DanielSiepmann\Tracking\Dashboard\Widgets;
use TYPO3\CMS\Core\Utility\ArrayUtility;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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);
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace DanielSiepmann\Tracking\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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;
/**
* @var string
*/
private $userAgent;
public function __construct(
int $pageUid,
SiteLanguage $language,
\DateTimeImmutable $crdate,
int $pageType,
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
{
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;
}
public function getUserAgent(): string
{
return $this->userAgent;
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace DanielSiepmann\Tracking\Domain\Pageview;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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(),
$request->getHeader('User-Agent')[0] ?? ''
);
}
private static function getRouting(ServerRequestInterface $request): PageArguments
{
return $request->getAttribute('routing');
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace DanielSiepmann\Tracking\Domain\Pageview;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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;
}

View file

@ -0,0 +1,54 @@
<?php
namespace DanielSiepmann\Tracking\Domain\Repository;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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(),
'user_agent' => $pageview->getUserAgent(),
]
);
}
}

29
Classes/Extension.php Normal file
View file

@ -0,0 +1,29 @@
<?php
namespace DanielSiepmann\Tracking;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 . '/Resources/Private/Language/locallang.xlf';
}

View file

@ -0,0 +1,77 @@
<?php
namespace DanielSiepmann\Tracking\Middleware;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use TYPO3\CMS\Core\Context\Context;
class Pageview implements MiddlewareInterface
{
/**
* @var Repository
*/
private $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 {
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,
]);
}
}

View file

@ -0,0 +1,6 @@
<?php
return [
'tracking' => [
'title' => 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widget.group.tracking',
],
];

View file

@ -0,0 +1,15 @@
<?php
return [
'frontend' => [
'tracking-pageview' => [
'target' => \DanielSiepmann\Tracking\Middleware\Pageview::class,
'before' => [
'typo3/cms-frontend/content-length-headers',
],
'after' => [
'typo3/cms-frontend/shortcut-and-mountpoint-redirect',
],
],
],
];

View file

@ -0,0 +1,77 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false
DanielSiepmann\Tracking\:
resource: '../Classes/*'
# 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'
- '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\Tracking\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|TYPO3|TYPO3 linkvalidator/")
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
DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar:
class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsBar'
arguments:
$identifier: 'pageViewsBar'
$queryBuilder: '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView'
$settings: '@DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsBar'
tags:
- name: 'dashboard.widget'
identifier: 'pageViewsBar'
widgetGroups: 'tracking'
DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut:
class: 'DanielSiepmann\Tracking\Dashboard\Widgets\PageViewsPerPageDoughnut'
arguments:
$identifier: 'pageViewsPerPageDoughnut'
$queryBuilder: '@DanielSiepmann\Tracking\DI\QueryBuilder\PageView'
$settings: '@DanielSiepmann\Tracking\DI\Dashboard\Widgets\Settings\PageViewsPerPageDoughnut'
tags:
- name: 'dashboard.widget'
identifier: 'pageViewsPerPageDoughnut'
widgetGroups: 'tracking'

View file

@ -0,0 +1,78 @@
<?php
return [
'ctrl' => [
'label' => 'url',
'label_alt' => 'crdate',
'label_alt_force' => true,
'sortby' => 'crdate DESC',
'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, user_agent, type, sys_language_uid, crdate, tstamp, crdate, cruser_id'
],
'types' => [
'0' => [
'showitem' => 'sys_language_uid, pid, url, user_agent, 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,
]
],
'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' => [
'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,
],
],
],
];

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
<file source-language="en" datatype="plaintext">
<header/>
<body>
<trans-unit id="dashboard.widget.group.tracking">
<source>Tracking</source>
</trans-unit>
<trans-unit id="dashboard.widgets.pageViewsBar.title">
<source>Page Views / Day</source>
</trans-unit>
<trans-unit id="dashboard.widgets.pageViewsBar.description">
<source>Displays total page views per day.</source>
</trans-unit>
<trans-unit id="dashboard.widgets.pageViewsBar.chart.dataSet.0">
<source>Total Page Views</source>
</trans-unit>
<trans-unit id="dashboard.widgets.pageViewsPerPageDoughnut.title">
<source>Page Views / Page</source>
</trans-unit>
<trans-unit id="dashboard.widgets.pageViewsPerPageDoughnut.description">
<source>Displays total page views per page.</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
<file source-language="en" datatype="plaintext">
<header/>
<body>
<trans-unit id="table.pageview">
<source>Pageview</source>
</trans-unit>
<trans-unit id="table.pageview.pid">
<source>Page</source>
</trans-unit>
<trans-unit id="table.pageview.url">
<source>URL</source>
</trans-unit>
<trans-unit id="table.pageview.sys_language">
<source>System language</source>
</trans-unit>
<trans-unit id="table.pageview.sys_language.0">
<source>Default system language</source>
</trans-unit>
<trans-unit id="table.pageview.crdate">
<source>Date + Time</source>
</trans-unit>
<trans-unit id="table.pageview.type">
<source>Pagetype</source>
</trans-unit>
<trans-unit id="table.pageview.user_agent">
<source>User agent</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -0,0 +1,169 @@
<?php
namespace DanielSiepmann\Tracking\Unit\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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()
);
}
}

View file

@ -0,0 +1,196 @@
<?php
namespace DanielSiepmann\Tracking\Unit\Domain\Pageview;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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()
);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace DanielSiepmann\Tracking\Unit\Domain\Repository;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace DanielSiepmann\Tracking\Unit\Middleware;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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);
}
}

55
composer.json Normal file
View file

@ -0,0 +1,55 @@
{
"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/"
}
},
"autoload-dev": {
"psr-4": {
"DanielSiepmann\\Tracking\\Tests\\": "Tests/"
}
},
"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"
},
"extra": {
"typo3/cms": {
"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"
]
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.5",
"phpunit/phpunit": "^9.0",
"typo3/cms-dashboard": "^10.3.0"
}
}

23
ext_emconf.php Normal file
View file

@ -0,0 +1,23 @@
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'Tracking',
'description' => 'Tracks page visits in TYPO3.',
'category' => 'fe',
'state' => 'alpha',
'uploadfolder' => 0,
'createDirs' => '',
'clearCacheOnLoad' => 0,
'author' => 'Daniel Siepmann',
'author_email' => 'coding@daniel-siepmann.de',
'author_company' => '',
'version' => '0.1.0',
'constraints' => [
'depends' => [
'core' => '',
],
'conflicts' => [],
'suggests' => [
'dashboard' => '',
],
],
];

3
ext_localconf.php Normal file
View file

@ -0,0 +1,3 @@
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages('tx_tracking_pageview');

5
ext_tables.sql Normal file
View file

@ -0,0 +1,5 @@
CREATE TABLE tx_tracking_pageview (
url text,
user_agent text,
type int(11) unsigned DEFAULT '0' NOT NULL,
);

16
phpcs.xml.dist Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<ruleset name="project">
<description>This project coding standard</description>
<file>Classes/</file>
<file>Tests/</file>
<!-- Set default settings -->
<arg value="sp"/>
<arg name="colors"/>
<arg name="encoding" value="utf-8" />
<arg name="extensions" value="php" />
<!-- Base rules -->
<rule ref="PSR12" />
</ruleset>

26
phpunit.xml.dist Normal file
View file

@ -0,0 +1,26 @@
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="false"
convertWarningsToExceptions="false"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
verbose="false">
<testsuites>
<testsuite name="unit-tests">
<directory>Tests/Unit/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">Classes</directory>
</whitelist>
</filter>
</phpunit>

59
readme.rst Normal file
View file

@ -0,0 +1,59 @@
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
=====
#. 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")