landestreffen-webseite/plugins/matomo/matomo.php

242 lines
8.5 KiB
PHP

<?php
namespace Grav\Plugin;
use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;
use RocketTheme\Toolbox\Event\Event;
use MatomoTracker;
/**
* Class MatomoPlugin
* @package Grav\Plugin
*/
class MatomoPlugin extends Plugin
{
/**
* @return array
*
* The getSubscribedEvents() gives the core a list of events
* that the plugin wants to listen to. The key of each
* array section is the event that the plugin listens to
* and the value (in the form of an array) contains the
* callable (or function) as well as the priority. The
* higher the number the higher the priority.
*/
public static function getSubscribedEvents(): array
{
return [
'onPluginsInitialized' => [
['autoload', 100000], // TODO: Remove when plugin requires Grav >=1.7
['onPluginsInitialized', 0]
]
];
}
/**
* Composer autoload.
*is
* @return ClassLoader
*/
public function autoload(): ClassLoader
{
return require __DIR__ . '/vendor/autoload.php';
}
/**
* Initialize the plugin
*/
public function onPluginsInitialized(): void
{
// Don't proceed if we are in the admin plugin
if ($this->isAdmin()) {
if ($this->grav['config']->get('plugins.matomo.dashboard_token')) {
$this->enable([
'onTwigTemplatePaths' => ['onTwigAdminTemplatePaths', 0],
'onAdminMenu' => ['onAdminMenu', 0]
]);
}
return;
}
// Enable the main events we are interested in
$this->enable([
'onPageInitialized' => ['onPageInitialized', 0],
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0]
]);
}
/**
* Add plugin templates path
*/
public function onTwigAdminTemplatePaths()
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates';
}
/**
* Add navigation item to the admin plugin
*/
public function onAdminMenu()
{
$this->grav['twig']->plugins_hooked_nav['Matomo'] = ['route' => 'matomo', 'icon' => 'fa-bar-chart'];
}
public function onPageInitialized(Event $event)
{
$page = $event['page'];
// Merge configs and then check for the active flag per page
$config = $this->mergeConfig($page);
if (!$config->get('active', true)) {
$this->documentBlockingReason("Plugin is not active for this page.");
return;
}
// Check if client set "Do Not Track" header
// NOTE: Ignoring the header only works for php tracking, not for javascript tracking.
// That's because the matomo instance will also evaluate the header and ignore the request.
if ($config->get('respect_do_not_track', true)) {
if (isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] == 1) {
$this->documentBlockingReason("Client requests 'DO NOT TRACK'.");
return;
}
}
// Don't proceed if a blocking cookie is set
$blockingCookieName = $config->get('blockingCookie');
if (!empty($blockingCookieName) && !empty($_COOKIE[$blockingCookieName])) {
$this->documentBlockingReason("Blocking cookie \"$blockingCookieName\" is set.");
return;
}
// Don't proceed if the IP address is blocked
if (in_array($_SERVER['REMOTE_ADDR'], $config->get('blockedIpAddresses', []))) {
$this->documentBlockingReason("Client ip " . $_SERVER['REMOTE_ADDR'] . " is in blockedIps.");
return;
}
// Don't proceed if the IP address is within a blocked range
foreach ($config->get('blockedIpRanges', []) as $blockedIpRange) {
if ($this->inIPAddressRange($this->packedIPAddress($_SERVER['REMOTE_ADDR']), $blockedIpRange)) {
$this->documentBlockingReason("Client ip " . $_SERVER['REMOTE_ADDR'] . " is in range \"" . $blockedIpRange . "\".");
return;
}
}
$matomo_url = $config->get('matomo_url');
$site_id = $config->get('site_id');
$token = $config->get('token');
if (!$matomo_url || !$site_id || !$token || $matomo_url === 'https://example.tld') {
$this->documentBlockingReason('Invalid Matomo configuration detected.');
return;
}
// Add javascript tracking code (and disable php tracker to avoid duplicate entries)
if ($config->get('enable_javascript', false)) {
$matomo_js = $this->grav['twig']->processTemplate('js/matomo.js.twig');
$this->grav['assets']->addInlineJs($matomo_js,
[
'type' => 'text/javascript',
'priority' => 0,
'position' => 'after'
]);
return;
}
// Matomo object
$matomoTracker = new MatomoTracker((int)$site_id, $matomo_url);
// Optionally enable cookies
if (!$config->get('enable_cookies', false)) {
$matomoTracker->disableCookieSupport();
}
// Set authentication token
$matomoTracker->setTokenAuth($token);
// Track page view
// NOTE: Url, Ip, Referrer, Browser Language and UserAgent is set by the API automatically
// TODO return value is currently useless: https://github.com/matomo-org/matomo-php-tracker/issues/85
try {
$ret = $matomoTracker->doTrackPageView($page->title());
}
catch (\RuntimeException $e) {
$this->grav['log']->error('Error tracking page view via Matomo: ' . $e->getMessage());
}
}
/**
* Add templates directory to twig lookup paths.
*/
public function onTwigTemplatePaths()
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
}
/**
* Documents the reason for blocking Matomo tracking in a JavaScript comment
* @param string $reason
*/
private function documentBlockingReason(string $reason)
{
if ($this->config->get('plugins.matomo.debug', false)) {
$this->grav['assets']->addInlineJs("/* Matomo tracking blocked, reason: $reason */");
}
}
/**
* Returns a packed IP address which can be directly compared to another packed IP address
* @param string $humanReadableIPAddress IPv4 or IPv6 address in human-readable notation
* @return string (16 byte packed representation)
*/
private function packedIPAddress(string $humanReadableIPAddress): string
{
$result = inet_pton($humanReadableIPAddress);
if ($result == false) {
return $this->packedIPAddress('::0');
}
// IPv6 native
elseif (strlen($result) == 16) {
return $result;
}
// IPv4, expanded to IPv6 compatible length
else {
return "\0\0\0\0\0\0\0\0\0\0\0\0" . $result;
}
}
/**
* Returns true if a packed IP address is within the specified address range
* @param string $packedAddress
* @param string $range
* @return bool
* @noinspection SpellCheckingInspection
*/
private function inIPAddressRange(string $packedAddress, string $range): bool
{
if ($range === 'private') { // RFC 6890, RFC 4193
return ($this->inIPAddressRange($packedAddress, "10.0.0.0-10.255.255.255")
|| $this->inIPAddressRange($packedAddress, "172.16.0.0-172.31.255.255")
|| $this->inIPAddressRange($packedAddress, "192.168.0.0-192.168.255.255")
|| $this->inIPAddressRange($packedAddress, "fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
} elseif ($range === 'loopback') { // RFC 6890
return ($this->inIPAddressRange($packedAddress, "127.0.0.1-127.255.255.255")
|| $this->inIPAddressRange($packedAddress, "::1-::1"));
} elseif ($range === 'link-local') { // RFC 6890, RFC 4291
return ($this->inIPAddressRange($packedAddress, "169.254.0.0-169.254.255.255")
|| $this->inIPAddressRange($packedAddress, "fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
} else {
$rangeLimits = explode('-', $range);
if (count($rangeLimits) == 2) {
$lowerLimit = $this->packedIPAddress($rangeLimits[0]);
$upperLimit = $this->packedIPAddress($rangeLimits[1]);
return $lowerLimit <= $packedAddress && $packedAddress <= $upperLimit;
}
}
return false;
}
}