您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

2111 行
70 KiB

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