pax_global_header00006660000000000000000000000064144073373060014521gustar00rootroot0000000000000052 comment=c9e7816d63ddf99bffb1414cfbe45f4c4edf1d4b matomo-php-tracker-3.2.0/000077500000000000000000000000001440733730600152355ustar00rootroot00000000000000matomo-php-tracker-3.2.0/.gitattributes000066400000000000000000000000601440733730600201240ustar00rootroot00000000000000tests/ export-ignore run_tests.sh export-ignore matomo-php-tracker-3.2.0/.gitignore000066400000000000000000000000571440733730600172270ustar00rootroot00000000000000/.idea/ /vendor/ /tests/.phpunit.result.cache matomo-php-tracker-3.2.0/CHANGELOG.md000066400000000000000000000021411440733730600170440ustar00rootroot00000000000000# Matomo PHP Tracker Changelog This is the Developer Changelog for Matomo PHP Tracker. All breaking changes or new features are listed below. ## Matomo PHP Tracker 3.0.0 Attention: This version of Matomo PHP Tracker is no longer compatible with Matomo 3.x or earlier - Support for new page performance metrics (added in Matomo 4) has been added. You can use `setPerformanceTimings()` to set them for page views. - Setting page generation time using `setGenerationTime()` has been discontinued. The method still exists to not break applications still using it, but it does not have any effect. Please use new page performance metrics as replacement. - Sending requests using cURL will now throw an exception if an error occurs in a request. - Matomo does not longer support tracking of these browser plugins: Gears, Director. Therefor the signature of `setPlugins()` changed. - Implementation of ecommerce views changed from custom variables to raw parameters - It is now possible to configure cookie options for Secure, HTTPOnly and SameSite. - Add method setRequestMethodNonBulk() to allow (non bulk) POST requests. matomo-php-tracker-3.2.0/LICENSE000066400000000000000000000027401440733730600162450ustar00rootroot00000000000000Copyright (c) 2014, Matomo Open Source Analytics All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. matomo-php-tracker-3.2.0/MatomoTracker.php000066400000000000000000002433111440733730600205220ustar00rootroot00000000000000ecommerceItems = []; $this->attributionInfo = false; $this->eventCustomVar = []; $this->forcedDatetime = false; $this->forcedNewVisit = false; $this->networkTime = false; $this->serverTime = false; $this->transferTime = false; $this->domProcessingTime = false; $this->domCompletionTime = false; $this->onLoadTime = false; $this->pageCustomVar = []; $this->ecommerceView = []; $this->customParameters = []; $this->customDimensions = []; $this->customData = false; $this->hasCookies = false; $this->token_auth = false; $this->userAgent = false; $this->country = false; $this->region = false; $this->city = false; $this->lat = false; $this->long = false; $this->width = false; $this->height = false; $this->plugins = false; $this->localHour = false; $this->localMinute = false; $this->localSecond = false; $this->idPageview = false; $this->idSite = $idSite; $this->urlReferrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; $this->pageCharset = self::DEFAULT_CHARSET_PARAMETER_VALUES; $this->pageUrl = self::getCurrentUrl(); $this->ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; $this->acceptLanguage = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : false; $this->userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : false; $this->clientHints = []; $this->setClientHints( !empty($_SERVER['HTTP_SEC_CH_UA_MODEL']) ? $_SERVER['HTTP_SEC_CH_UA_MODEL'] : '', !empty($_SERVER['HTTP_SEC_CH_UA_PLATFORM']) ? $_SERVER['HTTP_SEC_CH_UA_PLATFORM'] : '', !empty($_SERVER['HTTP_SEC_CH_UA_PLATFORM_VERSION']) ? $_SERVER['HTTP_SEC_CH_UA_PLATFORM_VERSION'] : '', !empty($_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST']) ? $_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST'] : '', !empty($_SERVER['HTTP_SEC_CH_UA_FULL_VERSION']) ? $_SERVER['HTTP_SEC_CH_UA_FULL_VERSION'] : '' ); if (!empty($apiUrl)) { self::$URL = $apiUrl; } // Life of the visitor cookie (in sec) $this->configVisitorCookieTimeout = 33955200; // 13 months (365 + 28 days) // Life of the session cookie (in sec) $this->configSessionCookieTimeout = 1800; // 30 minutes // Life of the session cookie (in sec) $this->configReferralCookieTimeout = 15768000; // 6 months // Visitor Ids in order $this->userId = false; $this->forcedVisitorId = false; $this->cookieVisitorId = false; $this->randomVisitorId = false; $this->setNewVisitorId(); $this->configCookiesDisabled = false; $this->configCookiePath = self::DEFAULT_COOKIE_PATH; $this->configCookieDomain = ''; $this->configCookieSameSite = ''; $this->configCookieSecure = false; $this->configCookieHTTPOnly = false; $this->currentTs = time(); $this->createTs = $this->currentTs; // Allow debug while blocking the request $this->requestTimeout = 600; $this->requestConnectTimeout = 300; $this->doBulkRequests = false; $this->storedTrackingActions = []; $this->sendImageResponse = true; $this->visitorCustomVar = $this->getCustomVariablesFromCookie(); $this->outgoingTrackerCookies = []; $this->incomingTrackerCookies = []; } public function setApiUrl(string $url) { self::$URL = $url; } /** * By default, Matomo expects utf-8 encoded values, for example * for the page URL parameter values, Page Title, etc. * It is recommended to only send UTF-8 data to Matomo. * If required though, you can also specify another charset using this function. * * @param string $charset * @return $this */ public function setPageCharset($charset = '') { $this->pageCharset = $charset; return $this; } /** * Sets the current URL being tracked * * @param string $url Raw URL (not URL encoded) * @return $this */ public function setUrl($url) { $this->pageUrl = $url; return $this; } /** * Sets the URL referrer used to track Referrers details for new visits. * * @param string $url Raw URL (not URL encoded) * @return $this */ public function setUrlReferrer($url) { $this->urlReferrer = $url; return $this; } /** * This method is deprecated and does nothing. It used to set the time that it took to generate the document on the server side. * * @param int $timeMs Generation time in ms * @return $this * * @deprecated this metric is deprecated please use performance timings instead * @see setPerformanceTimings */ public function setGenerationTime($timeMs) { return $this; } /** * Sets timings for various browser performance metrics. * @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming * * @param null|int $network Network time in ms (connectEnd – fetchStart) * @param null|int $server Server time in ms (responseStart – requestStart) * @param null|int $transfer Transfer time in ms (responseEnd – responseStart) * @param null|int $domProcessing DOM Processing to Interactive time in ms (domInteractive – domLoading) * @param null|int $domCompletion DOM Interactive to Complete time in ms (domComplete – domInteractive) * @param null|int $onload Onload time in ms (loadEventEnd – loadEventStart) * @return $this */ public function setPerformanceTimings($network = null, $server = null, $transfer = null, $domProcessing = null, $domCompletion = null, $onload = null) { $this->networkTime = $network; $this->serverTime = $server; $this->transferTime = $transfer; $this->domProcessingTime = $domProcessing; $this->domCompletionTime = $domCompletion; $this->onLoadTime = $onload; return $this; } /** * Clear / reset all previously set performance metrics. */ public function clearPerformanceTimings() { $this->networkTime = false; $this->serverTime = false; $this->transferTime = false; $this->domProcessingTime = false; $this->domCompletionTime = false; $this->onLoadTime = false; } /** * @deprecated * @ignore */ public function setUrlReferer($url) { $this->setUrlReferrer($url); return $this; } /** * Sets the attribution information to the visit, so that subsequent Goal conversions are * properly attributed to the right Referrer URL, timestamp, Campaign Name & Keyword. * * This must be a JSON encoded string that would typically be fetched from the JS API: * matomoTracker.getAttributionInfo() and that you have JSON encoded via JSON2.stringify() * * If you call enableCookies() then these referral attribution values will be set * to the 'ref' first party cookie storing referral information. * * @param string $jsonEncoded JSON encoded array containing Attribution info * @return $this * @throws Exception * @see function getAttributionInfo() in https://github.com/matomo-org/matomo/blob/master/js/matomo.js */ public function setAttributionInfo($jsonEncoded) { $decoded = json_decode($jsonEncoded, $assoc = true); if (!is_array($decoded)) { throw new Exception("setAttributionInfo() is expecting a JSON encoded string, $jsonEncoded given"); } $this->attributionInfo = $decoded; return $this; } /** * Sets Visit Custom Variable. * See https://matomo.org/docs/custom-variables/ * * @param int $id Custom variable slot ID from 1-5 * @param string $name Custom variable name * @param string $value Custom variable value * @param string $scope Custom variable scope. Possible values: visit, page, event * @return $this * @throws Exception */ public function setCustomVariable($id, $name, $value, $scope = 'visit') { if (!is_int($id)) { throw new Exception("Parameter id to setCustomVariable should be an integer"); } if ($scope == 'page') { $this->pageCustomVar[$id] = array($name, $value); } elseif ($scope == 'event') { $this->eventCustomVar[$id] = array($name, $value); } elseif ($scope == 'visit') { $this->visitorCustomVar[$id] = array($name, $value); } else { throw new Exception("Invalid 'scope' parameter value"); } return $this; } /** * Returns the currently assigned Custom Variable. * * If scope is 'visit', it will attempt to read the value set in the first party cookie created by Matomo Tracker * ($_COOKIE array). * * @param int $id Custom Variable integer index to fetch from cookie. Should be a value from 1 to 5 * @param string $scope Custom variable scope. Possible values: visit, page, event * * @throws Exception * @return mixed An array with this format: array( 0 => CustomVariableName, 1 => CustomVariableValue ) or false * @see matomo.js getCustomVariable() */ public function getCustomVariable($id, $scope = 'visit') { if ($scope == 'page') { return isset($this->pageCustomVar[$id]) ? $this->pageCustomVar[$id] : false; } elseif ($scope == 'event') { return isset($this->eventCustomVar[$id]) ? $this->eventCustomVar[$id] : false; } else { if ($scope != 'visit') { throw new Exception("Invalid 'scope' parameter value"); } } if (!empty($this->visitorCustomVar[$id])) { return $this->visitorCustomVar[$id]; } $cookieDecoded = $this->getCustomVariablesFromCookie(); if (!is_int($id)) { throw new Exception("Parameter to getCustomVariable should be an integer"); } if (!is_array($cookieDecoded) || !isset($cookieDecoded[$id]) || !is_array($cookieDecoded[$id]) || count($cookieDecoded[$id]) != 2 ) { return false; } return $cookieDecoded[$id]; } /** * Clears any Custom Variable that may be have been set. * * This can be useful when you have enabled bulk requests, * and you wish to clear Custom Variables of 'visit' scope. */ public function clearCustomVariables() { $this->visitorCustomVar = []; $this->pageCustomVar = []; $this->eventCustomVar = []; } /** * Sets a specific custom dimension * * @param int $id id of custom dimension * @param string $value value for custom dimension * @return $this */ public function setCustomDimension($id, $value) { $this->customDimensions['dimension'.(int)$id] = $value; return $this; } /** * Clears all previously set custom dimensions */ public function clearCustomDimensions() { $this->customDimensions = []; } /** * Returns the value of the custom dimension with the given id * * @param int $id id of custom dimension * @return string|null */ public function getCustomDimension($id) { return $this->customDimensions['dimension'.(int)$id] ?? null; } /** * Sets a custom tracking parameter. This is useful if you need to send any tracking parameters for a 3rd party * plugin that is not shipped with Matomo itself. Please note that custom parameters are cleared after each * tracking request. * * @param string $trackingApiParameter The name of the tracking API parameter, eg 'bw_bytes' * @param string $value Tracking parameter value that shall be sent for this tracking parameter. * @return $this * @throws Exception */ public function setCustomTrackingParameter($trackingApiParameter, $value) { $matches = []; if (preg_match('/^dimension([0-9]+)$/', $trackingApiParameter, $matches)) { $this->setCustomDimension($matches[1], $value); return $this; } $this->customParameters[$trackingApiParameter] = $value; return $this; } /** * Clear / reset all previously set custom tracking parameters. */ public function clearCustomTrackingParameters() { $this->customParameters = array(); } /** * Sets the current visitor ID to a random new one. * @return $this */ public function setNewVisitorId() { $this->randomVisitorId = substr(md5(uniqid(rand(), true)), 0, self::LENGTH_VISITOR_ID); $this->forcedVisitorId = false; $this->cookieVisitorId = false; return $this; } /** * Sets the current site ID. * * @param int $idSite * @return $this */ public function setIdSite($idSite) { $this->idSite = $idSite; return $this; } /** * Sets the Browser language. Used to guess visitor countries when GeoIP is not enabled * * @param string $acceptLanguage For example "fr-fr" * @return $this */ public function setBrowserLanguage($acceptLanguage) { $this->acceptLanguage = $acceptLanguage; return $this; } /** * Sets the user agent, used to detect OS and browser. * If this function is not called, the User Agent will default to the current user agent. * * @param string $userAgent * @return $this */ public function setUserAgent($userAgent) { $this->userAgent = $userAgent; return $this; } /** * Sets the client hints, used to detect OS and browser. * If this function is not called, the client hints sent with the current request will be used. * * Supported as of Matomo 4.12.0 * * @param string $model Value of the header 'HTTP_SEC_CH_UA_MODEL' * @param string $platform Value of the header 'HTTP_SEC_CH_UA_PLATFORM' * @param string $platformVersion Value of the header 'HTTP_SEC_CH_UA_PLATFORM_VERSION' * @param string|array $fullVersionList Value of header 'HTTP_SEC_CH_UA_FULL_VERSION_LIST' or an array containing * all brands with the structure * [['brand' => 'Chrome', 'version' => '10.0.2'], ['brand' => '...] * @param string $uaFullVersion Value of the header 'HTTP_SEC_CH_UA_FULL_VERSION' * * @return $this */ public function setClientHints($model = '', $platform = '', $platformVersion = '', $fullVersionList = '', $uaFullVersion = '') { if (is_string($fullVersionList)) { $reg = '/^"([^"]+)"; ?v="([^"]+)"(?:, )?/'; $list = []; while (\preg_match($reg, $fullVersionList, $matches)) { $list[] = ['brand' => $matches[1], 'version' => $matches[2]]; $fullVersionList = \substr($fullVersionList, \strlen($matches[0])); } $fullVersionList = $list; } elseif (!is_array($fullVersionList)) { $fullVersionList = []; } $this->clientHints = array_filter([ 'model' => $model, 'platform' => $platform, 'platformVersion' => $platformVersion, 'uaFullVersion' => $uaFullVersion, 'fullVersionList' => $fullVersionList, ]); return $this; } /** * Sets the country of the visitor. If not used, Matomo will try to find the country * using either the visitor's IP address or language. * * Allowed only for Admin/Super User, must be used along with setTokenAuth(). * @param string $country * @return $this */ public function setCountry($country) { $this->country = $country; return $this; } /** * Sets the region of the visitor. If not used, Matomo may try to find the region * using the visitor's IP address (if configured to do so). * * Allowed only for Admin/Super User, must be used along with setTokenAuth(). * @param string $region * @return $this */ public function setRegion($region) { $this->region = $region; return $this; } /** * Sets the city of the visitor. If not used, Matomo may try to find the city * using the visitor's IP address (if configured to do so). * * Allowed only for Admin/Super User, must be used along with setTokenAuth(). * @param string $city * @return $this */ public function setCity($city) { $this->city = $city; return $this; } /** * Sets the latitude of the visitor. If not used, Matomo may try to find the visitor's * latitude using the visitor's IP address (if configured to do so). * * Allowed only for Admin/Super User, must be used along with setTokenAuth(). * @param float $lat * @return $this */ public function setLatitude($lat) { $this->lat = $lat; return $this; } /** * Sets the longitude of the visitor. If not used, Matomo may try to find the visitor's * longitude using the visitor's IP address (if configured to do so). * * Allowed only for Admin/Super User, must be used along with setTokenAuth(). * @param float $long * @return $this */ public function setLongitude($long) { $this->long = $long; return $this; } /** * Enables the bulk request feature. When used, each tracking action is stored until the * doBulkTrack method is called. This method will send all tracking data at once. * */ public function enableBulkTracking() { $this->doBulkRequests = true; } /** * Disables the bulk request feature. Make sure to call `doBulkTrack()` before disabling it if you have stored * tracking actions previously as this method won't be sending any previously stored actions before disabling it. * */ public function disableBulkTracking() { $this->doBulkRequests = false; } /** * Enable Cookie Creation - this will cause a first party VisitorId cookie to be set when the VisitorId is set or reset * * @param string $domain (optional) Set first-party cookie domain. * Accepted values: example.com, *.example.com (same as .example.com) or subdomain.example.com * @param string $path (optional) Set first-party cookie path * @param bool $secure (optional) Set secure flag for cookies * @param bool $httpOnly (optional) Set HTTPOnly flag for cookies * @param string $sameSite (optional) Set SameSite flag for cookies */ public function enableCookies($domain = '', $path = '/', $secure = false, $httpOnly = false, $sameSite = '') { $this->configCookiesDisabled = false; $this->configCookieDomain = self::domainFixup($domain); $this->configCookiePath = $path; $this->configCookieSecure = $secure; $this->configCookieHTTPOnly = $httpOnly; $this->configCookieSameSite = $sameSite; } /** * If image response is disabled Matomo will respond with a HTTP 204 header instead of responding with a gif. */ public function disableSendImageResponse() { $this->sendImageResponse = false; } /** * Fix-up domain */ protected static function domainFixup($domain) { if (strlen($domain) > 0) { $dl = strlen($domain) - 1; // remove trailing '.' if ($domain[$dl] === '.') { $domain = substr($domain, 0, $dl); } // remove leading '*' if (substr($domain, 0, 2) === '*.') { $domain = substr($domain, 1); } } return $domain; } /** * Get cookie name with prefix and domain hash * @param string $cookieName * @return string */ protected function getCookieName($cookieName) { // NOTE: If the cookie name is changed, we must also update the method in matomo.js with the same name. $hash = substr( sha1( ($this->configCookieDomain == '' ? self::getCurrentHost() : $this->configCookieDomain) . $this->configCookiePath ), 0, 4 ); return self::FIRST_PARTY_COOKIES_PREFIX . $cookieName . '.' . $this->idSite . '.' . $hash; } /** * Tracks a page view * * @param string $documentTitle Page title as it will appear in the Actions > Page titles report * @return mixed Response string or true if using bulk requests. */ public function doTrackPageView($documentTitle) { $this->generateNewPageviewId(); $url = $this->getUrlTrackPageView($documentTitle); return $this->sendRequest($url); } private function generateNewPageviewId() { $this->idPageview = substr(md5(uniqid(rand(), true)), 0, 6); } /** * Tracks an event * * @param string $category The Event Category (Videos, Music, Games...) * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) * @param string|bool $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...) * @param float|bool $value (optional) The Event's value * @return mixed Response string or true if using bulk requests. */ public function doTrackEvent($category, $action, $name = false, $value = false) { $url = $this->getUrlTrackEvent($category, $action, $name, $value); return $this->sendRequest($url); } /** * Tracks a content impression * * @param string $contentName The name of the content. For instance 'Ad Foo Bar' * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text * @param string|bool $contentTarget (optional) The target of the content. For instance the URL of a landing page. * @return mixed Response string or true if using bulk requests. */ public function doTrackContentImpression($contentName, $contentPiece = 'Unknown', $contentTarget = false) { $url = $this->getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget); return $this->sendRequest($url); } /** * Tracks a content interaction. Make sure you have tracked a content impression using the same content name and * content piece, otherwise it will not count. To do so you should call the method doTrackContentImpression(); * * @param string $interaction The name of the interaction with the content. For instance a 'click' * @param string $contentName The name of the content. For instance 'Ad Foo Bar' * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text * @param string|bool $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page. * @return mixed Response string or true if using bulk requests. */ public function doTrackContentInteraction( $interaction, $contentName, $contentPiece = 'Unknown', $contentTarget = false ) { $url = $this->getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget); return $this->sendRequest($url); } /** * Tracks an internal Site Search query, and optionally tracks the Search Category, and Search results Count. * These are used to populate reports in Actions > Site Search. * * @param string $keyword Searched query on the site * @param string $category (optional) Search engine category if applicable * @param bool|int $countResults (optional) results displayed on the search result page. Used to track "zero result" keywords. * * @return mixed Response or true if using bulk requests. */ public function doTrackSiteSearch($keyword, $category = '', $countResults = false) { $url = $this->getUrlTrackSiteSearch($keyword, $category, $countResults); return $this->sendRequest($url); } /** * Records a Goal conversion * * @param int $idGoal Id Goal to record a conversion * @param float $revenue Revenue for this conversion * @return mixed Response or true if using bulk request */ public function doTrackGoal($idGoal, $revenue = 0.0) { $url = $this->getUrlTrackGoal($idGoal, $revenue); return $this->sendRequest($url); } /** * Tracks a download or outlink * * @param string $actionUrl URL of the download or outlink * @param string $actionType Type of the action: 'download' or 'link' * @return mixed Response or true if using bulk request */ public function doTrackAction($actionUrl, $actionType) { // Referrer could be udpated to be the current URL temporarily (to mimic JS behavior) $url = $this->getUrlTrackAction($actionUrl, $actionType); return $this->sendRequest($url); } /** * Adds an item in the Ecommerce order. * * This should be called before doTrackEcommerceOrder(), or before doTrackEcommerceCartUpdate(). * This function can be called for all individual products in the cart (or order). * SKU parameter is mandatory. Other parameters are optional (set to false if value not known). * Ecommerce items added via this function are automatically cleared when doTrackEcommerceOrder() or getUrlTrackEcommerceOrder() is called. * * @param string $sku (required) SKU, Product identifier * @param string $name (optional) Product name * @param string|array $category (optional) Product category, or array of product categories (up to 5 categories can be specified for a given product) * @param float|int $price (optional) Individual product price (supports integer and decimal prices) * @param int $quantity (optional) Product quantity. If not specified, will default to 1 in the Reports * @throws Exception * @return $this */ public function addEcommerceItem($sku, $name = '', $category = '', $price = 0.0, $quantity = 1) { if (empty($sku)) { throw new Exception("You must specify a SKU for the Ecommerce item"); } $price = $this->forceDotAsSeparatorForDecimalPoint($price); $this->ecommerceItems[] = array($sku, $name, $category, $price, $quantity); return $this; } /** * Tracks a Cart Update (add item, remove item, update item). * * On every Cart update, you must call addEcommerceItem() for each item (product) in the cart, * including the items that haven't been updated since the last cart update. * Items which were in the previous cart and are not sent in later Cart updates will be deleted from the cart (in the database). * * @param float $grandTotal Cart grandTotal (typically the sum of all items' prices) * @return mixed Response or true if using bulk request */ public function doTrackEcommerceCartUpdate($grandTotal) { $url = $this->getUrlTrackEcommerceCartUpdate($grandTotal); return $this->sendRequest($url); } /** * Sends all stored tracking actions at once. Only has an effect if bulk tracking is enabled. * * To enable bulk tracking, call enableBulkTracking(). * * @throws Exception * @return string Response */ public function doBulkTrack() { if (empty($this->storedTrackingActions)) { throw new Exception( "Error: you must call the function doTrackPageView or doTrackGoal from this class, before calling this method doBulkTrack()" ); } $data = array('requests' => $this->storedTrackingActions); // token_auth is not required by default, except if bulk_requests_require_authentication=1 if (!empty($this->token_auth)) { $data['token_auth'] = $this->token_auth; } $postData = json_encode($data); $response = $this->sendRequest($this->getBaseUrl(), 'POST', $postData, $force = true); $this->storedTrackingActions = array(); return $response; } /** * Tracks an Ecommerce order. * * If the Ecommerce order contains items (products), you must call first the addEcommerceItem() for each item in the order. * All revenues (grandTotal, subTotal, tax, shipping, discount) will be individually summed and reported in Matomo reports. * Only the parameters $orderId and $grandTotal are required. * * @param string|int $orderId (required) Unique Order ID. * This will be used to count this order only once in the event the order page is reloaded several times. * orderId must be unique for each transaction, even on different days, or the transaction will not be recorded by Matomo. * @param float $grandTotal (required) Grand Total revenue of the transaction (including tax, shipping, etc.) * @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) * @param float $tax (optional) Tax amount for this order * @param float $shipping (optional) Shipping amount for this order * @param float $discount (optional) Discounted amount in this order * @return mixed Response or true if using bulk request */ public function doTrackEcommerceOrder( $orderId, $grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0 ) { $url = $this->getUrlTrackEcommerceOrder($orderId, $grandTotal, $subTotal, $tax, $shipping, $discount); return $this->sendRequest($url); } /** * Tracks a PHP Throwable a crash (requires CrashAnalytics to be enabled in the target Matomo) * * @param Throwable $ex (required) the throwable to track. The message, stack trace, file location and line number * of the crash are deduced from this parameter. The crash type is set to the class name of * the Throwable. * @param string|null $category (optional) a category value for this crash. This can be any information you want * to attach to the crash. * @return mixed Response or true if using bulk request */ public function doTrackPhpThrowable(\Throwable $ex, $category = null) { $message = $ex->getMessage(); $stack = $ex->getTraceAsString(); $type = get_class($ex); $location = $ex->getFile(); $line = $ex->getLine(); return $this->doTrackCrash($message, $type, $category, $stack, $location, $line); } /** * Track a crash (requires CrashAnalytics to be enabled in the target Matomo) * * @param string $message (required) the error message. * @param string|null $type (optional) the error type, such as the class name of an Exception. * @param string|null $category (optional) a category value for this crash. This can be any information you want * to attach to the crash. * @param string|null $stack (optional) the stack trace of the crash. * @param string|null $location (optional) the source file URI where the crash originated. * @param int|null $line (optional) the source file line where the crash originated. * @param int|null $column (optional) the source file column where the crash originated. * @return mixed Response or true if using bulk request */ public function doTrackCrash($message, $type = null, $category = null, $stack = null, $location = null, $line = null, $column = null) { $url = $this->getUrlTrackCrash($message, $type, $category, $stack, $location, $line, $column); return $this->sendRequest($url); } /** * Sends a ping request. * * Ping requests do not track new actions. If they are sent within the standard visit length (see global.ini.php), * they will extend the existing visit and the current last action for the visit. If after the standard visit length, * ping requests will create a new visit using the last action in the last known visit. * * @return mixed Response or true if using bulk request */ public function doPing() { $url = $this->getRequest($this->idSite); $url .= '&ping=1'; return $this->sendRequest($url); } /** * Sets the current page view as an item (product) page view, or an Ecommerce Category page view. * * This must be called before doTrackPageView() on this product/category page. * * On a category page, you may set the parameter $category only and set the other parameters to false. * * Tracking Product/Category page views will allow Matomo to report on Product & Categories * conversion rates (Conversion rate = Ecommerce orders containing this product or category / Visits to the product or category) * * @param string $sku Product SKU being viewed * @param string $name Product Name being viewed * @param string|array $category Category being viewed. On a Product page, this is the product's category. * You can also specify an array of up to 5 categories for a given page view. * @param float $price Specify the price at which the item was displayed * @return $this */ public function setEcommerceView($sku = '', $name = '', $category = '', $price = 0.0) { $this->ecommerceView = []; if (!empty($category)) { if (is_array($category)) { $category = json_encode($category); } } else { $category = ""; } $this->ecommerceView['_pkc'] = $category; if (!empty($price)) { $price = (float)$price; $price = $this->forceDotAsSeparatorForDecimalPoint($price); $this->ecommerceView['_pkp'] = $price; } // On a category page, do not record "Product name not defined" if (empty($sku) && empty($name)) { return $this; } if (!empty($sku)) { $this->ecommerceView['_pks'] = $sku; } if (empty($name)) { $name = ""; } $this->ecommerceView['_pkn'] = $name; return $this; } /** * Force the separator for decimal point to be a dot. See https://github.com/matomo-org/matomo/issues/6435 * If for instance a German locale is used it would be a comma otherwise. * * @param float|string $value * @return string */ private function forceDotAsSeparatorForDecimalPoint($value) { if (null === $value || false === $value) { return $value; } return str_replace(',', '.', $value); } /** * Returns URL used to track Ecommerce Cart updates * Calling this function will reinitializes the property ecommerceItems to empty array * so items will have to be added again via addEcommerceItem() * @ignore */ public function getUrlTrackEcommerceCartUpdate($grandTotal) { $url = $this->getUrlTrackEcommerce($grandTotal); return $url; } /** * Returns URL used to track Ecommerce Orders * Calling this function will reinitializes the property ecommerceItems to empty array * so items will have to be added again via addEcommerceItem() * @ignore */ public function getUrlTrackEcommerceOrder( $orderId, $grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0 ) { if (empty($orderId)) { throw new Exception("You must specifiy an orderId for the Ecommerce order"); } $url = $this->getUrlTrackEcommerce($grandTotal, $subTotal, $tax, $shipping, $discount); $url .= '&ec_id=' . urlencode($orderId); return $url; } /** * Returns URL used to track Ecommerce orders * * Calling this function will reinitializes the property ecommerceItems to empty array * so items will have to be added again via addEcommerceItem() * * @ignore */ protected function getUrlTrackEcommerce($grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0) { if (!is_numeric($grandTotal)) { throw new Exception("You must specifiy a grandTotal for the Ecommerce order (or Cart update)"); } $url = $this->getRequest($this->idSite); $url .= '&idgoal=0'; if (!empty($grandTotal)) { $grandTotal = $this->forceDotAsSeparatorForDecimalPoint($grandTotal); $url .= '&revenue=' . $grandTotal; } if (!empty($subTotal)) { $subTotal = $this->forceDotAsSeparatorForDecimalPoint($subTotal); $url .= '&ec_st=' . $subTotal; } if (!empty($tax)) { $tax = $this->forceDotAsSeparatorForDecimalPoint($tax); $url .= '&ec_tx=' . $tax; } if (!empty($shipping)) { $shipping = $this->forceDotAsSeparatorForDecimalPoint($shipping); $url .= '&ec_sh=' . $shipping; } if (!empty($discount)) { $discount = $this->forceDotAsSeparatorForDecimalPoint($discount); $url .= '&ec_dt=' . $discount; } if (!empty($this->ecommerceItems)) { $url .= '&ec_items=' . urlencode(json_encode($this->ecommerceItems)); } $this->ecommerceItems = array(); return $url; } /** * Builds URL to track a page view. * * @see doTrackPageView() * @param string $documentTitle Page view name as it will appear in Matomo reports * @return string URL to matomo.php with all parameters set to track the pageview */ public function getUrlTrackPageView($documentTitle = '') { $url = $this->getRequest($this->idSite); if (strlen($documentTitle) > 0) { $url .= '&action_name=' . urlencode($documentTitle); } return $url; } /** * Builds URL to track a custom event. * * @see doTrackEvent() * @param string $category The Event Category (Videos, Music, Games...) * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) * @param string|bool $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...) * @param float|bool $value (optional) The Event's value * @return string URL to matomo.php with all parameters set to track the pageview * @throws */ public function getUrlTrackEvent($category, $action, $name = false, $value = false) { $url = $this->getRequest($this->idSite); if (strlen($category) == 0) { throw new Exception("You must specify an Event Category name (Music, Videos, Games...)."); } if (strlen($action) == 0) { throw new Exception("You must specify an Event action (click, view, add...)."); } $url .= '&e_c=' . urlencode($category); $url .= '&e_a=' . urlencode($action); if (strlen($name) > 0) { $url .= '&e_n=' . urlencode($name); } if (strlen($value) > 0) { $value = $this->forceDotAsSeparatorForDecimalPoint($value); $url .= '&e_v=' . $value; } return $url; } /** * Builds URL to track a content impression. * * @see doTrackContentImpression() * @param string $contentName The name of the content. For instance 'Ad Foo Bar' * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text * @param string|false $contentTarget (optional) The target of the content. For instance the URL of a landing page. * @throws Exception In case $contentName is empty * @return string URL to matomo.php with all parameters set to track the pageview */ public function getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget) { $url = $this->getRequest($this->idSite); if (strlen($contentName) == 0) { throw new Exception("You must specify a content name"); } $url .= '&c_n=' . urlencode($contentName); if (!empty($contentPiece) && strlen($contentPiece) > 0) { $url .= '&c_p=' . urlencode($contentPiece); } if (!empty($contentTarget) && strlen($contentTarget) > 0) { $url .= '&c_t=' . urlencode($contentTarget); } return $url; } /** * Builds URL to track a content impression. * * @see doTrackContentInteraction() * @param string $interaction The name of the interaction with the content. For instance a 'click' * @param string $contentName The name of the content. For instance 'Ad Foo Bar' * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text * @param string|false $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page. * @throws Exception In case $interaction or $contentName is empty * @return string URL to matomo.php with all parameters set to track the pageview */ public function getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget) { $url = $this->getRequest($this->idSite); if (strlen($interaction) == 0) { throw new Exception("You must specify a name for the interaction"); } if (strlen($contentName) == 0) { throw new Exception("You must specify a content name"); } $url .= '&c_i=' . urlencode($interaction); $url .= '&c_n=' . urlencode($contentName); if (!empty($contentPiece) && strlen($contentPiece) > 0) { $url .= '&c_p=' . urlencode($contentPiece); } if (!empty($contentTarget) && strlen($contentTarget) > 0) { $url .= '&c_t=' . urlencode($contentTarget); } return $url; } /** * Builds URL to track a site search. * * @see doTrackSiteSearch() * @param string $keyword * @param string $category * @param int $countResults * @return string */ public function getUrlTrackSiteSearch($keyword, $category, $countResults) { $url = $this->getRequest($this->idSite); $url .= '&search=' . urlencode($keyword); if (strlen($category) > 0) { $url .= '&search_cat=' . urlencode($category); } if (!empty($countResults) || $countResults === 0) { $url .= '&search_count=' . (int)$countResults; } return $url; } /** * Builds URL to track a goal with idGoal and revenue. * * @see doTrackGoal() * @param int $idGoal Id Goal to record a conversion * @param float $revenue Revenue for this conversion * @return string URL to matomo.php with all parameters set to track the goal conversion */ public function getUrlTrackGoal($idGoal, $revenue = 0.0) { $url = $this->getRequest($this->idSite); $url .= '&idgoal=' . $idGoal; if (!empty($revenue)) { $revenue = $this->forceDotAsSeparatorForDecimalPoint($revenue); $url .= '&revenue=' . $revenue; } return $url; } /** * Builds URL to track a new action. * * @see doTrackAction() * @param string $actionUrl URL of the download or outlink * @param string $actionType Type of the action: 'download' or 'link' * @return string URL to matomo.php with all parameters set to track an action */ public function getUrlTrackAction($actionUrl, $actionType) { $url = $this->getRequest($this->idSite); $url .= '&' . $actionType . '=' . urlencode($actionUrl); return $url; } /** * Builds URL to track a crash. * * @see doTrackCrash() * @param string $message (required) the error message. * @param string|null $type (optional) the error type, such as the class name of an Exception. * @param string|null $category (optional) a category value for this crash. This can be any information you want * to attach to the crash. * @param string|null $stack (optional) the stack trace of the crash. * @param string|null $location (optional) the source file URI where the crash originated. * @param int|null $line (optional) the source file line where the crash originated. * @param int|null $column (optional) the source file column where the crash originated. * @return string URL to matomo.php with all parameters set to track an action */ public function getUrlTrackCrash($message, $type = null, $category = null, $stack = null, $location = null, $line = null, $column = null) { $url = $this->getRequest($this->idSite); $url .= '&ca=1&cra=' . urlencode($message); if ($type) { $url .= '&cra_tp=' . urlencode($type); } if ($category) { $url .= '&cra_ct=' . urlencode($category); } if ($stack) { $url .= '&cra_st=' . urlencode($stack); } if ($location) { $url .= '&cra_ru=' . urlencode($location); } if ($line) { $url .= '&cra_rl=' . urlencode($line); } if ($column) { $url .= '&cra_rc=' . urlencode($column); } return $url; } /** * Overrides server date and time for the tracking requests. * By default Matomo will track requests for the "current datetime" but this function allows you * to track visits in the past. All times are in UTC. * * Allowed only for Admin/Super User, must be used along with setTokenAuth() * @see setTokenAuth() * @param string $dateTime Date with the format 'Y-m-d H:i:s', or a UNIX timestamp. * 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. * @return $this */ public function setForceVisitDateTime($dateTime) { $this->forcedDatetime = $dateTime; return $this; } /** * Forces Matomo to create a new visit for the tracking request. * * By default, Matomo will create a new visit if the last request by this user was more than 30 minutes ago. * If you call setForceNewVisit() before calling doTrack*, then a new visit will be created for this request. * @return $this */ public function setForceNewVisit() { $this->forcedNewVisit = true; return $this; } /** * Overrides IP address * * Allowed only for Admin/Super User, must be used along with setTokenAuth() * @see setTokenAuth() * @param string $ip IP string, eg. 130.54.2.1 * @return $this */ public function setIp($ip) { $this->ip = $ip; return $this; } /** * Force the action to be recorded for a specific User. The User ID is a string representing a given user in your system. * * A User ID can be a username, UUID or an email address, or any number or string that uniquely identifies a user or client. * * @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. * @return $this * @throws Exception */ public function setUserId($userId) { if ($userId === '') { throw new Exception("User ID cannot be empty."); } $this->userId = $userId; return $this; } /** * Hash function used internally by Matomo to hash a User ID into the Visitor ID. * * Note: matches implementation of Tracker\Request->getUserIdHashed() * * @param $id * @return string */ public static function getUserIdHashed($id) { return substr(sha1($id), 0, 16); } /** * Forces the requests to be recorded for the specified Visitor ID. * * Rather than letting Matomo attribute the user with a heuristic based on IP and other user fingeprinting attributes, * force the action to be recorded for a particular visitor. * * If not set, the visitor ID will be fetched from the 1st party cookie, or will be set to a random UUID. * * @param string $visitorId 16 hexadecimal characters visitor ID, eg. "33c31e01394bdc63" * @return $this * @throws Exception */ public function setVisitorId($visitorId) { $hexChars = '01234567890abcdefABCDEF'; if (strlen($visitorId) != self::LENGTH_VISITOR_ID || strspn($visitorId, $hexChars) !== strlen($visitorId) ) { throw new Exception( "setVisitorId() expects a " . self::LENGTH_VISITOR_ID . " characters hexadecimal string (containing only the following: " . $hexChars . ")" ); } $this->forcedVisitorId = $visitorId; return $this; } /** * If the user initiating the request has the Matomo first party cookie, * this function will try and return the ID parsed from this first party cookie (found in $_COOKIE). * * If you call this function from a server, where the call is triggered by a cron or script * not initiated by the actual visitor being tracked, then it will return * the random Visitor ID that was assigned to this visit object. * * This can be used if you wish to record more visits, actions or goals for this visitor ID later on. * * @return string 16 hex chars visitor ID string */ public function getVisitorId() { if (!empty($this->forcedVisitorId)) { return $this->forcedVisitorId; } if ($this->loadVisitorIdCookie()) { return $this->cookieVisitorId; } return $this->randomVisitorId; } /** * Returns the currently set user agent. * @return string */ public function getUserAgent() { return $this->userAgent; } /** * Returns the currently set IP address. * @return string */ public function getIp() { return $this->ip; } /** * Returns the User ID string, which may have been set via: * $v->setUserId('username@example.org'); * * @return bool */ public function getUserId() { return $this->userId; } /** * Loads values from the VisitorId Cookie * * @return bool True if cookie exists and is valid, False otherwise */ protected function loadVisitorIdCookie() { $idCookie = $this->getCookieMatchingName('id'); if ($idCookie === false) { return false; } $parts = explode('.', $idCookie); if (strlen($parts[0]) != self::LENGTH_VISITOR_ID) { return false; } /* $this->cookieVisitorId provides backward compatibility since getVisitorId() didn't change any existing VisitorId value */ $this->cookieVisitorId = $parts[0]; $this->createTs = $parts[1]; return true; } /** * Deletes all first party cookies from the client */ public function deleteCookies() { $cookies = array('id', 'ses', 'cvar', 'ref'); foreach ($cookies as $cookie) { $this->setCookie($cookie, '', -86400); } } /** * Returns the currently assigned Attribution Information stored in a first party cookie. * * This function will only work if the user is initiating the current request, and his cookies * can be read by PHP from the $_COOKIE array. * * @return string JSON Encoded string containing the Referrer information for Goal conversion attribution. * Will return false if the cookie could not be found * @see matomo.js getAttributionInfo() */ public function getAttributionInfo() { if (!empty($this->attributionInfo)) { return json_encode($this->attributionInfo); } return $this->getCookieMatchingName('ref'); } /** * Some Tracking API functionality requires express authentication, using either the * Super User token_auth, or a user with 'admin' access to the website. * * The following features require access: * - force the visitor IP * - force the date & time of the tracking requests rather than track for the current datetime * * @param string $token_auth token_auth 32 chars token_auth string * @return $this */ public function setTokenAuth($token_auth) { $this->token_auth = $token_auth; return $this; } /** * Sets local visitor time * * @param string $time HH:MM:SS format * @return $this */ public function setLocalTime($time) { list($hour, $minute, $second) = explode(':', $time); $this->localHour = (int)$hour; $this->localMinute = (int)$minute; $this->localSecond = (int)$second; return $this; } /** * Sets user resolution width and height. * * @param int $width * @param int $height * @return $this */ public function setResolution($width, $height) { $this->width = $width; $this->height = $height; return $this; } /** * Sets if the browser supports cookies * This is reported in "List of plugins" report in Matomo. * * @param bool $bool * @return $this */ public function setBrowserHasCookies($bool) { $this->hasCookies = $bool; return $this; } /** * Will append a custom string at the end of the Tracking request. * @param string $string * @return $this */ public function setDebugStringAppend($string) { $this->DEBUG_APPEND_URL = '&' . $string; return $this; } /** * Sets visitor browser supported plugins * * @param bool $flash * @param bool $java * @param bool $quickTime * @param bool $realPlayer * @param bool $pdf * @param bool $windowsMedia * @param bool $silverlight * @return $this */ public function setPlugins( $flash = false, $java = false, $quickTime = false, $realPlayer = false, $pdf = false, $windowsMedia = false, $silverlight = false ) { $this->plugins = '&fla=' . (int)$flash . '&java=' . (int)$java . '&qt=' . (int)$quickTime . '&realp=' . (int)$realPlayer . '&pdf=' . (int)$pdf . '&wma=' . (int)$windowsMedia . '&ag=' . (int)$silverlight; return $this; } /** * By default, MatomoTracker will read first party cookies * from the request and write updated cookies in the response (using setrawcookie). * This can be disabled by calling this function. */ public function disableCookieSupport() { $this->configCookiesDisabled = true; } /** * Returns the maximum number of seconds the tracker will spend waiting for a response * from Matomo. Defaults to 600 seconds. */ public function getRequestTimeout() { return $this->requestTimeout; } /** * Sets the maximum number of seconds that the tracker will spend waiting for a response * from Matomo. * * @param int $timeout * @return $this * @throws Exception */ public function setRequestTimeout($timeout) { if (!is_int($timeout) || $timeout < 0) { throw new Exception("Invalid value supplied for request timeout: $timeout"); } $this->requestTimeout = $timeout; return $this; } /** * Returns the maximum number of seconds the tracker will spend trying to connect to Matomo. * Defaults to 300 seconds. */ public function getRequestConnectTimeout() { return $this->requestConnectTimeout; } /** * Sets the maximum number of seconds that the tracker will spend tryint to connect to Matomo. * * @param int $timeout * @return $this * @throws Exception */ public function setRequestConnectTimeout($timeout) { if (!is_int($timeout) || $timeout < 0) { throw new Exception("Invalid value supplied for request connect timeout: $timeout"); } $this->requestConnectTimeout = $timeout; return $this; } /** * Sets the request method to POST, which is recommended when using setTokenAuth() * to prevent the token from being recorded in server logs. Avoid using redirects * when using POST to prevent the loss of POST values. When using Log Analytics, * be aware that POST requests are not parseable/replayable. * * @param string $method Either 'POST' or 'GET' * @return $this */ public function setRequestMethodNonBulk($method) { $this->requestMethod = strtoupper($method) === 'POST' ? 'POST' : 'GET'; return $this; } /** * If a proxy is needed to look up the address of the Matomo site, set it with this * @param string $proxy IP as string, for example "173.234.92.107" * @param int $proxyPort */ public function setProxy($proxy, $proxyPort = 80) { $this->proxy = $proxy; $this->proxyPort = $proxyPort; } /** * If the proxy IP and the proxy port have been set, with the setProxy() function * returns a string, like "173.234.92.107:80" */ private function getProxy() { if (isset($this->proxy) && isset($this->proxyPort)) { return $this->proxy.":".$this->proxyPort; } return null; } /** * Used in tests to output useful error messages. * * @ignore */ static public $DEBUG_LAST_REQUESTED_URL = false; /** * Returns array of curl options for request */ protected function prepareCurlOptions($url, $method, $data, $forcePostUrlEncoded) { $options = array( CURLOPT_URL => $url, CURLOPT_USERAGENT => $this->userAgent, CURLOPT_HEADER => true, CURLOPT_TIMEOUT => $this->requestTimeout, CURLOPT_CONNECTTIMEOUT => $this->requestConnectTimeout, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array( 'Accept-Language: ' . $this->acceptLanguage, ), ); if ($method === 'GET') { $options[CURLOPT_FOLLOWLOCATION] = true; } if (defined('PATH_TO_CERTIFICATES_FILE')) { $options[CURLOPT_CAINFO] = PATH_TO_CERTIFICATES_FILE; } $proxy = $this->getProxy(); if (isset($proxy)) { $options[CURLOPT_PROXY] = $proxy; } switch ($method) { case 'POST': $options[CURLOPT_POST] = true; break; default: break; } // only supports JSON data if (!empty($data) && $forcePostUrlEncoded) { $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; $options[CURLOPT_POSTFIELDS] = $data; $options[CURLOPT_POST] = true; if (defined('CURL_REDIR_POST_ALL')) { $options[CURLOPT_POSTREDIR] = CURL_REDIR_POST_ALL; $options[CURLOPT_FOLLOWLOCATION] = true; } } elseif (!empty($data)) { $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json'; $options[CURLOPT_HTTPHEADER][] = 'Expect:'; $options[CURLOPT_POSTFIELDS] = $data; } if (!empty($this->outgoingTrackerCookies)) { $options[CURLOPT_COOKIE] = http_build_query($this->outgoingTrackerCookies); $this->outgoingTrackerCookies = array(); } return $options; } /** * Returns array of stream options for request */ protected function prepareStreamOptions($method, $data, $forcePostUrlEncoded) { $stream_options = array( 'http' => array( 'method' => $method, 'user_agent' => $this->userAgent, 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n", 'timeout' => $this->requestTimeout, ), ); $proxy = $this->getProxy(); if (isset($proxy)) { $stream_options['http']['proxy'] = $proxy; } // only supports JSON data if (!empty($data) && $forcePostUrlEncoded) { $stream_options['http']['header'] .= "Content-Type: application/x-www-form-urlencoded \r\n"; $stream_options['http']['content'] = $data; } elseif (!empty($data)) { $stream_options['http']['header'] .= "Content-Type: application/json \r\n"; $stream_options['http']['content'] = $data; } if (!empty($this->outgoingTrackerCookies)) { $stream_options['http']['header'] .= 'Cookie: ' . http_build_query($this->outgoingTrackerCookies) . "\r\n"; $this->outgoingTrackerCookies = array(); } return $stream_options; } /** * @ignore */ protected function sendRequest($url, $method = 'GET', $data = null, $force = false) { self::$DEBUG_LAST_REQUESTED_URL = $url; // if doing a bulk request, store the url if ($this->doBulkRequests && !$force) { $this->storedTrackingActions[] = $url . (!empty($this->userAgent) ? ('&ua=' . urlencode($this->userAgent)) : '') . (!empty($this->acceptLanguage) ? ('&lang=' . urlencode($this->acceptLanguage)) : ''); // Clear custom variables & dimensions so they don't get copied over to other users in the bulk request $this->clearCustomVariables(); $this->clearCustomDimensions(); $this->clearCustomTrackingParameters(); $this->userAgent = false; $this->clientHints = false; $this->acceptLanguage = false; return true; } $forcePostUrlEncoded = false; if (!$this->doBulkRequests) { if (!empty($this->requestMethod) && strtoupper($this->requestMethod) === 'POST') { // POST ALL parameters and have no GET parameters $urlParts = explode('?', $url); $url = $urlParts[0]; $data = $urlParts[1]; $forcePostUrlEncoded = true; $method = 'POST'; } if (!empty($this->token_auth)) { $appendTokenString = '&token_auth=' . urlencode($this->token_auth); if (empty($this->requestMethod) || $method === 'POST') { // Only post token_auth but use GET URL parameters for everything else $forcePostUrlEncoded = true; if (empty($data)) { $data = ''; } $data .= $appendTokenString; $data = ltrim($data, '&'); // when no request method set we don't want it to start with '&' } elseif (!empty($this->token_auth)) { // Use GET for all URL parameters $url .= $appendTokenString; } } } if (function_exists('curl_init') && function_exists('curl_exec')) { $options = $this->prepareCurlOptions($url, $method, $data, $forcePostUrlEncoded); $ch = curl_init(); curl_setopt_array($ch, $options); ob_start(); $response = @curl_exec($ch); ob_end_clean(); $header = ''; $content = ''; if ($response === false) { throw new \RuntimeException(curl_error($ch)); } if (!empty($response)) { // extract header $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($response, 0, $headerSize); // extract content $content = substr($response, $headerSize); } $this->parseIncomingCookies(explode("\r\n", $header)); } elseif (function_exists('stream_context_create')) { $stream_options = $this->prepareStreamOptions($method, $data, $forcePostUrlEncoded); $ctx = stream_context_create($stream_options); $response = file_get_contents($url, 0, $ctx); $content = $response; $this->parseIncomingCookies($http_response_header); } return $content; } /** * Returns current timestamp, or forced timestamp/datetime if it was set * @return string|int */ protected function getTimestamp() { return !empty($this->forcedDatetime) ? strtotime($this->forcedDatetime) : time(); } /** * Returns the base URL for the Matomo server. */ protected function getBaseUrl() { if (empty(self::$URL)) { throw new Exception( 'You must first set the Matomo Tracker URL by calling MatomoTracker::$URL = \'http://your-website.org/matomo/\';' ); } if (strpos(self::$URL, '/matomo.php') === false && strpos(self::$URL, '/proxy-matomo.php') === false ) { self::$URL = rtrim(self::$URL, '/'); self::$URL .= '/matomo.php'; } return self::$URL; } /** * @ignore */ protected function getRequest($idSite) { $this->setFirstPartyCookies(); $customFields = ''; if (!empty($this->customParameters)) { $customFields = '&' . http_build_query($this->customParameters, '', '&'); } $customDimensions = ''; if (!empty($this->customDimensions)) { $customDimensions = '&' . http_build_query($this->customDimensions, '', '&'); } $baseUrl = $this->getBaseUrl(); $start = '?'; if (strpos($baseUrl, '?') !== false) { $start = '&'; } $url = $baseUrl . $start . 'idsite=' . $idSite . '&rec=1' . '&apiv=' . self::VERSION . '&r=' . substr(strval(mt_rand()), 2, 6) . // XDEBUG_SESSIONS_START and KEY are related to the PHP Debugger, this can be ignored in other languages (!empty($_GET['XDEBUG_SESSION_START']) ? '&XDEBUG_SESSION_START=' . @urlencode($_GET['XDEBUG_SESSION_START']) : '') . (!empty($_GET['KEY']) ? '&KEY=' . @urlencode($_GET['KEY']) : '') . // Only allowed for Admin/Super User, token_auth required, ((!empty($this->ip) && !empty($this->token_auth)) ? '&cip=' . $this->ip : '') . (!empty($this->userId) ? '&uid=' . urlencode($this->userId) : '') . (!empty($this->forcedDatetime) ? '&cdt=' . urlencode($this->forcedDatetime) : '') . (!empty($this->forcedNewVisit) ? '&new_visit=1' : '') . // Values collected from cookie '&_idts=' . $this->createTs . // These parameters are set by the JS, but optional when using API (!empty($this->plugins) ? $this->plugins : '') . (($this->localHour !== false && $this->localMinute !== false && $this->localSecond !== false) ? '&h=' . $this->localHour . '&m=' . $this->localMinute . '&s=' . $this->localSecond : '') . (!empty($this->width) && !empty($this->height) ? '&res=' . $this->width . 'x' . $this->height : '') . (!empty($this->hasCookies) ? '&cookie=' . $this->hasCookies : '') . // Various important attributes (!empty($this->customData) ? '&data=' . $this->customData : '') . (!empty($this->visitorCustomVar) ? '&_cvar=' . urlencode(json_encode($this->visitorCustomVar)) : '') . (!empty($this->pageCustomVar) ? '&cvar=' . urlencode(json_encode($this->pageCustomVar)) : '') . (!empty($this->eventCustomVar) ? '&e_cvar=' . urlencode(json_encode($this->eventCustomVar)) : '') . (!empty($this->forcedVisitorId) ? '&cid=' . $this->forcedVisitorId : '&_id=' . $this->getVisitorId()) . // URL parameters '&url=' . urlencode($this->pageUrl ?? '') . '&urlref=' . urlencode($this->urlReferrer ?? '') . ((!empty($this->pageCharset) && $this->pageCharset != self::DEFAULT_CHARSET_PARAMETER_VALUES) ? '&cs=' . $this->pageCharset : '') . // unique pageview id (!empty($this->idPageview) ? '&pv_id=' . urlencode($this->idPageview) : '') . // Attribution information, so that Goal conversions are attributed to the right referrer or campaign // Campaign name (!empty($this->attributionInfo[0]) ? '&_rcn=' . urlencode($this->attributionInfo[0]) : '') . // Campaign keyword (!empty($this->attributionInfo[1]) ? '&_rck=' . urlencode($this->attributionInfo[1]) : '') . // Timestamp at which the referrer was set (!empty($this->attributionInfo[2]) ? '&_refts=' . $this->attributionInfo[2] : '') . // Referrer URL (!empty($this->attributionInfo[3]) ? '&_ref=' . urlencode($this->attributionInfo[3]) : '') . // custom location info (!empty($this->country) ? '&country=' . urlencode($this->country) : '') . (!empty($this->region) ? '®ion=' . urlencode($this->region) : '') . (!empty($this->city) ? '&city=' . urlencode($this->city) : '') . (!empty($this->lat) ? '&lat=' . urlencode($this->lat) : '') . (!empty($this->long) ? '&long=' . urlencode($this->long) : '') . $customFields . $customDimensions . (!$this->sendImageResponse ? '&send_image=0' : '') . // client hints (!empty($this->clientHints) ? ('&uadata=' . urlencode(json_encode($this->clientHints))) : '') . // DEBUG $this->DEBUG_APPEND_URL; if (!empty($this->idPageview)) { $url .= ($this->networkTime !== false ? '&pf_net=' . ((int)$this->networkTime) : '') . ($this->serverTime !== false ? '&pf_srv=' . ((int)$this->serverTime) : '') . ($this->transferTime !== false ? '&pf_tfr=' . ((int)$this->transferTime) : '') . ($this->domProcessingTime !== false ? '&pf_dm1=' . ((int)$this->domProcessingTime) : '') . ($this->domCompletionTime !== false ? '&pf_dm2=' . ((int)$this->domCompletionTime) : '') . ($this->onLoadTime !== false ? '&pf_onl=' . ((int)$this->onLoadTime) : ''); $this->clearPerformanceTimings(); } foreach ($this->ecommerceView as $param => $value) { $url .= '&' . $param . '=' . urlencode($value); } // Reset page level custom variables after this page view $this->ecommerceView = array(); $this->pageCustomVar = array(); $this->eventCustomVar = array(); $this->clearCustomDimensions(); $this->clearCustomTrackingParameters(); // force new visit only once, user must call again setForceNewVisit() $this->forcedNewVisit = false; return $url; } /** * Returns a first party cookie which name contains $name * * @param string $name * @return string String value of cookie, or false if not found * @ignore */ protected function getCookieMatchingName($name) { if ($this->configCookiesDisabled) { return false; } if (!is_array($_COOKIE)) { return false; } $name = $this->getCookieName($name); // Matomo cookie names use dots separators in matomo.js, // but PHP Replaces . with _ http://www.php.net/manual/en/language.variables.predefined.php#72571 $name = str_replace('.', '_', $name); foreach ($_COOKIE as $cookieName => $cookieValue) { if (strpos($cookieName, $name) !== false) { return $cookieValue; } } return false; } /** * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" * will return "/dir1/dir2/index.php" * * @return string * @ignore */ protected static function getCurrentScriptName() { $url = ''; if (!empty($_SERVER['PATH_INFO'])) { $url = $_SERVER['PATH_INFO']; } else { if (!empty($_SERVER['REQUEST_URI'])) { if (($pos = strpos($_SERVER['REQUEST_URI'], '?')) !== false) { $url = substr($_SERVER['REQUEST_URI'], 0, $pos); } else { $url = $_SERVER['REQUEST_URI']; } } } if (empty($url) && isset($_SERVER['SCRIPT_NAME'])) { $url = $_SERVER['SCRIPT_NAME']; } elseif (empty($url)) { $url = '/'; } if (!empty($url) && $url[0] !== '/') { $url = '/' . $url; } return $url; } /** * If the current URL is 'http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" * will return 'http' * * @return string 'https' or 'http' * @ignore */ protected static function getCurrentScheme() { if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true) ) { return 'https'; } return 'http'; } /** * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" * will return "http://example.org" * * @return string * @ignore */ protected static function getCurrentHost() { if (isset($_SERVER['HTTP_HOST'])) { return $_SERVER['HTTP_HOST']; } return 'unknown'; } /** * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" * will return "?param1=value1¶m2=value2" * * @return string * @ignore */ protected static function getCurrentQueryString() { $url = ''; if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']) ) { $url .= '?' . $_SERVER['QUERY_STRING']; } return $url; } /** * Returns the current full URL (scheme, host, path and query string. * * @return string * @ignore */ protected static function getCurrentUrl() { return self::getCurrentScheme() . '://' . self::getCurrentHost() . self::getCurrentScriptName() . self::getCurrentQueryString(); } /** * Sets the first party cookies as would the matomo.js * All cookies are supported: 'id' and 'ses' and 'ref' and 'cvar' cookies. * @return $this */ protected function setFirstPartyCookies() { if ($this->configCookiesDisabled) { return $this; } if (empty($this->cookieVisitorId)) { $this->loadVisitorIdCookie(); } // Set the 'ref' cookie $attributionInfo = $this->getAttributionInfo(); if (!empty($attributionInfo)) { $this->setCookie('ref', $attributionInfo, $this->configReferralCookieTimeout); } // Set the 'ses' cookie $this->setCookie('ses', '*', $this->configSessionCookieTimeout); // Set the 'id' cookie $cookieValue = $this->getVisitorId() . '.' . $this->createTs; $this->setCookie('id', $cookieValue, $this->configVisitorCookieTimeout); // Set the 'cvar' cookie $this->setCookie('cvar', json_encode($this->visitorCustomVar), $this->configSessionCookieTimeout); return $this; } /** * Sets a first party cookie to the client to improve dual JS-PHP tracking. * * This replicates the matomo.js tracker algorithms for consistency and better accuracy. * * @param $cookieName * @param $cookieValue * @param $cookieTTL * @return $this */ protected function setCookie($cookieName, $cookieValue, $cookieTTL) { $cookieExpire = $this->currentTs + $cookieTTL; if (!headers_sent()) { $header = 'Set-Cookie: ' . rawurlencode($this->getCookieName($cookieName)) . '=' . rawurlencode($cookieValue) . (empty($cookieExpire) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $cookieExpire) . ' GMT') . (empty($this->configCookiePath) ? '' : '; path=' . $this->configCookiePath) . (empty($this->configCookieDomain) ? '' : '; domain=' . rawurlencode($this->configCookieDomain)) . (!$this->configCookieSecure ? '' : '; secure') . (!$this->configCookieHTTPOnly ? '' : '; HttpOnly') . (!$this->configCookieSameSite ? '' : '; SameSite=' . rawurlencode($this->configCookieSameSite)); header($header, false); } return $this; } /** * @return array */ protected function getCustomVariablesFromCookie() { $cookie = $this->getCookieMatchingName('cvar'); if (!$cookie) { return []; } return json_decode($cookie, $assoc = true); } /** * Sets a cookie to be sent to the tracking server. * * @param $name * @param $value */ public function setOutgoingTrackerCookie($name, $value) { if ($value === null) { unset($this->outgoingTrackerCookies[$name]); } else { $this->outgoingTrackerCookies[$name] = $value; } } /** * Gets a cookie which was set by the tracking server. * * @param $name * * @return bool|string */ public function getIncomingTrackerCookie($name) { if (isset($this->incomingTrackerCookies[$name])) { return $this->incomingTrackerCookies[$name]; } return false; } /** * Reads incoming tracking server cookies. * * @param array $headers Array with HTTP response headers as values */ protected function parseIncomingCookies($headers) { $this->incomingTrackerCookies = array(); if (!empty($headers)) { $headerName = 'set-cookie:'; $headerNameLength = strlen($headerName); foreach($headers as $header) { if (strpos(strtolower($header), $headerName) !== 0) { continue; } $cookies = trim(substr($header, $headerNameLength)); $posEnd = strpos($cookies, ';'); if ($posEnd !== false) { $cookies = substr($cookies, 0, $posEnd); } parse_str($cookies, $this->incomingTrackerCookies); } } } } /** * Helper function to quickly generate the URL to track a page view. * * @param $idSite * @param string $documentTitle * @return string */ function Matomo_getUrlTrackPageView($idSite, $documentTitle = '') { $tracker = new MatomoTracker($idSite); return $tracker->getUrlTrackPageView($documentTitle); } /** * Helper function to quickly generate the URL to track a goal. * * @param $idSite * @param $idGoal * @param float $revenue * @return string */ function Matomo_getUrlTrackGoal($idSite, $idGoal, $revenue = 0.0) { $tracker = new MatomoTracker($idSite); return $tracker->getUrlTrackGoal($idGoal, $revenue); } /** * Ensure PiwikTracker class is available as well * * @deprecated */ if (!class_exists('\PiwikTracker')) { include_once('PiwikTracker.php'); } matomo-php-tracker-3.2.0/PiwikTracker.php000066400000000000000000000020671440733730600203520ustar00rootroot00000000000000setTokenAuth($matomoToken); // Track page view $matomoTracker->doTrackPageView($matomoPageTitle); ``` ## Requirements: * JSON extension (json_decode, json_encode) * cURL or stream extension (to issue the HTTPS request to Matomo) ## Installation ### Composer ``` composer require matomo/matomo-php-tracker ``` ### Manually Alternatively, you can download the files and require the Matomo tracker manually: ``` require_once("MatomoTracker.php"); ``` ## License Released under the [BSD License](https://opensource.org/licenses/BSD-3-Clause) matomo-php-tracker-3.2.0/composer.json000066400000000000000000000016351440733730600177640ustar00rootroot00000000000000{ "name": "matomo/matomo-php-tracker", "description": "PHP Client for Matomo Analytics Tracking API", "keywords": ["matomo","piwik","tracker","analytics"], "homepage": "https://matomo.org", "license": "BSD-3-Clause", "authors": [ { "name": "The Matomo Team", "email": "hello@matomo.org", "homepage": "https://matomo.org/team/" } ], "support": { "forum": "https://forum.matomo.org/", "issues": "https://github.com/matomo-org/matomo-php-tracker/issues", "source": "https://github.com/matomo-org/matomo-php-tracker" }, "require": { "php": "^7.0 || ^8.0", "ext-json": "*" }, "suggest": { "ext-curl": "Using this extension to issue the HTTPS request to Matomo" }, "autoload": { "classmap": ["."] }, "require-dev": { "phpunit/phpunit": "^9.3" } } matomo-php-tracker-3.2.0/composer.lock000066400000000000000000002064211440733730600177430ustar00rootroot00000000000000{ "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "8ec49efde7b8c6d6ea9c1b81422e501f", "packages": [], "packages-dev": [ { "name": "doctrine/instantiator", "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.13", "phpstan/phpstan-phpunit": "^0.11", "phpstan/phpstan-shim": "^0.11", "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.2.x-dev" } }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", "homepage": "http://ocramius.github.com/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", "type": "custom" }, { "url": "https://www.patreon.com/phpdoctrine", "type": "patreon" }, { "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", "type": "tidelift" } ], "time": "2020-05-29T17:27:14+00:00" }, { "name": "myclabs/deep-copy", "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "replace": { "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" }, "files": [ "src/DeepCopy/deep_copy.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", "keywords": [ "clone", "copy", "duplicate", "object", "object graph" ], "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], "time": "2020-06-29T13:22:24+00:00" }, { "name": "nikic/php-parser", "version": "v4.9.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", "reference": "88e519766fc58bd46b8265561fb79b54e2e00b28" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/88e519766fc58bd46b8265561fb79b54e2e00b28", "reference": "88e519766fc58bd46b8265561fb79b54e2e00b28", "shasum": "" }, "require": { "ext-tokenizer": "*", "php": ">=7.0" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" ], "type": "library", "extra": { "branch-alias": { "dev-master": "4.9-dev" } }, "autoload": { "psr-4": { "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Nikita Popov" } ], "description": "A PHP parser written in PHP", "keywords": [ "parser", "php" ], "time": "2020-08-30T16:15:20+00:00" }, { "name": "phar-io/manifest", "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.0.x-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Arne Blankerts", "email": "arne@blankerts.de", "role": "Developer" }, { "name": "Sebastian Heuer", "email": "sebastian@phpeople.de", "role": "Developer" }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "Developer" } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "time": "2020-06-27T14:33:11+00:00" }, { "name": "phar-io/version", "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phar-io/version/zipball/c6bb6825def89e0a32220f88337f8ceaf1975fa0", "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Arne Blankerts", "email": "arne@blankerts.de", "role": "Developer" }, { "name": "Sebastian Heuer", "email": "sebastian@phpeople.de", "role": "Developer" }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "Developer" } ], "description": "Library for handling version information and constraints", "time": "2020-06-27T14:39:04+00:00" }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jaap van Otterdijk", "email": "opensource@ijaap.nl" } ], "description": "Common reflection classes used by phpdocumentor to reflect the code structure", "homepage": "http://www.phpdoc.org", "keywords": [ "FQSEN", "phpDocumentor", "phpdoc", "reflection", "static analysis" ], "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", "version": "5.2.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", "reference": "d870572532cd70bc3fab58f2e23ad423c8404c44" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d870572532cd70bc3fab58f2e23ad423c8404c44", "reference": "d870572532cd70bc3fab58f2e23ad423c8404c44", "shasum": "" }, "require": { "ext-filter": "*", "php": "^7.2 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.3", "webmozart/assert": "^1.9.1" }, "require-dev": { "mockery/mockery": "~1.3.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Mike van Riel", "email": "me@mikevanriel.com" }, { "name": "Jaap van Otterdijk", "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "time": "2020-08-15T11:14:08+00:00" }, { "name": "phpdocumentor/type-resolver", "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", "reference": "e878a14a65245fbe78f8080eba03b47c3b705651" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651", "reference": "e878a14a65245fbe78f8080eba03b47c3b705651", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*" }, "type": "library", "extra": { "branch-alias": { "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Mike van Riel", "email": "me@mikevanriel.com" } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "time": "2020-06-27T10:12:23+00:00" }, { "name": "phpspec/prophecy", "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160", "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", "php": "^7.2", "phpdocumentor/reflection-docblock": "^5.0", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { "phpspec/phpspec": "^6.0", "phpunit/phpunit": "^8.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.11.x-dev" } }, "autoload": { "psr-4": { "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Konstantin Kudryashov", "email": "ever.zet@gmail.com", "homepage": "http://everzet.com" }, { "name": "Marcello Duarte", "email": "marcello.duarte@gmail.com" } ], "description": "Highly opinionated mocking framework for PHP 5.3+", "homepage": "https://github.com/phpspec/prophecy", "keywords": [ "Double", "Dummy", "fake", "mock", "spy", "stub" ], "time": "2020-07-08T12:44:21+00:00" }, { "name": "phpunit/php-code-coverage", "version": "9.1.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", "reference": "2ef92bec3186a827faf7362ff92ae4e8ec2e49d2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2ef92bec3186a827faf7362ff92ae4e8ec2e49d2", "reference": "2ef92bec3186a827faf7362ff92ae4e8ec2e49d2", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^4.8", "php": "^7.3 || ^8.0", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", "sebastian/code-unit-reverse-lookup": "^2.0.2", "sebastian/complexity": "^2.0", "sebastian/environment": "^5.1.2", "sebastian/lines-of-code": "^1.0", "sebastian/version": "^3.0.1", "theseer/tokenizer": "^1.2.0" }, "require-dev": { "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcov": "*", "ext-xdebug": "*" }, "type": "library", "extra": { "branch-alias": { "dev-master": "9.1-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ "coverage", "testing", "xunit" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-09-03T07:09:19+00:00" }, { "name": "phpunit/php-file-iterator", "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/25fefc5b19835ca653877fe081644a3f8c1d915e", "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "3.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ "filesystem", "iterator" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-07-11T05:18:21+00:00" }, { "name": "phpunit/php-invoker", "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", "reference": "7a85b66acc48cacffdf87dadd3694e7123674298" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7a85b66acc48cacffdf87dadd3694e7123674298", "reference": "7a85b66acc48cacffdf87dadd3694e7123674298", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "ext-pcntl": "*", "phpunit/phpunit": "^9.0" }, "suggest": { "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { "dev-master": "3.1-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Invoke callables with a timeout", "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ "process" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-08-06T07:04:15+00:00" }, { "name": "phpunit/php-text-template", "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Simple template engine.", "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ "template" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T11:55:37+00:00" }, { "name": "phpunit/php-timer", "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/cc49734779cbb302bf51a44297dab8c4bbf941e7", "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "5.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Utility class for timing", "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ "timer" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T11:58:13+00:00" }, { "name": "phpunit/phpunit", "version": "9.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", "reference": "93d78d8e2a06393a0d0c1ead6fe9984f1af1f88c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/93d78d8e2a06393a0d0c1ead6fe9984f1af1f88c", "reference": "93d78d8e2a06393a0d0c1ead6fe9984f1af1f88c", "shasum": "" }, "require": { "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.1", "phar-io/version": "^3.0.2", "php": "^7.3 || ^8.0", "phpspec/prophecy": "^1.11.1", "phpunit/php-code-coverage": "^9.1.5", "phpunit/php-file-iterator": "^3.0.4", "phpunit/php-invoker": "^3.1", "phpunit/php-text-template": "^2.0.2", "phpunit/php-timer": "^5.0.1", "sebastian/cli-parser": "^1.0", "sebastian/code-unit": "^1.0.5", "sebastian/comparator": "^4.0.3", "sebastian/diff": "^4.0.2", "sebastian/environment": "^5.1.2", "sebastian/exporter": "^4.0.2", "sebastian/global-state": "^5.0", "sebastian/object-enumerator": "^4.0.2", "sebastian/resource-operations": "^3.0.2", "sebastian/type": "^2.2.1", "sebastian/version": "^3.0.1" }, "require-dev": { "ext-pdo": "*", "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" }, "bin": [ "phpunit" ], "type": "library", "extra": { "branch-alias": { "dev-master": "9.3-dev" } }, "autoload": { "classmap": [ "src/" ], "files": [ "src/Framework/Assert/Functions.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", "keywords": [ "phpunit", "testing", "xunit" ], "funding": [ { "url": "https://phpunit.de/donate.html", "type": "custom" }, { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-08-27T06:30:58+00:00" }, { "name": "sebastian/cli-parser", "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", "reference": "2a4a38c56e62f7295bedb8b1b7439ad523d4ea82" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2a4a38c56e62f7295bedb8b1b7439ad523d4ea82", "reference": "2a4a38c56e62f7295bedb8b1b7439ad523d4ea82", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-08-12T10:49:21+00:00" }, { "name": "sebastian/code-unit", "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", "reference": "c1e2df332c905079980b119c4db103117e5e5c90" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/c1e2df332c905079980b119c4db103117e5e5c90", "reference": "c1e2df332c905079980b119c4db103117e5e5c90", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:50:45+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ee51f9bb0c6d8a43337055db3120829fa14da819", "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:04:00+00:00" }, { "name": "sebastian/comparator", "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", "shasum": "" }, "require": { "php": "^7.3 || ^8.0", "sebastian/diff": "^4.0", "sebastian/exporter": "^4.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "4.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" }, { "name": "Volker Dusch", "email": "github@wallbash.com" }, { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" } ], "description": "Provides the functionality to compare PHP values for equality", "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:05:46+00:00" }, { "name": "sebastian/complexity", "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", "reference": "33fcd6a26656c6546f70871244ecba4b4dced097" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/33fcd6a26656c6546f70871244ecba4b4dced097", "reference": "33fcd6a26656c6546f70871244ecba4b4dced097", "shasum": "" }, "require": { "nikic/php-parser": "^4.7", "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-07-25T14:01:34+00:00" }, { "name": "sebastian/diff", "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { "dev-master": "4.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Kore Nordmann", "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ "diff", "udiff", "unidiff", "unified diff" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-30T04:46:02+00:00" }, { "name": "sebastian/environment", "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "suggest": { "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { "dev-master": "5.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Provides functionality to handle HHVM/PHP environments", "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", "hhvm" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:07:24+00:00" }, { "name": "sebastian/exporter", "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", "reference": "571d721db4aec847a0e59690b954af33ebf9f023" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/571d721db4aec847a0e59690b954af33ebf9f023", "reference": "571d721db4aec847a0e59690b954af33ebf9f023", "shasum": "" }, "require": { "php": "^7.3 || ^8.0", "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "4.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" }, { "name": "Volker Dusch", "email": "github@wallbash.com" }, { "name": "Adam Harvey", "email": "aharvey@php.net" }, { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", "homepage": "http://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:08:55+00:00" }, { "name": "sebastian/global-state", "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", "reference": "22ae663c951bdc39da96603edc3239ed3a299097" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/22ae663c951bdc39da96603edc3239ed3a299097", "reference": "22ae663c951bdc39da96603edc3239ed3a299097", "shasum": "" }, "require": { "php": "^7.3 || ^8.0", "sebastian/object-reflector": "^2.0", "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" }, "type": "library", "extra": { "branch-alias": { "dev-master": "5.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Snapshotting of global state", "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-08-07T04:09:03+00:00" }, { "name": "sebastian/lines-of-code", "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e02bf626f404b5daec382a7b8a6a4456e49017e5", "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5", "shasum": "" }, "require": { "nikic/php-parser": "^4.6", "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-07-22T18:33:42+00:00" }, { "name": "sebastian/object-enumerator", "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/074fed2d0a6d08e1677dd8ce9d32aecb384917b8", "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8", "shasum": "" }, "require": { "php": "^7.3 || ^8.0", "sebastian/object-reflector": "^2.0", "sebastian/recursion-context": "^4.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "4.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:11:32+00:00" }, { "name": "sebastian/object-reflector", "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", "reference": "127a46f6b057441b201253526f81d5406d6c7840" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/127a46f6b057441b201253526f81d5406d6c7840", "reference": "127a46f6b057441b201253526f81d5406d6c7840", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:12:55+00:00" }, { "name": "sebastian/recursion-context", "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/062231bf61d2b9448c4fa5a7643b5e1829c11d63", "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "4.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" }, { "name": "Adam Harvey", "email": "aharvey@php.net" } ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:14:17+00:00" }, { "name": "sebastian/resource-operations", "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", "reference": "0653718a5a629b065e91f774595267f8dc32e213" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0653718a5a629b065e91f774595267f8dc32e213", "reference": "0653718a5a629b065e91f774595267f8dc32e213", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "3.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" } ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:16:22+00:00" }, { "name": "sebastian/type", "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/86991e2b33446cd96e648c18bcdb1e95afb2c05a", "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { "dev-master": "2.2-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-07-05T08:31:53+00:00" }, { "name": "sebastian/version", "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", "reference": "626586115d0ed31cb71483be55beb759b5af5a3c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/626586115d0ed31cb71483be55beb759b5af5a3c", "reference": "626586115d0ed31cb71483be55beb759b5af5a3c", "shasum": "" }, "require": { "php": "^7.3 || ^8.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "3.0-dev" } }, "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], "time": "2020-06-26T12:18:43+00:00" }, { "name": "symfony/polyfill-ctype", "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", "shasum": "" }, "require": { "php": ">=5.3.3" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.18-dev" }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, "files": [ "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "ctype", "polyfill", "portable" ], "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2020-07-14T12:35:20+00:00" }, { "name": "theseer/tokenizer", "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", "reference": "75a63c33a8577608444246075ea0af0d052e452a" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", "reference": "75a63c33a8577608444246075ea0af0d052e452a", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { "classmap": [ "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Arne Blankerts", "email": "arne@blankerts.de", "role": "Developer" } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "funding": [ { "url": "https://github.com/theseer", "type": "github" } ], "time": "2020-07-12T23:59:07+00:00" }, { "name": "webmozart/assert", "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ "assert", "check", "validate" ], "time": "2020-07-08T17:02:28+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.3", "ext-json": "*" }, "platform-dev": [], "plugin-api-version": "1.1.0" }