Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

PiwikTracker.php 63KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * Client to record visits, page views, Goals, Ecommerce activity (product views, add to carts, Ecommerce orders) in a Piwik server.
  6. * This is a PHP Version of the piwik.js standard Tracking API.
  7. * For more information, see http://piwik.org/docs/tracking-api/
  8. *
  9. * This class requires:
  10. * - json extension (json_decode, json_encode)
  11. * - CURL or STREAM extensions (to issue the http request to Piwik)
  12. *
  13. * @license released under BSD License http://www.opensource.org/licenses/bsd-license.php
  14. * @link http://piwik.org/docs/tracking-api/
  15. *
  16. * @category Piwik
  17. * @package PiwikTracker
  18. */
  19. /**
  20. * PiwikTracker implements the Piwik Tracking Web API.
  21. *
  22. * The PHP Tracking Client provides all features of the Javascript Tracker, such as Ecommerce Tracking, Custom Variable, Event tracking and more.
  23. * Functions are named the same as the Javascript functions.
  24. *
  25. * See introduction docs at: {@link http://piwik.org/docs/tracking-api/}
  26. *
  27. * ### Example: using the PHP PiwikTracker class
  28. *
  29. * The following code snippet is an advanced example of how to track a Page View using the Tracking API PHP client.
  30. *
  31. * $t = new PiwikTracker( $idSite = 1, 'http://example.org/piwik/');
  32. *
  33. * // Optional function calls
  34. * $t->setUserAgent( "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB) Firefox/3.6.6");
  35. * $t->setBrowserLanguage('fr');
  36. * $t->setLocalTime( '12:34:06' );
  37. * $t->setResolution( 1024, 768 );
  38. * $t->setBrowserHasCookies(true);
  39. * $t->setPlugins($flash = true, $java = true, $director = false);
  40. *
  41. * // set a Custom Variable called 'Gender'
  42. * $t->setCustomVariable( 1, 'gender', 'male' );
  43. *
  44. * // If you want to force the visitor IP, or force the server date time to a date in the past,
  45. * // it is required to authenticate the Tracking request by calling setTokenAuth
  46. * // You can pass the Super User token_auth or any user with 'admin' privilege on the website $idSite
  47. * $t->setTokenAuth( $token_auth );
  48. * $t->setIp( "134.10.22.1" );
  49. * $t->setForceVisitDateTime( '2011-04-05 23:55:02' );
  50. *
  51. * // if you wanted to force to record the page view or conversion to a specific User ID
  52. * // $t->setUserId( "username@example.org" );
  53. * // Mandatory: set the URL being tracked
  54. * $t->setUrl( $url = 'http://example.org/store/list-category-toys/' );
  55. *
  56. * // Finally, track the page view with a Custom Page Title
  57. * // In the standard JS API, the content of the <title> tag would be set as the page title
  58. * $t->doTrackPageView('This is the page title');
  59. *
  60. * ### Example: tracking Ecommerce interactions
  61. *
  62. * Here is an example showing how to track Ecommerce interactions on your website, using the PHP Tracking API.
  63. * Usually, Ecommerce tracking is done using standard Javascript code,
  64. * but it is very common to record Ecommerce interactions after the fact
  65. * (for example, when payment is done with Paypal and user doesn't come back on the website after purchase).
  66. * For more information about Ecommerce tracking in Piwik, check out the documentation: Tracking Ecommerce in Piwik.
  67. *
  68. * $t = new PiwikTracker( $idSite = 1, 'http://example.org/piwik/');
  69. *
  70. * // Force IP to the actual visitor IP
  71. * $t->setTokenAuth( $token_auth );
  72. * $t->setIp( "134.10.22.1" );
  73. *
  74. * // Example 1: on a Product page, track an "Ecommerce Product view"
  75. * $t->setUrl( $url = 'http://www.mystore.com/Endurance-Shackletons-Legendary-Antarctic-Expedition' );
  76. * $t->setEcommerceView($sku = 'SKU0011', $name = 'Endurance - Shackleton', $category = 'Books');
  77. * $t->doTrackPageView( 'Endurance Shackletons Legendary Antarctic Expedition - Mystore.com');
  78. *
  79. * // Example 2: Tracking Ecommerce Cart containing 2 products
  80. * $t->addEcommerceItem($sku = 'SKU0011', $name = 'Endurance - Shackleton' , $category = 'Books', $price = 17, $quantity = 1);
  81. * // Note that when setting a product category, you can specify an array of up to 5 categories to track for this product
  82. * $t->addEcommerceItem($sku = 'SKU0321', $name = 'Amélie' , $categories = array('DVD Foreign','Best sellers','Our pick'), $price = 25, $quantity = 1);
  83. * $t->doTrackEcommerceCartUpdate($grandTotal = 42);
  84. *
  85. * // Example 3: Tracking Ecommerce Order
  86. * $t->addEcommerceItem($sku = 'SKU0011', $name = 'Endurance - Shackleton' , $category = 'Books', $price = 17, $quantity = 1);
  87. * $t->addEcommerceItem($sku = 'SKU0321', $name = 'Amélie' , $categories = array('DVD Foreign','Best sellers','Our pick'), $price = 25, $quantity = 1);
  88. * $t->doTrackEcommerceOrder($orderId = 'B000111387', $grandTotal = 55.5, $subTotal = 42, $tax = 8, $shipping = 5.5, $discount = 10);
  89. *
  90. * ### Note: authenticating with the token_auth
  91. *
  92. * To set the visitor IP, or the date and time of the visit, or to force to record the visit (or page, or goal conversion) to a specific Visitor ID,
  93. * you must call setTokenAuth( $token_auth ). The token_auth must be either the Super User token_auth,
  94. * or the token_auth of any user with 'admin' permission for the website you are recording data against.
  95. *
  96. * @package PiwikTracker
  97. * @api
  98. */
  99. class PiwikTracker
  100. {
  101. /**
  102. * Piwik base URL, for example http://example.org/piwik/
  103. * Must be set before using the class by calling
  104. * PiwikTracker::$URL = 'http://yourwebsite.org/piwik/';
  105. *
  106. * @var string
  107. */
  108. static public $URL = '';
  109. /**
  110. * API Version
  111. *
  112. * @ignore
  113. * @var int
  114. */
  115. const VERSION = 1;
  116. /**
  117. * @ignore
  118. */
  119. public $DEBUG_APPEND_URL = '';
  120. /**
  121. * Visitor ID length
  122. *
  123. * @ignore
  124. */
  125. const LENGTH_VISITOR_ID = 16;
  126. /**
  127. * Charset
  128. * @see setPageCharset
  129. * @ignore
  130. */
  131. const DEFAULT_CHARSET_PARAMETER_VALUES = 'utf-8';
  132. /**
  133. * See piwik.js
  134. */
  135. const FIRST_PARTY_COOKIES_PREFIX = '_pk_';
  136. /**
  137. * Ecommerce item page view tracking stores item's metadata in these Custom Variables slots.
  138. */
  139. const CVAR_INDEX_ECOMMERCE_ITEM_PRICE = 2;
  140. const CVAR_INDEX_ECOMMERCE_ITEM_SKU = 3;
  141. const CVAR_INDEX_ECOMMERCE_ITEM_NAME = 4;
  142. const CVAR_INDEX_ECOMMERCE_ITEM_CATEGORY = 5;
  143. const DEFAULT_COOKIE_PATH = '/';
  144. /**
  145. * Builds a PiwikTracker object, used to track visits, pages and Goal conversions
  146. * for a specific website, by using the Piwik Tracking API.
  147. *
  148. * @param int $idSite Id site to be tracked
  149. * @param string $apiUrl "http://example.org/piwik/" or "http://piwik.example.org/"
  150. * If set, will overwrite PiwikTracker::$URL
  151. */
  152. function __construct($idSite, $apiUrl = '')
  153. {
  154. $this->userAgent = false;
  155. $this->localHour = false;
  156. $this->localMinute = false;
  157. $this->localSecond = false;
  158. $this->hasCookies = false;
  159. $this->plugins = false;
  160. $this->pageCustomVar = false;
  161. $this->eventCustomVar = false;
  162. $this->customData = false;
  163. $this->forcedDatetime = false;
  164. $this->forcedNewVisit = false;
  165. $this->token_auth = false;
  166. $this->attributionInfo = false;
  167. $this->ecommerceLastOrderTimestamp = false;
  168. $this->ecommerceItems = array();
  169. $this->generationTime = false;
  170. $this->idSite = $idSite;
  171. $this->urlReferrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false;
  172. $this->pageCharset = self::DEFAULT_CHARSET_PARAMETER_VALUES;
  173. $this->pageUrl = self::getCurrentUrl();
  174. $this->ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
  175. $this->acceptLanguage = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : false;
  176. $this->userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : false;
  177. if (!empty($apiUrl)) {
  178. self::$URL = $apiUrl;
  179. }
  180. // Life of the visitor cookie (in sec)
  181. $this->configVisitorCookieTimeout = 63072000; // 2 years
  182. // Life of the session cookie (in sec)
  183. $this->configSessionCookieTimeout = 1800; // 30 minutes
  184. // Life of the session cookie (in sec)
  185. $this->configReferralCookieTimeout = 15768000; // 6 months
  186. // Visitor Ids in order
  187. $this->userId = false;
  188. $this->forcedVisitorId = false;
  189. $this->cookieVisitorId = false;
  190. $this->randomVisitorId = false;
  191. $this->setNewVisitorId();
  192. $this->configCookiesDisabled = false;
  193. $this->configCookiePath = self::DEFAULT_COOKIE_PATH;
  194. $this->configCookieDomain = '';
  195. $this->currentTs = time();
  196. $this->createTs = $this->currentTs;
  197. $this->visitCount = 0;
  198. $this->currentVisitTs = false;
  199. $this->lastVisitTs = false;
  200. $this->lastEcommerceOrderTs = false;
  201. // Allow debug while blocking the request
  202. $this->requestTimeout = 600;
  203. $this->doBulkRequests = false;
  204. $this->storedTrackingActions = array();
  205. $this->visitorCustomVar = $this->getCustomVariablesFromCookie();
  206. }
  207. /**
  208. * By default, Piwik expects utf-8 encoded values, for example
  209. * for the page URL parameter values, Page Title, etc.
  210. * It is recommended to only send UTF-8 data to Piwik.
  211. * If required though, you can also specify another charset using this function.
  212. *
  213. * @param string $charset
  214. */
  215. public function setPageCharset($charset = '')
  216. {
  217. $this->pageCharset = $charset;
  218. }
  219. /**
  220. * Sets the current URL being tracked
  221. *
  222. * @param string $url Raw URL (not URL encoded)
  223. */
  224. public function setUrl($url)
  225. {
  226. $this->pageUrl = $url;
  227. }
  228. /**
  229. * Sets the URL referrer used to track Referrers details for new visits.
  230. *
  231. * @param string $url Raw URL (not URL encoded)
  232. */
  233. public function setUrlReferrer($url)
  234. {
  235. $this->urlReferrer = $url;
  236. }
  237. /**
  238. * Sets the time that generating the document on the server side took.
  239. *
  240. * @param int $timeMs Generation time in ms
  241. */
  242. public function setGenerationTime($timeMs)
  243. {
  244. $this->generationTime = $timeMs;
  245. }
  246. /**
  247. * @deprecated
  248. * @ignore
  249. */
  250. public function setUrlReferer($url)
  251. {
  252. $this->setUrlReferrer($url);
  253. }
  254. /**
  255. * Sets the attribution information to the visit, so that subsequent Goal conversions are
  256. * properly attributed to the right Referrer URL, timestamp, Campaign Name & Keyword.
  257. *
  258. * This must be a JSON encoded string that would typically be fetched from the JS API:
  259. * piwikTracker.getAttributionInfo() and that you have JSON encoded via JSON2.stringify()
  260. *
  261. * If you call enableCookies() then these referral attribution values will be set
  262. * to the 'ref' first party cookie storing referral information.
  263. *
  264. * @param string $jsonEncoded JSON encoded array containing Attribution info
  265. * @throws Exception
  266. * @see function getAttributionInfo() in https://github.com/piwik/piwik/blob/master/js/piwik.js
  267. */
  268. public function setAttributionInfo($jsonEncoded)
  269. {
  270. $decoded = json_decode($jsonEncoded, $assoc = true);
  271. if (!is_array($decoded)) {
  272. throw new Exception("setAttributionInfo() is expecting a JSON encoded string, $jsonEncoded given");
  273. }
  274. $this->attributionInfo = $decoded;
  275. }
  276. /**
  277. * Sets Visit Custom Variable.
  278. * See http://piwik.org/docs/custom-variables/
  279. *
  280. * @param int $id Custom variable slot ID from 1-5
  281. * @param string $name Custom variable name
  282. * @param string $value Custom variable value
  283. * @param string $scope Custom variable scope. Possible values: visit, page, event
  284. * @throws Exception
  285. */
  286. public function setCustomVariable($id, $name, $value, $scope = 'visit')
  287. {
  288. if (!is_int($id)) {
  289. throw new Exception("Parameter id to setCustomVariable should be an integer");
  290. }
  291. if ($scope == 'page') {
  292. $this->pageCustomVar[$id] = array($name, $value);
  293. } elseif($scope == 'event') {
  294. $this->eventCustomVar[$id] = array($name, $value);
  295. } elseif ($scope == 'visit') {
  296. $this->visitorCustomVar[$id] = array($name, $value);
  297. } else {
  298. throw new Exception("Invalid 'scope' parameter value");
  299. }
  300. }
  301. /**
  302. * Returns the currently assigned Custom Variable.
  303. *
  304. * If scope is 'visit', it will attempt to read the value set in the first party cookie created by Piwik Tracker ($_COOKIE array).
  305. *
  306. * @param int $id Custom Variable integer index to fetch from cookie. Should be a value from 1 to 5
  307. * @param string $scope Custom variable scope. Possible values: visit, page, event
  308. *
  309. * @throws Exception
  310. * @return mixed An array with this format: array( 0 => CustomVariableName, 1 => CustomVariableValue ) or false
  311. * @see Piwik.js getCustomVariable()
  312. */
  313. public function getCustomVariable($id, $scope = 'visit')
  314. {
  315. if ($scope == 'page') {
  316. return isset($this->pageCustomVar[$id]) ? $this->pageCustomVar[$id] : false;
  317. } elseif ($scope == 'event') {
  318. return isset($this->eventCustomVar[$id]) ? $this->eventCustomVar[$id] : false;
  319. } else if ($scope != 'visit') {
  320. throw new Exception("Invalid 'scope' parameter value");
  321. }
  322. if (!empty($this->visitorCustomVar[$id])) {
  323. return $this->visitorCustomVar[$id];
  324. }
  325. $cookieDecoded = $this->getCustomVariablesFromCookie();
  326. if (!is_int($id)) {
  327. throw new Exception("Parameter to getCustomVariable should be an integer");
  328. }
  329. if (!is_array($cookieDecoded)
  330. || !isset($cookieDecoded[$id])
  331. || !is_array($cookieDecoded[$id])
  332. || count($cookieDecoded[$id]) != 2
  333. ) {
  334. return false;
  335. }
  336. return $cookieDecoded[$id];
  337. }
  338. /**
  339. * Clears any Custom Variable that may be have been set.
  340. *
  341. * This can be useful when you have enabled bulk requests,
  342. * and you wish to clear Custom Variables of 'visit' scope.
  343. */
  344. public function clearCustomVariables()
  345. {
  346. $this->visitorCustomVar = array();
  347. $this->pageCustomVar = array();
  348. $this->eventCustomVar = array();
  349. }
  350. /**
  351. * Sets the current visitor ID to a random new one.
  352. */
  353. public function setNewVisitorId()
  354. {
  355. $this->randomVisitorId = substr(md5(uniqid(rand(), true)), 0, self::LENGTH_VISITOR_ID);
  356. $this->userId = false;
  357. $this->forcedVisitorId = false;
  358. $this->cookieVisitorId = false;
  359. }
  360. /**
  361. * Sets the current site ID.
  362. *
  363. * @param int $idSite
  364. */
  365. public function setIdSite($idSite)
  366. {
  367. $this->idSite = $idSite;
  368. }
  369. /**
  370. * Sets the Browser language. Used to guess visitor countries when GeoIP is not enabled
  371. *
  372. * @param string $acceptLanguage For example "fr-fr"
  373. */
  374. public function setBrowserLanguage($acceptLanguage)
  375. {
  376. $this->acceptLanguage = $acceptLanguage;
  377. }
  378. /**
  379. * Sets the user agent, used to detect OS and browser.
  380. * If this function is not called, the User Agent will default to the current user agent.
  381. *
  382. * @param string $userAgent
  383. */
  384. public function setUserAgent($userAgent)
  385. {
  386. $this->userAgent = $userAgent;
  387. }
  388. /**
  389. * Sets the country of the visitor. If not used, Piwik will try to find the country
  390. * using either the visitor's IP address or language.
  391. *
  392. * Allowed only for Admin/Super User, must be used along with setTokenAuth().
  393. * @param string $country
  394. */
  395. public function setCountry($country)
  396. {
  397. $this->country = $country;
  398. }
  399. /**
  400. * Sets the region of the visitor. If not used, Piwik may try to find the region
  401. * using the visitor's IP address (if configured to do so).
  402. *
  403. * Allowed only for Admin/Super User, must be used along with setTokenAuth().
  404. * @param string $region
  405. */
  406. public function setRegion($region)
  407. {
  408. $this->region = $region;
  409. }
  410. /**
  411. * Sets the city of the visitor. If not used, Piwik may try to find the city
  412. * using the visitor's IP address (if configured to do so).
  413. *
  414. * Allowed only for Admin/Super User, must be used along with setTokenAuth().
  415. * @param string $city
  416. */
  417. public function setCity($city)
  418. {
  419. $this->city = $city;
  420. }
  421. /**
  422. * Sets the latitude of the visitor. If not used, Piwik may try to find the visitor's
  423. * latitude using the visitor's IP address (if configured to do so).
  424. *
  425. * Allowed only for Admin/Super User, must be used along with setTokenAuth().
  426. * @param float $lat
  427. */
  428. public function setLatitude($lat)
  429. {
  430. $this->lat = $lat;
  431. }
  432. /**
  433. * Sets the longitude of the visitor. If not used, Piwik may try to find the visitor's
  434. * longitude using the visitor's IP address (if configured to do so).
  435. *
  436. * Allowed only for Admin/Super User, must be used along with setTokenAuth().
  437. * @param float $long
  438. */
  439. public function setLongitude($long)
  440. {
  441. $this->long = $long;
  442. }
  443. /**
  444. * Enables the bulk request feature. When used, each tracking action is stored until the
  445. * doBulkTrack method is called. This method will send all tracking data at once.
  446. *
  447. */
  448. public function enableBulkTracking()
  449. {
  450. $this->doBulkRequests = true;
  451. }
  452. /**
  453. * Enable Cookie Creation - this will cause a first party VisitorId cookie to be set when the VisitorId is set or reset
  454. *
  455. * @param string $domain (optional) Set first-party cookie domain. Accepted values: example.com, *.example.com (same as .example.com) or subdomain.example.com
  456. * @param string $path (optional) Set first-party cookie path
  457. */
  458. public function enableCookies( $domain = '', $path = '/' )
  459. {
  460. $this->configCookiesDisabled = false;
  461. $this->configCookieDomain = self::domainFixup($domain);
  462. $this->configCookiePath = $path;
  463. }
  464. /**
  465. * Fix-up domain
  466. */
  467. static protected function domainFixup($domain)
  468. {
  469. $dl = strlen($domain) - 1;
  470. // remove trailing '.'
  471. if ($domain{$dl} === '.') {
  472. $domain = substr($domain, 0, $dl);
  473. }
  474. // remove leading '*'
  475. if (substr($domain, 0, 2) === '*.') {
  476. $domain = substr($domain, 1);
  477. }
  478. return $domain;
  479. }
  480. /**
  481. * Get cookie name with prefix and domain hash
  482. */
  483. protected function getCookieName($cookieName) {
  484. // NOTE: If the cookie name is changed, we must also update the method in piwik.js with the same name.
  485. $hash = substr( sha1( ($this->configCookieDomain == '' ? self::getCurrentHost() : $this->configCookieDomain) . $this->configCookiePath ), 0, 4);
  486. return self::FIRST_PARTY_COOKIES_PREFIX . $cookieName . '.' . $this->idSite . '.' . $hash;
  487. }
  488. /**
  489. * Tracks a page view
  490. *
  491. * @param string $documentTitle Page title as it will appear in the Actions > Page titles report
  492. * @return mixed Response string or true if using bulk requests.
  493. */
  494. public function doTrackPageView($documentTitle)
  495. {
  496. $url = $this->getUrlTrackPageView($documentTitle);
  497. return $this->sendRequest($url);
  498. }
  499. /**
  500. * Tracks an event
  501. *
  502. * @param string $category The Event Category (Videos, Music, Games...)
  503. * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
  504. * @param string $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...)
  505. * @param float $value (optional) The Event's value
  506. * @return mixed Response string or true if using bulk requests.
  507. */
  508. public function doTrackEvent($category, $action, $name = false, $value = false)
  509. {
  510. $url = $this->getUrlTrackEvent($category, $action, $name, $value);
  511. return $this->sendRequest($url);
  512. }
  513. /**
  514. * Tracks a content impression
  515. *
  516. * @param string $contentName The name of the content. For instance 'Ad Foo Bar'
  517. * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text
  518. * @param string|false $contentTarget (optional) The target of the content. For instance the URL of a landing page.
  519. * @return mixed Response string or true if using bulk requests.
  520. */
  521. public function doTrackContentImpression($contentName, $contentPiece = 'Unknown', $contentTarget = false)
  522. {
  523. $url = $this->getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget);
  524. return $this->sendRequest($url);
  525. }
  526. /**
  527. * Tracks a content interaction. Make sure you have tracked a content impression using the same content name and
  528. * content piece, otherwise it will not count. To do so you should call the method doTrackContentImpression();
  529. *
  530. * @param string $interaction The name of the interaction with the content. For instance a 'click'
  531. * @param string $contentName The name of the content. For instance 'Ad Foo Bar'
  532. * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text
  533. * @param string|false $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page.
  534. * @return mixed Response string or true if using bulk requests.
  535. */
  536. public function doTrackContentInteraction($interaction, $contentName, $contentPiece = 'Unknown', $contentTarget = false)
  537. {
  538. $url = $this->getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget);
  539. return $this->sendRequest($url);
  540. }
  541. /**
  542. * Tracks an internal Site Search query, and optionally tracks the Search Category, and Search results Count.
  543. * These are used to populate reports in Actions > Site Search.
  544. *
  545. * @param string $keyword Searched query on the site
  546. * @param string $category (optional) Search engine category if applicable
  547. * @param bool|int $countResults (optional) results displayed on the search result page. Used to track "zero result" keywords.
  548. *
  549. * @return mixed Response or true if using bulk requests.
  550. */
  551. public function doTrackSiteSearch($keyword, $category = '', $countResults = false)
  552. {
  553. $url = $this->getUrlTrackSiteSearch($keyword, $category, $countResults);
  554. return $this->sendRequest($url);
  555. }
  556. /**
  557. * Records a Goal conversion
  558. *
  559. * @param int $idGoal Id Goal to record a conversion
  560. * @param float $revenue Revenue for this conversion
  561. * @return mixed Response or true if using bulk request
  562. */
  563. public function doTrackGoal($idGoal, $revenue = 0.0)
  564. {
  565. $url = $this->getUrlTrackGoal($idGoal, $revenue);
  566. return $this->sendRequest($url);
  567. }
  568. /**
  569. * Tracks a download or outlink
  570. *
  571. * @param string $actionUrl URL of the download or outlink
  572. * @param string $actionType Type of the action: 'download' or 'link'
  573. * @return mixed Response or true if using bulk request
  574. */
  575. public function doTrackAction($actionUrl, $actionType)
  576. {
  577. // Referrer could be udpated to be the current URL temporarily (to mimic JS behavior)
  578. $url = $this->getUrlTrackAction($actionUrl, $actionType);
  579. return $this->sendRequest($url);
  580. }
  581. /**
  582. * Adds an item in the Ecommerce order.
  583. *
  584. * This should be called before doTrackEcommerceOrder(), or before doTrackEcommerceCartUpdate().
  585. * This function can be called for all individual products in the cart (or order).
  586. * SKU parameter is mandatory. Other parameters are optional (set to false if value not known).
  587. * Ecommerce items added via this function are automatically cleared when doTrackEcommerceOrder() or getUrlTrackEcommerceOrder() is called.
  588. *
  589. * @param string $sku (required) SKU, Product identifier
  590. * @param string $name (optional) Product name
  591. * @param string|array $category (optional) Product category, or array of product categories (up to 5 categories can be specified for a given product)
  592. * @param float|int $price (optional) Individual product price (supports integer and decimal prices)
  593. * @param int $quantity (optional) Product quantity. If not specified, will default to 1 in the Reports
  594. * @throws Exception
  595. */
  596. public function addEcommerceItem($sku, $name = '', $category = '', $price = 0.0, $quantity = 1)
  597. {
  598. if (empty($sku)) {
  599. throw new Exception("You must specify a SKU for the Ecommerce item");
  600. }
  601. $this->ecommerceItems[$sku] = array($sku, $name, $category, $price, $quantity);
  602. }
  603. /**
  604. * Tracks a Cart Update (add item, remove item, update item).
  605. *
  606. * On every Cart update, you must call addEcommerceItem() for each item (product) in the cart,
  607. * including the items that haven't been updated since the last cart update.
  608. * Items which were in the previous cart and are not sent in later Cart updates will be deleted from the cart (in the database).
  609. *
  610. * @param float $grandTotal Cart grandTotal (typically the sum of all items' prices)
  611. * @return mixed Response or true if using bulk request
  612. */
  613. public function doTrackEcommerceCartUpdate($grandTotal)
  614. {
  615. $url = $this->getUrlTrackEcommerceCartUpdate($grandTotal);
  616. return $this->sendRequest($url);
  617. }
  618. /**
  619. * Sends all stored tracking actions at once. Only has an effect if bulk tracking is enabled.
  620. *
  621. * To enable bulk tracking, call enableBulkTracking().
  622. *
  623. * @throws Exception
  624. * @return string Response
  625. */
  626. public function doBulkTrack()
  627. {
  628. if (empty($this->storedTrackingActions)) {
  629. throw new Exception("Error: you must call the function doTrackPageView or doTrackGoal from this class, before calling this method doBulkTrack()");
  630. }
  631. $data = array('requests' => $this->storedTrackingActions);
  632. // token_auth is not required by default, except if bulk_requests_require_authentication=1
  633. if(!empty($this->token_auth)) {
  634. $data['token_auth'] = $this->token_auth;
  635. }
  636. $postData = json_encode($data);
  637. $response = $this->sendRequest($this->getBaseUrl(), 'POST', $postData, $force = true);
  638. $this->storedTrackingActions = array();
  639. return $response;
  640. }
  641. /**
  642. * Tracks an Ecommerce order.
  643. *
  644. * If the Ecommerce order contains items (products), you must call first the addEcommerceItem() for each item in the order.
  645. * All revenues (grandTotal, subTotal, tax, shipping, discount) will be individually summed and reported in Piwik reports.
  646. * Only the parameters $orderId and $grandTotal are required.
  647. *
  648. * @param string|int $orderId (required) Unique Order ID.
  649. * This will be used to count this order only once in the event the order page is reloaded several times.
  650. * orderId must be unique for each transaction, even on different days, or the transaction will not be recorded by Piwik.
  651. * @param float $grandTotal (required) Grand Total revenue of the transaction (including tax, shipping, etc.)
  652. * @param float $subTotal (optional) Sub total amount, typically the sum of items prices for all items in this order (before Tax and Shipping costs are applied)
  653. * @param float $tax (optional) Tax amount for this order
  654. * @param float $shipping (optional) Shipping amount for this order
  655. * @param float $discount (optional) Discounted amount in this order
  656. * @return mixed Response or true if using bulk request
  657. */
  658. public function doTrackEcommerceOrder($orderId, $grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0)
  659. {
  660. $url = $this->getUrlTrackEcommerceOrder($orderId, $grandTotal, $subTotal, $tax, $shipping, $discount);
  661. return $this->sendRequest($url);
  662. }
  663. /**
  664. * Sets the current page view as an item (product) page view, or an Ecommerce Category page view.
  665. *
  666. * This must be called before doTrackPageView() on this product/category page.
  667. * It will set 3 custom variables of scope "page" with the SKU, Name and Category for this page view.
  668. * Note: Custom Variables of scope "page" slots 3, 4 and 5 will be used.
  669. *
  670. * On a category page, you may set the parameter $category only and set the other parameters to false.
  671. *
  672. * Tracking Product/Category page views will allow Piwik to report on Product & Categories
  673. * conversion rates (Conversion rate = Ecommerce orders containing this product or category / Visits to the product or category)
  674. *
  675. * @param string $sku Product SKU being viewed
  676. * @param string $name Product Name being viewed
  677. * @param string|array $category Category being viewed. On a Product page, this is the product's category.
  678. * You can also specify an array of up to 5 categories for a given page view.
  679. * @param float $price Specify the price at which the item was displayed
  680. */
  681. public function setEcommerceView($sku = '', $name = '', $category = '', $price = 0.0)
  682. {
  683. if (!empty($category)) {
  684. if (is_array($category)) {
  685. $category = json_encode($category);
  686. }
  687. } else {
  688. $category = "";
  689. }
  690. $this->pageCustomVar[self::CVAR_INDEX_ECOMMERCE_ITEM_CATEGORY] = array('_pkc', $category);
  691. if (!empty($price)) {
  692. $this->pageCustomVar[self::CVAR_INDEX_ECOMMERCE_ITEM_PRICE] = array('_pkp', (float)$price);
  693. }
  694. // On a category page, do not record "Product name not defined"
  695. if (empty($sku) && empty($name)) {
  696. return;
  697. }
  698. if (!empty($sku)) {
  699. $this->pageCustomVar[self::CVAR_INDEX_ECOMMERCE_ITEM_SKU] = array('_pks', $sku);
  700. }
  701. if (empty($name)) {
  702. $name = "";
  703. }
  704. $this->pageCustomVar[self::CVAR_INDEX_ECOMMERCE_ITEM_NAME] = array('_pkn', $name);
  705. }
  706. /**
  707. * Returns URL used to track Ecommerce Cart updates
  708. * Calling this function will reinitializes the property ecommerceItems to empty array
  709. * so items will have to be added again via addEcommerceItem()
  710. * @ignore
  711. */
  712. public function getUrlTrackEcommerceCartUpdate($grandTotal)
  713. {
  714. $url = $this->getUrlTrackEcommerce($grandTotal);
  715. return $url;
  716. }
  717. /**
  718. * Returns URL used to track Ecommerce Orders
  719. * Calling this function will reinitializes the property ecommerceItems to empty array
  720. * so items will have to be added again via addEcommerceItem()
  721. * @ignore
  722. */
  723. public function getUrlTrackEcommerceOrder($orderId, $grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0)
  724. {
  725. if (empty($orderId)) {
  726. throw new Exception("You must specifiy an orderId for the Ecommerce order");
  727. }
  728. $url = $this->getUrlTrackEcommerce($grandTotal, $subTotal, $tax, $shipping, $discount);
  729. $url .= '&ec_id=' . urlencode($orderId);
  730. $this->ecommerceLastOrderTimestamp = $this->getTimestamp();
  731. return $url;
  732. }
  733. /**
  734. * Returns URL used to track Ecommerce orders
  735. * Calling this function will reinitializes the property ecommerceItems to empty array
  736. * so items will have to be added again via addEcommerceItem()
  737. * @ignore
  738. */
  739. protected function getUrlTrackEcommerce($grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0)
  740. {
  741. if (!is_numeric($grandTotal)) {
  742. throw new Exception("You must specifiy a grandTotal for the Ecommerce order (or Cart update)");
  743. }
  744. $url = $this->getRequest($this->idSite);
  745. $url .= '&idgoal=0';
  746. if (!empty($grandTotal)) {
  747. $url .= '&revenue=' . $grandTotal;
  748. }
  749. if (!empty($subTotal)) {
  750. $url .= '&ec_st=' . $subTotal;
  751. }
  752. if (!empty($tax)) {
  753. $url .= '&ec_tx=' . $tax;
  754. }
  755. if (!empty($shipping)) {
  756. $url .= '&ec_sh=' . $shipping;
  757. }
  758. if (!empty($discount)) {
  759. $url .= '&ec_dt=' . $discount;
  760. }
  761. if (!empty($this->ecommerceItems)) {
  762. // Removing the SKU index in the array before JSON encoding
  763. $items = array();
  764. foreach ($this->ecommerceItems as $item) {
  765. $items[] = $item;
  766. }
  767. $url .= '&ec_items=' . urlencode(json_encode($items));
  768. }
  769. $this->ecommerceItems = array();
  770. return $url;
  771. }
  772. /**
  773. * Builds URL to track a page view.
  774. *
  775. * @see doTrackPageView()
  776. * @param string $documentTitle Page view name as it will appear in Piwik reports
  777. * @return string URL to piwik.php with all parameters set to track the pageview
  778. */
  779. public function getUrlTrackPageView($documentTitle = '')
  780. {
  781. $url = $this->getRequest($this->idSite);
  782. if (strlen($documentTitle) > 0) {
  783. $url .= '&action_name=' . urlencode($documentTitle);
  784. }
  785. return $url;
  786. }
  787. /**
  788. * Builds URL to track a custom event.
  789. *
  790. * @see doTrackEvent()
  791. * @param string $category The Event Category (Videos, Music, Games...)
  792. * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
  793. * @param string $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...)
  794. * @param float $value (optional) The Event's value
  795. * @return string URL to piwik.php with all parameters set to track the pageview
  796. */
  797. public function getUrlTrackEvent($category, $action, $name = false, $value = false)
  798. {
  799. $url = $this->getRequest($this->idSite);
  800. if(strlen($category) == 0) {
  801. throw new Exception("You must specify an Event Category name (Music, Videos, Games...).");
  802. }
  803. if(strlen($action) == 0) {
  804. throw new Exception("You must specify an Event action (click, view, add...).");
  805. }
  806. $url .= '&e_c=' . urlencode($category);
  807. $url .= '&e_a=' . urlencode($action);
  808. if(strlen($name) > 0) {
  809. $url .= '&e_n=' . urlencode($name);
  810. }
  811. if(strlen($value) > 0) {
  812. $url .= '&e_v=' . $value;
  813. }
  814. return $url;
  815. }
  816. /**
  817. * Builds URL to track a content impression.
  818. *
  819. * @see doTrackContentImpression()
  820. * @param string $contentName The name of the content. For instance 'Ad Foo Bar'
  821. * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text
  822. * @param string|false $contentTarget (optional) The target of the content. For instance the URL of a landing page.
  823. * @throws Exception In case $contentName is empty
  824. * @return string URL to piwik.php with all parameters set to track the pageview
  825. */
  826. public function getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget)
  827. {
  828. $url = $this->getRequest($this->idSite);
  829. if (strlen($contentName) == 0) {
  830. throw new Exception("You must specify a content name");
  831. }
  832. $url .= '&c_n=' . urlencode($contentName);
  833. if (!empty($contentPiece) && strlen($contentPiece) > 0) {
  834. $url .= '&c_p=' . urlencode($contentPiece);
  835. }
  836. if (!empty($contentTarget) && strlen($contentTarget) > 0) {
  837. $url .= '&c_t=' . urlencode($contentTarget);
  838. }
  839. return $url;
  840. }
  841. /**
  842. * Builds URL to track a content impression.
  843. *
  844. * @see doTrackContentInteraction()
  845. * @param string $interaction The name of the interaction with the content. For instance a 'click'
  846. * @param string $contentName The name of the content. For instance 'Ad Foo Bar'
  847. * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text
  848. * @param string|false $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page.
  849. * @throws Exception In case $interaction or $contentName is empty
  850. * @return string URL to piwik.php with all parameters set to track the pageview
  851. */
  852. public function getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget)
  853. {
  854. $url = $this->getRequest($this->idSite);
  855. if (strlen($interaction) == 0) {
  856. throw new Exception("You must specify a name for the interaction");
  857. }
  858. if (strlen($contentName) == 0) {
  859. throw new Exception("You must specify a content name");
  860. }
  861. $url .= '&c_i=' . urlencode($interaction);
  862. $url .= '&c_n=' . urlencode($contentName);
  863. if (!empty($contentPiece) && strlen($contentPiece) > 0) {
  864. $url .= '&c_p=' . urlencode($contentPiece);
  865. }
  866. if (!empty($contentTarget) && strlen($contentTarget) > 0) {
  867. $url .= '&c_t=' . urlencode($contentTarget);
  868. }
  869. return $url;
  870. }
  871. /**
  872. * Builds URL to track a site search.
  873. *
  874. * @see doTrackSiteSearch()
  875. * @param string $keyword
  876. * @param string $category
  877. * @param int $countResults
  878. * @return string
  879. */
  880. public function getUrlTrackSiteSearch($keyword, $category, $countResults)
  881. {
  882. $url = $this->getRequest($this->idSite);
  883. $url .= '&search=' . urlencode($keyword);
  884. if (strlen($category) > 0) {
  885. $url .= '&search_cat=' . urlencode($category);
  886. }
  887. if (!empty($countResults) || $countResults === 0) {
  888. $url .= '&search_count=' . (int)$countResults;
  889. }
  890. return $url;
  891. }
  892. /**
  893. * Builds URL to track a goal with idGoal and revenue.
  894. *
  895. * @see doTrackGoal()
  896. * @param int $idGoal Id Goal to record a conversion
  897. * @param float $revenue Revenue for this conversion
  898. * @return string URL to piwik.php with all parameters set to track the goal conversion
  899. */
  900. public function getUrlTrackGoal($idGoal, $revenue = 0.0)
  901. {
  902. $url = $this->getRequest($this->idSite);
  903. $url .= '&idgoal=' . $idGoal;
  904. if (!empty($revenue)) {
  905. $url .= '&revenue=' . $revenue;
  906. }
  907. return $url;
  908. }
  909. /**
  910. * Builds URL to track a new action.
  911. *
  912. * @see doTrackAction()
  913. * @param string $actionUrl URL of the download or outlink
  914. * @param string $actionType Type of the action: 'download' or 'link'
  915. * @return string URL to piwik.php with all parameters set to track an action
  916. */
  917. public function getUrlTrackAction($actionUrl, $actionType)
  918. {
  919. $url = $this->getRequest($this->idSite);
  920. $url .= '&' . $actionType . '=' . $actionUrl;
  921. return $url;
  922. }
  923. /**
  924. * Overrides server date and time for the tracking requests.
  925. * By default Piwik will track requests for the "current datetime" but this function allows you
  926. * to track visits in the past. All times are in UTC.
  927. *
  928. * Allowed only for Super User, must be used along with setTokenAuth()
  929. * @see setTokenAuth()
  930. * @param string $dateTime Date with the format 'Y-m-d H:i:s', or a UNIX timestamp
  931. */
  932. public function setForceVisitDateTime($dateTime)
  933. {
  934. $this->forcedDatetime = $dateTime;
  935. }
  936. /**
  937. * Forces Piwik to create a new visit for the tracking request.
  938. *
  939. * By default, Piwik will create a new visit if the last request by this user was more than 30 minutes ago.
  940. * If you call setForceNewVisit() before calling doTrack*, then a new visit will be created for this request.
  941. *
  942. */
  943. public function setForceNewVisit()
  944. {
  945. $this->forcedNewVisit = true;
  946. }
  947. /**
  948. * Overrides IP address
  949. *
  950. * Allowed only for Super User, must be used along with setTokenAuth()
  951. * @see setTokenAuth()
  952. * @param string $ip IP string, eg. 130.54.2.1
  953. */
  954. public function setIp($ip)
  955. {
  956. $this->ip = $ip;
  957. }
  958. /**
  959. * Force the action to be recorded for a specific User. The User ID is a string representing a given user in your system.
  960. *
  961. * A User ID can be a username, UUID or an email address, or any number or string that uniquely identifies a user or client.
  962. *
  963. * @param string $userId Any user ID string (eg. email address, ID, username). Must be non empty. Set to false to de-assign a user id previously set.
  964. * @throws Exception
  965. */
  966. public function setUserId($userId)
  967. {
  968. if($userId === false) {
  969. $this->setNewVisitorId();
  970. return;
  971. }
  972. if($userId === '') {
  973. throw new Exception("User ID cannot be empty.");
  974. }
  975. $this->userId = $userId;
  976. }
  977. /**
  978. * Hash function used internally by Piwik to hash a User ID into the Visitor ID.
  979. *
  980. * @param $id
  981. * @return string
  982. */
  983. static public function getUserIdHashed($id)
  984. {
  985. return substr( sha1( $id ), 0, 16);
  986. }
  987. /**
  988. * Forces the requests to be recorded for the specified Visitor ID.
  989. * Note: it is recommended to use ->setUserId($userId); instead.
  990. *
  991. * Rather than letting Piwik attribute the user with a heuristic based on IP and other user fingeprinting attributes,
  992. * force the action to be recorded for a particular visitor.
  993. *
  994. * If you use both setVisitorId and setUserId, setUserId will take precedence.
  995. * If not set, the visitor ID will be fetched from the 1st party cookie, or will be set to a random UUID.
  996. *
  997. * @deprecated We recommend to use ->setUserId($userId).
  998. * @param string $visitorId 16 hexadecimal characters visitor ID, eg. "33c31e01394bdc63"
  999. * @throws Exception
  1000. */
  1001. public function setVisitorId($visitorId)
  1002. {
  1003. $hexChars = '01234567890abcdefABCDEF';
  1004. if (strlen($visitorId) != self::LENGTH_VISITOR_ID
  1005. || strspn($visitorId, $hexChars) !== strlen($visitorId)
  1006. ) {
  1007. throw new Exception("setVisitorId() expects a "
  1008. . self::LENGTH_VISITOR_ID
  1009. . " characters hexadecimal string (containing only the following: "
  1010. . $hexChars
  1011. . ")");
  1012. }
  1013. $this->forcedVisitorId = $visitorId;
  1014. }
  1015. /**
  1016. * If the user initiating the request has the Piwik first party cookie,
  1017. * this function will try and return the ID parsed from this first party cookie (found in $_COOKIE).
  1018. *
  1019. * If you call this function from a server, where the call is triggered by a cron or script
  1020. * not initiated by the actual visitor being tracked, then it will return
  1021. * the random Visitor ID that was assigned to this visit object.
  1022. *
  1023. * This can be used if you wish to record more visits, actions or goals for this visitor ID later on.
  1024. *
  1025. * @return string 16 hex chars visitor ID string
  1026. */
  1027. public function getVisitorId()
  1028. {
  1029. if (!empty($this->userId)) {
  1030. return $this->getUserIdHashed($this->userId);
  1031. }
  1032. if (!empty($this->forcedVisitorId)) {
  1033. return $this->forcedVisitorId;
  1034. }
  1035. if ($this->loadVisitorIdCookie()) {
  1036. return $this->cookieVisitorId;
  1037. }
  1038. return $this->randomVisitorId;
  1039. }
  1040. /**
  1041. * Returns the User ID string, which may have been set via:
  1042. * $v->setUserId('username@example.org');
  1043. *
  1044. * @return bool
  1045. */
  1046. public function getUserId()
  1047. {
  1048. return $this->userId;
  1049. }
  1050. /**
  1051. * Loads values from the VisitorId Cookie
  1052. *
  1053. * @return bool True if cookie exists and is valid, False otherwise
  1054. */
  1055. protected function loadVisitorIdCookie()
  1056. {
  1057. $idCookie = $this->getCookieMatchingName('id');
  1058. if ($idCookie === false) {
  1059. return false;
  1060. }
  1061. $parts = explode('.', $idCookie);
  1062. if (strlen($parts[0]) != self::LENGTH_VISITOR_ID) {
  1063. return false;
  1064. }
  1065. $this->cookieVisitorId = $parts[0]; // provides backward compatibility since getVisitorId() didn't change any existing VisitorId value
  1066. $this->createTs = $parts[1];
  1067. $this->visitCount = (int)$parts[2];
  1068. $this->currentVisitTs = $parts[3];
  1069. $this->lastVisitTs = $parts[4];
  1070. if(isset($parts[5])) {
  1071. $this->lastEcommerceOrderTs = $parts[5];
  1072. }
  1073. return true;
  1074. }
  1075. /**
  1076. * Deletes all first party cookies from the client
  1077. */
  1078. public function deleteCookies()
  1079. {
  1080. $expire = $this->currentTs - 86400;
  1081. $cookies = array('id', 'ses', 'cvar', 'ref');
  1082. foreach($cookies as $cookie) {
  1083. $this->setCookie($cookie, '', $expire);
  1084. }
  1085. }
  1086. /**
  1087. * Returns the currently assigned Attribution Information stored in a first party cookie.
  1088. *
  1089. * This function will only work if the user is initiating the current request, and his cookies
  1090. * can be read by PHP from the $_COOKIE array.
  1091. *
  1092. * @return string JSON Encoded string containing the Referrer information for Goal conversion attribution.
  1093. * Will return false if the cookie could not be found
  1094. * @see Piwik.js getAttributionInfo()
  1095. */
  1096. public function getAttributionInfo()
  1097. {
  1098. if(!empty($this->attributionInfo)) {
  1099. return json_encode($this->attributionInfo);
  1100. }
  1101. return $this->getCookieMatchingName('ref');
  1102. }
  1103. /**
  1104. * Some Tracking API functionnality requires express authentication, using either the
  1105. * Super User token_auth, or a user with 'admin' access to the website.
  1106. *
  1107. * The following features require access:
  1108. * - force the visitor IP
  1109. * - force the date & time of the tracking requests rather than track for the current datetime
  1110. *
  1111. * @param string $token_auth token_auth 32 chars token_auth string
  1112. */
  1113. public function setTokenAuth($token_auth)
  1114. {
  1115. $this->token_auth = $token_auth;
  1116. }
  1117. /**
  1118. * Sets local visitor time
  1119. *
  1120. * @param string $time HH:MM:SS format
  1121. */
  1122. public function setLocalTime($time)
  1123. {
  1124. list($hour, $minute, $second) = explode(':', $time);
  1125. $this->localHour = (int)$hour;
  1126. $this->localMinute = (int)$minute;
  1127. $this->localSecond = (int)$second;
  1128. }
  1129. /**
  1130. * Sets user resolution width and height.
  1131. *
  1132. * @param int $width
  1133. * @param int $height
  1134. */
  1135. public function setResolution($width, $height)
  1136. {
  1137. $this->width = $width;
  1138. $this->height = $height;
  1139. }
  1140. /**
  1141. * Sets if the browser supports cookies
  1142. * This is reported in "List of plugins" report in Piwik.
  1143. *
  1144. * @param bool $bool
  1145. */
  1146. public function setBrowserHasCookies($bool)
  1147. {
  1148. $this->hasCookies = $bool;
  1149. }
  1150. /**
  1151. * Will append a custom string at the end of the Tracking request.
  1152. * @param string $string
  1153. */
  1154. public function setDebugStringAppend($string)
  1155. {
  1156. $this->DEBUG_APPEND_URL = '&' . $string;
  1157. }
  1158. /**
  1159. * Sets visitor browser supported plugins
  1160. *
  1161. * @param bool $flash
  1162. * @param bool $java
  1163. * @param bool $director
  1164. * @param bool $quickTime
  1165. * @param bool $realPlayer
  1166. * @param bool $pdf
  1167. * @param bool $windowsMedia
  1168. * @param bool $gears
  1169. * @param bool $silverlight
  1170. */
  1171. public function setPlugins($flash = false, $java = false, $director = false, $quickTime = false, $realPlayer = false, $pdf = false, $windowsMedia = false, $gears = false, $silverlight = false)
  1172. {
  1173. $this->plugins =
  1174. '&fla=' . (int)$flash .
  1175. '&java=' . (int)$java .
  1176. '&dir=' . (int)$director .
  1177. '&qt=' . (int)$quickTime .
  1178. '&realp=' . (int)$realPlayer .
  1179. '&pdf=' . (int)$pdf .
  1180. '&wma=' . (int)$windowsMedia .
  1181. '&gears=' . (int)$gears .
  1182. '&ag=' . (int)$silverlight;
  1183. }
  1184. /**
  1185. * By default, PiwikTracker will read first party cookies
  1186. * from the request and write updated cookies in the response (using setrawcookie).
  1187. * This can be disabled by calling this function.
  1188. */
  1189. public function disableCookieSupport()
  1190. {
  1191. $this->configCookiesDisabled = true;
  1192. }
  1193. /**
  1194. * Returns the maximum number of seconds the tracker will spend waiting for a response
  1195. * from Piwik. Defaults to 600 seconds.
  1196. */
  1197. public function getRequestTimeout()
  1198. {
  1199. return $this->requestTimeout;
  1200. }
  1201. /**
  1202. * Sets the maximum number of seconds that the tracker will spend waiting for a response
  1203. * from Piwik.
  1204. *
  1205. * @param int $timeout
  1206. * @throws Exception
  1207. */
  1208. public function setRequestTimeout($timeout)
  1209. {
  1210. if (!is_int($timeout) || $timeout < 0) {
  1211. throw new Exception("Invalid value supplied for request timeout: $timeout");
  1212. }
  1213. $this->requestTimeout = $timeout;
  1214. }
  1215. /**
  1216. * Used in tests to output useful error messages.
  1217. *
  1218. * @ignore
  1219. */
  1220. static public $DEBUG_LAST_REQUESTED_URL = false;
  1221. /**
  1222. * @ignore
  1223. */
  1224. protected function sendRequest($url, $method = 'GET', $data = null, $force = false)
  1225. {
  1226. self::$DEBUG_LAST_REQUESTED_URL = $url;
  1227. // if doing a bulk request, store the url
  1228. if ($this->doBulkRequests && !$force) {
  1229. $this->storedTrackingActions[]
  1230. = $url
  1231. . (!empty($this->userAgent) ? ('&ua=' . urlencode($this->userAgent)) : '')
  1232. . (!empty($this->acceptLanguage) ? ('&lang=' . urlencode($this->acceptLanguage)) : '');
  1233. // Clear custom variables so they don't get copied over to other users in the bulk request
  1234. $this->clearCustomVariables();
  1235. $this->userAgent = false;
  1236. $this->acceptLanguage = false;
  1237. return true;
  1238. }
  1239. if (function_exists('curl_init')) {
  1240. $options = array(
  1241. CURLOPT_URL => $url,
  1242. CURLOPT_USERAGENT => $this->userAgent,
  1243. CURLOPT_HEADER => true,
  1244. CURLOPT_TIMEOUT => $this->requestTimeout,
  1245. CURLOPT_RETURNTRANSFER => true,
  1246. CURLOPT_HTTPHEADER => array(
  1247. 'Accept-Language: ' . $this->acceptLanguage
  1248. ));
  1249. switch ($method) {
  1250. case 'POST':
  1251. $options[CURLOPT_POST] = TRUE;
  1252. break;
  1253. default:
  1254. break;
  1255. }
  1256. // only supports JSON data
  1257. if (!empty($data)) {
  1258. $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
  1259. $options[CURLOPT_HTTPHEADER][] = 'Expect:';
  1260. $options[CURLOPT_POSTFIELDS] = $data;
  1261. }
  1262. $ch = curl_init();
  1263. curl_setopt_array($ch, $options);
  1264. ob_start();
  1265. $response = @curl_exec($ch);
  1266. ob_end_clean();
  1267. $content = '';
  1268. if (!empty($response)) {
  1269. list($header, $content) = explode("\r\n\r\n", $response, $limitCount = 2);
  1270. }
  1271. } else if (function_exists('stream_context_create')) {
  1272. $stream_options = array(
  1273. 'http' => array(
  1274. 'method' => $method,
  1275. 'user_agent' => $this->userAgent,
  1276. 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n",
  1277. 'timeout' => $this->requestTimeout, // PHP 5.2.1
  1278. )
  1279. );
  1280. // only supports JSON data
  1281. if (!empty($data)) {
  1282. $stream_options['http']['header'] .= "Content-Type: application/json \r\n";
  1283. $stream_options['http']['content'] = $data;
  1284. }
  1285. $ctx = stream_context_create($stream_options);
  1286. $response = file_get_contents($url, 0, $ctx);
  1287. $content = $response;
  1288. }
  1289. return $content;
  1290. }
  1291. /**
  1292. * Returns current timestamp, or forced timestamp/datetime if it was set
  1293. * @return string|int
  1294. */
  1295. protected function getTimestamp()
  1296. {
  1297. return !empty($this->forcedDatetime)
  1298. ? strtotime($this->forcedDatetime)
  1299. : time();
  1300. }
  1301. /**
  1302. * Returns the base URL for the piwik server.
  1303. */
  1304. protected function getBaseUrl()
  1305. {
  1306. if (empty(self::$URL)) {
  1307. throw new Exception('You must first set the Piwik Tracker URL by calling PiwikTracker::$URL = \'http://your-website.org/piwik/\';');
  1308. }
  1309. if (strpos(self::$URL, '/piwik.php') === false
  1310. && strpos(self::$URL, '/proxy-piwik.php') === false
  1311. ) {
  1312. self::$URL .= '/piwik.php';
  1313. }
  1314. return self::$URL;
  1315. }
  1316. /**
  1317. * @ignore
  1318. */
  1319. protected function getRequest($idSite)
  1320. {
  1321. $this->setFirstPartyCookies();
  1322. $url = $this->getBaseUrl() .
  1323. '?idsite=' . $idSite .
  1324. '&rec=1' .
  1325. '&apiv=' . self::VERSION .
  1326. '&r=' . substr(strval(mt_rand()), 2, 6) .
  1327. // XDEBUG_SESSIONS_START and KEY are related to the PHP Debugger, this can be ignored in other languages
  1328. (!empty($_GET['XDEBUG_SESSION_START']) ? '&XDEBUG_SESSION_START=' . @urlencode($_GET['XDEBUG_SESSION_START']) : '') .
  1329. (!empty($_GET['KEY']) ? '&KEY=' . @urlencode($_GET['KEY']) : '') .
  1330. // Only allowed for Super User, token_auth required,
  1331. (!empty($this->ip) ? '&cip=' . $this->ip : '') .
  1332. (!empty($this->userId) ? '&uid=' . urlencode($this->userId) : '') .
  1333. (!empty($this->forcedDatetime) ? '&cdt=' . urlencode($this->forcedDatetime) : '') .
  1334. (!empty($this->forcedNewVisit) ? '&new_visit=1' : '') .
  1335. ((!empty($this->token_auth) && !$this->doBulkRequests) ? '&token_auth=' . urlencode($this->token_auth) : '') .
  1336. // Values collected from cookie
  1337. '&_idts=' . $this->createTs .
  1338. '&_idvc=' . $this->visitCount .
  1339. (!empty($this->lastVisitTs) ? '&_viewts=' . $this->lastVisitTs : '' ) .
  1340. (!empty($this->lastEcommerceOrderTs) ? '&_ects=' . $this->lastEcommerceOrderTs : '' ) .
  1341. // These parameters are set by the JS, but optional when using API
  1342. (!empty($this->plugins) ? $this->plugins : '') .
  1343. (($this->localHour !== false && $this->localMinute !== false && $this->localSecond !== false) ? '&h=' . $this->localHour . '&m=' . $this->localMinute . '&s=' . $this->localSecond : '') .
  1344. (!empty($this->width) && !empty($this->height) ? '&res=' . $this->width . 'x' . $this->height : '') .
  1345. (!empty($this->hasCookies) ? '&cookie=' . $this->hasCookies : '') .
  1346. (!empty($this->ecommerceLastOrderTimestamp) ? '&_ects=' . urlencode($this->ecommerceLastOrderTimestamp) : '') .
  1347. // Various important attributes
  1348. (!empty($this->customData) ? '&data=' . $this->customData : '') .
  1349. (!empty($this->visitorCustomVar) ? '&_cvar=' . urlencode(json_encode($this->visitorCustomVar)) : '') .
  1350. (!empty($this->pageCustomVar) ? '&cvar=' . urlencode(json_encode($this->pageCustomVar)) : '') .
  1351. (!empty($this->eventCustomVar) ? '&e_cvar=' . urlencode(json_encode($this->eventCustomVar)) : '') .
  1352. (!empty($this->generationTime) ? '&gt_ms=' . ((int)$this->generationTime) : '') .
  1353. (!empty($this->forcedVisitorId) ? '&cid=' . $this->forcedVisitorId : '&_id=' . $this->getVisitorId()) .
  1354. // URL parameters
  1355. '&url=' . urlencode($this->pageUrl) .
  1356. '&urlref=' . urlencode($this->urlReferrer) .
  1357. ((!empty($this->pageCharset) && $this->pageCharset != self::DEFAULT_CHARSET_PARAMETER_VALUES) ? '&cs=' . $this->pageCharset : '') .
  1358. // Attribution information, so that Goal conversions are attributed to the right referrer or campaign
  1359. // Campaign name
  1360. (!empty($this->attributionInfo[0]) ? '&_rcn=' . urlencode($this->attributionInfo[0]) : '') .
  1361. // Campaign keyword
  1362. (!empty($this->attributionInfo[1]) ? '&_rck=' . urlencode($this->attributionInfo[1]) : '') .
  1363. // Timestamp at which the referrer was set
  1364. (!empty($this->attributionInfo[2]) ? '&_refts=' . $this->attributionInfo[2] : '') .
  1365. // Referrer URL
  1366. (!empty($this->attributionInfo[3]) ? '&_ref=' . urlencode($this->attributionInfo[3]) : '') .
  1367. // custom location info
  1368. (!empty($this->country) ? '&country=' . urlencode($this->country) : '') .
  1369. (!empty($this->region) ? '&region=' . urlencode($this->region) : '') .
  1370. (!empty($this->city) ? '&city=' . urlencode($this->city) : '') .
  1371. (!empty($this->lat) ? '&lat=' . urlencode($this->lat) : '') .
  1372. (!empty($this->long) ? '&long=' . urlencode($this->long) : '') .
  1373. // DEBUG
  1374. $this->DEBUG_APPEND_URL;
  1375. // Reset page level custom variables after this page view
  1376. $this->pageCustomVar = array();
  1377. $this->eventCustomVar = array();
  1378. // force new visit only once, user must call again setForceNewVisit()
  1379. $this->forcedNewVisit = false;
  1380. return $url;
  1381. }
  1382. /**
  1383. * Returns a first party cookie which name contains $name
  1384. *
  1385. * @param string $name
  1386. * @return string String value of cookie, or false if not found
  1387. * @ignore
  1388. */
  1389. protected function getCookieMatchingName($name)
  1390. {
  1391. if($this->configCookiesDisabled) {
  1392. return false;
  1393. }
  1394. $name = $this->getCookieName($name);
  1395. // Piwik cookie names use dots separators in piwik.js,
  1396. // but PHP Replaces . with _ http://www.php.net/manual/en/language.variables.predefined.php#72571
  1397. $name = str_replace('.', '_', $name);
  1398. foreach ($_COOKIE as $cookieName => $cookieValue) {
  1399. if (strpos($cookieName, $name) !== false) {
  1400. return $cookieValue;
  1401. }
  1402. }
  1403. return false;
  1404. }
  1405. /**
  1406. * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
  1407. * will return "/dir1/dir2/index.php"
  1408. *
  1409. * @return string
  1410. * @ignore
  1411. */
  1412. static protected function getCurrentScriptName()
  1413. {
  1414. $url = '';
  1415. if (!empty($_SERVER['PATH_INFO'])) {
  1416. $url = $_SERVER['PATH_INFO'];
  1417. } else if (!empty($_SERVER['REQUEST_URI'])) {
  1418. if (($pos = strpos($_SERVER['REQUEST_URI'], '?')) !== false) {
  1419. $url = substr($_SERVER['REQUEST_URI'], 0, $pos);
  1420. } else {
  1421. $url = $_SERVER['REQUEST_URI'];
  1422. }
  1423. }
  1424. if (empty($url)) {
  1425. $url = $_SERVER['SCRIPT_NAME'];
  1426. }
  1427. if ($url[0] !== '/') {
  1428. $url = '/' . $url;
  1429. }
  1430. return $url;
  1431. }
  1432. /**
  1433. * If the current URL is 'http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
  1434. * will return 'http'
  1435. *
  1436. * @return string 'https' or 'http'
  1437. * @ignore
  1438. */
  1439. static protected function getCurrentScheme()
  1440. {
  1441. if (isset($_SERVER['HTTPS'])
  1442. && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)
  1443. ) {
  1444. return 'https';
  1445. }
  1446. return 'http';
  1447. }
  1448. /**
  1449. * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
  1450. * will return "http://example.org"
  1451. *
  1452. * @return string
  1453. * @ignore
  1454. */
  1455. static protected function getCurrentHost()
  1456. {
  1457. if (isset($_SERVER['HTTP_HOST'])) {
  1458. return $_SERVER['HTTP_HOST'];
  1459. }
  1460. return 'unknown';
  1461. }
  1462. /**
  1463. * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
  1464. * will return "?param1=value1&param2=value2"
  1465. *
  1466. * @return string
  1467. * @ignore
  1468. */
  1469. static protected function getCurrentQueryString()
  1470. {
  1471. $url = '';
  1472. if (isset($_SERVER['QUERY_STRING'])
  1473. && !empty($_SERVER['QUERY_STRING'])
  1474. ) {
  1475. $url .= '?' . $_SERVER['QUERY_STRING'];
  1476. }
  1477. return $url;
  1478. }
  1479. /**
  1480. * Returns the current full URL (scheme, host, path and query string.
  1481. *
  1482. * @return string
  1483. * @ignore
  1484. */
  1485. static protected function getCurrentUrl()
  1486. {
  1487. return self::getCurrentScheme() . '://'
  1488. . self::getCurrentHost()
  1489. . self::getCurrentScriptName()
  1490. . self::getCurrentQueryString();
  1491. }
  1492. /**
  1493. * Sets the first party cookies as would the piwik.js
  1494. * All cookies are supported: 'id' and 'ses' and 'ref' and 'cvar' cookies.
  1495. */
  1496. protected function setFirstPartyCookies()
  1497. {
  1498. if ($this->configCookiesDisabled) {
  1499. return;
  1500. }
  1501. if (empty($this->cookieVisitorId)) {
  1502. $this->loadVisitorIdCookie();
  1503. }
  1504. // Set the 'ref' cookie
  1505. $attributionInfo = $this->getAttributionInfo();
  1506. if(!empty($attributionInfo)) {
  1507. $this->setCookie('ref', $attributionInfo, $this->configReferralCookieTimeout);
  1508. }
  1509. // Set the 'ses' cookie
  1510. $this->setCookie('ses', '*', $this->configSessionCookieTimeout);
  1511. // Set the 'id' cookie
  1512. $visitCount = $this->visitCount + 1;
  1513. $cookieValue = $this->getVisitorId() . '.' . $this->createTs . '.' . $visitCount . '.' . $this->currentTs . '.' . $this->lastVisitTs . '.' . $this->lastEcommerceOrderTs;
  1514. $this->setCookie('id', $cookieValue, $this->configVisitorCookieTimeout);
  1515. // Set the 'cvar' cookie
  1516. $this->setCookie('cvar', json_encode($this->visitorCustomVar), $this->configSessionCookieTimeout);
  1517. }
  1518. /**
  1519. * Sets a first party cookie to the client to improve dual JS-PHP tracking.
  1520. *
  1521. * This replicates the piwik.js tracker algorithms for consistency and better accuracy.
  1522. *
  1523. * @param $cookieName
  1524. * @param $cookieValue
  1525. * @param $cookieTTL
  1526. */
  1527. protected function setCookie($cookieName, $cookieValue, $cookieTTL)
  1528. {
  1529. $cookieExpire = $this->currentTs + $cookieTTL;
  1530. if(!headers_sent()) {
  1531. setcookie($this->getCookieName($cookieName), $cookieValue, $cookieExpire, $this->configCookiePath, $this->configCookieDomain);
  1532. }
  1533. }
  1534. /**
  1535. * @return bool|mixed
  1536. */
  1537. protected function getCustomVariablesFromCookie()
  1538. {
  1539. $cookie = $this->getCookieMatchingName('cvar');
  1540. if (!$cookie) {
  1541. return false;
  1542. }
  1543. return json_decode($cookie, $assoc = true);
  1544. }
  1545. }
  1546. /**
  1547. * Helper function to quickly generate the URL to track a page view.
  1548. *
  1549. * @param $idSite
  1550. * @param string $documentTitle
  1551. * @return string
  1552. */
  1553. function Piwik_getUrlTrackPageView($idSite, $documentTitle = '')
  1554. {
  1555. $tracker = new PiwikTracker($idSite);
  1556. return $tracker->getUrlTrackPageView($documentTitle);
  1557. }
  1558. /**
  1559. * Helper function to quickly generate the URL to track a goal.
  1560. *
  1561. * @param $idSite
  1562. * @param $idGoal
  1563. * @param float $revenue
  1564. * @return string
  1565. */
  1566. function Piwik_getUrlTrackGoal($idSite, $idGoal, $revenue = 0.0)
  1567. {
  1568. $tracker = new PiwikTracker($idSite);
  1569. return $tracker->getUrlTrackGoal($idGoal, $revenue);
  1570. }