Skip to content

Commit beb1e13

Browse files
committed
Introduce new recMode param to indicate if tracking request is supposed to be a bot
1 parent fdd2dfc commit beb1e13

File tree

4 files changed

+228
-2
lines changed

4 files changed

+228
-2
lines changed

core/Tracker.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,25 @@ public function trackRequest(Request $request)
179179
*/
180180
Piwik::postEvent('Tracker.isBotRequest', [&$isBot, $request]);
181181

182-
if ($isBot) {
182+
$rawParams = $request->getRawParams();
183+
184+
/**
185+
* The recMode param will for now be used to keep BC.
186+
* If it is not set, which is currently the case for all tracking requests, it will be processed as Visit only
187+
* When set to 1, only bot tracking will be processed. In case the request is not detected as bot, it will be discarded
188+
* Setting it to 2 enables auto mode. Meaning it will be either processed as bot request or visit, depending on the detection
189+
*
190+
* @deprecated Remove this parameter handling with Matomo 6 and decide the tracking method based on the bot detection only.
191+
*/
192+
$recMode = $rawParams['recMode'] ?? null;
193+
194+
if (((int)$recMode === 1 || (int)$recMode === 2) && $isBot) {
183195
$botRequest = StaticContainer::get(BotRequest::class);
184196
$botRequest->setRequest($request);
185197
$botRequest->handle();
186198
}
187199

188-
if (!$isBot || $request->getParam('bots')) {
200+
if (empty($recMode) || ((int)$recMode === 2 && !$isBot)) {
189201
$visit = Visit\Factory::make();
190202
$visit->setRequest($request);
191203
$visit->handle();

plugins/BotTracking/tests/Integration/TrackerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function testAIAssistantIsTrackedCorrectly(string $userAgent, string $exp
3939
$t = Fixture::getTracker(1, '2025-02-02 12:00:00');
4040
$t->setUserAgent($userAgent);
4141
$t->setUrl('https://matomo.org/faq/123');
42+
$t->setCustomTrackingParameter('recMode', '1');
4243

4344
Fixture::checkResponse($t->doTrackPageView(''));
4445

@@ -85,6 +86,7 @@ public function testActionIdsAreCorrectlyReused(): void
8586
$t = Fixture::getTracker(1, '2025-02-02 12:00:00');
8687
$t->setUserAgent('ChatGPT-User/1.0');
8788
$t->setUrl('https://matomo.org/faq/123');
89+
$t->setCustomTrackingParameter('recMode', '1');
8890

8991
Fixture::checkResponse($t->doTrackPageView(''));
9092

@@ -108,6 +110,7 @@ public function testActionIdsAreCorrectlyReused(): void
108110
$t = Fixture::getTracker(1, '2025-02-02 12:00:00');
109111
$t->setUserAgent('Gemini-Deep-Research/1.0');
110112
$t->setUrl('https://matomo.org/faq/123');
113+
$t->setCustomTrackingParameter('recMode', '1');
111114

112115
Fixture::checkResponse($t->doTrackPageView(''));
113116

@@ -152,6 +155,7 @@ public function testVisitsAndBotsShareActions(): void
152155
$t = Fixture::getTracker(1, '2025-02-02 12:00:00');
153156
$t->setUserAgent('Gemini-Deep-Research/1.0');
154157
$t->setUrl('https://matomo.org/faq/123');
158+
$t->setCustomTrackingParameter('recMode', '1');
155159

156160
Fixture::checkResponse($t->doTrackPageView(''));
157161

plugins/BotTracking/tests/System/PurgeLogDataTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,19 @@ public function testLogDataPurgingRemovesBotRequests(): void
4242
$t = Fixture::getTracker(1, '2025-02-02 12:00:00');
4343
$t->setUserAgent('Mozilla/5.0 (compatible; ChatGPT-User/1.0)');
4444
$t->setUrl('https://matomo.org/faq/123');
45+
$t->setCustomTrackingParameter('recMode', '1');
4546
Fixture::checkResponse($t->doTrackPageView(''));
4647

4748
$t = Fixture::getTracker(1, '2025-02-02 17:00:00');
4849
$t->setUserAgent('Perplexity-User/1.0');
4950
$t->setUrl('https://matomo.org/faq/987');
51+
$t->setCustomTrackingParameter('recMode', '1');
5052
Fixture::checkResponse($t->doTrackPageView(''));
5153

5254
$t = Fixture::getTracker(1, '2025-02-03 01:00:00');
5355
$t->setUserAgent('MistralAI-User/2.0');
5456
$t->setUrl('https://matomo.org/faq/576');
57+
$t->setCustomTrackingParameter('recMode', '1');
5558
Fixture::checkResponse($t->doTrackPageView(''));
5659

5760
// check that all requests were tracked
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
3+
/**
4+
* Matomo - free/libre analytics platform
5+
*
6+
* @link https://matomo.org
7+
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Piwik\Tests\Integration\Tracker;
13+
14+
use Piwik\Container\Container;
15+
use Piwik\Plugin\RequestProcessors;
16+
use Piwik\Tests\Framework\Fixture;
17+
use Piwik\Tracker;
18+
use Piwik\Tracker\BotRequestProcessor;
19+
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
20+
use Piwik\Tracker\Request;
21+
use Piwik\Tracker\RequestProcessor;
22+
use Piwik\Tracker\Visit\VisitProperties;
23+
24+
/**
25+
* @group Core
26+
* @group Tracker
27+
* @group BotRequestHandling
28+
*/
29+
class BotRequestHandlingTest extends IntegrationTestCase
30+
{
31+
public static $eventsTriggered = [];
32+
33+
public function setUp(): void
34+
{
35+
parent::setUp();
36+
37+
Fixture::createWebsite('2025-05-05 20:00:00');
38+
39+
BotRequestHandlingTest::$eventsTriggered = [
40+
'BotRequestProcessor' => [],
41+
'RequestProcessor' => [],
42+
];
43+
}
44+
45+
/**
46+
* @param string|int|array|null $recMode
47+
* @dataProvider getTestModeData
48+
*/
49+
public function testVisitTrackingWithDefaultMode(string $ua, $recMode, array $expectedEvents): void
50+
{
51+
$tracker = new Tracker();
52+
53+
$request = new Request([
54+
'idsite' => 1,
55+
'url' => 'https://example.com/test',
56+
'ua' => $ua,
57+
'rec' => 1,
58+
'recMode' => $recMode,
59+
]);
60+
61+
$tracker->trackRequest($request);
62+
63+
self::assertEquals($expectedEvents, self::$eventsTriggered);
64+
}
65+
66+
public function getTestModeData(): iterable
67+
{
68+
yield 'Visit tracked in default mode should be processed by RequestProcessor only' => [
69+
'ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) RockMelt/0.9.58.494 Chrome/11.0.696.71 Safari/534.24',
70+
'recMode' => null,
71+
'expectedEvents' => [
72+
'BotRequestProcessor' => [],
73+
'RequestProcessor' => [
74+
'manipulateRequest',
75+
'processRequestParams',
76+
],
77+
],
78+
];
79+
80+
yield 'Bot tracked in default mode should be also processed by RequestProcessor only' => [
81+
'ua' => 'Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com)',
82+
'recMode' => '',
83+
'expectedEvents' => [
84+
'BotRequestProcessor' => [],
85+
'RequestProcessor' => [
86+
'manipulateRequest',
87+
'processRequestParams',
88+
],
89+
],
90+
];
91+
92+
yield 'Visit tracked in bot mode should not be processed at all' => [
93+
'ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) RockMelt/0.9.58.494 Chrome/11.0.696.71 Safari/534.24',
94+
'recMode' => 1,
95+
'expectedEvents' => [
96+
'BotRequestProcessor' => [],
97+
'RequestProcessor' => [],
98+
],
99+
];
100+
101+
yield 'Bot tracked in bot mode should be processed by BotRequestProcessor only' => [
102+
'ua' => 'Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com)',
103+
'recMode' => '1',
104+
'expectedEvents' => [
105+
'BotRequestProcessor' => [
106+
'manipulateRequest',
107+
'handleRequest',
108+
],
109+
'RequestProcessor' => [
110+
'manipulateRequest', // currently still triggered for bc reasons
111+
],
112+
],
113+
];
114+
115+
116+
yield 'Visit tracked in auto mode should be processed by RequestProcessor only' => [
117+
'ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) RockMelt/0.9.58.494 Chrome/11.0.696.71 Safari/534.24',
118+
'recMode' => 2,
119+
'expectedEvents' => [
120+
'BotRequestProcessor' => [],
121+
'RequestProcessor' => [
122+
'manipulateRequest',
123+
'processRequestParams',
124+
],
125+
],
126+
];
127+
128+
yield 'Bot tracked in auto mode should be processed by BotRequestProcessor only' => [
129+
'ua' => 'Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com)',
130+
'recMode' => '2',
131+
'expectedEvents' => [
132+
'BotRequestProcessor' => [
133+
'manipulateRequest',
134+
'handleRequest',
135+
],
136+
'RequestProcessor' => [
137+
'manipulateRequest', // currently still triggered for bc reasons
138+
],
139+
],
140+
];
141+
142+
yield 'Visit tracked with invalid integer mode should not be processed' => [
143+
'ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) RockMelt/0.9.58.494 Chrome/11.0.696.71 Safari/534.24',
144+
'recMode' => '5',
145+
'expectedEvents' => [
146+
'BotRequestProcessor' => [],
147+
'RequestProcessor' => [],
148+
],
149+
];
150+
151+
yield 'Visit tracked with invalid array mode should not be processed' => [
152+
'ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) RockMelt/0.9.58.494 Chrome/11.0.696.71 Safari/534.24',
153+
'recMode' => ['bla'],
154+
'expectedEvents' => [
155+
'BotRequestProcessor' => [],
156+
'RequestProcessor' => [],
157+
],
158+
];
159+
}
160+
161+
public function provideContainerConfig()
162+
{
163+
return [
164+
RequestProcessors::class => function (Container $c) {
165+
return new class extends RequestProcessors {
166+
public function getBotRequestProcessors(): array
167+
{
168+
return [
169+
new class extends BotRequestProcessor {
170+
public function manipulateRequest(Request $request): void
171+
{
172+
BotRequestHandlingTest::$eventsTriggered['BotRequestProcessor'][] = 'manipulateRequest';
173+
}
174+
175+
public function handleRequest(Request $request): bool
176+
{
177+
BotRequestHandlingTest::$eventsTriggered['BotRequestProcessor'][] = 'handleRequest';
178+
179+
return false;
180+
}
181+
},
182+
];
183+
}
184+
185+
public function getRequestProcessors(): array
186+
{
187+
return [
188+
new class extends RequestProcessor {
189+
public function manipulateRequest(Request $request)
190+
{
191+
BotRequestHandlingTest::$eventsTriggered['RequestProcessor'][] = 'manipulateRequest';
192+
}
193+
194+
public function processRequestParams(VisitProperties $visitProperties, Request $request)
195+
{
196+
BotRequestHandlingTest::$eventsTriggered['RequestProcessor'][] = 'processRequestParams';
197+
198+
return false;
199+
}
200+
},
201+
];
202+
}
203+
};
204+
},
205+
];
206+
}
207+
}

0 commit comments

Comments
 (0)