mirror of
https://github.com/DanielSiepmann/tracking.git
synced 2024-12-22 08:16:08 +01:00
commit
64b0261694
28 changed files with 1609 additions and 0 deletions
19
.github/workflows/ci.yaml
vendored
Normal file
19
.github/workflows/ci.yaml
vendored
Normal 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
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.Build/
|
||||
/composer.lock
|
||||
/vendor/
|
125
Classes/Dashboard/Widgets/PageViewsBar.php
Normal file
125
Classes/Dashboard/Widgets/PageViewsBar.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
121
Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php
Normal file
121
Classes/Dashboard/Widgets/PageViewsPerPageDoughnut.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
48
Classes/Dashboard/Widgets/SettingsFactory.php
Normal file
48
Classes/Dashboard/Widgets/SettingsFactory.php
Normal 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);
|
||||
}
|
||||
}
|
103
Classes/Domain/Model/Pageview.php
Normal file
103
Classes/Domain/Model/Pageview.php
Normal 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;
|
||||
}
|
||||
}
|
47
Classes/Domain/Pageview/Factory.php
Normal file
47
Classes/Domain/Pageview/Factory.php
Normal 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');
|
||||
}
|
||||
}
|
30
Classes/Domain/Pageview/FromRequest.php
Normal file
30
Classes/Domain/Pageview/FromRequest.php
Normal 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;
|
||||
}
|
54
Classes/Domain/Repository/Pageview.php
Normal file
54
Classes/Domain/Repository/Pageview.php
Normal 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
29
Classes/Extension.php
Normal 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';
|
||||
}
|
77
Classes/Middleware/Pageview.php
Normal file
77
Classes/Middleware/Pageview.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
6
Configuration/Backend/DashboardWidgetGroups.php
Normal file
6
Configuration/Backend/DashboardWidgetGroups.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
return [
|
||||
'tracking' => [
|
||||
'title' => 'LLL:EXT:tracking/Resources/Private/Language/locallang.xlf:dashboard.widget.group.tracking',
|
||||
],
|
||||
];
|
15
Configuration/RequestMiddlewares.php
Normal file
15
Configuration/RequestMiddlewares.php
Normal 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',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
77
Configuration/Services.yaml
Normal file
77
Configuration/Services.yaml
Normal 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'
|
78
Configuration/TCA/tx_tracking_pageview.php
Normal file
78
Configuration/TCA/tx_tracking_pageview.php
Normal 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,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
26
Resources/Private/Language/locallang.xlf
Normal file
26
Resources/Private/Language/locallang.xlf
Normal 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>
|
32
Resources/Private/Language/locallang_tca.xlf
Normal file
32
Resources/Private/Language/locallang_tca.xlf
Normal 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>
|
169
Tests/Unit/Domain/Model/PageviewTest.php
Normal file
169
Tests/Unit/Domain/Model/PageviewTest.php
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
196
Tests/Unit/Domain/Pageview/FactoryTest.php
Normal file
196
Tests/Unit/Domain/Pageview/FactoryTest.php
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
72
Tests/Unit/Domain/Repository/PageviewTest.php
Normal file
72
Tests/Unit/Domain/Repository/PageviewTest.php
Normal 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());
|
||||
}
|
||||
}
|
95
Tests/Unit/Middleware/PageviewTest.php
Normal file
95
Tests/Unit/Middleware/PageviewTest.php
Normal 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
55
composer.json
Normal 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
23
ext_emconf.php
Normal 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
3
ext_localconf.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages('tx_tracking_pageview');
|
5
ext_tables.sql
Normal file
5
ext_tables.sql
Normal 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
16
phpcs.xml.dist
Normal 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
26
phpunit.xml.dist
Normal 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
59
readme.rst
Normal 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")
|
Loading…
Reference in a new issue