Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
14 changes: 14 additions & 0 deletions Columns/CampaignName.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
42 changes: 41 additions & 1 deletion tests/Integration/ForceNewVisitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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(
Expand Down