Skip to content

Commit 71bab3d

Browse files
Merge pull request #196 from matomo-org/dev-19639
Avoid incorrectly forcing new visit when AI Assistants provide utm_source parameter
2 parents 5e83c02 + b523cb9 commit 71bab3d

File tree

4 files changed

+59
-2
lines changed

4 files changed

+59
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## Changelog
22

3+
#### 5.1.4 - 2025-11-10
4+
- Fixed incorrectly forcing a new visit for visits referred by AI referrers
5+
36
#### 5.1.3 - 2025-10-13
47
- Fix for handling AI referrers correctly, providing utm_source parameter
58

Columns/CampaignName.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ public function shouldForceNewVisit(Request $request, Visitor $visitor, Action $
5151
$campaignParameters
5252
);
5353

54+
// Never start a new visit, if the visit was detected as AI Assistant by core, unless
55+
// there are campaign parameters detected, that do not resolve to an AI Assistant.
56+
// This is a hacky workaround to solves issues where randomly new visits are started when
57+
// e.g. someone comes from ChatGPT having the `utm_source=chatgpt.com` url parameter.
58+
// That one will be detected as AI Assistant by core. But if someone reloads that page
59+
// and the utm source is still present, the check here might otherwise force a new visit,
60+
// if the `utm_source` parameter is configured.
61+
if ((int)$visitor->getVisitorColumn('referer_type') === 8 && count($campaignDimensions) === 1) {
62+
$paramValue = reset($campaignDimensions);
63+
if (class_exists('Piwik\Plugins\Referrers\AIAssistant') && \Piwik\Plugins\Referrers\AIAssistant::getInstance()->getAIAssistantFromDomain($paramValue)) {
64+
return false;
65+
}
66+
}
67+
5468
// we force a new visit if the referrer is a campaign and it's different than the currently recorded referrer.
5569
// if the current referrer is 'direct entry', however, we assume the referrer information was sent in a later request, and
5670
// we just update the existing referrer information instead of creating a visit.

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "MarketingCampaignsReporting",
33
"description": "Measure the effectiveness of your marketing campaigns. New reports, segments & track up to five channels: campaign, source, medium, keyword, content.",
4-
"version": "5.1.3",
4+
"version": "5.1.4",
55
"keywords": ["Campaign", "Marketing", "Channels", "UTM tags"],
66
"license": "GPL v3+",
77
"homepage": "https://matomo.org",

tests/Integration/ForceNewVisitTest.php

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function setUp(): void
4747
$configOverride['MarketingCampaignsReporting'] = [
4848
(new CampaignName())->getColumnName() => 'pk_campaign,custom_name_parameter',
4949
(new CampaignKeyword())->getColumnName() => 'pk_keyword,custom_keyword_parameter',
50-
(new CampaignSource())->getColumnName() => 'pk_source,custom_source_parameter',
50+
(new CampaignSource())->getColumnName() => 'utm_source,pk_source,custom_source_parameter',
5151
(new CampaignMedium())->getColumnName() => 'pk_medium,custom_medium_parameter',
5252
(new CampaignContent())->getColumnName() => 'pk_content,custom_content_parameter',
5353
(new CampaignId())->getColumnName() => 'pk_id,custom_id_parameter',
@@ -211,6 +211,46 @@ public function testTrackingWithPluginParameters()
211211
$this->assertVisits(2, 1, 2);
212212
}
213213

214+
public function testTrackingAIAssistantDoesNotForceNewVisit(): void
215+
{
216+
$url = $this->getUrlForTracking(['utm_source' => 'chatgpt.com']);
217+
218+
$this->tracker->setUrl($url);
219+
$this->tracker->setUrlReferrer('https://chatgpt.com');
220+
221+
Fixture::checkResponse($this->tracker->doTrackPageView('Track visit'));
222+
223+
$this->assertVisits(1, 1, 1);
224+
225+
// simulating a page reload
226+
$this->moveTimeForward(0.05);
227+
$this->tracker->setUrlReferrer('');
228+
Fixture::checkResponse($this->tracker->doTrackPageView('Track visit'));
229+
230+
$this->assertVisits(1, 1, 2);
231+
}
232+
233+
public function testCampaignAfterAIAssistantForcesNewVisit(): void
234+
{
235+
$url = $this->getUrlForTracking(['utm_source' => 'chatgpt.com']);
236+
237+
$this->tracker->setUrl($url);
238+
$this->tracker->setUrlReferrer('https://chatgpt.com');
239+
240+
Fixture::checkResponse($this->tracker->doTrackPageView('Track visit'));
241+
242+
$this->assertVisits(1, 1, 1);
243+
244+
$this->moveTimeForward(0.05);
245+
$this->tracker->setUrlReferrer('');
246+
$url = $this->getUrlForTracking(['pk_campaign' => 'custom name']);
247+
248+
$this->tracker->setUrl($url);
249+
Fixture::checkResponse($this->tracker->doTrackPageView('Track visit'));
250+
251+
$this->assertVisits(2, 1, 2);
252+
}
253+
214254
private function assertVisits($visitsExpected, $uniqueVisitsExpected, $actionsExpected)
215255
{
216256
$counters = LiveAPI::getInstance()->getCounters(

0 commit comments

Comments
 (0)