add piwik installation
This commit is contained in:
parent
90aa4ef157
commit
8c5d4f0c31
3197 changed files with 563902 additions and 0 deletions
310
www/analytics/core/Tracker/Action.php
Normal file
310
www/analytics/core/Tracker/Action.php
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* An action
|
||||
*
|
||||
*/
|
||||
abstract class Action
|
||||
{
|
||||
const TYPE_PAGE_URL = 1;
|
||||
const TYPE_OUTLINK = 2;
|
||||
const TYPE_DOWNLOAD = 3;
|
||||
const TYPE_PAGE_TITLE = 4;
|
||||
const TYPE_ECOMMERCE_ITEM_SKU = 5;
|
||||
const TYPE_ECOMMERCE_ITEM_NAME = 6;
|
||||
const TYPE_ECOMMERCE_ITEM_CATEGORY = 7;
|
||||
const TYPE_SITE_SEARCH = 8;
|
||||
|
||||
const TYPE_EVENT = 10; // Alias TYPE_EVENT_CATEGORY
|
||||
const TYPE_EVENT_CATEGORY = 10;
|
||||
const TYPE_EVENT_ACTION = 11;
|
||||
const TYPE_EVENT_NAME = 12;
|
||||
|
||||
const DB_COLUMN_CUSTOM_FLOAT = 'custom_float';
|
||||
|
||||
/**
|
||||
* Makes the correct Action object based on the request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return ActionClickUrl|ActionPageview|ActionSiteSearch
|
||||
*/
|
||||
static public function factory(Request $request)
|
||||
{
|
||||
$downloadUrl = $request->getParam('download');
|
||||
if (!empty($downloadUrl)) {
|
||||
return new ActionClickUrl(self::TYPE_DOWNLOAD, $downloadUrl, $request);
|
||||
}
|
||||
|
||||
$outlinkUrl = $request->getParam('link');
|
||||
if (!empty($outlinkUrl)) {
|
||||
return new ActionClickUrl(self::TYPE_OUTLINK, $outlinkUrl, $request);
|
||||
}
|
||||
|
||||
$url = $request->getParam('url');
|
||||
|
||||
$eventCategory = $request->getParam('e_c');
|
||||
$eventAction = $request->getParam('e_a');
|
||||
if(strlen($eventCategory) > 0 && strlen($eventAction) > 0 ) {
|
||||
return new ActionEvent($eventCategory, $eventAction, $url, $request);
|
||||
}
|
||||
|
||||
$action = new ActionSiteSearch($url, $request);
|
||||
if ($action->isSearchDetected()) {
|
||||
return $action;
|
||||
}
|
||||
return new ActionPageview($url, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
private $idLinkVisitAction;
|
||||
private $actionIdsCached = array();
|
||||
private $actionName;
|
||||
private $actionType;
|
||||
private $actionUrl;
|
||||
|
||||
public function __construct($type, Request $request)
|
||||
{
|
||||
$this->actionType = $type;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URL of the page currently being tracked, or the file being downloaded, or the outlink being clicked
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActionUrl()
|
||||
{
|
||||
return $this->actionUrl;
|
||||
}
|
||||
|
||||
public function getActionName()
|
||||
{
|
||||
return $this->actionName;
|
||||
}
|
||||
|
||||
public function getActionType()
|
||||
{
|
||||
return $this->actionType;
|
||||
}
|
||||
|
||||
public function getCustomVariables()
|
||||
{
|
||||
$customVariables = $this->request->getCustomVariables($scope = 'page');
|
||||
return $customVariables;
|
||||
}
|
||||
|
||||
// custom_float column
|
||||
public function getCustomFloatValue()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected function setActionName($name)
|
||||
{
|
||||
$name = PageUrl::cleanupString((string)$name);
|
||||
$this->actionName = $name;
|
||||
}
|
||||
|
||||
protected function setActionUrl($url)
|
||||
{
|
||||
$urlBefore = $url;
|
||||
$url = PageUrl::excludeQueryParametersFromUrl($url, $this->request->getIdSite());
|
||||
|
||||
if ($url != $urlBefore) {
|
||||
Common::printDebug(' Before was "' . $urlBefore . '"');
|
||||
Common::printDebug(' After is "' . $url . '"');
|
||||
}
|
||||
|
||||
$url = PageUrl::getUrlIfLookValid($url);
|
||||
$this->actionUrl = $url;
|
||||
}
|
||||
|
||||
abstract protected function getActionsToLookup();
|
||||
|
||||
protected function getUrlAndType()
|
||||
{
|
||||
$url = $this->getActionUrl();
|
||||
if (!empty($url)) {
|
||||
// normalize urls by stripping protocol and www
|
||||
$url = PageUrl::normalizeUrl($url);
|
||||
return array($url['url'], Tracker\Action::TYPE_PAGE_URL, $url['prefixId']);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getIdActionUrl()
|
||||
{
|
||||
$idUrl = $this->actionIdsCached['idaction_url'];
|
||||
// note; idaction_url = 0 is displayed as "Page URL Not Defined"
|
||||
return (int)$idUrl;
|
||||
}
|
||||
|
||||
|
||||
public function getIdActionUrlForEntryAndExitIds()
|
||||
{
|
||||
return $this->getIdActionUrl();
|
||||
}
|
||||
|
||||
public function getIdActionNameForEntryAndExitIds()
|
||||
{
|
||||
return $this->getIdActionName();
|
||||
}
|
||||
|
||||
public function getIdActionName()
|
||||
{
|
||||
if(!isset($this->actionIdsCached['idaction_name'])) {
|
||||
return false;
|
||||
}
|
||||
return $this->actionIdsCached['idaction_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the newly created record in the log_link_visit_action table
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIdLinkVisitAction()
|
||||
{
|
||||
return $this->idLinkVisitAction;
|
||||
}
|
||||
|
||||
public function writeDebugInfo()
|
||||
{
|
||||
$type = self::getTypeAsString($this->getActionType());
|
||||
Common::printDebug("Action is a $type,
|
||||
Action name = " . $this->getActionName() . ",
|
||||
Action URL = " . $this->getActionUrl());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getTypeAsString($type)
|
||||
{
|
||||
$class = new \ReflectionClass("\\Piwik\\Tracker\\Action");
|
||||
$constants = $class->getConstants();
|
||||
|
||||
$typeId = array_search($type, $constants);
|
||||
if($typeId === false) {
|
||||
throw new Exception("Unexpected action type " . $type);
|
||||
}
|
||||
return str_replace('TYPE_', '', $typeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the idaction of the current action name and the current action url.
|
||||
* These idactions are used in the visitor logging table to link the visit information
|
||||
* (entry action, exit action) to the actions.
|
||||
* These idactions are also used in the table that links the visits and their actions.
|
||||
*
|
||||
* The methods takes care of creating a new record(s) in the action table if the existing
|
||||
* action name and action url doesn't exist yet.
|
||||
*/
|
||||
public function loadIdsFromLogActionTable()
|
||||
{
|
||||
if(!empty($this->actionIdsCached)) {
|
||||
return;
|
||||
}
|
||||
$actions = $this->getActionsToLookup();
|
||||
$actions = array_filter($actions, 'count');
|
||||
|
||||
if(empty($actions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$loadedActionIds = TableLogAction::loadIdsAction($actions);
|
||||
|
||||
$this->actionIdsCached = $loadedActionIds;
|
||||
return $this->actionIdsCached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records in the DB the association between the visit and this action.
|
||||
*
|
||||
* @param int $idVisit is the ID of the current visit in the DB table log_visit
|
||||
* @param $visitorIdCookie
|
||||
* @param int $idReferrerActionUrl is the ID of the last action done by the current visit.
|
||||
* @param $idReferrerActionName
|
||||
* @param int $timeSpentReferrerAction is the number of seconds since the last action was done.
|
||||
* It is directly related to idReferrerActionUrl.
|
||||
*/
|
||||
public function record($idVisit, $visitorIdCookie, $idReferrerActionUrl, $idReferrerActionName, $timeSpentReferrerAction)
|
||||
{
|
||||
$this->loadIdsFromLogActionTable();
|
||||
|
||||
$idActionName = in_array($this->getActionType(), array(Tracker\Action::TYPE_PAGE_TITLE,
|
||||
Tracker\Action::TYPE_PAGE_URL,
|
||||
Tracker\Action::TYPE_SITE_SEARCH
|
||||
))
|
||||
? (int)$this->getIdActionName()
|
||||
: null;
|
||||
|
||||
$visitAction = array(
|
||||
'idvisit' => $idVisit,
|
||||
'idsite' => $this->request->getIdSite(),
|
||||
'idvisitor' => $visitorIdCookie,
|
||||
'server_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()),
|
||||
'idaction_url' => $this->getIdActionUrl(),
|
||||
'idaction_name' => $idActionName,
|
||||
'idaction_url_ref' => $idReferrerActionUrl,
|
||||
'idaction_name_ref' => $idReferrerActionName,
|
||||
'time_spent_ref_action' => $timeSpentReferrerAction
|
||||
);
|
||||
|
||||
foreach($this->actionIdsCached as $field => $idAction) {
|
||||
$visitAction[$field] = $idAction;
|
||||
}
|
||||
|
||||
$customValue = $this->getCustomFloatValue();
|
||||
if (!empty($customValue)) {
|
||||
$visitAction[self::DB_COLUMN_CUSTOM_FLOAT] = $customValue;
|
||||
}
|
||||
|
||||
$customVariables = $this->getCustomVariables();
|
||||
if (!empty($customVariables)) {
|
||||
Common::printDebug("Page level Custom Variables: ");
|
||||
Common::printDebug($customVariables);
|
||||
}
|
||||
|
||||
$visitAction = array_merge($visitAction, $customVariables);
|
||||
$fields = implode(", ", array_keys($visitAction));
|
||||
$bind = array_values($visitAction);
|
||||
$values = Common::getSqlStringFieldsArray($visitAction);
|
||||
|
||||
$sql = "INSERT INTO " . Common::prefixTable('log_link_visit_action') . " ($fields) VALUES ($values)";
|
||||
Tracker::getDatabase()->query($sql, $bind);
|
||||
|
||||
$this->idLinkVisitAction = Tracker::getDatabase()->lastInsertId();
|
||||
$visitAction['idlink_va'] = $this->idLinkVisitAction;
|
||||
|
||||
Common::printDebug("Inserted new action:");
|
||||
Common::printDebug($visitAction);
|
||||
|
||||
/**
|
||||
* Triggered after successfully persisting a [visit action entity](/guides/persistence-and-the-mysql-backend#visit-actions).
|
||||
*
|
||||
* @param Action $tracker Action The Action tracker instance.
|
||||
* @param array $visitAction The visit action entity that was persisted. Read
|
||||
* [this](/guides/persistence-and-the-mysql-backend#visit-actions) to see what it contains.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.recordAction', array($trackerAction = $this, $visitAction));
|
||||
}
|
||||
}
|
||||
63
www/analytics/core/Tracker/ActionClickUrl.php
Normal file
63
www/analytics/core/Tracker/ActionClickUrl.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* This class represents a download or an outlink.
|
||||
* This is a particular type of Action: it has no 'name'
|
||||
*
|
||||
*/
|
||||
class ActionClickUrl extends Action
|
||||
{
|
||||
function __construct($type, $url, Request $request)
|
||||
{
|
||||
parent::__construct($type, $request);
|
||||
$this->setActionUrl($url);
|
||||
}
|
||||
|
||||
protected function getActionsToLookup()
|
||||
{
|
||||
return array(
|
||||
// Note: we do not normalize download/oulink URL
|
||||
'idaction_url' => array($this->getActionUrl(), $this->getActionType())
|
||||
);
|
||||
}
|
||||
|
||||
function writeDebugInfo()
|
||||
{
|
||||
parent::writeDebugInfo();
|
||||
|
||||
if (self::detectActionIsOutlinkOnAliasHost($this, $this->request->getIdSite())) {
|
||||
Common::printDebug("INFO: The outlink URL host is one of the known host for this website. ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether action is an outlink given host aliases
|
||||
*
|
||||
* @param Action $action
|
||||
* @return bool true if the outlink the visitor clicked on points to one of the known hosts for this website
|
||||
*/
|
||||
public static function detectActionIsOutlinkOnAliasHost(Action $action, $idSite)
|
||||
{
|
||||
if ($action->getActionType() != Action::TYPE_OUTLINK) {
|
||||
return false;
|
||||
}
|
||||
$decodedActionUrl = $action->getActionUrl();
|
||||
$actionUrlParsed = @parse_url($decodedActionUrl);
|
||||
if (!isset($actionUrlParsed['host'])) {
|
||||
return false;
|
||||
}
|
||||
return Visit::isHostKnownAliasHost($actionUrlParsed['host'], $idSite);
|
||||
}
|
||||
}
|
||||
78
www/analytics/core/Tracker/ActionEvent.php
Normal file
78
www/analytics/core/Tracker/ActionEvent.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* An Event is composed of a URL, a Category name, an Action name, and optionally a Name and Value.
|
||||
*
|
||||
*/
|
||||
class ActionEvent extends Action
|
||||
{
|
||||
function __construct($eventCategory, $eventAction, $url, Request $request)
|
||||
{
|
||||
parent::__construct(Action::TYPE_EVENT, $request);
|
||||
$this->setActionUrl($url);
|
||||
$this->eventCategory = trim($eventCategory);
|
||||
$this->eventAction = trim($eventAction);
|
||||
$this->eventName = trim($request->getParam('e_n'));
|
||||
$this->eventValue = trim($request->getParam('e_v'));
|
||||
}
|
||||
|
||||
function getCustomFloatValue()
|
||||
{
|
||||
return $this->eventValue;
|
||||
}
|
||||
|
||||
protected function getActionsToLookup()
|
||||
{
|
||||
$actions = array(
|
||||
'idaction_url' => $this->getUrlAndType()
|
||||
);
|
||||
|
||||
if(strlen($this->eventName) > 0) {
|
||||
$actions['idaction_name'] = array($this->eventName, Action::TYPE_EVENT_NAME);
|
||||
}
|
||||
if(strlen($this->eventCategory) > 0) {
|
||||
$actions['idaction_event_category'] = array($this->eventCategory, Action::TYPE_EVENT_CATEGORY);
|
||||
}
|
||||
if(strlen($this->eventAction) > 0) {
|
||||
$actions['idaction_event_action'] = array($this->eventAction, Action::TYPE_EVENT_ACTION);
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
// Do not track this Event URL as Entry/Exit Page URL (leave the existing entry/exit)
|
||||
public function getIdActionUrlForEntryAndExitIds()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not track this Event Name as Entry/Exit Page Title (leave the existing entry/exit)
|
||||
public function getIdActionNameForEntryAndExitIds()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function writeDebugInfo()
|
||||
{
|
||||
$write = parent::writeDebugInfo();
|
||||
if($write) {
|
||||
Common::printDebug("Event Category = " . $this->eventCategory . ",
|
||||
Event Action = " . $this->eventAction . ",
|
||||
Event Name = " . $this->eventName . ",
|
||||
Event Value = " . $this->getCustomFloatValue());
|
||||
}
|
||||
return $write;
|
||||
}
|
||||
|
||||
}
|
||||
71
www/analytics/core/Tracker/ActionPageview.php
Normal file
71
www/analytics/core/Tracker/ActionPageview.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Config;
|
||||
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* This class represents a page view, tracking URL, page title and generation time.
|
||||
*
|
||||
*/
|
||||
class ActionPageview extends Action
|
||||
{
|
||||
protected $timeGeneration = false;
|
||||
|
||||
function __construct($url, Request $request)
|
||||
{
|
||||
parent::__construct(Action::TYPE_PAGE_URL, $request);
|
||||
|
||||
$this->setActionUrl($url);
|
||||
|
||||
$actionName = $request->getParam('action_name');
|
||||
$actionName = $this->cleanupActionName($actionName);
|
||||
$this->setActionName($actionName);
|
||||
|
||||
$this->timeGeneration = $this->request->getPageGenerationTime();
|
||||
}
|
||||
|
||||
protected function getActionsToLookup()
|
||||
{
|
||||
return array(
|
||||
'idaction_name' => array($this->getActionName(), Action::TYPE_PAGE_TITLE),
|
||||
'idaction_url' => $this->getUrlAndType()
|
||||
);
|
||||
}
|
||||
|
||||
function getCustomFloatValue()
|
||||
{
|
||||
return $this->request->getPageGenerationTime();
|
||||
}
|
||||
|
||||
protected function cleanupActionName($actionName)
|
||||
{
|
||||
// get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067)
|
||||
$actionCategoryDelimiter = isset(Config::getInstance()->General['action_category_delimiter'])
|
||||
? Config::getInstance()->General['action_category_delimiter']
|
||||
: Config::getInstance()->General['action_url_category_delimiter'];
|
||||
|
||||
// create an array of the categories delimited by the delimiter
|
||||
$split = explode($actionCategoryDelimiter, $actionName);
|
||||
|
||||
// trim every category
|
||||
$split = array_map('trim', $split);
|
||||
|
||||
// remove empty categories
|
||||
$split = array_filter($split, 'strlen');
|
||||
|
||||
// rebuild the name from the array of cleaned categories
|
||||
$actionName = implode($actionCategoryDelimiter, $split);
|
||||
return $actionName;
|
||||
}
|
||||
|
||||
}
|
||||
254
www/analytics/core/Tracker/ActionSiteSearch.php
Normal file
254
www/analytics/core/Tracker/ActionSiteSearch.php
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Tracker;
|
||||
use Piwik\UrlHelper;
|
||||
|
||||
/**
|
||||
* This class represents a search on the site.
|
||||
* - Its name is the search keyword
|
||||
* - by default the URL is not recorded (since it's not used)
|
||||
* - tracks site search result count and site search category as custom variables
|
||||
*
|
||||
*/
|
||||
class ActionSiteSearch extends Action
|
||||
{
|
||||
private $searchCategory = false;
|
||||
private $searchCount = false;
|
||||
|
||||
const CVAR_KEY_SEARCH_CATEGORY = '_pk_scat';
|
||||
const CVAR_KEY_SEARCH_COUNT = '_pk_scount';
|
||||
const CVAR_INDEX_SEARCH_CATEGORY = '4';
|
||||
const CVAR_INDEX_SEARCH_COUNT = '5';
|
||||
|
||||
|
||||
function __construct($url, Request $request)
|
||||
{
|
||||
parent::__construct(Action::TYPE_SITE_SEARCH, $request);
|
||||
$this->originalUrl = $url;
|
||||
}
|
||||
|
||||
protected function getActionsToLookup()
|
||||
{
|
||||
return array(
|
||||
'idaction_name' => array($this->getActionName(), Action::TYPE_SITE_SEARCH),
|
||||
);
|
||||
}
|
||||
|
||||
public function getIdActionUrl()
|
||||
{
|
||||
// Site Search, by default, will not track URL. We do not want URL to appear as "Page URL not defined"
|
||||
// so we specifically set it to NULL in the table (the archiving query does IS NOT NULL)
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCustomFloatValue()
|
||||
{
|
||||
return $this->request->getPageGenerationTime();
|
||||
}
|
||||
|
||||
function isSearchDetected()
|
||||
{
|
||||
$siteSearch = $this->detectSiteSearch($this->originalUrl);
|
||||
|
||||
if(empty($siteSearch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($actionName, $url, $category, $count) = $siteSearch;
|
||||
|
||||
if (!empty($category)) {
|
||||
$this->searchCategory = trim($category);
|
||||
}
|
||||
if ($count !== false) {
|
||||
$this->searchCount = $count;
|
||||
}
|
||||
$this->setActionName($actionName);
|
||||
$this->setActionUrl($url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function getCustomVariables()
|
||||
{
|
||||
$customVariables = parent::getCustomVariables();
|
||||
|
||||
// Enrich Site Search actions with Custom Variables, overwriting existing values
|
||||
if (!empty($this->searchCategory)) {
|
||||
if (!empty($customVariables['custom_var_k' . self::CVAR_INDEX_SEARCH_CATEGORY])) {
|
||||
Common::printDebug("WARNING: Overwriting existing Custom Variable in slot " . self::CVAR_INDEX_SEARCH_CATEGORY . " for this page view");
|
||||
}
|
||||
$customVariables['custom_var_k' . self::CVAR_INDEX_SEARCH_CATEGORY] = self::CVAR_KEY_SEARCH_CATEGORY;
|
||||
$customVariables['custom_var_v' . self::CVAR_INDEX_SEARCH_CATEGORY] = Request::truncateCustomVariable($this->searchCategory);
|
||||
}
|
||||
if ($this->searchCount !== false) {
|
||||
if (!empty($customVariables['custom_var_k' . self::CVAR_INDEX_SEARCH_COUNT])) {
|
||||
Common::printDebug("WARNING: Overwriting existing Custom Variable in slot " . self::CVAR_INDEX_SEARCH_COUNT . " for this page view");
|
||||
}
|
||||
$customVariables['custom_var_k' . self::CVAR_INDEX_SEARCH_COUNT] = self::CVAR_KEY_SEARCH_COUNT;
|
||||
$customVariables['custom_var_v' . self::CVAR_INDEX_SEARCH_COUNT] = (int)$this->searchCount;
|
||||
}
|
||||
return $customVariables;
|
||||
}
|
||||
|
||||
protected function detectSiteSearchFromUrl($website, $parsedUrl)
|
||||
{
|
||||
$doRemoveSearchParametersFromUrl = true;
|
||||
$separator = '&';
|
||||
$count = $actionName = $categoryName = false;
|
||||
|
||||
$keywordParameters = isset($website['sitesearch_keyword_parameters'])
|
||||
? $website['sitesearch_keyword_parameters']
|
||||
: array();
|
||||
$queryString = (!empty($parsedUrl['query']) ? $parsedUrl['query'] : '') . (!empty($parsedUrl['fragment']) ? $separator . $parsedUrl['fragment'] : '');
|
||||
$parametersRaw = UrlHelper::getArrayFromQueryString($queryString);
|
||||
|
||||
// strtolower the parameter names for smooth site search detection
|
||||
$parameters = array();
|
||||
foreach ($parametersRaw as $k => $v) {
|
||||
$parameters[Common::mb_strtolower($k)] = $v;
|
||||
}
|
||||
// decode values if they were sent from a client using another charset
|
||||
$pageEncoding = $this->request->getParam('cs');
|
||||
PageUrl::reencodeParameters($parameters, $pageEncoding);
|
||||
|
||||
// Detect Site Search keyword
|
||||
foreach ($keywordParameters as $keywordParameterRaw) {
|
||||
$keywordParameter = Common::mb_strtolower($keywordParameterRaw);
|
||||
if (!empty($parameters[$keywordParameter])) {
|
||||
$actionName = $parameters[$keywordParameter];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($actionName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$categoryParameters = isset($website['sitesearch_category_parameters'])
|
||||
? $website['sitesearch_category_parameters']
|
||||
: array();
|
||||
|
||||
foreach ($categoryParameters as $categoryParameterRaw) {
|
||||
$categoryParameter = Common::mb_strtolower($categoryParameterRaw);
|
||||
if (!empty($parameters[$categoryParameter])) {
|
||||
$categoryName = $parameters[$categoryParameter];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parameters['search_count'])
|
||||
&& $this->isValidSearchCount($parameters['search_count'])
|
||||
) {
|
||||
$count = $parameters['search_count'];
|
||||
}
|
||||
// Remove search kwd from URL
|
||||
if ($doRemoveSearchParametersFromUrl) {
|
||||
// @see excludeQueryParametersFromUrl()
|
||||
// Excluded the detected parameters from the URL
|
||||
$parametersToExclude = array($categoryParameterRaw, $keywordParameterRaw);
|
||||
if(isset($parsedUrl['query'])) {
|
||||
$parsedUrl['query'] = UrlHelper::getQueryStringWithExcludedParameters(UrlHelper::getArrayFromQueryString($parsedUrl['query']), $parametersToExclude);
|
||||
}
|
||||
if(isset($parsedUrl['fragment'])) {
|
||||
$parsedUrl['fragment'] = UrlHelper::getQueryStringWithExcludedParameters(UrlHelper::getArrayFromQueryString($parsedUrl['fragment']), $parametersToExclude);
|
||||
}
|
||||
}
|
||||
$url = UrlHelper::getParseUrlReverse($parsedUrl);
|
||||
if (is_array($actionName)) {
|
||||
$actionName = reset($actionName);
|
||||
}
|
||||
$actionName = trim(urldecode($actionName));
|
||||
if (empty($actionName)) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($categoryName)) {
|
||||
$categoryName = reset($categoryName);
|
||||
}
|
||||
$categoryName = trim(urldecode($categoryName));
|
||||
return array($url, $actionName, $categoryName, $count);
|
||||
}
|
||||
|
||||
protected function isValidSearchCount($count)
|
||||
{
|
||||
return is_numeric($count) && $count >= 0;
|
||||
}
|
||||
|
||||
protected function detectSiteSearch($originalUrl)
|
||||
{
|
||||
$website = Cache::getCacheWebsiteAttributes($this->request->getIdSite());
|
||||
if (empty($website['sitesearch'])) {
|
||||
Common::printDebug("Internal 'Site Search' tracking is not enabled for this site. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
$actionName = $url = $categoryName = $count = false;
|
||||
|
||||
$originalUrl = PageUrl::cleanupUrl($originalUrl);
|
||||
|
||||
// Detect Site search from Tracking API parameters rather than URL
|
||||
$searchKwd = $this->request->getParam('search');
|
||||
if (!empty($searchKwd)) {
|
||||
$actionName = $searchKwd;
|
||||
$isCategoryName = $this->request->getParam('search_cat');
|
||||
if (!empty($isCategoryName)) {
|
||||
$categoryName = $isCategoryName;
|
||||
}
|
||||
$isCount = $this->request->getParam('search_count');
|
||||
if ($this->isValidSearchCount($isCount)) {
|
||||
$count = $isCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($actionName)) {
|
||||
$parsedUrl = @parse_url($originalUrl);
|
||||
|
||||
// Detect Site Search from URL query parameters
|
||||
if (!empty($parsedUrl['query']) || !empty($parsedUrl['fragment'])) {
|
||||
// array($url, $actionName, $categoryName, $count);
|
||||
$searchInfo = $this->detectSiteSearchFromUrl($website, $parsedUrl);
|
||||
if (!empty($searchInfo)) {
|
||||
list ($url, $actionName, $categoryName, $count) = $searchInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$actionName = trim($actionName);
|
||||
$categoryName = trim($categoryName);
|
||||
|
||||
if (empty($actionName)) {
|
||||
Common::printDebug("(this is not a Site Search request)");
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::printDebug("Detected Site Search keyword '$actionName'. ");
|
||||
if (!empty($categoryName)) {
|
||||
Common::printDebug("- Detected Site Search Category '$categoryName'. ");
|
||||
}
|
||||
if ($count !== false) {
|
||||
Common::printDebug("- Search Results Count was '$count'. ");
|
||||
}
|
||||
if ($url != $originalUrl) {
|
||||
Common::printDebug("NOTE: The Page URL was changed / removed, during the Site Search detection, was '$originalUrl', now is '$url'");
|
||||
}
|
||||
|
||||
return array(
|
||||
$actionName,
|
||||
$url,
|
||||
$categoryName,
|
||||
$count
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
205
www/analytics/core/Tracker/Cache.php
Normal file
205
www/analytics/core/Tracker/Cache.php
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\ArchiveProcessor\Rules;
|
||||
use Piwik\CacheFile;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Log;
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* Simple cache mechanism used in Tracker to avoid requesting settings from mysql on every request
|
||||
*
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/**
|
||||
* Public for tests only
|
||||
* @var CacheFile
|
||||
*/
|
||||
static public $trackerCache = null;
|
||||
|
||||
static protected function getInstance()
|
||||
{
|
||||
if (is_null(self::$trackerCache)) {
|
||||
$ttl = Config::getInstance()->Tracker['tracker_cache_file_ttl'];
|
||||
self::$trackerCache = new CacheFile('tracker', $ttl);
|
||||
}
|
||||
return self::$trackerCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array containing data about the website: goals, URLs, etc.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @return array
|
||||
*/
|
||||
static function getCacheWebsiteAttributes($idSite)
|
||||
{
|
||||
if($idSite == 'all') {
|
||||
return array();
|
||||
}
|
||||
$idSite = (int)$idSite;
|
||||
if($idSite <= 0) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$cache = self::getInstance();
|
||||
if (($cacheContent = $cache->get($idSite)) !== false) {
|
||||
return $cacheContent;
|
||||
}
|
||||
|
||||
Tracker::initCorePiwikInTrackerMode();
|
||||
|
||||
// save current user privilege and temporarily assume Super User privilege
|
||||
$isSuperUser = Piwik::hasUserSuperUserAccess();
|
||||
Piwik::setUserHasSuperUserAccess();
|
||||
|
||||
$content = array();
|
||||
|
||||
/**
|
||||
* Triggered to get the attributes of a site entity that might be used by the
|
||||
* Tracker.
|
||||
*
|
||||
* Plugins add new site attributes for use in other tracking events must
|
||||
* use this event to put those attributes in the Tracker Cache.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* public function getSiteAttributes($content, $idSite)
|
||||
* {
|
||||
* $sql = "SELECT info FROM " . Common::prefixTable('myplugin_extra_site_info') . " WHERE idsite = ?";
|
||||
* $content['myplugin_site_data'] = Db::fetchOne($sql, array($idSite));
|
||||
* }
|
||||
*
|
||||
* @param array &$content Array mapping of site attribute names with values.
|
||||
* @param int $idSite The site ID to get attributes for.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.Cache.getSiteAttributes', array(&$content, $idSite));
|
||||
Common::printDebug("Website $idSite tracker cache was re-created.");
|
||||
|
||||
// restore original user privilege
|
||||
Piwik::setUserHasSuperUserAccess($isSuperUser);
|
||||
|
||||
// if nothing is returned from the plugins, we don't save the content
|
||||
// this is not expected: all websites are expected to have at least one URL
|
||||
if (!empty($content)) {
|
||||
$cache->set($idSite, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear general (global) cache
|
||||
*/
|
||||
static public function clearCacheGeneral()
|
||||
{
|
||||
self::getInstance()->delete('general');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns contents of general (global) cache.
|
||||
* If the cache file tmp/cache/tracker/general.php does not exist yet, create it
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function getCacheGeneral()
|
||||
{
|
||||
$cache = self::getInstance();
|
||||
$cacheId = 'general';
|
||||
|
||||
if (($cacheContent = $cache->get($cacheId)) !== false) {
|
||||
return $cacheContent;
|
||||
}
|
||||
|
||||
Tracker::initCorePiwikInTrackerMode();
|
||||
$cacheContent = array(
|
||||
'isBrowserTriggerEnabled' => Rules::isBrowserTriggerEnabled(),
|
||||
'lastTrackerCronRun' => Option::get('lastTrackerCronRun'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Triggered before the [general tracker cache](/guides/all-about-tracking#the-tracker-cache)
|
||||
* is saved to disk. This event can be used to add extra content to the cache.
|
||||
*
|
||||
* Data that is used during tracking but is expensive to compute/query should be
|
||||
* cached to keep tracking efficient. One example of such data are options
|
||||
* that are stored in the piwik_option table. Querying data for each tracking
|
||||
* request means an extra unnecessary database query for each visitor action. Using
|
||||
* a cache solves this problem.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* public function setTrackerCacheGeneral(&$cacheContent)
|
||||
* {
|
||||
* $cacheContent['MyPlugin.myCacheKey'] = Option::get('MyPlugin_myOption');
|
||||
* }
|
||||
*
|
||||
* @param array &$cacheContent Array of cached data. Each piece of data must be
|
||||
* mapped by name.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.setTrackerCacheGeneral', array(&$cacheContent));
|
||||
self::setCacheGeneral($cacheContent);
|
||||
Common::printDebug("General tracker cache was re-created.");
|
||||
return $cacheContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data in general (global cache)
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
static public function setCacheGeneral($value)
|
||||
{
|
||||
$cache = self::getInstance();
|
||||
$cacheId = 'general';
|
||||
$cache->set($cacheId, $value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate Tracker cache files
|
||||
*
|
||||
* @param array|int $idSites Array of idSites to clear cache for
|
||||
*/
|
||||
static public function regenerateCacheWebsiteAttributes($idSites = array())
|
||||
{
|
||||
if (!is_array($idSites)) {
|
||||
$idSites = array($idSites);
|
||||
}
|
||||
foreach ($idSites as $idSite) {
|
||||
self::deleteCacheWebsiteAttributes($idSite);
|
||||
self::getCacheWebsiteAttributes($idSite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing Tracker cache
|
||||
*
|
||||
* @param string $idSite (website ID of the site to clear cache for
|
||||
*/
|
||||
static public function deleteCacheWebsiteAttributes($idSite)
|
||||
{
|
||||
$idSite = (int)$idSite;
|
||||
self::getInstance()->delete($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all Tracker cache files
|
||||
*/
|
||||
static public function deleteTrackerCache()
|
||||
{
|
||||
self::getInstance()->deleteAll();
|
||||
}
|
||||
}
|
||||
225
www/analytics/core/Tracker/Db.php
Normal file
225
www/analytics/core/Tracker/Db.php
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Exception;
|
||||
use PDOStatement;
|
||||
use Piwik\Common;
|
||||
use Piwik\Timer;
|
||||
use Piwik\Tracker\Db\DbException;
|
||||
|
||||
/**
|
||||
* Simple database wrapper.
|
||||
* We can't afford to have a dependency with the Zend_Db module in Tracker.
|
||||
* We wrote this simple class
|
||||
*
|
||||
*/
|
||||
abstract class Db
|
||||
{
|
||||
protected static $profiling = false;
|
||||
|
||||
protected $queriesProfiling = array();
|
||||
|
||||
protected $connection = null;
|
||||
|
||||
/**
|
||||
* Enables the SQL profiling.
|
||||
* For each query, saves in the DB the time spent on this query.
|
||||
* Very useful to see the slow query under heavy load.
|
||||
* You can then use Piwik::displayDbTrackerProfile();
|
||||
* to display the SQLProfiling report and see which queries take time, etc.
|
||||
*/
|
||||
public static function enableProfiling()
|
||||
{
|
||||
self::$profiling = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the SQL profiling logging.
|
||||
*/
|
||||
public static function disableProfiling()
|
||||
{
|
||||
self::$profiling = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the SQL profiler is enabled
|
||||
* Only used by the unit test that tests that the profiler is off on a production server
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isProfilingEnabled()
|
||||
{
|
||||
return self::$profiling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize profiler
|
||||
*
|
||||
* @return Timer
|
||||
*/
|
||||
protected function initProfiler()
|
||||
{
|
||||
return new Timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record query profile
|
||||
*
|
||||
* @param string $query
|
||||
* @param Timer $timer
|
||||
*/
|
||||
protected function recordQueryProfile($query, $timer)
|
||||
{
|
||||
if (!isset($this->queriesProfiling[$query])) $this->queriesProfiling[$query] = array('sum_time_ms' => 0, 'count' => 0);
|
||||
$time = $timer->getTimeMs(2);
|
||||
$time += $this->queriesProfiling[$query]['sum_time_ms'];
|
||||
$count = $this->queriesProfiling[$query]['count'] + 1;
|
||||
$this->queriesProfiling[$query] = array('sum_time_ms' => $time, 'count' => $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* When destroyed, if SQL profiled enabled, logs the SQL profiling information
|
||||
*/
|
||||
public function recordProfiling()
|
||||
{
|
||||
if (is_null($this->connection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// turn off the profiler so we don't profile the following queries
|
||||
self::$profiling = false;
|
||||
|
||||
foreach ($this->queriesProfiling as $query => $info) {
|
||||
$time = $info['sum_time_ms'];
|
||||
$count = $info['count'];
|
||||
|
||||
$queryProfiling = "INSERT INTO " . Common::prefixTable('log_profiling') . "
|
||||
(query,count,sum_time_ms) VALUES (?,$count,$time)
|
||||
ON DUPLICATE KEY
|
||||
UPDATE count=count+$count,sum_time_ms=sum_time_ms+$time";
|
||||
$this->query($queryProfiling, array($query));
|
||||
}
|
||||
|
||||
// turn back on profiling
|
||||
self::$profiling = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the DB
|
||||
*
|
||||
* @throws \Piwik\Tracker\Db\DbException if there was an error connecting the DB
|
||||
*/
|
||||
abstract public function connect();
|
||||
|
||||
/**
|
||||
* Disconnects from the server
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all the rows of a query result, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @see query()
|
||||
* @throws \Piwik\Tracker\Db\DbException if an exception occurred
|
||||
*/
|
||||
abstract public function fetchAll($query, $parameters = array());
|
||||
|
||||
/**
|
||||
* Returns the first row of a query result, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @see also query()
|
||||
*
|
||||
* @throws DbException if an exception occurred
|
||||
*/
|
||||
abstract public function fetch($query, $parameters = array());
|
||||
|
||||
/**
|
||||
* This function is a proxy to fetch(), used to maintain compatibility with Zend_Db interface
|
||||
*
|
||||
* @see fetch()
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return
|
||||
*/
|
||||
public function fetchRow($query, $parameters = array())
|
||||
{
|
||||
return $this->fetch($query, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a proxy to fetch(), used to maintain compatibility with Zend_Db interface
|
||||
*
|
||||
* @see fetch()
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function fetchOne($query, $parameters = array())
|
||||
{
|
||||
$result = $this->fetch($query, $parameters);
|
||||
return is_array($result) && !empty($result) ? reset($result) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a proxy to fetch(), used to maintain compatibility with Zend_Db + PDO interface
|
||||
*
|
||||
* @see fetch()
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return
|
||||
*/
|
||||
public function exec($query, $parameters = array())
|
||||
{
|
||||
return $this->fetch($query, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of affected rows in last query
|
||||
*
|
||||
* @param mixed $queryResult Result from query()
|
||||
* @return int
|
||||
*/
|
||||
abstract public function rowCount($queryResult);
|
||||
|
||||
/**
|
||||
* Executes a query, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind array('idsite'=> 1)
|
||||
*
|
||||
* @return PDOStatement or false if failed
|
||||
* @throws DbException if an exception occurred
|
||||
*/
|
||||
abstract public function query($query, $parameters = array());
|
||||
|
||||
/**
|
||||
* Returns the last inserted ID in the DB
|
||||
* Wrapper of PDO::lastInsertId()
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function lastInsertId();
|
||||
|
||||
/**
|
||||
* Test error number
|
||||
*
|
||||
* @param Exception $e
|
||||
* @param string $errno
|
||||
* @return bool True if error number matches; false otherwise
|
||||
*/
|
||||
abstract public function isErrNo($e, $errno);
|
||||
}
|
||||
20
www/analytics/core/Tracker/Db/DbException.php
Normal file
20
www/analytics/core/Tracker/Db/DbException.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker\Db;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Database Exception
|
||||
*
|
||||
*/
|
||||
class DbException extends Exception
|
||||
{
|
||||
}
|
||||
279
www/analytics/core/Tracker/Db/Mysqli.php
Normal file
279
www/analytics/core/Tracker/Db/Mysqli.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker\Db;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Tracker\Db;
|
||||
|
||||
/**
|
||||
* mysqli wrapper
|
||||
*
|
||||
*/
|
||||
class Mysqli extends Db
|
||||
{
|
||||
protected $connection = null;
|
||||
protected $host;
|
||||
protected $port;
|
||||
protected $socket;
|
||||
protected $dbname;
|
||||
protected $username;
|
||||
protected $password;
|
||||
protected $charset;
|
||||
|
||||
/**
|
||||
* Builds the DB object
|
||||
*
|
||||
* @param array $dbInfo
|
||||
* @param string $driverName
|
||||
*/
|
||||
public function __construct($dbInfo, $driverName = 'mysql')
|
||||
{
|
||||
if (isset($dbInfo['unix_socket']) && $dbInfo['unix_socket'][0] == '/') {
|
||||
$this->host = null;
|
||||
$this->port = null;
|
||||
$this->socket = $dbInfo['unix_socket'];
|
||||
} else if ($dbInfo['port'][0] == '/') {
|
||||
$this->host = null;
|
||||
$this->port = null;
|
||||
$this->socket = $dbInfo['port'];
|
||||
} else {
|
||||
$this->host = $dbInfo['host'];
|
||||
$this->port = $dbInfo['port'];
|
||||
$this->socket = null;
|
||||
}
|
||||
$this->dbname = $dbInfo['dbname'];
|
||||
$this->username = $dbInfo['username'];
|
||||
$this->password = $dbInfo['password'];
|
||||
$this->charset = isset($dbInfo['charset']) ? $dbInfo['charset'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the DB
|
||||
*
|
||||
* @throws Exception|DbException if there was an error connecting the DB
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket);
|
||||
if (!$this->connection || mysqli_connect_errno()) {
|
||||
throw new DbException("Connect failed: " . mysqli_connect_error());
|
||||
}
|
||||
|
||||
if ($this->charset && !mysqli_set_charset($this->connection, $this->charset)) {
|
||||
throw new DbException("Set Charset failed: " . mysqli_error($this->connection));
|
||||
}
|
||||
|
||||
$this->password = '';
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile('connect', $timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the server
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
mysqli_close($this->connection);
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all the rows of a query result, using optional bound parameters.
|
||||
*
|
||||
* @see query()
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return array
|
||||
* @throws Exception|DbException if an exception occured
|
||||
*/
|
||||
public function fetchAll($query, $parameters = array())
|
||||
{
|
||||
try {
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$query = $this->prepare($query, $parameters);
|
||||
$rs = mysqli_query($this->connection, $query);
|
||||
if (is_bool($rs)) {
|
||||
throw new DbException('fetchAll() failed: ' . mysqli_error($this->connection) . ' : ' . $query);
|
||||
}
|
||||
|
||||
while ($row = mysqli_fetch_array($rs, MYSQLI_ASSOC)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
mysqli_free_result($rs);
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile($query, $timer);
|
||||
}
|
||||
return $rows;
|
||||
} catch (Exception $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first row of a query result, using optional bound parameters.
|
||||
*
|
||||
* @see query()
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws DbException if an exception occurred
|
||||
*/
|
||||
public function fetch($query, $parameters = array())
|
||||
{
|
||||
try {
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$query = $this->prepare($query, $parameters);
|
||||
$rs = mysqli_query($this->connection, $query);
|
||||
if (is_bool($rs)) {
|
||||
throw new DbException('fetch() failed: ' . mysqli_error($this->connection) . ' : ' . $query);
|
||||
}
|
||||
|
||||
$row = mysqli_fetch_array($rs, MYSQLI_ASSOC);
|
||||
mysqli_free_result($rs);
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile($query, $timer);
|
||||
}
|
||||
return $row;
|
||||
} catch (Exception $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array|string $parameters Parameters to bind array('idsite'=> 1)
|
||||
*
|
||||
* @return bool|resource false if failed
|
||||
* @throws DbException if an exception occurred
|
||||
*/
|
||||
public function query($query, $parameters = array())
|
||||
{
|
||||
if (is_null($this->connection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$query = $this->prepare($query, $parameters);
|
||||
$result = mysqli_query($this->connection, $query);
|
||||
if (!is_bool($result)) {
|
||||
mysqli_free_result($result);
|
||||
}
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile($query, $timer);
|
||||
}
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage() . "
|
||||
In query: $query
|
||||
Parameters: " . var_export($parameters, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last inserted ID in the DB
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function lastInsertId()
|
||||
{
|
||||
return mysqli_insert_id($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input is a prepared SQL statement and parameters
|
||||
* Returns the SQL statement
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $parameters
|
||||
* @return string
|
||||
*/
|
||||
private function prepare($query, $parameters)
|
||||
{
|
||||
if (!$parameters) {
|
||||
$parameters = array();
|
||||
} else if (!is_array($parameters)) {
|
||||
$parameters = array($parameters);
|
||||
}
|
||||
|
||||
$this->paramNb = 0;
|
||||
$this->params = & $parameters;
|
||||
$query = preg_replace_callback('/\?/', array($this, 'replaceParam'), $query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function replaceParam($match)
|
||||
{
|
||||
$param = & $this->params[$this->paramNb];
|
||||
$this->paramNb++;
|
||||
|
||||
if ($param === null) {
|
||||
return 'NULL';
|
||||
} else {
|
||||
return "'" . addslashes($param) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error number
|
||||
*
|
||||
* @param Exception $e
|
||||
* @param string $errno
|
||||
* @return bool
|
||||
*/
|
||||
public function isErrNo($e, $errno)
|
||||
{
|
||||
return mysqli_errno($this->connection) == $errno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of affected rows in last query
|
||||
*
|
||||
* @param mixed $queryResult Result from query()
|
||||
* @return int
|
||||
*/
|
||||
public function rowCount($queryResult)
|
||||
{
|
||||
return mysqli_affected_rows($this->connection);
|
||||
}
|
||||
}
|
||||
237
www/analytics/core/Tracker/Db/Pdo/Mysql.php
Normal file
237
www/analytics/core/Tracker/Db/Pdo/Mysql.php
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker\Db\Pdo;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use Piwik\Tracker\Db;
|
||||
use Piwik\Tracker\Db\DbException;
|
||||
|
||||
/**
|
||||
* PDO MySQL wrapper
|
||||
*
|
||||
*/
|
||||
class Mysql extends Db
|
||||
{
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected $connection = null;
|
||||
protected $dsn;
|
||||
protected $username;
|
||||
protected $password;
|
||||
protected $charset;
|
||||
|
||||
/**
|
||||
* Builds the DB object
|
||||
*
|
||||
* @param array $dbInfo
|
||||
* @param string $driverName
|
||||
*/
|
||||
public function __construct($dbInfo, $driverName = 'mysql')
|
||||
{
|
||||
if (isset($dbInfo['unix_socket']) && $dbInfo['unix_socket'][0] == '/') {
|
||||
$this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['unix_socket'];
|
||||
} else if (!empty($dbInfo['port']) && $dbInfo['port'][0] == '/') {
|
||||
$this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['port'];
|
||||
} else {
|
||||
$this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';host=' . $dbInfo['host'] . ';port=' . $dbInfo['port'];
|
||||
}
|
||||
$this->username = $dbInfo['username'];
|
||||
$this->password = $dbInfo['password'];
|
||||
$this->charset = isset($dbInfo['charset']) ? $dbInfo['charset'] : null;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the DB
|
||||
*
|
||||
* @throws Exception if there was an error connecting the DB
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$this->connection = @new PDO($this->dsn, $this->username, $this->password, $config = array());
|
||||
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
// we may want to setAttribute(PDO::ATTR_TIMEOUT ) to a few seconds (default is 60) in case the DB is locked
|
||||
// the piwik.php would stay waiting for the database... bad!
|
||||
// we delete the password from this object "just in case" it could be printed
|
||||
$this->password = '';
|
||||
|
||||
/*
|
||||
* Lazy initialization via MYSQL_ATTR_INIT_COMMAND depends
|
||||
* on mysqlnd support, PHP version, and OS.
|
||||
* see ZF-7428 and http://bugs.php.net/bug.php?id=47224
|
||||
*/
|
||||
if (!empty($this->charset)) {
|
||||
$sql = "SET NAMES '" . $this->charset . "'";
|
||||
$this->connection->exec($sql);
|
||||
}
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile('connect', $timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the server
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all the rows of a query result, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return array|bool
|
||||
* @see query()
|
||||
* @throws Exception|DbException if an exception occurred
|
||||
*/
|
||||
public function fetchAll($query, $parameters = array())
|
||||
{
|
||||
try {
|
||||
$sth = $this->query($query, $parameters);
|
||||
if ($sth === false) {
|
||||
return false;
|
||||
}
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the first column of all SQL result rows as an array.
|
||||
*
|
||||
* @param string $sql An SQL SELECT statement.
|
||||
* @param mixed $bind Data to bind into SELECT placeholders.
|
||||
* @throws \Piwik\Tracker\Db\DbException
|
||||
* @return string
|
||||
*/
|
||||
public function fetchCol($sql, $bind = array())
|
||||
{
|
||||
try {
|
||||
$sth = $this->query($sql, $bind);
|
||||
if ($sth === false) {
|
||||
return false;
|
||||
}
|
||||
$result = $sth->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
return $result;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first row of a query result, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array $parameters Parameters to bind
|
||||
* @return bool|mixed
|
||||
* @see query()
|
||||
* @throws Exception|DbException if an exception occurred
|
||||
*/
|
||||
public function fetch($query, $parameters = array())
|
||||
{
|
||||
try {
|
||||
$sth = $this->query($query, $parameters);
|
||||
if ($sth === false) {
|
||||
return false;
|
||||
}
|
||||
return $sth->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query, using optional bound parameters.
|
||||
*
|
||||
* @param string $query Query
|
||||
* @param array|string $parameters Parameters to bind array('idsite'=> 1)
|
||||
* @return PDOStatement|bool PDOStatement or false if failed
|
||||
* @throws DbException if an exception occured
|
||||
*/
|
||||
public function query($query, $parameters = array())
|
||||
{
|
||||
if (is_null($this->connection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
if (!is_array($parameters)) {
|
||||
$parameters = array($parameters);
|
||||
}
|
||||
$sth = $this->connection->prepare($query);
|
||||
$sth->execute($parameters);
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile($query, $timer);
|
||||
}
|
||||
return $sth;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error query: " . $e->getMessage() . "
|
||||
In query: $query
|
||||
Parameters: " . var_export($parameters, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last inserted ID in the DB
|
||||
* Wrapper of PDO::lastInsertId()
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function lastInsertId()
|
||||
{
|
||||
return $this->connection->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error number
|
||||
*
|
||||
* @param Exception $e
|
||||
* @param string $errno
|
||||
* @return bool
|
||||
*/
|
||||
public function isErrNo($e, $errno)
|
||||
{
|
||||
if (preg_match('/([0-9]{4})/', $e->getMessage(), $match)) {
|
||||
return $match[1] == $errno;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of affected rows in last query
|
||||
*
|
||||
* @param mixed $queryResult Result from query()
|
||||
* @return int
|
||||
*/
|
||||
public function rowCount($queryResult)
|
||||
{
|
||||
return $queryResult->rowCount();
|
||||
}
|
||||
}
|
||||
116
www/analytics/core/Tracker/Db/Pdo/Pgsql.php
Normal file
116
www/analytics/core/Tracker/Db/Pdo/Pgsql.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker\Db\Pdo;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* PDO PostgreSQL wrapper
|
||||
*
|
||||
*/
|
||||
class Pgsql extends Mysql
|
||||
{
|
||||
/**
|
||||
* Builds the DB object
|
||||
*
|
||||
* @param array $dbInfo
|
||||
* @param string $driverName
|
||||
*/
|
||||
public function __construct($dbInfo, $driverName = 'pgsql')
|
||||
{
|
||||
parent::__construct($dbInfo, $driverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the DB
|
||||
*
|
||||
* @throws Exception if there was an error connecting the DB
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if (self::$profiling) {
|
||||
$timer = $this->initProfiler();
|
||||
}
|
||||
|
||||
$this->connection = new PDO($this->dsn, $this->username, $this->password, $config = array());
|
||||
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
// we may want to setAttribute(PDO::ATTR_TIMEOUT ) to a few seconds (default is 60) in case the DB is locked
|
||||
// the piwik.php would stay waiting for the database... bad!
|
||||
// we delete the password from this object "just in case" it could be printed
|
||||
$this->password = '';
|
||||
|
||||
if (!empty($this->charset)) {
|
||||
$sql = "SET NAMES '" . $this->charset . "'";
|
||||
$this->connection->exec($sql);
|
||||
}
|
||||
|
||||
if (self::$profiling && isset($timer)) {
|
||||
$this->recordQueryProfile('connect', $timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error number
|
||||
*
|
||||
* @param Exception $e
|
||||
* @param string $errno
|
||||
* @return bool
|
||||
*/
|
||||
public function isErrNo($e, $errno)
|
||||
{
|
||||
// map MySQL driver-specific error codes to PostgreSQL SQLSTATE
|
||||
$map = array(
|
||||
// MySQL: Unknown database '%s'
|
||||
// PostgreSQL: database "%s" does not exist
|
||||
'1049' => '08006',
|
||||
|
||||
// MySQL: Table '%s' already exists
|
||||
// PostgreSQL: relation "%s" already exists
|
||||
'1050' => '42P07',
|
||||
|
||||
// MySQL: Unknown column '%s' in '%s'
|
||||
// PostgreSQL: column "%s" does not exist
|
||||
'1054' => '42703',
|
||||
|
||||
// MySQL: Duplicate column name '%s'
|
||||
// PostgreSQL: column "%s" of relation "%s" already exists
|
||||
'1060' => '42701',
|
||||
|
||||
// MySQL: Duplicate entry '%s' for key '%s'
|
||||
// PostgreSQL: duplicate key violates unique constraint
|
||||
'1062' => '23505',
|
||||
|
||||
// MySQL: Can't DROP '%s'; check that column/key exists
|
||||
// PostgreSQL: index "%s" does not exist
|
||||
'1091' => '42704',
|
||||
|
||||
// MySQL: Table '%s.%s' doesn't exist
|
||||
// PostgreSQL: relation "%s" does not exist
|
||||
'1146' => '42P01',
|
||||
);
|
||||
|
||||
if (preg_match('/([0-9]{2}[0-9P][0-9]{2})/', $e->getMessage(), $match)) {
|
||||
return $match[1] == $map[$errno];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of affected rows in last query
|
||||
*
|
||||
* @param mixed $queryResult Result from query()
|
||||
* @return int
|
||||
*/
|
||||
public function rowCount($queryResult)
|
||||
{
|
||||
return $queryResult->rowCount();
|
||||
}
|
||||
}
|
||||
905
www/analytics/core/Tracker/GoalManager.php
Normal file
905
www/analytics/core/Tracker/GoalManager.php
Normal file
|
|
@ -0,0 +1,905 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CustomVariables\CustomVariables;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
*/
|
||||
class GoalManager
|
||||
{
|
||||
// log_visit.visit_goal_buyer
|
||||
const TYPE_BUYER_NONE = 0;
|
||||
const TYPE_BUYER_ORDERED = 1;
|
||||
const TYPE_BUYER_OPEN_CART = 2;
|
||||
const TYPE_BUYER_ORDERED_AND_OPEN_CART = 3;
|
||||
|
||||
// log_conversion.idorder is NULLable, but not log_conversion_item which defaults to zero for carts
|
||||
const ITEM_IDORDER_ABANDONED_CART = 0;
|
||||
|
||||
// log_conversion.idgoal special values
|
||||
const IDGOAL_CART = -1;
|
||||
const IDGOAL_ORDER = 0;
|
||||
|
||||
const REVENUE_PRECISION = 2;
|
||||
|
||||
const MAXIMUM_PRODUCT_CATEGORIES = 5;
|
||||
public $idGoal;
|
||||
public $requestIsEcommerce;
|
||||
public $isGoalAnOrder;
|
||||
|
||||
/**
|
||||
* @var Action
|
||||
*/
|
||||
protected $action = null;
|
||||
protected $convertedGoals = array();
|
||||
protected $isThereExistingCartInVisit = false;
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
protected $orderId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param Request $request
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
function init()
|
||||
{
|
||||
$this->orderId = $this->request->getParam('ec_id');
|
||||
$this->isGoalAnOrder = !empty($this->orderId);
|
||||
$this->idGoal = $this->request->getParam('idgoal');
|
||||
$this->requestIsEcommerce = ($this->idGoal == 0);
|
||||
}
|
||||
|
||||
function getBuyerType($existingType = GoalManager::TYPE_BUYER_NONE)
|
||||
{
|
||||
// Was there a Cart for this visit prior to the order?
|
||||
$this->isThereExistingCartInVisit = in_array($existingType,
|
||||
array(GoalManager::TYPE_BUYER_OPEN_CART,
|
||||
GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART));
|
||||
|
||||
if (!$this->requestIsEcommerce) {
|
||||
return $existingType;
|
||||
}
|
||||
if ($this->isGoalAnOrder) {
|
||||
return self::TYPE_BUYER_ORDERED;
|
||||
}
|
||||
// request is Add to Cart
|
||||
if ($existingType == self::TYPE_BUYER_ORDERED
|
||||
|| $existingType == self::TYPE_BUYER_ORDERED_AND_OPEN_CART
|
||||
) {
|
||||
return self::TYPE_BUYER_ORDERED_AND_OPEN_CART;
|
||||
}
|
||||
return self::TYPE_BUYER_OPEN_CART;
|
||||
}
|
||||
|
||||
static public function getGoalDefinitions($idSite)
|
||||
{
|
||||
$websiteAttributes = Cache::getCacheWebsiteAttributes($idSite);
|
||||
if (isset($websiteAttributes['goals'])) {
|
||||
return $websiteAttributes['goals'];
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
static public function getGoalDefinition($idSite, $idGoal)
|
||||
{
|
||||
$goals = self::getGoalDefinitions($idSite);
|
||||
foreach ($goals as $goal) {
|
||||
if ($goal['idgoal'] == $idGoal) {
|
||||
return $goal;
|
||||
}
|
||||
}
|
||||
throw new Exception('Goal not found');
|
||||
}
|
||||
|
||||
static public function getGoalIds($idSite)
|
||||
{
|
||||
$goals = self::getGoalDefinitions($idSite);
|
||||
$goalIds = array();
|
||||
foreach ($goals as $goal) {
|
||||
$goalIds[] = $goal['idgoal'];
|
||||
}
|
||||
return $goalIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the URL or Page Title and sees if it matches any existing Goal definition
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param Action $action
|
||||
* @throws Exception
|
||||
* @return int Number of goals matched
|
||||
*/
|
||||
function detectGoalsMatchingUrl($idSite, $action)
|
||||
{
|
||||
if (!Common::isGoalPluginEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$decodedActionUrl = $action->getActionUrl();
|
||||
$actionType = $action->getActionType();
|
||||
$goals = $this->getGoalDefinitions($idSite);
|
||||
foreach ($goals as $goal) {
|
||||
$attribute = $goal['match_attribute'];
|
||||
// if the attribute to match is not the type of the current action
|
||||
if ( (($attribute == 'url' || $attribute == 'title') && $actionType != Action::TYPE_PAGE_URL)
|
||||
|| ($attribute == 'file' && $actionType != Action::TYPE_DOWNLOAD)
|
||||
|| ($attribute == 'external_website' && $actionType != Action::TYPE_OUTLINK)
|
||||
|| ($attribute == 'manually')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $decodedActionUrl;
|
||||
// Matching on Page Title
|
||||
if ($attribute == 'title') {
|
||||
$url = $action->getActionName();
|
||||
}
|
||||
$pattern_type = $goal['pattern_type'];
|
||||
|
||||
$match = $this->isUrlMatchingGoal($goal, $pattern_type, $url);
|
||||
if ($match) {
|
||||
$goal['url'] = $decodedActionUrl;
|
||||
$this->convertedGoals[] = $goal;
|
||||
}
|
||||
}
|
||||
return count($this->convertedGoals) > 0;
|
||||
}
|
||||
|
||||
function detectGoalId($idSite)
|
||||
{
|
||||
if (!Common::isGoalPluginEnabled()) {
|
||||
return false;
|
||||
}
|
||||
$goals = $this->getGoalDefinitions($idSite);
|
||||
if (!isset($goals[$this->idGoal])) {
|
||||
return false;
|
||||
}
|
||||
$goal = $goals[$this->idGoal];
|
||||
|
||||
$url = $this->request->getParam('url');
|
||||
$goal['url'] = PageUrl::excludeQueryParametersFromUrl($url, $idSite);
|
||||
$goal['revenue'] = $this->getRevenue($this->request->getGoalRevenue($goal['revenue']));
|
||||
$this->convertedGoals[] = $goal;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records one or several goals matched in this request.
|
||||
*
|
||||
* @param int $idSite
|
||||
* @param array $visitorInformation
|
||||
* @param array $visitCustomVariables
|
||||
* @param Action $action
|
||||
*/
|
||||
public function recordGoals($idSite, $visitorInformation, $visitCustomVariables, $action)
|
||||
{
|
||||
$referrerTimestamp = $this->request->getParam('_refts');
|
||||
$referrerUrl = $this->request->getParam('_ref');
|
||||
$referrerCampaignName = trim(urldecode($this->request->getParam('_rcn')));
|
||||
$referrerCampaignKeyword = trim(urldecode($this->request->getParam('_rck')));
|
||||
$browserLanguage = $this->request->getBrowserLanguage();
|
||||
|
||||
$location_country = isset($visitorInformation['location_country'])
|
||||
? $visitorInformation['location_country']
|
||||
: Common::getCountry(
|
||||
$browserLanguage,
|
||||
$enableLanguageToCountryGuess = Config::getInstance()->Tracker['enable_language_to_country_guess'],
|
||||
$visitorInformation['location_ip']
|
||||
);
|
||||
|
||||
$goal = array(
|
||||
'idvisit' => $visitorInformation['idvisit'],
|
||||
'idsite' => $idSite,
|
||||
'idvisitor' => $visitorInformation['idvisitor'],
|
||||
'server_time' => Tracker::getDatetimeFromTimestamp($visitorInformation['visit_last_action_time']),
|
||||
'location_country' => $location_country,
|
||||
'visitor_returning' => $visitorInformation['visitor_returning'],
|
||||
'visitor_days_since_first' => $visitorInformation['visitor_days_since_first'],
|
||||
'visitor_days_since_order' => $visitorInformation['visitor_days_since_order'],
|
||||
'visitor_count_visits' => $visitorInformation['visitor_count_visits'],
|
||||
);
|
||||
|
||||
$extraLocationCols = array('location_region', 'location_city', 'location_latitude', 'location_longitude');
|
||||
foreach ($extraLocationCols as $col) {
|
||||
if (isset($visitorInformation[$col])) {
|
||||
$goal[$col] = $visitorInformation[$col];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Custom Variables from Visit row to the Goal conversion
|
||||
// Otherwise, set the Custom Variables found in the cookie sent with this request
|
||||
$goal += $visitCustomVariables;
|
||||
$maxCustomVariables = CustomVariables::getMaxCustomVariables();
|
||||
|
||||
for ($i = 1; $i <= $maxCustomVariables; $i++) {
|
||||
if (isset($visitorInformation['custom_var_k' . $i])
|
||||
&& strlen($visitorInformation['custom_var_k' . $i])
|
||||
) {
|
||||
$goal['custom_var_k' . $i] = $visitorInformation['custom_var_k' . $i];
|
||||
}
|
||||
if (isset($visitorInformation['custom_var_v' . $i])
|
||||
&& strlen($visitorInformation['custom_var_v' . $i])
|
||||
) {
|
||||
$goal['custom_var_v' . $i] = $visitorInformation['custom_var_v' . $i];
|
||||
}
|
||||
}
|
||||
|
||||
// Attributing the correct Referrer to this conversion.
|
||||
// Priority order is as follows:
|
||||
// 0) In some cases, the campaign is not passed from the JS so we look it up from the current visit
|
||||
// 1) Campaign name/kwd parsed in the JS
|
||||
// 2) Referrer URL stored in the _ref cookie
|
||||
// 3) If no info from the cookie, attribute to the current visit referrer
|
||||
|
||||
// 3) Default values: current referrer
|
||||
$type = $visitorInformation['referer_type'];
|
||||
$name = $visitorInformation['referer_name'];
|
||||
$keyword = $visitorInformation['referer_keyword'];
|
||||
$time = $visitorInformation['visit_first_action_time'];
|
||||
|
||||
// 0) In some (unknown!?) cases the campaign is not found in the attribution cookie, but the URL ref was found.
|
||||
// In this case we look up if the current visit is credited to a campaign and will credit this campaign rather than the URL ref (since campaigns have higher priority)
|
||||
if (empty($referrerCampaignName)
|
||||
&& $type == Common::REFERRER_TYPE_CAMPAIGN
|
||||
&& !empty($name)
|
||||
) {
|
||||
// Use default values per above
|
||||
} // 1) Campaigns from 1st party cookie
|
||||
elseif (!empty($referrerCampaignName)) {
|
||||
$type = Common::REFERRER_TYPE_CAMPAIGN;
|
||||
$name = $referrerCampaignName;
|
||||
$keyword = $referrerCampaignKeyword;
|
||||
$time = $referrerTimestamp;
|
||||
} // 2) Referrer URL parsing
|
||||
elseif (!empty($referrerUrl)) {
|
||||
$referrer = new Referrer();
|
||||
$referrer = $referrer->getReferrerInformation($referrerUrl, $currentUrl = '', $idSite);
|
||||
|
||||
// if the parsed referrer is interesting enough, ie. website or search engine
|
||||
if (in_array($referrer['referer_type'], array(Common::REFERRER_TYPE_SEARCH_ENGINE, Common::REFERRER_TYPE_WEBSITE))) {
|
||||
$type = $referrer['referer_type'];
|
||||
$name = $referrer['referer_name'];
|
||||
$keyword = $referrer['referer_keyword'];
|
||||
$time = $referrerTimestamp;
|
||||
}
|
||||
}
|
||||
$this->setCampaignValuesToLowercase($type, $name, $keyword);
|
||||
|
||||
$goal += array(
|
||||
'referer_type' => $type,
|
||||
'referer_name' => $name,
|
||||
'referer_keyword' => $keyword,
|
||||
// this field is currently unused
|
||||
'referer_visit_server_date' => date("Y-m-d", $time),
|
||||
);
|
||||
|
||||
// some goals are converted, so must be ecommerce Order or Cart Update
|
||||
if ($this->requestIsEcommerce) {
|
||||
$this->recordEcommerceGoal($goal, $visitorInformation);
|
||||
} else {
|
||||
$this->recordStandardGoals($goal, $action, $visitorInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns rounded decimal revenue, or if revenue is integer, then returns as is.
|
||||
*
|
||||
* @param int|float $revenue
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getRevenue($revenue)
|
||||
{
|
||||
if (round($revenue) == $revenue) {
|
||||
return $revenue;
|
||||
}
|
||||
return round($revenue, self::REVENUE_PRECISION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an Ecommerce conversion in the DB. Deals with Items found in the request.
|
||||
* Will deal with 2 types of conversions: Ecommerce Order and Ecommerce Cart update (Add to cart, Update Cart etc).
|
||||
*
|
||||
* @param array $conversion
|
||||
* @param array $visitInformation
|
||||
*/
|
||||
protected function recordEcommerceGoal($conversion, $visitInformation)
|
||||
{
|
||||
if ($this->isThereExistingCartInVisit) {
|
||||
Common::printDebug("There is an existing cart for this visit");
|
||||
}
|
||||
if ($this->isGoalAnOrder) {
|
||||
$conversion['idgoal'] = self::IDGOAL_ORDER;
|
||||
$conversion['idorder'] = $this->orderId;
|
||||
$conversion['buster'] = Common::hashStringToInt($this->orderId);
|
||||
$conversion['revenue_subtotal'] = $this->getRevenue($this->request->getParam('ec_st'));
|
||||
$conversion['revenue_tax'] = $this->getRevenue($this->request->getParam('ec_tx'));
|
||||
$conversion['revenue_shipping'] = $this->getRevenue($this->request->getParam('ec_sh'));
|
||||
$conversion['revenue_discount'] = $this->getRevenue($this->request->getParam('ec_dt'));
|
||||
|
||||
$debugMessage = 'The conversion is an Ecommerce order';
|
||||
} // If Cart update, select current items in the previous Cart
|
||||
else {
|
||||
$conversion['buster'] = 0;
|
||||
$conversion['idgoal'] = self::IDGOAL_CART;
|
||||
$debugMessage = 'The conversion is an Ecommerce Cart Update';
|
||||
}
|
||||
$conversion['revenue'] = $this->getRevenue($this->request->getGoalRevenue($defaultRevenue = 0));
|
||||
|
||||
Common::printDebug($debugMessage . ':' . var_export($conversion, true));
|
||||
|
||||
// INSERT or Sync items in the Cart / Order for this visit & order
|
||||
$items = $this->getEcommerceItemsFromRequest();
|
||||
if ($items === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$itemsCount = 0;
|
||||
foreach ($items as $item) {
|
||||
$itemsCount += $item[self::INTERNAL_ITEM_QUANTITY];
|
||||
}
|
||||
$conversion['items'] = $itemsCount;
|
||||
|
||||
if($this->isThereExistingCartInVisit) {
|
||||
$updateWhere = array(
|
||||
'idvisit' => $visitInformation['idvisit'],
|
||||
'idgoal' => self::IDGOAL_CART,
|
||||
'buster' => 0,
|
||||
);
|
||||
$recorded = $this->updateExistingConversion($conversion, $updateWhere);
|
||||
} else {
|
||||
$recorded = $this->insertNewConversion($conversion, $visitInformation);
|
||||
}
|
||||
|
||||
if ($recorded) {
|
||||
$this->recordEcommerceItems($conversion, $items, $visitInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered after successfully persisting an ecommerce conversion.
|
||||
*
|
||||
* _Note: Subscribers should be wary of doing any expensive computation here as it may slow
|
||||
* the tracker down._
|
||||
*
|
||||
* @param array $conversion The conversion entity that was just persisted. See what information
|
||||
* it contains [here](/guides/persistence-and-the-mysql-backend#conversions).
|
||||
* @param array $visitInformation The visit entity that we are tracking a conversion for. See what
|
||||
* information it contains [here](/guides/persistence-and-the-mysql-backend#visits).
|
||||
*/
|
||||
Piwik::postEvent('Tracker.recordEcommerceGoal', array($conversion, $visitInformation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Items read from the request string
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function getEcommerceItemsFromRequest()
|
||||
{
|
||||
$items = Common::unsanitizeInputValue($this->request->getParam('ec_items'));
|
||||
if (empty($items)) {
|
||||
Common::printDebug("There are no Ecommerce items in the request");
|
||||
// we still record an Ecommerce order without any item in it
|
||||
return array();
|
||||
}
|
||||
$items = Common::json_decode($items, $assoc = true);
|
||||
if (!is_array($items)) {
|
||||
Common::printDebug("Error while json_decode the Ecommerce items = " . var_export($items, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
$cleanedItems = $this->getCleanedEcommerceItems($items);
|
||||
return $cleanedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Ecommerce items from the request and records them in the DB
|
||||
*
|
||||
* @param array $goal
|
||||
* @param array $items
|
||||
* @throws Exception
|
||||
* @return int Number of items in the cart
|
||||
*/
|
||||
protected function recordEcommerceItems($goal, $items)
|
||||
{
|
||||
$itemInCartBySku = array();
|
||||
foreach ($items as $item) {
|
||||
$itemInCartBySku[$item[0]] = $item;
|
||||
}
|
||||
|
||||
// Select all items currently in the Cart if any
|
||||
$sql = "SELECT idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, idorder as idorder_original_value
|
||||
FROM " . Common::prefixTable('log_conversion_item') . "
|
||||
WHERE idvisit = ?
|
||||
AND (idorder = ? OR idorder = ?)";
|
||||
|
||||
$bind = array($goal['idvisit'],
|
||||
isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART,
|
||||
self::ITEM_IDORDER_ABANDONED_CART
|
||||
);
|
||||
|
||||
$itemsInDb = Tracker::getDatabase()->fetchAll($sql, $bind);
|
||||
|
||||
Common::printDebug("Items found in current cart, for conversion_item (visit,idorder)=" . var_export($bind, true));
|
||||
Common::printDebug($itemsInDb);
|
||||
// Look at which items need to be deleted, which need to be added or updated, based on the SKU
|
||||
$skuFoundInDb = $itemsToUpdate = array();
|
||||
foreach ($itemsInDb as $itemInDb) {
|
||||
$skuFoundInDb[] = $itemInDb['idaction_sku'];
|
||||
|
||||
// Ensure price comparisons will have the same assumption
|
||||
$itemInDb['price'] = $this->getRevenue($itemInDb['price']);
|
||||
$itemInDbOriginal = $itemInDb;
|
||||
$itemInDb = array_values($itemInDb);
|
||||
|
||||
// Cast all as string, because what comes out of the fetchAll() are strings
|
||||
$itemInDb = $this->getItemRowCast($itemInDb);
|
||||
|
||||
//Item in the cart in the DB, but not anymore in the cart
|
||||
if (!isset($itemInCartBySku[$itemInDb[0]])) {
|
||||
$itemToUpdate = array_merge($itemInDb,
|
||||
array('deleted' => 1,
|
||||
'idorder_original_value' => $itemInDbOriginal['idorder_original_value']
|
||||
)
|
||||
);
|
||||
|
||||
$itemsToUpdate[] = $itemToUpdate;
|
||||
Common::printDebug("Item found in the previous Cart, but no in the current cart/order");
|
||||
Common::printDebug($itemToUpdate);
|
||||
continue;
|
||||
}
|
||||
|
||||
$newItem = $itemInCartBySku[$itemInDb[0]];
|
||||
$newItem = $this->getItemRowCast($newItem);
|
||||
|
||||
if (count($itemInDb) != count($newItem)) {
|
||||
Common::printDebug("ERROR: Different format in items from cart and DB");
|
||||
throw new Exception(" Item in DB and Item in cart have a different format, this is not expected... " . var_export($itemInDb, true) . var_export($newItem, true));
|
||||
}
|
||||
Common::printDebug("Item has changed since the last cart. Previous item stored in cart in database:");
|
||||
Common::printDebug($itemInDb);
|
||||
Common::printDebug("New item to UPDATE the previous row:");
|
||||
$newItem['idorder_original_value'] = $itemInDbOriginal['idorder_original_value'];
|
||||
Common::printDebug($newItem);
|
||||
$itemsToUpdate[] = $newItem;
|
||||
}
|
||||
|
||||
// Items to UPDATE
|
||||
$this->updateEcommerceItems($goal, $itemsToUpdate);
|
||||
|
||||
// Items to INSERT
|
||||
$itemsToInsert = array();
|
||||
foreach ($items as $item) {
|
||||
if (!in_array($item[0], $skuFoundInDb)) {
|
||||
$itemsToInsert[] = $item;
|
||||
}
|
||||
}
|
||||
$this->insertEcommerceItems($goal, $itemsToInsert);
|
||||
}
|
||||
|
||||
// In the GET items parameter, each item has the following array of information
|
||||
const INDEX_ITEM_SKU = 0;
|
||||
const INDEX_ITEM_NAME = 1;
|
||||
const INDEX_ITEM_CATEGORY = 2;
|
||||
const INDEX_ITEM_PRICE = 3;
|
||||
const INDEX_ITEM_QUANTITY = 4;
|
||||
|
||||
// Used in the array of items, internally to this class
|
||||
const INTERNAL_ITEM_SKU = 0;
|
||||
const INTERNAL_ITEM_NAME = 1;
|
||||
const INTERNAL_ITEM_CATEGORY = 2;
|
||||
const INTERNAL_ITEM_CATEGORY2 = 3;
|
||||
const INTERNAL_ITEM_CATEGORY3 = 4;
|
||||
const INTERNAL_ITEM_CATEGORY4 = 5;
|
||||
const INTERNAL_ITEM_CATEGORY5 = 6;
|
||||
const INTERNAL_ITEM_PRICE = 7;
|
||||
const INTERNAL_ITEM_QUANTITY = 8;
|
||||
|
||||
/**
|
||||
* Reads items from the request, then looks up the names from the lookup table
|
||||
* and returns a clean array of items ready for the database.
|
||||
*
|
||||
* @param array $items
|
||||
* @return array $cleanedItems
|
||||
*/
|
||||
protected function getCleanedEcommerceItems($items)
|
||||
{
|
||||
// Clean up the items array
|
||||
$cleanedItems = array();
|
||||
foreach ($items as $item) {
|
||||
$name = $category = $category2 = $category3 = $category4 = $category5 = false;
|
||||
$price = 0;
|
||||
$quantity = 1;
|
||||
// items are passed in the request as an array: ( $sku, $name, $category, $price, $quantity )
|
||||
if (empty($item[self::INDEX_ITEM_SKU])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sku = $item[self::INDEX_ITEM_SKU];
|
||||
if (!empty($item[self::INDEX_ITEM_NAME])) {
|
||||
$name = $item[self::INDEX_ITEM_NAME];
|
||||
}
|
||||
|
||||
if (!empty($item[self::INDEX_ITEM_CATEGORY])) {
|
||||
$category = $item[self::INDEX_ITEM_CATEGORY];
|
||||
}
|
||||
|
||||
if (isset($item[self::INDEX_ITEM_PRICE])
|
||||
&& is_numeric($item[self::INDEX_ITEM_PRICE])
|
||||
) {
|
||||
$price = $this->getRevenue($item[self::INDEX_ITEM_PRICE]);
|
||||
}
|
||||
if (!empty($item[self::INDEX_ITEM_QUANTITY])
|
||||
&& is_numeric($item[self::INDEX_ITEM_QUANTITY])
|
||||
) {
|
||||
$quantity = (int)$item[self::INDEX_ITEM_QUANTITY];
|
||||
}
|
||||
|
||||
// self::INDEX_ITEM_* are in order
|
||||
$cleanedItems[] = array(
|
||||
self::INTERNAL_ITEM_SKU => $sku,
|
||||
self::INTERNAL_ITEM_NAME => $name,
|
||||
self::INTERNAL_ITEM_CATEGORY => $category,
|
||||
self::INTERNAL_ITEM_CATEGORY2 => $category2,
|
||||
self::INTERNAL_ITEM_CATEGORY3 => $category3,
|
||||
self::INTERNAL_ITEM_CATEGORY4 => $category4,
|
||||
self::INTERNAL_ITEM_CATEGORY5 => $category5,
|
||||
self::INTERNAL_ITEM_PRICE => $price,
|
||||
self::INTERNAL_ITEM_QUANTITY => $quantity
|
||||
);
|
||||
}
|
||||
|
||||
// Lookup Item SKUs, Names & Categories Ids
|
||||
$actionsToLookupAllItems = array();
|
||||
|
||||
// Each item has 7 potential "ids" to lookup in the lookup table
|
||||
$columnsInEachRow = 1 + 1 + self::MAXIMUM_PRODUCT_CATEGORIES;
|
||||
|
||||
foreach ($cleanedItems as $item) {
|
||||
$actionsToLookup = array();
|
||||
list($sku, $name, $category, $price, $quantity) = $item;
|
||||
$actionsToLookup[] = array(trim($sku), Action::TYPE_ECOMMERCE_ITEM_SKU);
|
||||
$actionsToLookup[] = array(trim($name), Action::TYPE_ECOMMERCE_ITEM_NAME);
|
||||
|
||||
// Only one category
|
||||
if (!is_array($category)) {
|
||||
$actionsToLookup[] = array(trim($category), Action::TYPE_ECOMMERCE_ITEM_CATEGORY);
|
||||
} // Multiple categories
|
||||
else {
|
||||
$countCategories = 0;
|
||||
foreach ($category as $productCategory) {
|
||||
$productCategory = trim($productCategory);
|
||||
if (empty($productCategory)) {
|
||||
continue;
|
||||
}
|
||||
$countCategories++;
|
||||
if ($countCategories > self::MAXIMUM_PRODUCT_CATEGORIES) {
|
||||
break;
|
||||
}
|
||||
$actionsToLookup[] = array($productCategory, Action::TYPE_ECOMMERCE_ITEM_CATEGORY);
|
||||
}
|
||||
}
|
||||
// Ensure that each row has the same number of columns, fill in the blanks
|
||||
for ($i = count($actionsToLookup); $i < $columnsInEachRow; $i++) {
|
||||
$actionsToLookup[] = array(false, Action::TYPE_ECOMMERCE_ITEM_CATEGORY);
|
||||
}
|
||||
$actionsToLookupAllItems = array_merge($actionsToLookupAllItems, $actionsToLookup);
|
||||
}
|
||||
|
||||
$actionsLookedUp = TableLogAction::loadIdsAction($actionsToLookupAllItems);
|
||||
|
||||
// Replace SKU, name & category by their ID action
|
||||
foreach ($cleanedItems as $index => &$item) {
|
||||
// SKU
|
||||
$item[0] = $actionsLookedUp[$index * $columnsInEachRow + 0];
|
||||
// Name
|
||||
$item[1] = $actionsLookedUp[$index * $columnsInEachRow + 1];
|
||||
// Categories
|
||||
$item[2] = $actionsLookedUp[$index * $columnsInEachRow + 2];
|
||||
$item[3] = $actionsLookedUp[$index * $columnsInEachRow + 3];
|
||||
$item[4] = $actionsLookedUp[$index * $columnsInEachRow + 4];
|
||||
$item[5] = $actionsLookedUp[$index * $columnsInEachRow + 5];
|
||||
$item[6] = $actionsLookedUp[$index * $columnsInEachRow + 6];
|
||||
}
|
||||
return $cleanedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cart items in the DB
|
||||
* that have been modified since the last cart update
|
||||
*
|
||||
* @param array $goal
|
||||
* @param array $itemsToUpdate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function updateEcommerceItems($goal, $itemsToUpdate)
|
||||
{
|
||||
if (empty($itemsToUpdate)) {
|
||||
return;
|
||||
}
|
||||
Common::printDebug("Goal data used to update ecommerce items:");
|
||||
Common::printDebug($goal);
|
||||
|
||||
foreach ($itemsToUpdate as $item) {
|
||||
$newRow = $this->getItemRowEnriched($goal, $item);
|
||||
Common::printDebug($newRow);
|
||||
$updateParts = $sqlBind = array();
|
||||
foreach ($newRow AS $name => $value) {
|
||||
$updateParts[] = $name . " = ?";
|
||||
$sqlBind[] = $value;
|
||||
}
|
||||
$sql = 'UPDATE ' . Common::prefixTable('log_conversion_item') . "
|
||||
SET " . implode($updateParts, ', ') . "
|
||||
WHERE idvisit = ?
|
||||
AND idorder = ?
|
||||
AND idaction_sku = ?";
|
||||
$sqlBind[] = $newRow['idvisit'];
|
||||
$sqlBind[] = $item['idorder_original_value'];
|
||||
$sqlBind[] = $newRow['idaction_sku'];
|
||||
Tracker::getDatabase()->query($sql, $sqlBind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts in the cart in the DB the new items
|
||||
* that were not previously in the cart
|
||||
*
|
||||
* @param array $goal
|
||||
* @param array $itemsToInsert
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function insertEcommerceItems($goal, $itemsToInsert)
|
||||
{
|
||||
if (empty($itemsToInsert)) {
|
||||
return;
|
||||
}
|
||||
Common::printDebug("Ecommerce items that are added to the cart/order");
|
||||
Common::printDebug($itemsToInsert);
|
||||
|
||||
$sql = "INSERT INTO " . Common::prefixTable('log_conversion_item') . "
|
||||
(idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted,
|
||||
idorder, idsite, idvisitor, server_time, idvisit)
|
||||
VALUES ";
|
||||
$i = 0;
|
||||
$bind = array();
|
||||
foreach ($itemsToInsert as $item) {
|
||||
if ($i > 0) {
|
||||
$sql .= ',';
|
||||
}
|
||||
$newRow = array_values($this->getItemRowEnriched($goal, $item));
|
||||
$sql .= " ( " . Common::getSqlStringFieldsArray($newRow) . " ) ";
|
||||
$i++;
|
||||
$bind = array_merge($bind, $newRow);
|
||||
}
|
||||
Tracker::getDatabase()->query($sql, $bind);
|
||||
Common::printDebug($sql);
|
||||
Common::printDebug($bind);
|
||||
}
|
||||
|
||||
protected function getItemRowEnriched($goal, $item)
|
||||
{
|
||||
$newRow = array(
|
||||
'idaction_sku' => (int)$item[self::INTERNAL_ITEM_SKU],
|
||||
'idaction_name' => (int)$item[self::INTERNAL_ITEM_NAME],
|
||||
'idaction_category' => (int)$item[self::INTERNAL_ITEM_CATEGORY],
|
||||
'idaction_category2' => (int)$item[self::INTERNAL_ITEM_CATEGORY2],
|
||||
'idaction_category3' => (int)$item[self::INTERNAL_ITEM_CATEGORY3],
|
||||
'idaction_category4' => (int)$item[self::INTERNAL_ITEM_CATEGORY4],
|
||||
'idaction_category5' => (int)$item[self::INTERNAL_ITEM_CATEGORY5],
|
||||
'price' => $item[self::INTERNAL_ITEM_PRICE],
|
||||
'quantity' => $item[self::INTERNAL_ITEM_QUANTITY],
|
||||
'deleted' => isset($item['deleted']) ? $item['deleted'] : 0, //deleted
|
||||
'idorder' => isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, //idorder = 0 in log_conversion_item for carts
|
||||
'idsite' => $goal['idsite'],
|
||||
'idvisitor' => $goal['idvisitor'],
|
||||
'server_time' => $goal['server_time'],
|
||||
'idvisit' => $goal['idvisit']
|
||||
);
|
||||
return $newRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a standard non-Ecommerce goal in the DB (URL/Title matching),
|
||||
* linking the conversion to the action that triggered it
|
||||
* @param $goal
|
||||
* @param Action $action
|
||||
* @param $visitorInformation
|
||||
*/
|
||||
protected function recordStandardGoals($goal, $action, $visitorInformation)
|
||||
{
|
||||
foreach ($this->convertedGoals as $convertedGoal) {
|
||||
Common::printDebug("- Goal " . $convertedGoal['idgoal'] . " matched. Recording...");
|
||||
$conversion = $goal;
|
||||
$conversion['idgoal'] = $convertedGoal['idgoal'];
|
||||
$conversion['url'] = $convertedGoal['url'];
|
||||
$conversion['revenue'] = $this->getRevenue($convertedGoal['revenue']);
|
||||
|
||||
if (!is_null($action)) {
|
||||
$conversion['idaction_url'] = $action->getIdActionUrl();
|
||||
$conversion['idlink_va'] = $action->getIdLinkVisitAction();
|
||||
}
|
||||
|
||||
// If multiple Goal conversions per visit, set a cache buster
|
||||
$conversion['buster'] = $convertedGoal['allow_multiple'] == 0
|
||||
? '0'
|
||||
: $visitorInformation['visit_last_action_time'];
|
||||
|
||||
$this->insertNewConversion($conversion, $visitorInformation);
|
||||
|
||||
/**
|
||||
* Triggered after successfully recording a non-ecommerce conversion.
|
||||
*
|
||||
* _Note: Subscribers should be wary of doing any expensive computation here as it may slow
|
||||
* the tracker down._
|
||||
*
|
||||
* @param array $conversion The conversion entity that was just persisted. See what information
|
||||
* it contains [here](/guides/persistence-and-the-mysql-backend#conversions).
|
||||
*/
|
||||
Piwik::postEvent('Tracker.recordStandardGoals', array($conversion));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used by other record* methods which will INSERT or UPDATE the conversion in the DB
|
||||
*
|
||||
* @param array $conversion
|
||||
* @param array $visitInformation
|
||||
* @return bool
|
||||
*/
|
||||
protected function insertNewConversion($conversion, $visitInformation)
|
||||
{
|
||||
/**
|
||||
* Triggered before persisting a new [conversion entity](/guides/persistence-and-the-mysql-backend#conversions).
|
||||
*
|
||||
* This event can be used to modify conversion information or to add new information to be persisted.
|
||||
*
|
||||
* @param array $conversion The conversion entity. Read [this](/guides/persistence-and-the-mysql-backend#conversions)
|
||||
* to see what it contains.
|
||||
* @param array $visitInformation The visit entity that we are tracking a conversion for. See what
|
||||
* information it contains [here](/guides/persistence-and-the-mysql-backend#visits).
|
||||
* @param \Piwik\Tracker\Request $request An object describing the tracking request being processed.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.newConversionInformation', array(&$conversion, $visitInformation, $this->request));
|
||||
|
||||
$newGoalDebug = $conversion;
|
||||
$newGoalDebug['idvisitor'] = bin2hex($newGoalDebug['idvisitor']);
|
||||
Common::printDebug($newGoalDebug);
|
||||
|
||||
$fields = implode(", ", array_keys($conversion));
|
||||
$bindFields = Common::getSqlStringFieldsArray($conversion);
|
||||
$sql = 'INSERT IGNORE INTO ' . Common::prefixTable('log_conversion') . "
|
||||
($fields) VALUES ($bindFields) ";
|
||||
$bind = array_values($conversion);
|
||||
$result = Tracker::getDatabase()->query($sql, $bind);
|
||||
|
||||
// If a record was inserted, we return true
|
||||
return Tracker::getDatabase()->rowCount($result) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts the item array so that array comparisons work nicely
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function getItemRowCast($row)
|
||||
{
|
||||
return array(
|
||||
(string)(int)$row[self::INTERNAL_ITEM_SKU],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_NAME],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_CATEGORY],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_CATEGORY2],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_CATEGORY3],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_CATEGORY4],
|
||||
(string)(int)$row[self::INTERNAL_ITEM_CATEGORY5],
|
||||
(string)$row[self::INTERNAL_ITEM_PRICE],
|
||||
(string)$row[self::INTERNAL_ITEM_QUANTITY],
|
||||
);
|
||||
}
|
||||
|
||||
protected function updateExistingConversion($newGoal, $updateWhere)
|
||||
{
|
||||
$updateParts = $sqlBind = $updateWhereParts = array();
|
||||
foreach ($newGoal AS $name => $value) {
|
||||
$updateParts[] = $name . " = ?";
|
||||
$sqlBind[] = $value;
|
||||
}
|
||||
foreach ($updateWhere as $name => $value) {
|
||||
$updateWhereParts[] = $name . " = ?";
|
||||
$sqlBind[] = $value;
|
||||
}
|
||||
$sql = 'UPDATE ' . Common::prefixTable('log_conversion') . "
|
||||
SET " . implode($updateParts, ', ') . "
|
||||
WHERE " . implode($updateWhereParts, ' AND ');
|
||||
|
||||
try {
|
||||
Tracker::getDatabase()->query($sql, $sqlBind);
|
||||
} catch(Exception $e){
|
||||
Common::printDebug("There was an error while updating the Conversion: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param $name
|
||||
* @param $keyword
|
||||
*/
|
||||
protected function setCampaignValuesToLowercase($type, &$name, &$keyword)
|
||||
{
|
||||
if ($type === Common::REFERRER_TYPE_CAMPAIGN) {
|
||||
if (!empty($name)) {
|
||||
$name = Common::mb_strtolower($name);
|
||||
}
|
||||
if (!empty($keyword)) {
|
||||
$keyword = Common::mb_strtolower($keyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $goal
|
||||
* @param $pattern_type
|
||||
* @param $url
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isUrlMatchingGoal($goal, $pattern_type, $url)
|
||||
{
|
||||
switch ($pattern_type) {
|
||||
case 'regex':
|
||||
$pattern = $goal['pattern'];
|
||||
if (strpos($pattern, '/') !== false
|
||||
&& strpos($pattern, '\\/') === false
|
||||
) {
|
||||
$pattern = str_replace('/', '\\/', $pattern);
|
||||
}
|
||||
$pattern = '/' . $pattern . '/';
|
||||
if (!$goal['case_sensitive']) {
|
||||
$pattern .= 'i';
|
||||
}
|
||||
$match = (@preg_match($pattern, $url) == 1);
|
||||
break;
|
||||
case 'contains':
|
||||
if ($goal['case_sensitive']) {
|
||||
$matched = strpos($url, $goal['pattern']);
|
||||
} else {
|
||||
$matched = stripos($url, $goal['pattern']);
|
||||
}
|
||||
$match = ($matched !== false);
|
||||
break;
|
||||
case 'exact':
|
||||
if ($goal['case_sensitive']) {
|
||||
$matched = strcmp($goal['pattern'], $url);
|
||||
} else {
|
||||
$matched = strcasecmp($goal['pattern'], $url);
|
||||
}
|
||||
$match = ($matched == 0);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Piwik::translate('General_ExceptionInvalidGoalPattern', array($pattern_type)));
|
||||
break;
|
||||
}
|
||||
return $match;
|
||||
}
|
||||
}
|
||||
73
www/analytics/core/Tracker/IgnoreCookie.php
Normal file
73
www/analytics/core/Tracker/IgnoreCookie.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Config;
|
||||
use Piwik\Cookie;
|
||||
|
||||
/**
|
||||
* Tracking cookies.
|
||||
*
|
||||
*/
|
||||
class IgnoreCookie
|
||||
{
|
||||
/**
|
||||
* Get tracking cookie
|
||||
*
|
||||
* @return Cookie
|
||||
*/
|
||||
static public function getTrackingCookie()
|
||||
{
|
||||
$cookie_name = @Config::getInstance()->Tracker['cookie_name'];
|
||||
$cookie_path = @Config::getInstance()->Tracker['cookie_path'];
|
||||
|
||||
return new Cookie($cookie_name, null, $cookie_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ignore (visit) cookie
|
||||
*
|
||||
* @return Cookie
|
||||
*/
|
||||
static public function getIgnoreCookie()
|
||||
{
|
||||
$cookie_name = @Config::getInstance()->Tracker['ignore_visits_cookie_name'];
|
||||
$cookie_path = @Config::getInstance()->Tracker['cookie_path'];
|
||||
|
||||
return new Cookie($cookie_name, null, $cookie_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ignore (visit) cookie or deletes it if already present
|
||||
*/
|
||||
static public function setIgnoreCookie()
|
||||
{
|
||||
$ignoreCookie = self::getIgnoreCookie();
|
||||
if ($ignoreCookie->isCookieFound()) {
|
||||
$ignoreCookie->delete();
|
||||
} else {
|
||||
$ignoreCookie->set('ignore', '*');
|
||||
$ignoreCookie->save();
|
||||
|
||||
$trackingCookie = self::getTrackingCookie();
|
||||
$trackingCookie->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if ignore (visit) cookie is present
|
||||
*
|
||||
* @return bool True if ignore cookie found; false otherwise
|
||||
*/
|
||||
static public function isIgnoreCookieFound()
|
||||
{
|
||||
$cookie = self::getIgnoreCookie();
|
||||
return $cookie->isCookieFound() && $cookie->get('ignore') === '*';
|
||||
}
|
||||
}
|
||||
328
www/analytics/core/Tracker/PageUrl.php
Normal file
328
www/analytics/core/Tracker/PageUrl.php
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\UrlHelper;
|
||||
|
||||
class PageUrl
|
||||
{
|
||||
|
||||
/**
|
||||
* Map URL prefixes to integers.
|
||||
* @see self::normalizeUrl(), self::reconstructNormalizedUrl()
|
||||
*/
|
||||
public static $urlPrefixMap = array(
|
||||
'http://www.' => 1,
|
||||
'http://' => 0,
|
||||
'https://www.' => 3,
|
||||
'https://' => 2
|
||||
);
|
||||
|
||||
protected static $queryParametersToExclude = array('gclid', 'fb_xd_fragment', 'fb_comment_id',
|
||||
'phpsessid', 'jsessionid', 'sessionid', 'aspsessionid',
|
||||
'doing_wp_cron');
|
||||
|
||||
/**
|
||||
* Given the Input URL, will exclude all query parameters set for this site
|
||||
*
|
||||
* @static
|
||||
* @param $originalUrl
|
||||
* @param $idSite
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function excludeQueryParametersFromUrl($originalUrl, $idSite)
|
||||
{
|
||||
$originalUrl = self::cleanupUrl($originalUrl);
|
||||
|
||||
$parsedUrl = @parse_url($originalUrl);
|
||||
$parsedUrl = self::cleanupHostAndHashTag($parsedUrl, $idSite);
|
||||
$parametersToExclude = self::getQueryParametersToExclude($idSite);
|
||||
|
||||
if (empty($parsedUrl['query'])) {
|
||||
if (empty($parsedUrl['fragment'])) {
|
||||
return UrlHelper::getParseUrlReverse($parsedUrl);
|
||||
}
|
||||
// Exclude from the hash tag as well
|
||||
$queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['fragment']);
|
||||
$parsedUrl['fragment'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
|
||||
$url = UrlHelper::getParseUrlReverse($parsedUrl);
|
||||
return $url;
|
||||
}
|
||||
$queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['query']);
|
||||
$parsedUrl['query'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
|
||||
$url = UrlHelper::getParseUrlReverse($parsedUrl);
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the array of parameters names that must be excluded from the Query String in all tracked URLs
|
||||
* @static
|
||||
* @param $idSite
|
||||
* @return array
|
||||
*/
|
||||
public static function getQueryParametersToExclude($idSite)
|
||||
{
|
||||
$campaignTrackingParameters = Common::getCampaignParameters();
|
||||
|
||||
$campaignTrackingParameters = array_merge(
|
||||
$campaignTrackingParameters[0], // campaign name parameters
|
||||
$campaignTrackingParameters[1] // campaign keyword parameters
|
||||
);
|
||||
|
||||
$website = Cache::getCacheWebsiteAttributes($idSite);
|
||||
$excludedParameters = isset($website['excluded_parameters'])
|
||||
? $website['excluded_parameters']
|
||||
: array();
|
||||
|
||||
if (!empty($excludedParameters)) {
|
||||
Common::printDebug('Excluding parameters "' . implode(',', $excludedParameters) . '" from URL');
|
||||
}
|
||||
|
||||
$parametersToExclude = array_merge($excludedParameters,
|
||||
self::$queryParametersToExclude,
|
||||
$campaignTrackingParameters);
|
||||
|
||||
$parametersToExclude = array_map('strtolower', $parametersToExclude);
|
||||
return $parametersToExclude;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if URL fragments should be removed for a specific site,
|
||||
* false if otherwise.
|
||||
*
|
||||
* This function uses the Tracker cache and not the MySQL database.
|
||||
*
|
||||
* @param $idSite int The ID of the site to check for.
|
||||
* @return bool
|
||||
*/
|
||||
public static function shouldRemoveURLFragmentFor($idSite)
|
||||
{
|
||||
$websiteAttributes = Cache::getCacheWebsiteAttributes($idSite);
|
||||
return !$websiteAttributes['keep_url_fragment'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and/or removes the URL fragment of a URL.
|
||||
*
|
||||
* @param $urlFragment string The URL fragment to process.
|
||||
* @param $idSite int|bool If not false, this function will check if URL fragments
|
||||
* should be removed for the site w/ this ID and if so,
|
||||
* the returned processed fragment will be empty.
|
||||
*
|
||||
* @return string The processed URL fragment.
|
||||
*/
|
||||
public static function processUrlFragment($urlFragment, $idSite = false)
|
||||
{
|
||||
// if we should discard the url fragment for this site, return an empty string as
|
||||
// the processed url fragment
|
||||
if ($idSite !== false
|
||||
&& PageUrl::shouldRemoveURLFragmentFor($idSite)
|
||||
) {
|
||||
return '';
|
||||
} else {
|
||||
// Remove trailing Hash tag in ?query#hash#
|
||||
if (substr($urlFragment, -1) == '#') {
|
||||
$urlFragment = substr($urlFragment, 0, strlen($urlFragment) - 1);
|
||||
}
|
||||
return $urlFragment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will cleanup the hostname (some browser do not strolower the hostname),
|
||||
* and deal ith the hash tag on incoming URLs based on website setting.
|
||||
*
|
||||
* @param $parsedUrl
|
||||
* @param $idSite int|bool The site ID of the current visit. This parameter is
|
||||
* only used by the tracker to see if we should remove
|
||||
* the URL fragment for this site.
|
||||
* @return array
|
||||
*/
|
||||
protected static function cleanupHostAndHashTag($parsedUrl, $idSite = false)
|
||||
{
|
||||
if (empty($parsedUrl)) {
|
||||
return $parsedUrl;
|
||||
}
|
||||
if (!empty($parsedUrl['host'])) {
|
||||
$parsedUrl['host'] = mb_strtolower($parsedUrl['host'], 'UTF-8');
|
||||
}
|
||||
|
||||
if (!empty($parsedUrl['fragment'])) {
|
||||
$parsedUrl['fragment'] = PageUrl::processUrlFragment($parsedUrl['fragment'], $idSite);
|
||||
}
|
||||
|
||||
return $parsedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Matrix URL format
|
||||
* from http://example.org/thing;paramA=1;paramB=6542
|
||||
* to http://example.org/thing?paramA=1¶mB=6542
|
||||
*
|
||||
* @param string $originalUrl
|
||||
* @return string
|
||||
*/
|
||||
public static function convertMatrixUrl($originalUrl)
|
||||
{
|
||||
$posFirstSemiColon = strpos($originalUrl, ";");
|
||||
if ($posFirstSemiColon === false) {
|
||||
return $originalUrl;
|
||||
}
|
||||
$posQuestionMark = strpos($originalUrl, "?");
|
||||
$replace = ($posQuestionMark === false);
|
||||
if ($posQuestionMark > $posFirstSemiColon) {
|
||||
$originalUrl = substr_replace($originalUrl, ";", $posQuestionMark, 1);
|
||||
$replace = true;
|
||||
}
|
||||
if ($replace) {
|
||||
$originalUrl = substr_replace($originalUrl, "?", strpos($originalUrl, ";"), 1);
|
||||
$originalUrl = str_replace(";", "&", $originalUrl);
|
||||
}
|
||||
return $originalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up string contents (filter, truncate, ...)
|
||||
*
|
||||
* @param string $string Dirty string
|
||||
* @return string
|
||||
*/
|
||||
public static function cleanupString($string)
|
||||
{
|
||||
$string = trim($string);
|
||||
$string = str_replace(array("\n", "\r", "\0"), '', $string);
|
||||
|
||||
$limit = Config::getInstance()->Tracker['page_maximum_length'];
|
||||
$clean = substr($string, 0, $limit);
|
||||
return $clean;
|
||||
}
|
||||
|
||||
protected static function reencodeParameterValue($value, $encoding)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$decoded = urldecode($value);
|
||||
if (@mb_check_encoding($decoded, $encoding)) {
|
||||
$value = urlencode(mb_convert_encoding($decoded, 'UTF-8', $encoding));
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected static function reencodeParametersArray($queryParameters, $encoding)
|
||||
{
|
||||
foreach ($queryParameters as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = self::reencodeParametersArray($value, $encoding);
|
||||
} else {
|
||||
$value = PageUrl::reencodeParameterValue($value, $encoding);
|
||||
}
|
||||
}
|
||||
return $queryParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if query parameters are of a non-UTF-8 encoding and converts the values
|
||||
* from the specified encoding to UTF-8.
|
||||
* This method is used to workaround browser/webapp bugs (see #3450). When
|
||||
* browsers fail to encode query parameters in UTF-8, the tracker will send the
|
||||
* charset of the page viewed and we can sometimes work around invalid data
|
||||
* being stored.
|
||||
*
|
||||
* @param array $queryParameters Name/value mapping of query parameters.
|
||||
* @param bool|string $encoding of the HTML page the URL is for. Used to workaround
|
||||
* browser bugs & mis-coded webapps. See #3450.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function reencodeParameters(&$queryParameters, $encoding = false)
|
||||
{
|
||||
// if query params are encoded w/ non-utf8 characters (due to browser bug or whatever),
|
||||
// encode to UTF-8.
|
||||
if ($encoding !== false
|
||||
&& strtolower($encoding) != 'utf-8'
|
||||
&& function_exists('mb_check_encoding')
|
||||
) {
|
||||
$queryParameters = PageUrl::reencodeParametersArray($queryParameters, $encoding);
|
||||
}
|
||||
return $queryParameters;
|
||||
}
|
||||
|
||||
public static function cleanupUrl($url)
|
||||
{
|
||||
$url = Common::unsanitizeInputValue($url);
|
||||
$url = PageUrl::cleanupString($url);
|
||||
$url = PageUrl::convertMatrixUrl($url);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the full URL from the prefix ID and the rest.
|
||||
*
|
||||
* @param string $url
|
||||
* @param integer $prefixId
|
||||
* @return string
|
||||
*/
|
||||
public static function reconstructNormalizedUrl($url, $prefixId)
|
||||
{
|
||||
$map = array_flip(self::$urlPrefixMap);
|
||||
if ($prefixId !== null && isset($map[$prefixId])) {
|
||||
$fullUrl = $map[$prefixId] . $url;
|
||||
} else {
|
||||
$fullUrl = $url;
|
||||
}
|
||||
|
||||
// Clean up host & hash tags, for URLs
|
||||
$parsedUrl = @parse_url($fullUrl);
|
||||
$parsedUrl = PageUrl::cleanupHostAndHashTag($parsedUrl);
|
||||
$url = UrlHelper::getParseUrlReverse($parsedUrl);
|
||||
if (!empty($url)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return $fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the prefix from a URL.
|
||||
* Return the prefix ID and the rest.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array
|
||||
*/
|
||||
public static function normalizeUrl($url)
|
||||
{
|
||||
foreach (self::$urlPrefixMap as $prefix => $id) {
|
||||
if (strtolower(substr($url, 0, strlen($prefix))) == $prefix) {
|
||||
return array(
|
||||
'url' => substr($url, strlen($prefix)),
|
||||
'prefixId' => $id
|
||||
);
|
||||
}
|
||||
}
|
||||
return array('url' => $url, 'prefixId' => null);
|
||||
}
|
||||
|
||||
public static function getUrlIfLookValid($url)
|
||||
{
|
||||
$url = PageUrl::cleanupString($url);
|
||||
|
||||
if (!UrlHelper::isLookLikeUrl($url)) {
|
||||
Common::printDebug("WARNING: URL looks invalid and is discarded");
|
||||
$url = false;
|
||||
return $url;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
301
www/analytics/core/Tracker/Referrer.php
Normal file
301
www/analytics/core/Tracker/Referrer.php
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\UrlHelper;
|
||||
|
||||
/**
|
||||
*/
|
||||
class Referrer
|
||||
{
|
||||
// @see detect*() referrer methods
|
||||
protected $typeReferrerAnalyzed;
|
||||
protected $nameReferrerAnalyzed;
|
||||
protected $keywordReferrerAnalyzed;
|
||||
protected $referrerHost;
|
||||
protected $referrerUrl;
|
||||
protected $referrerUrlParse;
|
||||
protected $currentUrlParse;
|
||||
protected $idsite;
|
||||
|
||||
// Used to prefix when a adsense referrer is detected
|
||||
const LABEL_PREFIX_ADWORDS_KEYWORD = '(adwords) ';
|
||||
const LABEL_ADWORDS_NAME = 'AdWords';
|
||||
|
||||
/**
|
||||
* Returns an array containing the following information:
|
||||
* - referer_type
|
||||
* - direct -- absence of referrer URL OR referrer URL has the same host
|
||||
* - site -- based on the referrer URL
|
||||
* - search_engine -- based on the referrer URL
|
||||
* - campaign -- based on campaign URL parameter
|
||||
*
|
||||
* - referer_name
|
||||
* - ()
|
||||
* - piwik.net -- site host name
|
||||
* - google.fr -- search engine host name
|
||||
* - adwords-search -- campaign name
|
||||
*
|
||||
* - referer_keyword
|
||||
* - ()
|
||||
* - ()
|
||||
* - my keyword
|
||||
* - my paid keyword
|
||||
* - ()
|
||||
* - ()
|
||||
*
|
||||
* - referer_url : the same for all the referrer types
|
||||
*
|
||||
* @param string $referrerUrl must be URL Encoded
|
||||
* @param string $currentUrl
|
||||
* @param int $idSite
|
||||
* @return array
|
||||
*/
|
||||
public function getReferrerInformation($referrerUrl, $currentUrl, $idSite)
|
||||
{
|
||||
$this->idsite = $idSite;
|
||||
|
||||
// default values for the referer_* fields
|
||||
$referrerUrl = Common::unsanitizeInputValue($referrerUrl);
|
||||
if (!empty($referrerUrl)
|
||||
&& !UrlHelper::isLookLikeUrl($referrerUrl)
|
||||
) {
|
||||
$referrerUrl = '';
|
||||
}
|
||||
|
||||
$currentUrl = PageUrl::cleanupUrl($currentUrl);
|
||||
|
||||
$this->referrerUrl = $referrerUrl;
|
||||
$this->referrerUrlParse = @parse_url($this->referrerUrl);
|
||||
$this->currentUrlParse = @parse_url($currentUrl);
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY;
|
||||
$this->nameReferrerAnalyzed = '';
|
||||
$this->keywordReferrerAnalyzed = '';
|
||||
$this->referrerHost = '';
|
||||
|
||||
if (isset($this->referrerUrlParse['host'])) {
|
||||
$this->referrerHost = $this->referrerUrlParse['host'];
|
||||
}
|
||||
|
||||
$referrerDetected = $this->detectReferrerCampaign();
|
||||
|
||||
if (!$referrerDetected) {
|
||||
if ($this->detectReferrerDirectEntry()
|
||||
|| $this->detectReferrerSearchEngine()
|
||||
) {
|
||||
$referrerDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->referrerHost)
|
||||
&& !$referrerDetected
|
||||
) {
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_WEBSITE;
|
||||
$this->nameReferrerAnalyzed = Common::mb_strtolower($this->referrerHost);
|
||||
}
|
||||
|
||||
$referrerInformation = array(
|
||||
'referer_type' => $this->typeReferrerAnalyzed,
|
||||
'referer_name' => $this->nameReferrerAnalyzed,
|
||||
'referer_keyword' => $this->keywordReferrerAnalyzed,
|
||||
'referer_url' => $this->referrerUrl,
|
||||
);
|
||||
|
||||
return $referrerInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search engine detection
|
||||
* @return bool
|
||||
*/
|
||||
protected function detectReferrerSearchEngine()
|
||||
{
|
||||
$searchEngineInformation = UrlHelper::extractSearchEngineInformationFromUrl($this->referrerUrl);
|
||||
|
||||
/**
|
||||
* Triggered when detecting the search engine of a referrer URL.
|
||||
*
|
||||
* Plugins can use this event to provide custom search engine detection
|
||||
* logic.
|
||||
*
|
||||
* @param array &$searchEngineInformation An array with the following information:
|
||||
*
|
||||
* - **name**: The search engine name.
|
||||
* - **keywords**: The search keywords used.
|
||||
*
|
||||
* This parameter is initialized to the results
|
||||
* of Piwik's default search engine detection
|
||||
* logic.
|
||||
* @param string referrerUrl The referrer URL from the tracking request.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.detectReferrerSearchEngine', array(&$searchEngineInformation, $this->referrerUrl));
|
||||
if ($searchEngineInformation === false) {
|
||||
return false;
|
||||
}
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_SEARCH_ENGINE;
|
||||
$this->nameReferrerAnalyzed = $searchEngineInformation['name'];
|
||||
$this->keywordReferrerAnalyzed = $searchEngineInformation['keywords'];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
protected function detectCampaignFromString($string)
|
||||
{
|
||||
foreach ($this->campaignNames as $campaignNameParameter) {
|
||||
$campaignName = trim(urldecode(UrlHelper::getParameterFromQueryString($string, $campaignNameParameter)));
|
||||
if (!empty($campaignName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($campaignName)) {
|
||||
return false;
|
||||
}
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_CAMPAIGN;
|
||||
$this->nameReferrerAnalyzed = $campaignName;
|
||||
|
||||
foreach ($this->campaignKeywords as $campaignKeywordParameter) {
|
||||
$campaignKeyword = UrlHelper::getParameterFromQueryString($string, $campaignKeywordParameter);
|
||||
if (!empty($campaignKeyword)) {
|
||||
$this->keywordReferrerAnalyzed = trim(urldecode($campaignKeyword));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return !empty($this->keywordReferrerAnalyzed);
|
||||
}
|
||||
|
||||
protected function detectReferrerCampaignFromLandingUrl()
|
||||
{
|
||||
if (!isset($this->currentUrlParse['query'])
|
||||
&& !isset($this->currentUrlParse['fragment'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
$campaignParameters = Common::getCampaignParameters();
|
||||
$this->campaignNames = $campaignParameters[0];
|
||||
$this->campaignKeywords = $campaignParameters[1];
|
||||
|
||||
$found = false;
|
||||
|
||||
// 1) Detect campaign from query string
|
||||
if (isset($this->currentUrlParse['query'])) {
|
||||
$found = $this->detectCampaignFromString($this->currentUrlParse['query']);
|
||||
}
|
||||
|
||||
// 2) Detect from fragment #hash
|
||||
if (!$found
|
||||
&& isset($this->currentUrlParse['fragment'])
|
||||
) {
|
||||
$this->detectCampaignFromString($this->currentUrlParse['fragment']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have previously tried to detect the campaign variables in the URL
|
||||
* so at this stage, if the referrer host is the current host,
|
||||
* or if the referrer host is any of the registered URL for this website,
|
||||
* it is considered a direct entry
|
||||
* @return bool
|
||||
*/
|
||||
protected function detectReferrerDirectEntry()
|
||||
{
|
||||
if (!empty($this->referrerHost)) {
|
||||
// is the referrer host the current host?
|
||||
if (isset($this->currentUrlParse['host'])) {
|
||||
$currentHost = mb_strtolower($this->currentUrlParse['host'], 'UTF-8');
|
||||
if ($currentHost == mb_strtolower($this->referrerHost, 'UTF-8')) {
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Visit::isHostKnownAliasHost($this->referrerHost, $this->idsite)) {
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function detectCampaignKeywordFromReferrerUrl()
|
||||
{
|
||||
if(!empty($this->nameReferrerAnalyzed)
|
||||
&& !empty($this->keywordReferrerAnalyzed)) {
|
||||
// keyword is already set, we skip
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the Campaign keyword to the keyword found in the Referrer URL if any
|
||||
if(!empty($this->nameReferrerAnalyzed)) {
|
||||
$referrerUrlInfo = UrlHelper::extractSearchEngineInformationFromUrl($this->referrerUrl);
|
||||
if (!empty($referrerUrlInfo['keywords'])) {
|
||||
$this->keywordReferrerAnalyzed = $referrerUrlInfo['keywords'];
|
||||
}
|
||||
}
|
||||
|
||||
// Set the keyword, to the hostname found, in a Adsense Referrer URL '&url=' parameter
|
||||
if (empty($this->keywordReferrerAnalyzed)
|
||||
&& !empty($this->referrerUrlParse['query'])
|
||||
&& !empty($this->referrerHost)
|
||||
&& (strpos($this->referrerHost, 'googleads') !== false || strpos($this->referrerHost, 'doubleclick') !== false)
|
||||
) {
|
||||
// This parameter sometimes is found & contains the page with the adsense ad bringing visitor to our site
|
||||
$value = $this->getParameterValueFromReferrerUrl('url');
|
||||
if (!empty($value)) {
|
||||
$parsedAdsenseReferrerUrl = parse_url($value);
|
||||
if (!empty($parsedAdsenseReferrerUrl['host'])) {
|
||||
|
||||
if(empty($this->nameReferrerAnalyzed)) {
|
||||
$type = $this->getParameterValueFromReferrerUrl('ad_type');
|
||||
$type = $type ? " ($type)" : '';
|
||||
$this->nameReferrerAnalyzed = self::LABEL_ADWORDS_NAME . $type;
|
||||
$this->typeReferrerAnalyzed = Common::REFERRER_TYPE_CAMPAIGN;
|
||||
}
|
||||
$this->keywordReferrerAnalyzed = self::LABEL_PREFIX_ADWORDS_KEYWORD . $parsedAdsenseReferrerUrl['host'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getParameterValueFromReferrerUrl($adsenseReferrerParameter)
|
||||
{
|
||||
$value = trim(urldecode(UrlHelper::getParameterFromQueryString($this->referrerUrlParse['query'], $adsenseReferrerParameter)));
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function detectReferrerCampaign()
|
||||
{
|
||||
$this->detectReferrerCampaignFromLandingUrl();
|
||||
$this->detectCampaignKeywordFromReferrerUrl();
|
||||
|
||||
if ($this->typeReferrerAnalyzed != Common::REFERRER_TYPE_CAMPAIGN) {
|
||||
return false;
|
||||
}
|
||||
// if we detected a campaign but there is still no keyword set, we set the keyword to the Referrer host
|
||||
if(empty($this->keywordReferrerAnalyzed)) {
|
||||
$this->keywordReferrerAnalyzed = $this->referrerHost;
|
||||
}
|
||||
|
||||
$this->keywordReferrerAnalyzed = Common::mb_strtolower($this->keywordReferrerAnalyzed);
|
||||
$this->nameReferrerAnalyzed = Common::mb_strtolower($this->nameReferrerAnalyzed);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
573
www/analytics/core/Tracker/Request.php
Normal file
573
www/analytics/core/Tracker/Request.php
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Cookie;
|
||||
use Piwik\IP;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CustomVariables\CustomVariables;
|
||||
use Piwik\Registry;
|
||||
use Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* The Request object holding the http parameters for this tracking request. Use getParam() to fetch a named parameter.
|
||||
*
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
protected $forcedVisitorId = false;
|
||||
|
||||
protected $isAuthenticated = null;
|
||||
|
||||
protected $tokenAuth;
|
||||
|
||||
const UNKNOWN_RESOLUTION = 'unknown';
|
||||
|
||||
/**
|
||||
* @param $params
|
||||
* @param bool|string $tokenAuth
|
||||
*/
|
||||
public function __construct($params, $tokenAuth = false)
|
||||
{
|
||||
if (!is_array($params)) {
|
||||
$params = array();
|
||||
}
|
||||
$this->params = $params;
|
||||
$this->tokenAuth = $tokenAuth;
|
||||
$this->timestamp = time();
|
||||
$this->enforcedIp = false;
|
||||
|
||||
// When the 'url' and referrer url parameter are not given, we might be in the 'Simple Image Tracker' mode.
|
||||
// The URL can default to the Referrer, which will be in this case
|
||||
// the URL of the page containing the Simple Image beacon
|
||||
if (empty($this->params['urlref'])
|
||||
&& empty($this->params['url'])
|
||||
) {
|
||||
$url = @$_SERVER['HTTP_REFERER'];
|
||||
if (!empty($url)) {
|
||||
$this->params['url'] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isAuthenticated()
|
||||
{
|
||||
if (is_null($this->isAuthenticated)) {
|
||||
$this->authenticateTrackingApi($this->tokenAuth);
|
||||
}
|
||||
|
||||
return $this->isAuthenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows to set custom IP + server time + visitor ID, when using Tracking API.
|
||||
* These two attributes can be only set by the Super User (passing token_auth).
|
||||
*/
|
||||
protected function authenticateTrackingApi($tokenAuthFromBulkRequest)
|
||||
{
|
||||
$shouldAuthenticate = Config::getInstance()->Tracker['tracking_requests_require_authentication'];
|
||||
if ($shouldAuthenticate) {
|
||||
$tokenAuth = $tokenAuthFromBulkRequest ? $tokenAuthFromBulkRequest : Common::getRequestVar('token_auth', false, 'string', $this->params);
|
||||
try {
|
||||
$idSite = $this->getIdSite();
|
||||
$this->isAuthenticated = $this->authenticateSuperUserOrAdmin($tokenAuth, $idSite);
|
||||
} catch (Exception $e) {
|
||||
$this->isAuthenticated = false;
|
||||
}
|
||||
if (!$this->isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
Common::printDebug("token_auth is authenticated!");
|
||||
} else {
|
||||
$this->isAuthenticated = true;
|
||||
Common::printDebug("token_auth authentication not required");
|
||||
}
|
||||
}
|
||||
|
||||
public static function authenticateSuperUserOrAdmin($tokenAuth, $idSite)
|
||||
{
|
||||
if (empty($tokenAuth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Piwik::postEvent('Request.initAuthenticationObject');
|
||||
|
||||
/** @var \Piwik\Auth $auth */
|
||||
$auth = Registry::get('auth');
|
||||
$auth->setTokenAuth($tokenAuth);
|
||||
$auth->setLogin(null);
|
||||
$access = $auth->authenticate();
|
||||
|
||||
if (!empty($access) && $access->hasSuperUserAccess()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now checking the list of admin token_auth cached in the Tracker config file
|
||||
if (!empty($idSite) && $idSite > 0) {
|
||||
$website = Cache::getCacheWebsiteAttributes($idSite);
|
||||
if (array_key_exists('admin_token_auth', $website) && in_array($tokenAuth, $website['admin_token_auth'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Common::printDebug("WARNING! token_auth = $tokenAuth is not valid, Super User / Admin was NOT authenticated");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getDaysSinceFirstVisit()
|
||||
{
|
||||
$cookieFirstVisitTimestamp = $this->getParam('_idts');
|
||||
if (!$this->isTimestampValid($cookieFirstVisitTimestamp)) {
|
||||
$cookieFirstVisitTimestamp = $this->getCurrentTimestamp();
|
||||
}
|
||||
$daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp) / 86400, $precision = 0);
|
||||
if ($daysSinceFirstVisit < 0) {
|
||||
$daysSinceFirstVisit = 0;
|
||||
}
|
||||
return $daysSinceFirstVisit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|float|int
|
||||
*/
|
||||
public function getDaysSinceLastOrder()
|
||||
{
|
||||
$daysSinceLastOrder = false;
|
||||
$lastOrderTimestamp = $this->getParam('_ects');
|
||||
if ($this->isTimestampValid($lastOrderTimestamp)) {
|
||||
$daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp) / 86400, $precision = 0);
|
||||
if ($daysSinceLastOrder < 0) {
|
||||
$daysSinceLastOrder = 0;
|
||||
}
|
||||
}
|
||||
return $daysSinceLastOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getDaysSinceLastVisit()
|
||||
{
|
||||
$daysSinceLastVisit = 0;
|
||||
$lastVisitTimestamp = $this->getParam('_viewts');
|
||||
if ($this->isTimestampValid($lastVisitTimestamp)) {
|
||||
$daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp) / 86400, $precision = 0);
|
||||
if ($daysSinceLastVisit < 0) {
|
||||
$daysSinceLastVisit = 0;
|
||||
}
|
||||
}
|
||||
return $daysSinceLastVisit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|mixed
|
||||
*/
|
||||
public function getVisitCount()
|
||||
{
|
||||
$visitCount = $this->getParam('_idvc');
|
||||
if ($visitCount < 1) {
|
||||
$visitCount = 1;
|
||||
}
|
||||
return $visitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language the visitor is viewing.
|
||||
*
|
||||
* @return string browser language code, eg. "en-gb,en;q=0.5"
|
||||
*/
|
||||
public function getBrowserLanguage()
|
||||
{
|
||||
return Common::getRequestVar('lang', Common::getBrowserLanguage(), 'string', $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalTime()
|
||||
{
|
||||
$localTimes = array(
|
||||
'h' => (string)Common::getRequestVar('h', $this->getCurrentDate("H"), 'int', $this->params),
|
||||
'i' => (string)Common::getRequestVar('m', $this->getCurrentDate("i"), 'int', $this->params),
|
||||
's' => (string)Common::getRequestVar('s', $this->getCurrentDate("s"), 'int', $this->params)
|
||||
);
|
||||
foreach ($localTimes as $k => $time) {
|
||||
if (strlen($time) == 1) {
|
||||
$localTimes[$k] = '0' . $time;
|
||||
}
|
||||
}
|
||||
$localTime = $localTimes['h'] . ':' . $localTimes['i'] . ':' . $localTimes['s'];
|
||||
return $localTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current date in the "Y-m-d" PHP format
|
||||
*
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
protected function getCurrentDate($format = "Y-m-d")
|
||||
{
|
||||
return date($format, $this->getCurrentTimestamp());
|
||||
}
|
||||
|
||||
public function getGoalRevenue($defaultGoalRevenue)
|
||||
{
|
||||
return Common::getRequestVar('revenue', $defaultGoalRevenue, 'float', $this->params);
|
||||
}
|
||||
|
||||
public function getParam($name)
|
||||
{
|
||||
static $supportedParams = array(
|
||||
// Name => array( defaultValue, type )
|
||||
'_refts' => array(0, 'int'),
|
||||
'_ref' => array('', 'string'),
|
||||
'_rcn' => array('', 'string'),
|
||||
'_rck' => array('', 'string'),
|
||||
'_idts' => array(0, 'int'),
|
||||
'_viewts' => array(0, 'int'),
|
||||
'_ects' => array(0, 'int'),
|
||||
'_idvc' => array(1, 'int'),
|
||||
'url' => array('', 'string'),
|
||||
'urlref' => array('', 'string'),
|
||||
'res' => array(self::UNKNOWN_RESOLUTION, 'string'),
|
||||
'idgoal' => array(-1, 'int'),
|
||||
|
||||
// other
|
||||
'bots' => array(0, 'int'),
|
||||
'dp' => array(0, 'int'),
|
||||
'rec' => array(false, 'int'),
|
||||
'new_visit' => array(0, 'int'),
|
||||
|
||||
// Ecommerce
|
||||
'ec_id' => array(false, 'string'),
|
||||
'ec_st' => array(false, 'float'),
|
||||
'ec_tx' => array(false, 'float'),
|
||||
'ec_sh' => array(false, 'float'),
|
||||
'ec_dt' => array(false, 'float'),
|
||||
'ec_items' => array('', 'string'),
|
||||
|
||||
// Events
|
||||
'e_c' => array(false, 'string'),
|
||||
'e_a' => array(false, 'string'),
|
||||
'e_n' => array(false, 'string'),
|
||||
'e_v' => array(false, 'float'),
|
||||
|
||||
// some visitor attributes can be overwritten
|
||||
'cip' => array(false, 'string'),
|
||||
'cdt' => array(false, 'string'),
|
||||
'cid' => array(false, 'string'),
|
||||
|
||||
// Actions / pages
|
||||
'cs' => array(false, 'string'),
|
||||
'download' => array('', 'string'),
|
||||
'link' => array('', 'string'),
|
||||
'action_name' => array('', 'string'),
|
||||
'search' => array('', 'string'),
|
||||
'search_cat' => array(false, 'string'),
|
||||
'search_count' => array(-1, 'int'),
|
||||
'gt_ms' => array(-1, 'int'),
|
||||
);
|
||||
|
||||
if (!isset($supportedParams[$name])) {
|
||||
throw new Exception("Requested parameter $name is not a known Tracking API Parameter.");
|
||||
}
|
||||
$paramDefaultValue = $supportedParams[$name][0];
|
||||
$paramType = $supportedParams[$name][1];
|
||||
|
||||
$value = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getCurrentTimestamp()
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
protected function isTimestampValid($time)
|
||||
{
|
||||
return $time <= $this->getCurrentTimestamp()
|
||||
&& $time > $this->getCurrentTimestamp() - 10 * 365 * 86400;
|
||||
}
|
||||
|
||||
public function getIdSite()
|
||||
{
|
||||
$idSite = Common::getRequestVar('idsite', 0, 'int', $this->params);
|
||||
|
||||
/**
|
||||
* Triggered when obtaining the ID of the site we are tracking a visit for.
|
||||
*
|
||||
* This event can be used to change the site ID so data is tracked for a different
|
||||
* website.
|
||||
*
|
||||
* @param int &$idSite Initialized to the value of the **idsite** query parameter. If a
|
||||
* subscriber sets this variable, the value it uses must be greater
|
||||
* than 0.
|
||||
* @param array $params The entire array of request parameters in the current tracking
|
||||
* request.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.Request.getIdSite', array(&$idSite, $this->params));
|
||||
if ($idSite <= 0) {
|
||||
throw new Exception('Invalid idSite: \'' . $idSite . '\'');
|
||||
}
|
||||
return $idSite;
|
||||
}
|
||||
|
||||
public function getUserAgent()
|
||||
{
|
||||
$default = @$_SERVER['HTTP_USER_AGENT'];
|
||||
return Common::getRequestVar('ua', is_null($default) ? false : $default, 'string', $this->params);
|
||||
}
|
||||
|
||||
public function getCustomVariables($scope)
|
||||
{
|
||||
if ($scope == 'visit') {
|
||||
$parameter = '_cvar';
|
||||
} else {
|
||||
$parameter = 'cvar';
|
||||
}
|
||||
|
||||
$customVar = Common::unsanitizeInputValues(Common::getRequestVar($parameter, '', 'json', $this->params));
|
||||
if (!is_array($customVar)) {
|
||||
return array();
|
||||
}
|
||||
$customVariables = array();
|
||||
$maxCustomVars = CustomVariables::getMaxCustomVariables();
|
||||
foreach ($customVar as $id => $keyValue) {
|
||||
$id = (int)$id;
|
||||
if ($id < 1
|
||||
|| $id > $maxCustomVars
|
||||
|| count($keyValue) != 2
|
||||
|| (!is_string($keyValue[0]) && !is_numeric($keyValue[0]))
|
||||
) {
|
||||
Common::printDebug("Invalid custom variables detected (id=$id)");
|
||||
continue;
|
||||
}
|
||||
if (strlen($keyValue[1]) == 0) {
|
||||
$keyValue[1] = "";
|
||||
}
|
||||
// We keep in the URL when Custom Variable have empty names
|
||||
// and values, as it means they can be deleted server side
|
||||
|
||||
$key = self::truncateCustomVariable($keyValue[0]);
|
||||
$value = self::truncateCustomVariable($keyValue[1]);
|
||||
$customVariables['custom_var_k' . $id] = $key;
|
||||
$customVariables['custom_var_v' . $id] = $value;
|
||||
}
|
||||
|
||||
return $customVariables;
|
||||
}
|
||||
|
||||
public static function truncateCustomVariable($input)
|
||||
{
|
||||
return substr(trim($input), 0, CustomVariables::getMaxLengthCustomVariables());
|
||||
}
|
||||
|
||||
protected function shouldUseThirdPartyCookie()
|
||||
{
|
||||
return (bool)Config::getInstance()->Tracker['use_third_party_id_cookie'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cookie information.
|
||||
*/
|
||||
public function setThirdPartyCookie($idVisitor)
|
||||
{
|
||||
if (!$this->shouldUseThirdPartyCookie()) {
|
||||
return;
|
||||
}
|
||||
Common::printDebug("We manage the cookie...");
|
||||
|
||||
$cookie = $this->makeThirdPartyCookie();
|
||||
// idcookie has been generated in handleNewVisit or we simply propagate the old value
|
||||
$cookie->set(0, bin2hex($idVisitor));
|
||||
$cookie->save();
|
||||
}
|
||||
|
||||
protected function makeThirdPartyCookie()
|
||||
{
|
||||
$cookie = new Cookie(
|
||||
$this->getCookieName(),
|
||||
$this->getCookieExpire(),
|
||||
$this->getCookiePath());
|
||||
Common::printDebug($cookie);
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
protected function getCookieName()
|
||||
{
|
||||
return Config::getInstance()->Tracker['cookie_name'];
|
||||
}
|
||||
|
||||
protected function getCookieExpire()
|
||||
{
|
||||
return $this->getCurrentTimestamp() + Config::getInstance()->Tracker['cookie_expire'];
|
||||
}
|
||||
|
||||
protected function getCookiePath()
|
||||
{
|
||||
return Config::getInstance()->Tracker['cookie_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getVisitorId()
|
||||
{
|
||||
$found = false;
|
||||
|
||||
// Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request?
|
||||
$idVisitor = $this->getForcedVisitorId();
|
||||
if (!empty($idVisitor)) {
|
||||
if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) {
|
||||
throw new Exception("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long");
|
||||
}
|
||||
Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor);
|
||||
$found = true;
|
||||
}
|
||||
|
||||
// - If set to use 3rd party cookies for Visit ID, read the cookie
|
||||
if (!$found) {
|
||||
// - By default, reads the first party cookie ID
|
||||
$useThirdPartyCookie = $this->shouldUseThirdPartyCookie();
|
||||
if ($useThirdPartyCookie) {
|
||||
$cookie = $this->makeThirdPartyCookie();
|
||||
$idVisitor = $cookie->get(0);
|
||||
if ($idVisitor !== false
|
||||
&& strlen($idVisitor) == Tracker::LENGTH_HEX_ID_STRING
|
||||
) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If a third party cookie was not found, we default to the first party cookie
|
||||
if (!$found) {
|
||||
$idVisitor = Common::getRequestVar('_id', '', 'string', $this->params);
|
||||
$found = strlen($idVisitor) >= Tracker::LENGTH_HEX_ID_STRING;
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
$truncated = substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING);
|
||||
$binVisitorId = @Common::hex2bin($truncated);
|
||||
if (!empty($binVisitorId)) {
|
||||
return $binVisitorId;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getIp()
|
||||
{
|
||||
if (!empty($this->enforcedIp)) {
|
||||
$ipString = $this->enforcedIp;
|
||||
} else {
|
||||
$ipString = IP::getIpFromHeader();
|
||||
}
|
||||
$ip = IP::P2N($ipString);
|
||||
return $ip;
|
||||
}
|
||||
|
||||
public function setForceIp($ip)
|
||||
{
|
||||
if (!empty($ip)) {
|
||||
$this->enforcedIp = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
public function setForceDateTime($dateTime)
|
||||
{
|
||||
if (!is_numeric($dateTime)) {
|
||||
$dateTime = strtotime($dateTime);
|
||||
}
|
||||
if (!empty($dateTime)) {
|
||||
$this->timestamp = $dateTime;
|
||||
}
|
||||
}
|
||||
|
||||
public function setForcedVisitorId($visitorId)
|
||||
{
|
||||
if (!empty($visitorId)) {
|
||||
$this->forcedVisitorId = $visitorId;
|
||||
}
|
||||
}
|
||||
|
||||
public function getForcedVisitorId()
|
||||
{
|
||||
return $this->forcedVisitorId;
|
||||
}
|
||||
|
||||
public function overrideLocation(&$visitorInfo)
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for location override query parameters (ie, lat, long, country, region, city)
|
||||
static $locationOverrideParams = array(
|
||||
'country' => array('string', 'location_country'),
|
||||
'region' => array('string', 'location_region'),
|
||||
'city' => array('string', 'location_city'),
|
||||
'lat' => array('float', 'location_latitude'),
|
||||
'long' => array('float', 'location_longitude'),
|
||||
);
|
||||
foreach ($locationOverrideParams as $queryParamName => $info) {
|
||||
list($type, $visitorInfoKey) = $info;
|
||||
|
||||
$value = Common::getRequestVar($queryParamName, false, $type, $this->params);
|
||||
if (!empty($value)) {
|
||||
$visitorInfo[$visitorInfoKey] = $value;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public function getPlugins()
|
||||
{
|
||||
static $pluginsInOrder = array('fla', 'java', 'dir', 'qt', 'realp', 'pdf', 'wma', 'gears', 'ag', 'cookie');
|
||||
$plugins = array();
|
||||
foreach ($pluginsInOrder as $param) {
|
||||
$plugins[] = Common::getRequestVar($param, 0, 'int', $this->params);
|
||||
}
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
public function getParamsCount()
|
||||
{
|
||||
return count($this->params);
|
||||
}
|
||||
|
||||
const GENERATION_TIME_MS_MAXIMUM = 3600000; // 1 hour
|
||||
|
||||
public function getPageGenerationTime()
|
||||
{
|
||||
$generationTime = $this->getParam('gt_ms');
|
||||
if ($generationTime > 0
|
||||
&& $generationTime < self::GENERATION_TIME_MS_MAXIMUM
|
||||
) {
|
||||
return (int)$generationTime;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
257
www/analytics/core/Tracker/TableLogAction.php
Normal file
257
www/analytics/core/Tracker/TableLogAction.php
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\SegmentExpression;
|
||||
use Piwik\Tracker;
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to query Action IDs from the log_action table.
|
||||
*
|
||||
* A pageview, outlink, download or site search are made of several "Action IDs"
|
||||
* For example pageview is idaction_url and idaction_name.
|
||||
*
|
||||
*/
|
||||
class TableLogAction
|
||||
{
|
||||
/**
|
||||
* This function will find the idaction from the lookup table piwik_log_action,
|
||||
* given an Action name, type, and an optional URL Prefix.
|
||||
*
|
||||
* This is used to record Page URLs, Page Titles, Ecommerce items SKUs, item names, item categories
|
||||
*
|
||||
* If the action name does not exist in the lookup table, it will INSERT it
|
||||
* @param array $actionsNameAndType Array of one or many (name,type)
|
||||
* @return array Returns the an array (Field name => idaction)
|
||||
*/
|
||||
public static function loadIdsAction($actionsNameAndType)
|
||||
{
|
||||
// Add url prefix if not set
|
||||
foreach($actionsNameAndType as &$action) {
|
||||
if(count($action) == 2) {
|
||||
$action[] = null;
|
||||
}
|
||||
}
|
||||
$actionIds = self::queryIdsAction($actionsNameAndType);
|
||||
|
||||
list($queriedIds, $fieldNamesToInsert) = self::processIdsToInsert($actionsNameAndType, $actionIds);
|
||||
|
||||
$insertedIds = self::insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert);
|
||||
|
||||
$queriedIds = $queriedIds + $insertedIds;
|
||||
|
||||
return $queriedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $type
|
||||
* @return string
|
||||
*/
|
||||
private static function getIdActionMatchingNameAndType($name, $type)
|
||||
{
|
||||
$sql = TableLogAction::getSqlSelectActionId();
|
||||
$bind = array($name, $name, $type);
|
||||
$idAction = \Piwik\Db::fetchOne($sql, $bind);
|
||||
return $idAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $matchType
|
||||
* @param $actionType
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function getSelectQueryWhereNameContains($matchType, $actionType)
|
||||
{
|
||||
// now, we handle the cases =@ (contains) and !@ (does not contain)
|
||||
// build the expression based on the match type
|
||||
$sql = 'SELECT idaction FROM ' . Common::prefixTable('log_action') . ' WHERE %s AND type = ' . $actionType . ' )';
|
||||
switch ($matchType) {
|
||||
case '=@':
|
||||
// use concat to make sure, no %s occurs because some plugins use %s in their sql
|
||||
$where = '( name LIKE CONCAT(\'%\', ?, \'%\') ';
|
||||
break;
|
||||
case '!@':
|
||||
$where = '( name NOT LIKE CONCAT(\'%\', ?, \'%\') ';
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("This match type $matchType is not available for action-segments.");
|
||||
break;
|
||||
}
|
||||
$sql = sprintf($sql, $where);
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private static function getSqlSelectActionId()
|
||||
{
|
||||
$sql = "SELECT idaction, type, name
|
||||
FROM " . Common::prefixTable('log_action')
|
||||
. " WHERE "
|
||||
. " ( hash = CRC32(?) AND name = ? AND type = ? ) ";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private static function insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert)
|
||||
{
|
||||
$sql = "INSERT INTO " . Common::prefixTable('log_action') .
|
||||
"( name, hash, type, url_prefix ) VALUES (?,CRC32(?),?,?)";
|
||||
// Then, we insert all new actions in the lookup table
|
||||
$inserted = array();
|
||||
foreach ($fieldNamesToInsert as $fieldName) {
|
||||
list($name, $type, $urlPrefix) = $actionsNameAndType[$fieldName];
|
||||
|
||||
Tracker::getDatabase()->query($sql, array($name, $name, $type, $urlPrefix));
|
||||
$actionId = Tracker::getDatabase()->lastInsertId();
|
||||
|
||||
$inserted[$fieldName] = $actionId;
|
||||
|
||||
Common::printDebug("Recorded a new action (" . Action::getTypeAsString($type) . ") in the lookup table: " . $name . " (idaction = " . $actionId . ")");
|
||||
}
|
||||
return $inserted;
|
||||
}
|
||||
|
||||
private static function queryIdsAction($actionsNameAndType)
|
||||
{
|
||||
$sql = TableLogAction::getSqlSelectActionId();
|
||||
$bind = array();
|
||||
$i = 0;
|
||||
foreach ($actionsNameAndType as &$actionNameType) {
|
||||
list($name, $type, $urlPrefix) = $actionNameType;
|
||||
if (empty($name)) {
|
||||
continue;
|
||||
}
|
||||
if ($i > 0) {
|
||||
$sql .= " OR ( hash = CRC32(?) AND name = ? AND type = ? ) ";
|
||||
}
|
||||
$bind[] = $name;
|
||||
$bind[] = $name;
|
||||
$bind[] = $type;
|
||||
$i++;
|
||||
}
|
||||
// Case URL & Title are empty
|
||||
if (empty($bind)) {
|
||||
return false;
|
||||
}
|
||||
$actionIds = Tracker::getDatabase()->fetchAll($sql, $bind);
|
||||
return $actionIds;
|
||||
}
|
||||
|
||||
private static function processIdsToInsert($actionsNameAndType, $actionIds)
|
||||
{
|
||||
// For the Actions found in the lookup table, add the idaction in the array,
|
||||
// If not found in lookup table, queue for INSERT
|
||||
$fieldNamesToInsert = $fieldNameToActionId = array();
|
||||
foreach ($actionsNameAndType as $fieldName => &$actionNameType) {
|
||||
@list($name, $type, $urlPrefix) = $actionNameType;
|
||||
if (empty($name)) {
|
||||
$fieldNameToActionId[$fieldName] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($actionIds as $row) {
|
||||
if ($name == $row['name']
|
||||
&& $type == $row['type']
|
||||
) {
|
||||
$found = true;
|
||||
|
||||
$fieldNameToActionId[$fieldName] = $row['idaction'];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
$fieldNamesToInsert[] = $fieldName;
|
||||
}
|
||||
}
|
||||
return array($fieldNameToActionId, $fieldNamesToInsert);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert segment expression to an action ID or an SQL expression.
|
||||
*
|
||||
* This method is used as a sqlFilter-callback for the segments of this plugin.
|
||||
* Usually, these callbacks only return a value that should be compared to the
|
||||
* column in the database. In this case, that doesn't work since multiple IDs
|
||||
* can match an expression (e.g. "pageUrl=@foo").
|
||||
* @param string $valueToMatch
|
||||
* @param string $sqlField
|
||||
* @param string $matchType
|
||||
* @param string $segmentName
|
||||
* @throws \Exception
|
||||
* @return array|int|string
|
||||
*/
|
||||
public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName)
|
||||
{
|
||||
$actionType = self::guessActionTypeFromSegment($segmentName);
|
||||
|
||||
if ($actionType == Action::TYPE_PAGE_URL) {
|
||||
// for urls trim protocol and www because it is not recorded in the db
|
||||
$valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch);
|
||||
}
|
||||
$valueToMatch = Common::sanitizeInputValue(Common::unsanitizeInputValue($valueToMatch));
|
||||
|
||||
if ($matchType == SegmentExpression::MATCH_EQUAL
|
||||
|| $matchType == SegmentExpression::MATCH_NOT_EQUAL
|
||||
) {
|
||||
$idAction = self::getIdActionMatchingNameAndType($valueToMatch, $actionType);
|
||||
// if the action is not found, we hack -100 to ensure it tries to match against an integer
|
||||
// otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Větrnásssssss)
|
||||
if (empty($idAction)) {
|
||||
$idAction = -100;
|
||||
}
|
||||
return $idAction;
|
||||
}
|
||||
|
||||
// "name contains $string" match can match several idaction so we cannot return yet an idaction
|
||||
// special case
|
||||
$sql = TableLogAction::getSelectQueryWhereNameContains($matchType, $actionType);
|
||||
return array(
|
||||
// mark that the returned value is an sql-expression instead of a literal value
|
||||
'SQL' => $sql,
|
||||
'bind' => $valueToMatch,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $segmentName
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function guessActionTypeFromSegment($segmentName)
|
||||
{
|
||||
$exactMatch = array(
|
||||
'eventAction' => Action::TYPE_EVENT_ACTION,
|
||||
'eventCategory' => Action::TYPE_EVENT_CATEGORY,
|
||||
'eventName' => Action::TYPE_EVENT_NAME,
|
||||
);
|
||||
if(!empty($exactMatch[$segmentName])) {
|
||||
return $exactMatch[$segmentName];
|
||||
}
|
||||
|
||||
if (stripos($segmentName, 'pageurl') !== false) {
|
||||
$actionType = Action::TYPE_PAGE_URL;
|
||||
return $actionType;
|
||||
} elseif (stripos($segmentName, 'pagetitle') !== false) {
|
||||
$actionType = Action::TYPE_PAGE_TITLE;
|
||||
return $actionType;
|
||||
} elseif (stripos($segmentName, 'sitesearch') !== false) {
|
||||
$actionType = Action::TYPE_SITE_SEARCH;
|
||||
return $actionType;
|
||||
} else {
|
||||
throw new \Exception("We cannot guess the action type from the segment $segmentName.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1010
www/analytics/core/Tracker/Visit.php
Normal file
1010
www/analytics/core/Tracker/Visit.php
Normal file
File diff suppressed because it is too large
Load diff
244
www/analytics/core/Tracker/VisitExcluded.php
Normal file
244
www/analytics/core/Tracker/VisitExcluded.php
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\IP;
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
* This class contains the logic to exclude some visitors from being tracked as per user settings
|
||||
*/
|
||||
class VisitExcluded
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param bool|string $ip
|
||||
* @param bool|string $userAgent
|
||||
*/
|
||||
public function __construct(Request $request, $ip = false, $userAgent = false)
|
||||
{
|
||||
if ($ip === false) {
|
||||
$ip = $request->getIp();
|
||||
}
|
||||
|
||||
if ($userAgent === false) {
|
||||
$userAgent = $request->getUserAgent();
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->idSite = $request->getIdSite();
|
||||
$this->userAgent = $userAgent;
|
||||
$this->ip = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the current visitor is excluded from the statistics.
|
||||
*
|
||||
* Plugins can for example exclude visitors based on the
|
||||
* - IP
|
||||
* - If a given cookie is found
|
||||
*
|
||||
* @return bool True if the visit must not be saved, false otherwise
|
||||
*/
|
||||
public function isExcluded()
|
||||
{
|
||||
$excluded = false;
|
||||
|
||||
if ($this->isNonHumanBot()) {
|
||||
Common::printDebug('Search bot detected, visit excluded');
|
||||
$excluded = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Requests built with piwik.js will contain a rec=1 parameter. This is used as
|
||||
* an indication that the request is made by a JS enabled device. By default, Piwik
|
||||
* doesn't track non-JS visitors.
|
||||
*/
|
||||
if (!$excluded) {
|
||||
$toRecord = $this->request->getParam($parameterForceRecord = 'rec');
|
||||
if (!$toRecord) {
|
||||
Common::printDebug(@$_SERVER['REQUEST_METHOD'] . ' parameter ' . $parameterForceRecord . ' not found in URL, request excluded');
|
||||
$excluded = true;
|
||||
Common::printDebug("'$parameterForceRecord' parameter not found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered on every tracking request.
|
||||
*
|
||||
* This event can be used to tell the Tracker not to record this particular action or visit.
|
||||
*
|
||||
* @param bool &$excluded Whether the request should be excluded or not. Initialized
|
||||
* to `false`. Event subscribers should set it to `true` in
|
||||
* order to exclude the request.
|
||||
*/
|
||||
Piwik::postEvent('Tracker.isExcludedVisit', array(&$excluded));
|
||||
|
||||
/*
|
||||
* Following exclude operations happen after the hook.
|
||||
* These are of higher priority and should not be overwritten by plugins.
|
||||
*/
|
||||
|
||||
// Checking if the Piwik ignore cookie is set
|
||||
if (!$excluded) {
|
||||
$excluded = $this->isIgnoreCookieFound();
|
||||
if ($excluded) {
|
||||
Common::printDebug("Ignore cookie found.");
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for excluded IPs
|
||||
if (!$excluded) {
|
||||
$excluded = $this->isVisitorIpExcluded();
|
||||
if ($excluded) {
|
||||
Common::printDebug("IP excluded.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user agent should be excluded
|
||||
if (!$excluded) {
|
||||
$excluded = $this->isUserAgentExcluded();
|
||||
if ($excluded) {
|
||||
Common::printDebug("User agent excluded.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$excluded) {
|
||||
if ($this->isPrefetchDetected()) {
|
||||
$excluded = true;
|
||||
Common::printDebug("Prefetch request detected, not a real visit so we Ignore this visit/pageview");
|
||||
}
|
||||
}
|
||||
|
||||
if ($excluded) {
|
||||
Common::printDebug("Visitor excluded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isPrefetchDetected()
|
||||
{
|
||||
return (isset($_SERVER["HTTP_X_PURPOSE"])
|
||||
&& in_array($_SERVER["HTTP_X_PURPOSE"], array("preview", "instant")))
|
||||
|| (isset($_SERVER['HTTP_X_MOZ'])
|
||||
&& $_SERVER['HTTP_X_MOZ'] == "prefetch");
|
||||
}
|
||||
|
||||
/**
|
||||
* Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites.
|
||||
* As a result, these sophisticated bots exhibit characteristics of
|
||||
* browsers (cookies enabled, executing JavaScript, etc).
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isNonHumanBot()
|
||||
{
|
||||
$allowBots = $this->request->getParam('bots');
|
||||
|
||||
return !$allowBots
|
||||
// Seen in the wild
|
||||
&& (strpos($this->userAgent, 'Googlebot') !== false // Googlebot
|
||||
|| strpos($this->userAgent, 'Google Web Preview') !== false // Google Instant
|
||||
|| strpos($this->userAgent, 'AdsBot-Google') !== false // Google Adwords landing pages
|
||||
|| strpos($this->userAgent, 'Google Page Speed Insights') !== false // #4049
|
||||
|| strpos($this->userAgent, 'Google (+https://developers.google.com') !== false // Google Snippet https://developers.google.com/+/web/snippet/
|
||||
|| strpos($this->userAgent, 'facebookexternalhit') !== false // http://www.facebook.com/externalhit_uatext.php
|
||||
|| strpos($this->userAgent, 'baidu') !== false // Baidu
|
||||
|| strpos($this->userAgent, 'bingbot') !== false // Bingbot
|
||||
|| strpos($this->userAgent, 'YottaaMonitor') !== false // Yottaa
|
||||
|| strpos($this->userAgent, 'CloudFlare') !== false // CloudFlare-AlwaysOnline
|
||||
|
||||
// Added as they are popular bots
|
||||
|| strpos($this->userAgent, 'pingdom') !== false // pingdom
|
||||
|| strpos($this->userAgent, 'yandex') !== false // yandex
|
||||
|| strpos($this->userAgent, 'exabot') !== false // Exabot
|
||||
|| strpos($this->userAgent, 'sogou') !== false // Sogou
|
||||
|| strpos($this->userAgent, 'soso') !== false // Soso
|
||||
|| IP::isIpInRange($this->ip, $this->getBotIpRanges()));
|
||||
}
|
||||
|
||||
protected function getBotIpRanges()
|
||||
{
|
||||
return array(
|
||||
// Live/Bing/MSN
|
||||
'64.4.0.0/18',
|
||||
'65.52.0.0/14',
|
||||
'157.54.0.0/15',
|
||||
'157.56.0.0/14',
|
||||
'157.60.0.0/16',
|
||||
'207.46.0.0/16',
|
||||
'207.68.128.0/18',
|
||||
'207.68.192.0/20',
|
||||
'131.253.26.0/20',
|
||||
'131.253.24.0/20',
|
||||
// Yahoo
|
||||
'72.30.198.0/20',
|
||||
'72.30.196.0/20',
|
||||
'98.137.207.0/20',
|
||||
// Chinese bot hammering websites
|
||||
'1.202.218.8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the ignore cookie that users can set in the Piwik admin screen.
|
||||
* @return bool
|
||||
*/
|
||||
protected function isIgnoreCookieFound()
|
||||
{
|
||||
if (IgnoreCookie::isIgnoreCookieFound()) {
|
||||
Common::printDebug('Piwik ignore cookie was found, visit not tracked.');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the visitor ip is in the excluded list
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isVisitorIpExcluded()
|
||||
{
|
||||
$websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite);
|
||||
if (!empty($websiteAttributes['excluded_ips'])) {
|
||||
if (IP::isIpInRange($this->ip, $websiteAttributes['excluded_ips'])) {
|
||||
Common::printDebug('Visitor IP ' . IP::N2P($this->ip) . ' is excluded from being tracked');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified user agent should be excluded for the current site or not.
|
||||
*
|
||||
* Visits whose user agent string contains one of the excluded_user_agents strings for the
|
||||
* site being tracked (or one of the global strings) will be excluded.
|
||||
*
|
||||
* @internal param string $this ->userAgent The user agent string.
|
||||
* @return bool
|
||||
*/
|
||||
protected function isUserAgentExcluded()
|
||||
{
|
||||
$websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite);
|
||||
if (!empty($websiteAttributes['excluded_user_agents'])) {
|
||||
foreach ($websiteAttributes['excluded_user_agents'] as $excludedUserAgent) {
|
||||
// if the excluded user agent string part is in this visit's user agent, this visit should be excluded
|
||||
if (stripos($this->userAgent, $excludedUserAgent) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
32
www/analytics/core/Tracker/VisitInterface.php
Normal file
32
www/analytics/core/Tracker/VisitInterface.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that track visit information for the Tracker.
|
||||
*
|
||||
*/
|
||||
interface VisitInterface
|
||||
{
|
||||
/**
|
||||
* Stores the object describing the current tracking request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
public function setRequest(Request $request);
|
||||
|
||||
/**
|
||||
* Tracks a visit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle();
|
||||
}
|
||||
17
www/analytics/core/Tracker/VisitorNotFoundInDb.php
Normal file
17
www/analytics/core/Tracker/VisitorNotFoundInDb.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Tracker;
|
||||
|
||||
/**
|
||||
*/
|
||||
class VisitorNotFoundInDb extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue