diff --git a/CHANGELOG.md b/CHANGELOG.md index d50864b..f1c232b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog +#### 5.1.4 - 2025-11-10 +- Fixed incorrectly forcing a new visit for visits referred by AI referrers + #### 5.1.3 - 2025-10-13 - Fix for handling AI referrers correctly, providing utm_source parameter diff --git a/Columns/CampaignName.php b/Columns/CampaignName.php index 3aef2f6..0a3a766 100644 --- a/Columns/CampaignName.php +++ b/Columns/CampaignName.php @@ -51,6 +51,20 @@ public function shouldForceNewVisit(Request $request, Visitor $visitor, Action $ $campaignParameters ); + // Never start a new visit, if the visit was detected as AI Assistant by core, unless + // there are campaign parameters detected, that do not resolve to an AI Assistant. + // This is a hacky workaround to solves issues where randomly new visits are started when + // e.g. someone comes from ChatGPT having the `utm_source=chatgpt.com` url parameter. + // That one will be detected as AI Assistant by core. But if someone reloads that page + // and the utm source is still present, the check here might otherwise force a new visit, + // if the `utm_source` parameter is configured. + if ((int)$visitor->getVisitorColumn('referer_type') === 8 && count($campaignDimensions) === 1) { + $paramValue = reset($campaignDimensions); + if (class_exists('Piwik\Plugins\Referrers\AIAssistant') && \Piwik\Plugins\Referrers\AIAssistant::getInstance()->getAIAssistantFromDomain($paramValue)) { + return false; + } + } + // we force a new visit if the referrer is a campaign and it's different than the currently recorded referrer. // if the current referrer is 'direct entry', however, we assume the referrer information was sent in a later request, and // we just update the existing referrer information instead of creating a visit. diff --git a/plugin.json b/plugin.json index 61a9d47..3552a45 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "name": "MarketingCampaignsReporting", "description": "Measure the effectiveness of your marketing campaigns. New reports, segments & track up to five channels: campaign, source, medium, keyword, content.", - "version": "5.1.3", + "version": "5.1.4", "keywords": ["Campaign", "Marketing", "Channels", "UTM tags"], "license": "GPL v3+", "homepage": "https://matomo.org", diff --git a/tests/Integration/ForceNewVisitTest.php b/tests/Integration/ForceNewVisitTest.php index 35bffd4..13be969 100644 --- a/tests/Integration/ForceNewVisitTest.php +++ b/tests/Integration/ForceNewVisitTest.php @@ -47,7 +47,7 @@ public function setUp(): void $configOverride['MarketingCampaignsReporting'] = [ (new CampaignName())->getColumnName() => 'pk_campaign,custom_name_parameter', (new CampaignKeyword())->getColumnName() => 'pk_keyword,custom_keyword_parameter', - (new CampaignSource())->getColumnName() => 'pk_source,custom_source_parameter', + (new CampaignSource())->getColumnName() => 'utm_source,pk_source,custom_source_parameter', (new CampaignMedium())->getColumnName() => 'pk_medium,custom_medium_parameter', (new CampaignContent())->getColumnName() => 'pk_content,custom_content_parameter', (new CampaignId())->getColumnName() => 'pk_id,custom_id_parameter', @@ -211,6 +211,46 @@ public function testTrackingWithPluginParameters() $this->assertVisits(2, 1, 2); } + public function testTrackingAIAssistantDoesNotForceNewVisit(): void + { + $url = $this->getUrlForTracking(['utm_source' => 'chatgpt.com']); + + $this->tracker->setUrl($url); + $this->tracker->setUrlReferrer('https://chatgpt.com'); + + Fixture::checkResponse($this->tracker->doTrackPageView('Track visit')); + + $this->assertVisits(1, 1, 1); + + // simulating a page reload + $this->moveTimeForward(0.05); + $this->tracker->setUrlReferrer(''); + Fixture::checkResponse($this->tracker->doTrackPageView('Track visit')); + + $this->assertVisits(1, 1, 2); + } + + public function testCampaignAfterAIAssistantForcesNewVisit(): void + { + $url = $this->getUrlForTracking(['utm_source' => 'chatgpt.com']); + + $this->tracker->setUrl($url); + $this->tracker->setUrlReferrer('https://chatgpt.com'); + + Fixture::checkResponse($this->tracker->doTrackPageView('Track visit')); + + $this->assertVisits(1, 1, 1); + + $this->moveTimeForward(0.05); + $this->tracker->setUrlReferrer(''); + $url = $this->getUrlForTracking(['pk_campaign' => 'custom name']); + + $this->tracker->setUrl($url); + Fixture::checkResponse($this->tracker->doTrackPageView('Track visit')); + + $this->assertVisits(2, 1, 2); + } + private function assertVisits($visitsExpected, $uniqueVisitsExpected, $actionsExpected) { $counters = LiveAPI::getInstance()->getCounters(