* @copyright Copyright (c) 2010 United Prototype GmbH (http://unitedprototype.com) */ namespace UnitedPrototype\GoogleAnalytics; use UnitedPrototype\GoogleAnalytics\Internals\Util; use DateTime; /** * You should serialize this object and store it in the user database to keep it * persistent for the same user permanently (similar to the "__umta" cookie of * the GA Javascript client). */ class Visitor { /** * Unique user ID, will be part of the "__utma" cookie parameter * * @see Internals\ParameterHolder::$__utma * @var int */ protected $uniqueId; /** * Time of the very first visit of this user, will be part of the "__utma" * cookie parameter * * @see Internals\ParameterHolder::$__utma * @var DateTime */ protected $firstVisitTime; /** * Time of the previous visit of this user, will be part of the "__utma" * cookie parameter * * @see Internals\ParameterHolder::$__utma * @see addSession * @var DateTime */ protected $previousVisitTime; /** * Time of the current visit of this user, will be part of the "__utma" * cookie parameter * * @see Internals\ParameterHolder::$__utma * @see addSession * @var DateTime */ protected $currentVisitTime; /** * Amount of total visits by this user, will be part of the "__utma" * cookie parameter * * @see Internals\ParameterHolder::$__utma * @var int */ protected $visitCount; /** * IP Address of the end user, e.g. "123.123.123.123", will be mapped to "utmip" parameter * and "X-Forwarded-For" request header * * @see Internals\ParameterHolder::$utmip * @see Internals\Request\HttpRequest::$xForwardedFor * @var string */ protected $ipAddress; /** * User agent string of the end user, will be mapped to "User-Agent" request header * * @see Internals\Request\HttpRequest::$userAgent * @var string */ protected $userAgent; /** * Locale string (country part optional), e.g. "de-DE", will be mapped to "utmul" parameter * * @see Internals\ParameterHolder::$utmul * @var string */ protected $locale; /** * Visitor's Flash version, e.g. "9.0 r28", will be maped to "utmfl" parameter * * @see Internals\ParameterHolder::$utmfl * @var string */ protected $flashVersion; /** * Visitor's Java support, will be mapped to "utmje" parameter * * @see Internals\ParameterHolder::$utmje * @var bool */ protected $javaEnabled; /** * Visitor's screen color depth, e.g. 32, will be mapped to "utmsc" parameter * * @see Internals\ParameterHolder::$utmsc * @var string */ protected $screenColorDepth; /** * Visitor's screen resolution, e.g. "1024x768", will be mapped to "utmsr" parameter * * @see Internals\ParameterHolder::$utmsr * @var string */ protected $screenResolution; /** * Creates a new visitor without any previous visit information. */ public function __construct() { // ga.js sets all three timestamps to now for new visitors, so we do the same $now = new DateTime(); $this->setFirstVisitTime($now); $this->setPreviousVisitTime($now); $this->setCurrentVisitTime($now); $this->setVisitCount(1); } /** * Will extract information for the "uniqueId", "firstVisitTime", "previousVisitTime", * "currentVisitTime" and "visitCount" properties from the given "__utma" cookie * value. * * @see Internals\ParameterHolder::$__utma * @see Internals\Request\Request::buildCookieParameters() * @param string $value * @return $this */ public function fromUtma($value) { $parts = explode('.', $value); if(count($parts) != 6) { Tracker::_raiseError('The given "__utma" cookie value is invalid.', __METHOD__); return $this; } $this->setUniqueId($parts[1]); $this->setFirstVisitTime(new DateTime('@' . $parts[2])); $this->setPreviousVisitTime(new DateTime('@' . $parts[3])); $this->setCurrentVisitTime(new DateTime('@' . $parts[4])); $this->setVisitCount($parts[5]); // Allow chaining return $this; } /** * Will extract information for the "ipAddress", "userAgent" and "locale" properties * from the given $_SERVER variable. * * @param array $value * @return $this */ public function fromServerVar(array $value) { if(!empty($value['REMOTE_ADDR'])) { $ip = null; foreach(array('X_FORWARDED_FOR', 'REMOTE_ADDR') as $key) { if(!empty($value[$key]) && !$ip) { $ips = explode(',', $value[$key]); $ip = trim($ips[(count($ips) - 1)]); // Double-check if the address has a valid format if(!preg_match('/^[\d+]{1,3}\.[\d+]{1,3}\.[\d+]{1,3}\.[\d+]{1,3}$/i', $ip)) { $ip = null; } // Exclude private IP address ranges if(preg_match('#^(?:127\.0\.0\.1|10\.|192\.168\.|172\.(?:1[6-9]|2[0-9]|3[0-1])\.)#', $ip)) { $ip = null; } } } if($ip) { $this->setIpAddress($ip); } } if(!empty($value['HTTP_USER_AGENT'])) { $this->setUserAgent($value['HTTP_USER_AGENT']); } if(!empty($value['HTTP_ACCEPT_LANGUAGE'])) { $parsedLocales = array(); if(preg_match_all('/(^|\s*,\s*)([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})))?/i', $value['HTTP_ACCEPT_LANGUAGE'], $matches)) { $matches[2] = array_map(function($part) { return str_replace('-', '_', $part); }, $matches[2]); $matches[5] = array_map(function($part) { return $part === '' ? 1 : $part; }, $matches[5]); $parsedLocales = array_combine($matches[2], $matches[5]); arsort($parsedLocales, SORT_NUMERIC); $parsedLocales = array_keys($parsedLocales); } if($parsedLocales) { $this->setLocale($parsedLocales[0]); } } // Allow chaining return $this; } /** * Generates a hashed value from user-specific properties. * * @link http://code.google.com/p/gaforflash/source/browse/trunk/src/com/google/analytics/v4/Tracker.as#542 * @return int */ protected function generateHash() { // TODO: Emulate orginal Google Analytics client library generation more closely $string = $this->userAgent; $string .= $this->screenResolution . $this->screenColorDepth; return Util::generateHash($string); } /** * Generates a unique user ID from the current user-specific properties. * * @link http://code.google.com/p/gaforflash/source/browse/trunk/src/com/google/analytics/v4/Tracker.as#563 * @return int */ protected function generateUniqueId() { // There seems to be an error in the gaforflash code, so we take the formula // from http://xahlee.org/js/google_analytics_tracker_2010-07-01_expanded.js line 711 // instead ("&" instead of "*") return ((Util::generate32bitRandom() ^ $this->generateHash()) & 0x7fffffff); } /** * @see generateUniqueId * @param int $value */ public function setUniqueId($value) { if($value < 0 || $value > 0x7fffffff) { Tracker::_raiseError('Visitor unique ID has to be a 32-bit integer between 0 and ' . 0x7fffffff . '.', __METHOD__); } $this->uniqueId = (int)$value; } /** * Will be generated on first call (if not set already) to include as much * user-specific information as possible. * * @return int */ public function getUniqueId() { if($this->uniqueId === null) { $this->uniqueId = $this->generateUniqueId(); } return $this->uniqueId; } /** * Updates the "previousVisitTime", "currentVisitTime" and "visitCount" * fields based on the given session object. * * @param Session $session */ public function addSession(Session $session) { $startTime = $session->getStartTime(); if($startTime != $this->currentVisitTime) { $this->previousVisitTime = $this->currentVisitTime; $this->currentVisitTime = $startTime; ++$this->visitCount; } } /** * @param DateTime $value */ public function setFirstVisitTime(DateTime $value) { $this->firstVisitTime = $value; } /** * @return DateTime */ public function getFirstVisitTime() { return $this->firstVisitTime; } /** * @param DateTime $value */ public function setPreviousVisitTime(DateTime $value) { $this->previousVisitTime = $value; } /** * @return DateTime */ public function getPreviousVisitTime() { return $this->previousVisitTime; } /** * @param DateTime $value */ public function setCurrentVisitTime(DateTime $value) { $this->currentVisitTime = $value; } /** * @return DateTime */ public function getCurrentVisitTime() { return $this->currentVisitTime; } /** * @param int $value */ public function setVisitCount($value) { $this->visitCount = (int)$value; } /** * @return int */ public function getVisitCount() { return $this->visitCount; } /** * @param string $value */ public function setIpAddress($value) { $this->ipAddress = $value; } /** * @return string */ public function getIpAddress() { return $this->ipAddress; } /** * @param string $value */ public function setUserAgent($value) { $this->userAgent = $value; } /** * @return string */ public function getUserAgent() { return $this->userAgent; } /** * @param string $value */ public function setLocale($value) { $this->locale = $value; } /** * @return string */ public function getLocale() { return $this->locale; } /** * @param string $value */ public function setFlashVersion($value) { $this->flashVersion = $value; } /** * @return string */ public function getFlashVersion() { return $this->flashVersion; } /** * @param bool $value */ public function setJavaEnabled($value) { $this->javaEnabled = (bool)$value; } /** * @return bool */ public function getJavaEnabled() { return $this->javaEnabled; } /** * @param int $value */ public function setScreenColorDepth($value) { $this->screenColorDepth = (int)$value; } /** * @return string */ public function getScreenColorDepth() { return $this->screenColorDepth; } /** * @param string $value */ public function setScreenResolution($value) { $this->screenResolution = $value; } /** * @return string */ public function getScreenResolution() { return $this->screenResolution; } } ?>