site-accueil-insa/matomo/core/Tracker/Failures.php

197 lines
7.2 KiB
PHP

<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Tracker;
use Piwik\Common;
use Piwik\Date;
use Piwik\Exception\InvalidRequestParameterException;
use Piwik\Exception\UnexpectedWebsiteFoundException;
use Piwik\Piwik;
use Piwik\Site;
use Piwik\Db as PiwikDb;
class Failures
{
const CLEANUP_OLD_FAILURES_DAYS = 2;
const FAILURE_ID_INVALID_SITE = 1;
const FAILURE_ID_NOT_AUTHENTICATED = 2;
private $table = 'tracking_failure';
private $tablePrefixed;
private $now;
public function __construct()
{
$this->tablePrefixed = Common::prefixTable($this->table);
}
public function setNow(Date $now)
{
$this->now = $now;
}
private function getNow()
{
if (isset($this->now)) {
return $this->now;
}
return Date::now();
}
public function logFailure($idFailure, Request $request)
{
$isVisitExcluded = $request->getMetadata('CoreHome', 'isVisitExcluded');
if ($isVisitExcluded === null) {
try {
$visitExcluded = new VisitExcluded($request);
$isVisitExcluded = $visitExcluded->isExcluded();
} catch (InvalidRequestParameterException $e) {
// we ignore this error and assume visit is not excluded... happens eg when using `cip` and request was
// not authenticated...
$isVisitExcluded = false;
}
}
if ($isVisitExcluded) {
return;
}
$idSite = (int) $request->getIdSiteUnverified();
$idFailure = (int) $idFailure;
if ($idSite > 9999999 || $idSite < 0 || $this->hasLoggedFailure($idSite, $idFailure)) {
return; // we prevent creating huge amount of entries in the cache
}
$params = $this->getParamsWithTokenAnonymized($request);
$sql = sprintf('INSERT INTO %s (`idsite`, `idfailure`, `date_first_occurred`, `request_url`) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE idsite=idsite;', $this->tablePrefixed);
PiwikDb::get()->query($sql, array($idSite, $idFailure, $this->getNow()->getDatetime(), http_build_query($params)));
}
private function hasLoggedFailure($idSite, $idFailure)
{
$sql = sprintf('SELECT idsite FROM %s WHERE idsite = ? and idfailure = ?', $this->tablePrefixed);
$row = PiwikDb::fetchRow($sql, array($idSite, $idFailure));
return !empty($row);
}
private function getParamsWithTokenAnonymized(Request $request)
{
// eg if there is a typo in the token auth we want to replace it as well to not accidentally leak a token
// eg imagine a super user tries to issue an API request for a site and sending the wrong parameter for a token...
// an admin may have view access for this and can see the super users token
$token = $request->getTokenAuth();
$params = $request->getRawParams();
foreach (array('token_auth', 'token', 'tokenauth', 'token__auth') as $key) {
if (isset($params[$key])) {
$params[$key] = '__TOKEN_AUTH__';
}
}
foreach ($params as $key => $value) {
if (!empty($token) && $value === $token) {
$params[$key] = '__TOKEN_AUTH__'; // user accidentally posted the token in a wrong field
} elseif (!empty($value) && is_string($value)
&& mb_strlen($value) >= 29 && mb_strlen($value) <= 36
&& ctype_xdigit($value)) {
$params[$key] = '__TOKEN_AUTH__'; // user maybe posted a token in a different field... it looks like it might be a token
}
}
return $params;
}
public function removeFailuresOlderThanDays($days)
{
$minutesAgo = $this->getNow()->subDay($days)->getDatetime();
PiwikDb::query(sprintf('DELETE FROM %s WHERE date_first_occurred < ?', $this->tablePrefixed), array($minutesAgo));
}
public function getAllFailures()
{
$failures = PiwikDb::fetchAll(sprintf('SELECT * FROM %s', $this->tablePrefixed));
return $this->enrichFailures($failures);
}
public function getFailuresForSites($idSites)
{
if (empty($idSites)) {
return array();
}
$idSites = array_map('intval', $idSites);
$idSites = implode(',', $idSites);
$failures = PiwikDb::fetchAll(sprintf('SELECT * FROM %s WHERE idsite IN (%s)', $this->tablePrefixed, $idSites));
return $this->enrichFailures($failures);
}
public function deleteTrackingFailure($idSite, $idFailure)
{
PiwikDb::query(sprintf('DELETE FROM %s WHERE idsite = ? and idfailure = ?', $this->tablePrefixed), array($idSite, $idFailure));
}
public function deleteTrackingFailures($idSites)
{
if (!empty($idSites)) {
$idSites = array_map('intval', $idSites);
$idSites = implode(',', $idSites);
PiwikDb::query(sprintf('DELETE FROM %s WHERE idsite IN(%s)', $this->tablePrefixed, $idSites));
}
}
public function deleteAllTrackingFailures()
{
PiwikDb::query(sprintf('DELETE FROM %s', $this->tablePrefixed));
}
private function enrichFailures($failures)
{
foreach ($failures as &$failure) {
try {
$failure['site_name'] = Site::getNameFor($failure['idsite']);
} catch (UnexpectedWebsiteFoundException $e) {
$failure['site_name'] = Piwik::translate('General_Unknown');
}
$failure['pretty_date_first_occurred'] = Date::factory($failure['date_first_occurred'])->getLocalized(Date::DATETIME_FORMAT_SHORT);
parse_str($failure['request_url'], $params);
if (empty($params['url'])) {
$params['url'] = ' ';// workaround it using the default provider in request constructor
}
$request = new Request($params);
$failure['url'] = trim($request->getParam('url'));
$failure['problem'] = '';
$failure['solution'] = '';
$failure['solution_url'] = '';
switch ($failure['idfailure']) {
case self::FAILURE_ID_INVALID_SITE:
$failure['problem'] = Piwik::translate('CoreAdminHome_TrackingFailureInvalidSiteProblem');
$failure['solution'] = Piwik::translate('CoreAdminHome_TrackingFailureInvalidSiteSolution');
$failure['solution_url'] = 'https://matomo.org/faq/how-to/faq_30838/';
break;
case self::FAILURE_ID_NOT_AUTHENTICATED:
$failure['problem'] = Piwik::translate('CoreAdminHome_TrackingFailureAuthenticationProblem');
$failure['solution'] = Piwik::translate('CoreAdminHome_TrackingFailureAuthenticationSolution');
$failure['solution_url'] = 'https://matomo.org/faq/how-to/faq_30835/';
break;
}
}
/**
* @ignore
* internal use only
*/
Piwik::postEvent('Tracking.makeFailuresHumanReadable', array(&$failures));
return $failures;
}
}