From eaeb3d849f308f21e799559e9082057910a72fd8 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Tue, 25 Nov 2025 00:09:25 -0800 Subject: [PATCH 1/9] add methods to detect and track pageviews if the current user agent is an AI bot --- MatomoTracker.php | 52 +++++++++++++++++++++++++++++++- tests/Unit/MatomoTrackerTest.php | 28 +++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 0e4f41b..90103ca 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -31,6 +31,18 @@ class MatomoTracker */ static public $URL = ''; + private static $aiBotUserAgentSubstrings = [ + 'GPTBot', + 'ChatGPT-User', + 'MistralAI-User', + 'Gemini-Deep-Research', + 'Claude-User', + 'Perplexity-User', + 'GoogleAgent', + 'Devin', + 'NovaAct', + ]; + /** * API Version * @@ -810,7 +822,25 @@ public function doTrackPageView(string $documentTitle) return $this->sendRequest($url); } - + + /** + * If the current user agent belongs to an AI agent bot, tracks a pageview action. + * + * This method should be used server side to track AI bots that do not execute + * JavaScript. + * + * @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 doTrackPageViewIfAIBot(string $documentTitle) + { + if (!self::isUserAgentAIBot($this->userAgent)) { + return null; + } + + return $this->doTrackPageView($documentTitle); + } + /** * Override PageView id for every use of `doTrackPageView()`. Do not use this if you call `doTrackPageView()` * multiple times during tracking (if, for example, you are tracking a single page application). @@ -2540,6 +2570,26 @@ protected function parseIncomingCookies(array $headers): void } } } + + /** + * Returns true if the given user agent belongs to a known AI bot. + * + * @param string $userAgent + * @return bool + */ + public static function isUserAgentAIBot(string $userAgent): bool + { + if (empty($userAgent)) { + return false; + } + + foreach (self::$aiBotUserAgentSubstrings as $substring) { + if (stripos($userAgent, $substring) !== false) { + return true; + } + } + return false; + } } /** diff --git a/tests/Unit/MatomoTrackerTest.php b/tests/Unit/MatomoTrackerTest.php index 703b631..edf45a8 100644 --- a/tests/Unit/MatomoTrackerTest.php +++ b/tests/Unit/MatomoTrackerTest.php @@ -84,4 +84,32 @@ public function test_setApiUrl() $this->assertSame(substr($url, 0, strlen($newApiUrl)), $newApiUrl); } + + /** + * @dataProvider getTestDataForIsUserAgentAIBot + */ + public function test_isUserAgentAIBot($userAgent, $expected) + { + $this->assertSame($expected, \MatomoTracker::isUserAgentAIBot($userAgent)); + } + + public function getTestDataForIsUserAgentAIBot(): array + { + return [ + ['', false], + + ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.3', false], + ['Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.3', false], + + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot', true], + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.1; +https://openai.com/gptbot', true], + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; MistralAI-User/1.0; +https://docs.mistral.ai/robots)', true], + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Gemini-Deep-Research; +https://gemini.google/overview/deep-research/) Chrome/135.0.0.0 Safari/537.36', true], + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +Claude-User@anthropic.com)', true], + ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)', true], + ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; compatible; GoogleAgent-Mariner; +https://developers.google[dot]com/search/docs/crawling-indexing/google-agent-mariner) Chrome/135.0.0.0 Safari/537.36', true], + ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36; Devin/1.0; +devin.ai', true], + ['test user agent NovaAct ...', true], + ]; + } } \ No newline at end of file From 70e704879d169d70d98cf13ac5022ce2833318cc Mon Sep 17 00:00:00 2001 From: diosmosis Date: Wed, 26 Nov 2025 05:56:27 -0800 Subject: [PATCH 2/9] add supported query parameters for AI bot tracking --- MatomoTracker.php | 97 +++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 90103ca..06d1d95 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -41,6 +41,8 @@ class MatomoTracker 'GoogleAgent', 'Devin', 'NovaAct', + 'Google-Extended', // TODO + 'Google-CloudVertexBot', // TODO ]; /** @@ -172,11 +174,11 @@ class MatomoTracker // Visitor Ids in order public $userId = false; - + public $forcedVisitorId = false; - + public $cookieVisitorId = false; - + public $randomVisitorId = false; public $configCookiesDisabled = false; @@ -197,11 +199,11 @@ class MatomoTracker // Allow debug while blocking the request public $requestTimeout = 600; - + public $requestConnectTimeout = 300; - + public $doBulkRequests = false; - + public $storedTrackingActions = []; public $sendImageResponse = true; @@ -245,7 +247,7 @@ public function __construct(int $idSite, string $apiUrl = '') $this->currentTs = time(); $this->createTs = $this->currentTs; - + $this->visitorCustomVar = $this->getCustomVariablesFromCookie(); } @@ -612,7 +614,7 @@ public function setClientHints( string $model = '', string $platform = '', string $platformVersion = '', - $fullVersionList = '', + $fullVersionList = '', string $uaFullVersion = '' ) { if (is_string($fullVersionList)) { @@ -725,7 +727,7 @@ public function enableBulkTracking(): void } /** - * Disables the bulk request feature. Make sure to call `doBulkTrack()` before disabling it if you have stored + * 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(): void @@ -829,16 +831,16 @@ public function doTrackPageView(string $documentTitle) * This method should be used server side to track AI bots that do not execute * JavaScript. * - * @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 doTrackPageViewIfAIBot(string $documentTitle) + public function doTrackPageViewIfAIBot(?int $httpStatus = null, ?int $responseSizeBytes = null, ?int $serverTimeMs = null, ?string $source = null) { if (!self::isUserAgentAIBot($this->userAgent)) { return null; } - return $this->doTrackPageView($documentTitle); + $url = $this->getUrlTrackAIBot($httpStatus, $responseSizeBytes, $serverTimeMs, $source); + return $this->sendRequest($url); } /** @@ -855,7 +857,7 @@ public function setPageviewId(string $idPageview): void * Returns the PageView id. If the id was manually set using `setPageViewId()`, that id will be returned. * If the id was not set manually, the id that was automatically generated in last `doTrackPageView()` will * be returned. If there was no last page view, this will be false. - * + * * @return string|false The PageView id as string or false if there is none yet. */ public function getPageviewId() @@ -880,8 +882,8 @@ private function generateNewPageviewId(): void public function doTrackEvent( string $category, string $action, - $name = false, - $value = false + $name = false, + $value = false ) { $url = $this->getUrlTrackEvent($category, $action, $name, $value); @@ -899,7 +901,7 @@ public function doTrackEvent( public function doTrackContentImpression( string $contentName, string $contentPiece = 'Unknown', - $contentTarget = false + $contentTarget = false ) { $url = $this->getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget); @@ -920,7 +922,7 @@ public function doTrackContentInteraction( string $interaction, string $contentName, string $contentPiece = 'Unknown', - $contentTarget = false + $contentTarget = false ) { $url = $this->getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget); @@ -940,7 +942,7 @@ public function doTrackContentInteraction( public function doTrackSiteSearch( string $keyword, string $category = '', - $countResults = false + $countResults = false ) { $url = $this->getUrlTrackSiteSearch($keyword, $category, $countResults); @@ -995,8 +997,8 @@ public function doTrackAction(string $actionUrl, string $actionType) public function addEcommerceItem( string $sku, string $name = '', - $category = '', - $price = 0.0, + $category = '', + $price = 0.0, int $quantity = 1 ) { if (empty($sku)) { @@ -1174,7 +1176,7 @@ public function doPing() public function setEcommerceView( string $sku = '', string $name = '', - $category = '', + $category = '', float $price = 0.0 ) { $this->ecommerceView = []; @@ -1223,6 +1225,35 @@ private function forceDotAsSeparatorForDecimalPoint($value): string return str_replace(',', '.', $value); } + /** + * TODO: tests + * TODO: docs + * + * @return string + */ + public function getUrlTrackAIBot(?int $httpStatus = null, ?int $responseSizeBytes = null, ?int $serverTimeMs = null, ?string $source = null): string + { + $url = $this->getRequest($this->idSite); + + if (!empty($httpStatus)) { + $url .= '&http_status=' . $httpStatus; + } + + if (!empty($responseSizeBytes)) { + $url .= '&bw_bytes=' . $responseSizeBytes; + } + + if (!empty($serverTimeMs)) { + $url .= '&pf_srv=' . $serverTimeMs; + } + + if (!empty($source)) { + $url .= '&source=' . $source; + } + + return $url; + } + /** * Returns URL used to track Ecommerce Cart updates * Calling this function will reinitializes the property ecommerceItems to empty array @@ -1332,8 +1363,8 @@ public function getUrlTrackPageView(string $documentTitle = ''): string public function getUrlTrackEvent( string $category, string $action, - $name = false, - $value = false + $name = false, + $value = false ): string { $url = $this->getRequest($this->idSite); if (strlen($category) === 0) { @@ -1370,7 +1401,7 @@ public function getUrlTrackEvent( public function getUrlTrackContentImpression( string $contentName, string $contentPiece, - $contentTarget + $contentTarget ): string { $url = $this->getRequest($this->idSite); @@ -1405,7 +1436,7 @@ public function getUrlTrackContentInteraction( string $interaction, string $contentName, string $contentPiece, - $contentTarget + $contentTarget ): string { $url = $this->getRequest($this->idSite); @@ -1884,7 +1915,7 @@ public function setRequestTimeout(int $timeout) return $this; } - + /** * Returns the maximum number of seconds the tracker will spend trying to connect to Matomo. * Defaults to 300 seconds. @@ -1912,7 +1943,7 @@ public function setRequestConnectTimeout(int $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, @@ -1965,7 +1996,7 @@ private function getProxy(): ?string protected function prepareCurlOptions( string $url, string $method, - $data, + $data, bool $forcePostUrlEncoded ): array { $options = [ @@ -2127,7 +2158,7 @@ protected function sendRequest(string $url, string $method = 'GET', $data = null curl_setopt_array($ch, $options); ob_start(); $response = @curl_exec($ch); - + try { $header = ''; @@ -2368,7 +2399,7 @@ protected static function getCurrentScriptName(): string if (empty($url) && isset($_SERVER['SCRIPT_NAME'])) { $url = $_SERVER['SCRIPT_NAME']; } elseif (empty($url)) { - $url = '/'; + $url = '/'; } if (!empty($url) && $url[0] !== '/') { @@ -2437,9 +2468,9 @@ protected static function getCurrentQueryString(): string protected static function getCurrentUrl(): string { return self::getCurrentScheme() . '://' - . self::getCurrentHost() - . self::getCurrentScriptName() - . self::getCurrentQueryString(); + . self::getCurrentHost() + . self::getCurrentScriptName() + . self::getCurrentQueryString(); } /** From adb1b0b41f1ea7f38e9d17a950ea995074e50ea4 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Fri, 28 Nov 2025 03:40:26 -0800 Subject: [PATCH 3/9] add more tests for AI bot tracking methods --- MatomoTracker.php | 6 ++-- tests/Unit/MatomoTrackerTest.php | 61 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 06d1d95..f7486c7 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -41,8 +41,8 @@ class MatomoTracker 'GoogleAgent', 'Devin', 'NovaAct', - 'Google-Extended', // TODO - 'Google-CloudVertexBot', // TODO + 'Google-Extended', + 'Google-CloudVertexBot', ]; /** @@ -1248,7 +1248,7 @@ public function getUrlTrackAIBot(?int $httpStatus = null, ?int $responseSizeByte } if (!empty($source)) { - $url .= '&source=' . $source; + $url .= '&source=' . rawurlencode($source); } return $url; diff --git a/tests/Unit/MatomoTrackerTest.php b/tests/Unit/MatomoTrackerTest.php index edf45a8..b0bfb89 100644 --- a/tests/Unit/MatomoTrackerTest.php +++ b/tests/Unit/MatomoTrackerTest.php @@ -110,6 +110,67 @@ public function getTestDataForIsUserAgentAIBot(): array ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; compatible; GoogleAgent-Mariner; +https://developers.google[dot]com/search/docs/crawling-indexing/google-agent-mariner) Chrome/135.0.0.0 Safari/537.36', true], ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36; Devin/1.0; +devin.ai', true], ['test user agent NovaAct ...', true], + ['Google-Extended', true], + ['test Google-CloudVertexBot test', true], ]; } + + /** + * @dataProvider getTestDataForGetUrlTrackAIBot + */ + public function test_getUrlTrackAIBot(?int $httpStatus, ?int $responseSizeBytes, ?int $serverTimeMs, ?string $source, string $expected) + { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot'; + + $tracker = new \MatomoTracker(1, $apiUrl = self::TEST_URL); + $tracker->setVisitorId('abcdef01234517ab'); + + $actual = $tracker->getUrlTrackAIBot($httpStatus, $responseSizeBytes, $serverTimeMs, $source); + $actual = $this->normalizeTrackingUrl($actual); + + $this->assertEquals($expected, $actual); + } + + public function getTestDataForGetUrlTrackAIBot(): array + { + return [ + [ + 200, + 34567, + 123, + 'wordpress', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&http_status=200&bw_bytes=34567&pf_srv=123&source=wordpress', + ], + + [ + null, + 34567, + null, + 'something else', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&bw_bytes=34567&source=something%20else', + ], + + [ + null, + null, + null, + null, + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=', + ], + ]; + } + + private function normalizeTrackingUrl(string $url) + { + $nonDeterministicParams = [ + 'r', + '_idts', + ]; + + foreach ($nonDeterministicParams as $param) { + $url = preg_replace('/&' . preg_quote($param) . '=[^&]+/', '&r=', $url); + } + + return $url; + } } \ No newline at end of file From 2a1772d09ccecf1dd5d0277c24b6c45502a47685 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Sun, 7 Dec 2025 14:20:27 -0800 Subject: [PATCH 4/9] use recMode parameter --- MatomoTracker.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index f7486c7..22fb26a 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -32,17 +32,13 @@ class MatomoTracker static public $URL = ''; private static $aiBotUserAgentSubstrings = [ - 'GPTBot', 'ChatGPT-User', 'MistralAI-User', 'Gemini-Deep-Research', 'Claude-User', 'Perplexity-User', - 'GoogleAgent', + 'Google-NotebookLM', 'Devin', - 'NovaAct', - 'Google-Extended', - 'Google-CloudVertexBot', ]; /** @@ -1235,6 +1231,8 @@ public function getUrlTrackAIBot(?int $httpStatus = null, ?int $responseSizeByte { $url = $this->getRequest($this->idSite); + $url .= '&recMode=1'; + if (!empty($httpStatus)) { $url .= '&http_status=' . $httpStatus; } From 24a1cd2451eaf1a47797a5d5e8b39e4ec5be5908 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Thu, 11 Dec 2025 21:36:24 -0800 Subject: [PATCH 5/9] make user agent substrings array a public const and fill out missing php docs --- MatomoTracker.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 22fb26a..39e2488 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -31,7 +31,7 @@ class MatomoTracker */ static public $URL = ''; - private static $aiBotUserAgentSubstrings = [ + public const AI_BOT_USER_AGENT_SUBSTRINGS = [ 'ChatGPT-User', 'MistralAI-User', 'Gemini-Deep-Research', @@ -1222,9 +1222,12 @@ private function forceDotAsSeparatorForDecimalPoint($value): string } /** - * TODO: tests - * TODO: docs + * Builds a URL to track a request from an AI bot. * + * @param int|null $httpStatus the request's HTTP status code, if it is known. + * @param int|null $responseSizeBytes the size of the response sent to the AI bot, if known. + * @param int|null $serverTimeMs the number of milliseconds it took to process the request, if known. + * @param string|null $source * @return string */ public function getUrlTrackAIBot(?int $httpStatus = null, ?int $responseSizeBytes = null, ?int $serverTimeMs = null, ?string $source = null): string @@ -2612,7 +2615,7 @@ public static function isUserAgentAIBot(string $userAgent): bool return false; } - foreach (self::$aiBotUserAgentSubstrings as $substring) { + foreach (self::AI_BOT_USER_AGENT_SUBSTRINGS as $substring) { if (stripos($userAgent, $substring) !== false) { return true; } From af83c1b18db22bccfe75e69c2fd7c782331355b9 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Thu, 11 Dec 2025 22:15:59 -0800 Subject: [PATCH 6/9] remove phpstorm added spacing --- MatomoTracker.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 39e2488..b367694 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -610,7 +610,7 @@ public function setClientHints( string $model = '', string $platform = '', string $platformVersion = '', - $fullVersionList = '', + $fullVersionList = '', string $uaFullVersion = '' ) { if (is_string($fullVersionList)) { @@ -878,8 +878,8 @@ private function generateNewPageviewId(): void public function doTrackEvent( string $category, string $action, - $name = false, - $value = false + $name = false, + $value = false ) { $url = $this->getUrlTrackEvent($category, $action, $name, $value); @@ -918,7 +918,7 @@ public function doTrackContentInteraction( string $interaction, string $contentName, string $contentPiece = 'Unknown', - $contentTarget = false + $contentTarget = false ) { $url = $this->getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget); @@ -938,7 +938,7 @@ public function doTrackContentInteraction( public function doTrackSiteSearch( string $keyword, string $category = '', - $countResults = false + $countResults = false ) { $url = $this->getUrlTrackSiteSearch($keyword, $category, $countResults); @@ -993,8 +993,8 @@ public function doTrackAction(string $actionUrl, string $actionType) public function addEcommerceItem( string $sku, string $name = '', - $category = '', - $price = 0.0, + $category = '', + $price = 0.0, int $quantity = 1 ) { if (empty($sku)) { @@ -1172,7 +1172,7 @@ public function doPing() public function setEcommerceView( string $sku = '', string $name = '', - $category = '', + $category = '', float $price = 0.0 ) { $this->ecommerceView = []; @@ -1364,8 +1364,8 @@ public function getUrlTrackPageView(string $documentTitle = ''): string public function getUrlTrackEvent( string $category, string $action, - $name = false, - $value = false + $name = false, + $value = false ): string { $url = $this->getRequest($this->idSite); if (strlen($category) === 0) { @@ -1437,7 +1437,7 @@ public function getUrlTrackContentInteraction( string $interaction, string $contentName, string $contentPiece, - $contentTarget + $contentTarget ): string { $url = $this->getRequest($this->idSite); From 81ec770617904d05c498d604fd7bea5a35bb4c08 Mon Sep 17 00:00:00 2001 From: diosmosis Date: Thu, 11 Dec 2025 23:48:53 -0800 Subject: [PATCH 7/9] update tests --- MatomoTracker.php | 1 + tests/Unit/MatomoTrackerTest.php | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index be57bd5..ef40878 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -39,6 +39,7 @@ class MatomoTracker 'Perplexity-User', 'Google-NotebookLM', 'Devin', + 'GPTBot', ]; /** diff --git a/tests/Unit/MatomoTrackerTest.php b/tests/Unit/MatomoTrackerTest.php index b0bfb89..dcfb7db 100644 --- a/tests/Unit/MatomoTrackerTest.php +++ b/tests/Unit/MatomoTrackerTest.php @@ -107,11 +107,7 @@ public function getTestDataForIsUserAgentAIBot(): array ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Gemini-Deep-Research; +https://gemini.google/overview/deep-research/) Chrome/135.0.0.0 Safari/537.36', true], ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +Claude-User@anthropic.com)', true], ['Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)', true], - ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; compatible; GoogleAgent-Mariner; +https://developers.google[dot]com/search/docs/crawling-indexing/google-agent-mariner) Chrome/135.0.0.0 Safari/537.36', true], ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36; Devin/1.0; +devin.ai', true], - ['test user agent NovaAct ...', true], - ['Google-Extended', true], - ['test Google-CloudVertexBot test', true], ]; } @@ -139,7 +135,7 @@ public function getTestDataForGetUrlTrackAIBot(): array 34567, 123, 'wordpress', - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&http_status=200&bw_bytes=34567&pf_srv=123&source=wordpress', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1&http_status=200&bw_bytes=34567&pf_srv=123&source=wordpress', ], [ @@ -147,7 +143,7 @@ public function getTestDataForGetUrlTrackAIBot(): array 34567, null, 'something else', - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&bw_bytes=34567&source=something%20else', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1&bw_bytes=34567&source=something%20else', ], [ @@ -155,7 +151,7 @@ public function getTestDataForGetUrlTrackAIBot(): array null, null, null, - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1', ], ]; } From 0bbf3b1d389e494ac0b636319a00dbde7eb4d68b Mon Sep 17 00:00:00 2001 From: diosmosis Date: Fri, 12 Dec 2025 08:30:02 -0800 Subject: [PATCH 8/9] set url in test --- tests/Unit/MatomoTrackerTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Unit/MatomoTrackerTest.php b/tests/Unit/MatomoTrackerTest.php index dcfb7db..c626f0f 100644 --- a/tests/Unit/MatomoTrackerTest.php +++ b/tests/Unit/MatomoTrackerTest.php @@ -119,6 +119,7 @@ public function test_getUrlTrackAIBot(?int $httpStatus, ?int $responseSizeBytes, $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot'; $tracker = new \MatomoTracker(1, $apiUrl = self::TEST_URL); + $tracker->setUrl('https://example.com/page'); $tracker->setVisitorId('abcdef01234517ab'); $actual = $tracker->getUrlTrackAIBot($httpStatus, $responseSizeBytes, $serverTimeMs, $source); @@ -135,7 +136,7 @@ public function getTestDataForGetUrlTrackAIBot(): array 34567, 123, 'wordpress', - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1&http_status=200&bw_bytes=34567&pf_srv=123&source=wordpress', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=https%3A%2F%2Fexample.com%2Fpage&urlref=&recMode=1&http_status=200&bw_bytes=34567&pf_srv=123&source=wordpress', ], [ @@ -143,7 +144,7 @@ public function getTestDataForGetUrlTrackAIBot(): array 34567, null, 'something else', - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1&bw_bytes=34567&source=something%20else', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=https%3A%2F%2Fexample.com%2Fpage&urlref=&recMode=1&bw_bytes=34567&source=something%20else', ], [ @@ -151,7 +152,7 @@ public function getTestDataForGetUrlTrackAIBot(): array null, null, null, - 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=http%3A%2F%2Funknown%2Fvendor%2Fbin%2Fphpunit&urlref=&recMode=1', + 'http://mymatomo.com/matomo.php?idsite=1&rec=1&apiv=1&r=&r=&cid=abcdef01234517ab&url=https%3A%2F%2Fexample.com%2Fpage&urlref=&recMode=1', ], ]; } From 0004dff2e4a68a422e42e57e817e326ad8a3a933 Mon Sep 17 00:00:00 2001 From: dizzy Date: Wed, 4 Feb 2026 14:16:36 -0800 Subject: [PATCH 9/9] remove Devin user agent check Co-authored-by: Thomas ZILLIOX --- MatomoTracker.php | 1 - 1 file changed, 1 deletion(-) diff --git a/MatomoTracker.php b/MatomoTracker.php index 67edcab..c2bbd28 100644 --- a/MatomoTracker.php +++ b/MatomoTracker.php @@ -39,7 +39,6 @@ class MatomoTracker 'Claude-User', 'Perplexity-User', 'Google-NotebookLM', - 'Devin', 'GPTBot', ];