Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authormattab <matthieu.aubry@gmail.com>2013-10-24 08:36:09 +0400
committermattab <matthieu.aubry@gmail.com>2013-10-24 08:36:09 +0400
commit87e938344233b997855af9c438f7b355e3784d34 (patch)
treed59eceaeff112fbef302fb9f8e21b67efb3f6ab6 /libs
parent03805c84c3a4774de52104b6b10ba833d661da9c (diff)
parent6e78c7bcb16929ab1f9ee0cbfc6b3dbcf4ed0404 (diff)
Merge branch 'master' into cevents
Diffstat (limited to 'libs')
-rw-r--r--libs/PiwikTracker/PiwikTracker.php257
1 files changed, 206 insertions, 51 deletions
diff --git a/libs/PiwikTracker/PiwikTracker.php b/libs/PiwikTracker/PiwikTracker.php
index 8456b6687a..8dbee694b3 100644
--- a/libs/PiwikTracker/PiwikTracker.php
+++ b/libs/PiwikTracker/PiwikTracker.php
@@ -61,6 +61,11 @@ class PiwikTracker
const DEFAULT_CHARSET_PARAMETER_VALUES = 'utf-8';
/**
+ * See piwik.js
+ */
+ const FIRST_PARTY_COOKIES_PREFIX = '_pk_';
+
+ /**
* Builds a PiwikTracker object, used to track visits, pages and Goal conversions
* for a specific website, by using the Piwik Tracking API.
*
@@ -70,14 +75,12 @@ class PiwikTracker
*/
function __construct($idSite, $apiUrl = '')
{
- $this->cookieSupport = true;
$this->userAgent = false;
$this->localHour = false;
$this->localMinute = false;
$this->localSecond = false;
$this->hasCookies = false;
$this->plugins = false;
- $this->visitorCustomVar = false;
$this->pageCustomVar = false;
$this->eventCustomVar = false;
$this->customData = false;
@@ -88,7 +91,6 @@ class PiwikTracker
$this->ecommerceItems = array();
$this->generationTime = false;
- $this->requestCookie = '';
$this->idSite = $idSite;
$this->urlReferrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false;
$this->pageCharset = self::DEFAULT_CHARSET_PARAMETER_VALUES;
@@ -99,12 +101,38 @@ class PiwikTracker
if (!empty($apiUrl)) {
self::$URL = $apiUrl;
}
+
+ // Life of the visitor cookie (in sec)
+ $this->configVisitorCookieTimeout = 63072000; // 2 years
+ // Life of the session cookie (in sec)
+ $this->configSessionCookieTimeout = 1800; // 30 minutes
+ // Life of the session cookie (in sec)
+ $this->configReferralCookieTimeout = 15768000; // 6 months
+
+ // Visitor Ids in order
+ $this->forcedVisitorId = false;
+ $this->cookieVisitorId = false;
+ $this->randomVisitorId = false;
+
$this->setNewVisitorId();
+ $this->configCookiesDisabled = false;
+ $this->configCookiePath = '/';
+ $this->configCookieDomain = '';
+
+ $this->currentTs = time();
+ $this->createTs = $this->currentTs;
+ $this->visitCount = false;
+ $this->currentVisitTs = false;
+ $this->lastVisitTs = false;
+ $this->lastEcommerceOrderTs = false;
+
// Allow debug while blocking the request
$this->requestTimeout = 600;
$this->doBulkRequests = false;
$this->storedTrackingActions = array();
+
+ $this->visitorCustomVar = $this->getCustomVariablesFromCookie();
}
/**
@@ -166,6 +194,9 @@ class PiwikTracker
* This must be a JSON encoded string that would typically be fetched from the JS API:
* piwikTracker.getAttributionInfo() and that you have JSON encoded via JSON2.stringify()
*
+ * If you call enableCookies() then these referral attribution values will be set
+ * to the 'ref' first party cookie storing referral information.
+ *
* @param string $jsonEncoded JSON encoded array containing Attribution info
* @throws Exception
* @see function getAttributionInfo() in https://github.com/piwik/piwik/blob/master/js/piwik.js
@@ -229,15 +260,10 @@ class PiwikTracker
if (!empty($this->visitorCustomVar[$id])) {
return $this->visitorCustomVar[$id];
}
- $customVariablesCookie = 'cvar.' . $this->idSite . '.';
- $cookie = $this->getCookieMatchingName($customVariablesCookie);
- if (!$cookie) {
- return false;
- }
+ $cookieDecoded = $this->getCustomVariablesFromCookie();
if (!is_int($id)) {
throw new Exception("Parameter to getCustomVariable should be an integer");
}
- $cookieDecoded = json_decode($cookie, $assoc = true);
if (!is_array($cookieDecoded)
|| !isset($cookieDecoded[$id])
|| !is_array($cookieDecoded[$id])
@@ -253,8 +279,9 @@ class PiwikTracker
*/
public function setNewVisitorId()
{
- $this->visitorId = substr(md5(uniqid(rand(), true)), 0, self::LENGTH_VISITOR_ID);
+ $this->randomVisitorId = substr(md5(uniqid(rand(), true)), 0, self::LENGTH_VISITOR_ID);
$this->forcedVisitorId = false;
+ $this->cookieVisitorId = false;
}
/**
@@ -358,6 +385,45 @@ class PiwikTracker
}
/**
+ * Enable Cookie Creation - this will cause a first party VisitorId cookie to be set when the VisitorId is set or reset
+ *
+ * @param string $domain (optional) Set first-party cookie domain. Accepted values: example.com, *.example.com (same as .example.com) or subdomain.example.com
+ * @param string $path (optional) Set first-party cookie path
+ */
+ public function enableCookies( $domain = '', $path = '/' )
+ {
+ $this->configCookiesDisabled = false;
+ $this->configCookieDomain = self::domainFixup($domain);
+ $this->configCookiePath = $path;
+ }
+
+ /**
+ * Fix-up domain
+ */
+ static protected function domainFixup($domain)
+ {
+ $dl = strlen($domain) - 1;
+ // remove trailing '.'
+ if ($domain{$dl} === '.') {
+ $domain = substr($domain, 0, $dl);
+ }
+ // remove leading '*'
+ if (substr($domain, 0, 2) === '*.') {
+ $domain = substr($domain, 1);
+ }
+ return $domain;
+ }
+
+ /**
+ * Get cookie name with prefix and domain hash
+ */
+ protected function getCookieName($cookieName) {
+ // NOTE: If the cookie name is changed, we must also update the method in piwik.js with the same name.
+ $hash = substr( sha1( ($this->configCookieDomain == '' ? self::getCurrentHost() : $this->configCookieDomain) . $this->configCookiePath ), 0, 4);
+ return self::FIRST_PARTY_COOKIES_PREFIX . $cookieName . '.' . $this->idSite . '.' . $hash;
+ }
+
+ /**
* Tracks a page view
*
* @param string $documentTitle Page title as it will appear in the Actions > Page titles report
@@ -389,8 +455,8 @@ class PiwikTracker
* These are used to populate reports in Actions > Site Search.
*
* @param string $keyword Searched query on the site
- * @param string $category Optional, Search engine category if applicable
- * @param bool|int $countResults results displayed on the search result page. Used to track "zero result" keywords.
+ * @param string $category (optional) Search engine category if applicable
+ * @param bool|int $countResults (optional) results displayed on the search result page. Used to track "zero result" keywords.
*
* @return mixed Response or true if using bulk requests.
*/
@@ -808,19 +874,49 @@ class PiwikTracker
{
if (!empty($this->forcedVisitorId)) {
return $this->forcedVisitorId;
+ } else if ($this->loadVisitorIdCookie()) {
+ return $this->cookieVisitorId;
+ } else {
+ return $this->randomVisitorId;
}
+ }
- $idCookieName = 'id.' . $this->idSite . '.';
- $idCookie = $this->getCookieMatchingName($idCookieName);
- if ($idCookie !== false) {
- $visitorId = substr($idCookie, 0, strpos($idCookie, '.'));
- if (strlen($visitorId) == self::LENGTH_VISITOR_ID) {
- return $visitorId;
- }
+ /**
+ * Loads values from the VisitorId Cookie
+ *
+ * @return bool True if cookie exists and is valid, False otherwise
+ */
+ protected function loadVisitorIdCookie()
+ {
+ $idCookie = $this->getCookieMatchingName('id');
+ if ($idCookie === false) {
+ return false;
}
- return $this->visitorId;
+ $parts = explode('.', $idCookie);
+ if (strlen($parts[0]) != self::LENGTH_VISITOR_ID) {
+ return false;
+ }
+ $this->cookieVisitorId = $parts[0]; // provides backward compatibility since getVisitorId() didn't change any existing VisitorId value
+ $this->createTs = $parts[1];
+ $this->visitCount = $parts[2];
+ $this->currentVisitTs = $parts[3];
+ $this->lastVisitTs = $parts[4];
+ $this->lastEcommerceOrderTs = $parts[5];
+ return true;
}
-
+
+ /**
+ * Deletes all first party cookies from the client
+ */
+ public function deleteCookies()
+ {
+ $expire = $this->currentTs - 86400;
+ $cookies = array('id', 'ses', 'cvar', 'ref');
+ foreach($cookies as $cookie) {
+ $this->setCookie($cookie, '', $expire);
+ }
+ }
+
/**
* Returns the currently assigned Attribution Information stored in a first party cookie.
*
@@ -833,8 +929,10 @@ class PiwikTracker
*/
public function getAttributionInfo()
{
- $attributionCookieName = 'ref.' . $this->idSite . '.';
- return $this->getCookieMatchingName($attributionCookieName);
+ if(!empty($this->attributionInfo)) {
+ return $this->attributionInfo;
+ }
+ return $this->getCookieMatchingName('ref');
}
/**
@@ -926,13 +1024,13 @@ class PiwikTracker
}
/**
- * By default, PiwikTracker will read third party cookies
- * from the response and sets them in the next request.
+ * By default, PiwikTracker will read first party cookies
+ * from the request and write updated cookies in the response (using setrawcookie).
* This can be disabled by calling this function.
*/
public function disableCookieSupport()
{
- $this->cookieSupport = false;
+ $this->configCookiesDisabled = true;
}
/**
@@ -974,9 +1072,6 @@ class PiwikTracker
return true;
}
- if (!$this->cookieSupport) {
- $this->requestCookie = '';
- }
if (function_exists('curl_init')) {
$options = array(
CURLOPT_URL => $url,
@@ -985,8 +1080,7 @@ class PiwikTracker
CURLOPT_TIMEOUT => $this->requestTimeout,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array(
- 'Accept-Language: ' . $this->acceptLanguage,
- 'Cookie: ' . $this->requestCookie,
+ 'Accept-Language: ' . $this->acceptLanguage
));
switch ($method) {
@@ -1009,7 +1103,7 @@ class PiwikTracker
ob_start();
$response = @curl_exec($ch);
ob_end_clean();
- $header = $content = '';
+ $content = '';
if (!empty($response)) {
list($header, $content) = explode("\r\n\r\n", $response, $limitCount = 2);
}
@@ -1018,8 +1112,7 @@ class PiwikTracker
'http' => array(
'method' => $method,
'user_agent' => $this->userAgent,
- 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n" .
- "Cookie: " . $this->requestCookie . "\r\n",
+ 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n",
'timeout' => $this->requestTimeout, // PHP 5.2.1
)
);
@@ -1029,27 +1122,10 @@ class PiwikTracker
$stream_options['http']['header'] .= "Content-Type: application/json \r\n";
$stream_options['http']['content'] = $data;
}
-
$ctx = stream_context_create($stream_options);
$response = file_get_contents($url, 0, $ctx);
- $header = implode("\r\n", $http_response_header);
$content = $response;
}
- // The cookie in the response will be set in the next request
- preg_match_all('/^Set-Cookie: (.*?);/m', $header, $cookie);
- if (!empty($cookie[1])) {
- // in case several cookies returned, we keep only the latest one (ie. XDEBUG puts its cookie first in the list)
- if (is_array($cookie[1])) {
- $cookie = end($cookie[1]);
- } else {
- $cookie = $cookie[1];
- }
- // XDEBUG is a PHP Debugger
- if (strpos($cookie, 'XDEBUG') === false) {
- $this->requestCookie = $cookie;
- }
- }
-
return $content;
}
@@ -1085,6 +1161,8 @@ class PiwikTracker
*/
protected function getRequest($idSite)
{
+ $this->setFirstPartyCookies();
+
$url = $this->getBaseUrl() .
'?idsite=' . $idSite .
'&rec=1' .
@@ -1101,6 +1179,12 @@ class PiwikTracker
(!empty($this->forcedDatetime) ? '&cdt=' . urlencode($this->forcedDatetime) : '') .
((!empty($this->token_auth) && !$this->doBulkRequests) ? '&token_auth=' . urlencode($this->token_auth) : '') .
+ // Values collected from cookie
+ '&_idts=' . $this->createTs .
+ '&_idvc=' . $this->visitCount .
+ (!empty($this->lastVisitTs) ? '&_viewts=' . $this->lastVisitTs : '' ) .
+ (!empty($this->lastEcommerceOrderTs) ? '&_ects=' . $this->lastEcommerceOrderTs : '' ) .
+
// These parameters are set by the JS, but optional when using API
(!empty($this->plugins) ? $this->plugins : '') .
(($this->localHour !== false && $this->localMinute !== false && $this->localSecond !== false) ? '&h=' . $this->localHour . '&m=' . $this->localMinute . '&s=' . $this->localSecond : '') .
@@ -1156,6 +1240,11 @@ class PiwikTracker
*/
protected function getCookieMatchingName($name)
{
+ if($this->configCookiesDisabled) {
+ return false;
+ }
+ $name = $this->getCookieName($name);
+
// Piwik cookie names use dots separators in piwik.js,
// but PHP Replaces . with _ http://www.php.net/manual/en/language.variables.predefined.php#72571
$name = str_replace('.', '_', $name);
@@ -1259,6 +1348,72 @@ class PiwikTracker
. self::getCurrentScriptName()
. self::getCurrentQueryString();
}
+
+ /**
+ * Sets the first party cookies as would the piwik.js
+ * All cookies are supported: 'id' and 'ses' and 'ref' and 'cvar' cookies.
+ */
+ protected function setFirstPartyCookies()
+ {
+ if ($this->configCookiesDisabled) {
+ return;
+ }
+
+ if (empty($this->cookieVisitorId)) {
+ $this->loadVisitorIdCookie();
+ }
+
+ // Set the 'ses' cookie
+ if (!$this->getCookieMatchingName('ses')) {
+ // new session (new visit)
+ $this->visitCount++;
+ $this->lastVisitTs = $this->currentVisitTs;
+
+ // Set the 'ref' cookie
+ $attributionInfo = $this->getAttributionInfo();
+ if(!empty($attributionInfo)) {
+ $this->setCookie('ref', $attributionInfo, $this->configReferralCookieTimeout);
+ }
+ }
+ $this->setCookie('ses', '*', $this->configSessionCookieTimeout);
+
+ // Set the 'id' cookie
+ $cookieValue = $this->getVisitorId() . '.' . $this->createTs . '.' . $this->visitCount . '.' . $this->currentTs . '.' . $this->lastVisitTs . '.' . $this->lastEcommerceOrderTs;
+ $this->setCookie('id', $cookieValue, $this->configVisitorCookieTimeout);
+
+ // Set the 'cvar' cookie
+ $this->setCookie('cvar', json_encode($this->visitorCustomVar), $this->configSessionCookieTimeout);
+
+ }
+
+ /**
+ * Sets a first party cookie to the client to improve dual JS-PHP tracking.
+ *
+ * This replicates the piwik.js tracker algorithms for consistency and better accuracy.
+ *
+ * @param $cookieName
+ * @param $cookieValue
+ * @param $cookieTTL
+ */
+ protected function setCookie($cookieName, $cookieValue, $cookieTTL)
+ {
+ $cookieExpire = $this->createTs + $cookieTTL;
+ if(!headers_sent()) {
+ setrawcookie($this->getCookieName($cookieName), $cookieValue, $cookieExpire, $this->configCookiePath, $this->configCookieDomain);
+ }
+ }
+
+ /**
+ * @return bool|mixed
+ */
+ protected function getCustomVariablesFromCookie()
+ {
+ $cookie = $this->getCookieMatchingName('cvar');
+ if (!$cookie) {
+ return false;
+ }
+ return json_decode($cookie, $assoc = true);
+ }
}
/**