PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.3.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.3.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Tracker / VisitorRecognizer.php
matomo / app / core / Tracker Last commit date
Db 5 years ago Handler 5 years ago TableLogAction 5 years ago Visit 5 years ago Action.php 5 years ago ActionPageview.php 5 years ago Cache.php 5 years ago Db.php 5 years ago Failures.php 6 years ago FingerprintSalt.php 6 years ago GoalManager.php 5 years ago Handler.php 5 years ago IgnoreCookie.php 5 years ago LogTable.php 5 years ago Model.php 5 years ago PageUrl.php 5 years ago Request.php 5 years ago RequestProcessor.php 5 years ago RequestSet.php 5 years ago Response.php 5 years ago ScheduledTasksRunner.php 5 years ago Settings.php 5 years ago TableLogAction.php 5 years ago TrackerCodeGenerator.php 5 years ago TrackerConfig.php 5 years ago Visit.php 5 years ago VisitExcluded.php 5 years ago VisitInterface.php 5 years ago Visitor.php 5 years ago VisitorNotFoundInDb.php 5 years ago VisitorRecognizer.php 5 years ago
VisitorRecognizer.php
305 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 */
8
9 namespace Piwik\Tracker;
10
11 use Piwik\Common;
12 use Piwik\EventDispatcher;
13 use Piwik\Plugin\Dimension\VisitDimension;
14 use Piwik\Tracker\Visit\VisitProperties;
15
16 /**
17 * Tracker service that finds the last known visit for the visitor being tracked.
18 */
19 class VisitorRecognizer
20 {
21 /**
22 * Set when a visit was found. Stores the original values of the row that is currently stored in the DB when
23 * the visit was selected.
24 */
25 const KEY_ORIGINAL_VISIT_ROW = 'originalVisit';
26
27 /**
28 * Local variable cache for the getVisitFieldsPersist() method.
29 *
30 * @var array
31 */
32 private $visitFieldsToSelect;
33
34 /**
35 * See http://piwik.org/faq/how-to/faq_175/.
36 *
37 * @var bool
38 */
39 private $trustCookiesOnly;
40
41 /**
42 * Length of a visit in seconds.
43 *
44 * @var int
45 */
46 private $visitStandardLength;
47
48 /**
49 * Number of seconds that have to pass after an action before a new action from the same visitor is
50 * considered a new visit. Defaults to $visitStandardLength.
51 *
52 * @var int
53 */
54 private $lookBackNSecondsCustom;
55
56 /**
57 * @var Model
58 */
59 private $model;
60
61 /**
62 * @var EventDispatcher
63 */
64 private $eventDispatcher;
65
66 /**
67 * @var array
68 */
69 private $visitRow;
70
71 public function __construct($trustCookiesOnly, $visitStandardLength, $lookbackNSecondsCustom,
72 Model $model, EventDispatcher $eventDispatcher)
73 {
74 $this->trustCookiesOnly = $trustCookiesOnly;
75 $this->visitStandardLength = $visitStandardLength;
76 $this->lookBackNSecondsCustom = $lookbackNSecondsCustom;
77
78 $this->model = $model;
79 $this->eventDispatcher = $eventDispatcher;
80 }
81
82 public function setTrustCookiesOnly($trustCookiesOnly)
83 {
84 $this->trustCookiesOnly = $trustCookiesOnly;
85 }
86
87 public function findKnownVisitor($configId, VisitProperties $visitProperties, Request $request)
88 {
89 $idSite = $request->getIdSite();
90 $idVisitor = $request->getVisitorId();
91 $userId = $request->getForcedUserId();
92
93 $isVisitorIdToLookup = !empty($idVisitor);
94
95 if ($isVisitorIdToLookup) {
96 $visitProperties->setProperty('idvisitor', $idVisitor);
97 Common::printDebug("Matching visitors with: visitorId=" . bin2hex($idVisitor) . " OR configId=" . bin2hex($configId));
98 } else {
99 Common::printDebug("Visitor doesn't have the piwik cookie...");
100 }
101
102 $persistedVisitAttributes = $this->getVisitorFieldsPersist();
103
104 $shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, $request);
105 list($timeLookBack, $timeLookAhead) = $this->getWindowLookupThisVisit($request);
106
107 $maxActions = TrackerConfig::getConfigValue('create_new_visit_after_x_actions');
108
109 $visitRow = $this->model->findVisitor($idSite, $configId, $idVisitor, $userId, $persistedVisitAttributes, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead);
110
111 if (!empty($maxActions) && $maxActions > 0
112 && !empty($visitRow['visit_total_actions'])
113 && $maxActions <= $visitRow['visit_total_actions']) {
114 $this->visitRow = false;
115 return false;
116 }
117
118 $this->visitRow = $visitRow;
119
120 if ($visitRow
121 && count($visitRow) > 0
122 ) {
123 $visitProperties->setProperty('idvisitor', $visitRow['idvisitor']);
124 $visitProperties->setProperty('user_id', $visitRow['user_id']);
125
126 Common::printDebug("The visitor is known (idvisitor = " . bin2hex($visitProperties->getProperty('idvisitor')) . ",
127 config_id = " . bin2hex($configId) . ",
128 last action = " . date("r", $visitProperties->getProperty('visit_last_action_time')) . ",
129 first action = " . date("r", $visitProperties->getProperty('visit_first_action_time')) . ")");
130
131 return true;
132 } else {
133 Common::printDebug("The visitor was not matched with an existing visitor...");
134
135 return false;
136 }
137 }
138
139 public function removeUnchangedValues($visit, VisitProperties $originalVisit = null)
140 {
141 if (empty($originalVisit)) {
142 return $visit;
143 }
144
145 $originalRow = $originalVisit->getProperties();
146 if (!empty($originalRow['idvisitor'])
147 && !empty($visit['idvisitor'])
148 && bin2hex($originalRow['idvisitor']) === bin2hex($visit['idvisitor'])) {
149 unset($visit['idvisitor']);
150 }
151
152 $fieldsToCompareValue = array('user_id', 'visit_last_action_time', 'visit_total_time');
153 foreach ($fieldsToCompareValue as $field) {
154 if (!empty($originalRow[$field])
155 && !empty($visit[$field])
156 && $visit[$field] == $originalRow[$field]) {
157 // we can't use === eg for visit_total_time which may be partially an integer and sometimes a string
158 // because we check for !empty things should still work as expected though
159 // (eg we wouldn't compare false with 0)
160 unset($visit[$field]);
161 }
162 }
163
164 return $visit;
165 }
166
167 public function updateVisitPropertiesFromLastVisitRow(VisitProperties $visitProperties)
168 {
169 // These values will be used throughout the request
170 foreach ($this->getVisitorFieldsPersist() as $field) {
171 $value = $this->visitRow[$field];
172 if ($field == 'visit_last_action_time' || $field == 'visit_first_action_time') {
173 $value = strtotime($value);
174 }
175
176 $visitProperties->setProperty($field, $value);
177 }
178
179 Common::printDebug("The visit is part of an existing visit (
180 idvisit = {$visitProperties->getProperty('idvisit')},
181 visit_goal_buyer' = " . $visitProperties->getProperty('visit_goal_buyer') . ")");
182 }
183
184 protected function shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, Request $request)
185 {
186 $isForcedUserIdMustMatch = (false !== $request->getForcedUserId());
187
188 // This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP
189 // are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie.
190 if ($isVisitorIdToLookup && $this->trustCookiesOnly) {
191 return true;
192 }
193
194 if ($isForcedUserIdMustMatch) {
195 // if &iud was set, we must try and match both idvisitor and config_id
196 return false;
197 }
198
199 // If a &cid= was set, we force to select this visitor (or create a new one)
200 $isForcedVisitorIdMustMatch = ($request->getForcedVisitorId() != null);
201
202 if ($isForcedVisitorIdMustMatch) {
203 return true;
204 }
205
206 if (!$isVisitorIdToLookup) {
207 return true;
208 }
209
210 return false;
211 }
212
213 /**
214 * By default, we look back 30 minutes to find a previous visitor (for performance reasons).
215 * In some cases, it is useful to look back and count unique visitors more accurately. You can set custom lookback window in
216 * [Tracker] window_look_back_for_visitor
217 *
218 * The returned value is the window range (Min, max) that the matched visitor should fall within
219 *
220 * @return array( datetimeMin, datetimeMax )
221 */
222 protected function getWindowLookupThisVisit(Request $request)
223 {
224 $lookAheadNSeconds = $this->visitStandardLength;
225 $lookBackNSeconds = $this->visitStandardLength;
226 if ($this->lookBackNSecondsCustom > $lookBackNSeconds) {
227 $lookBackNSeconds = $this->lookBackNSecondsCustom;
228 }
229
230 $timeLookBack = date('Y-m-d H:i:s', $request->getCurrentTimestamp() - $lookBackNSeconds);
231 $timeLookAhead = date('Y-m-d H:i:s', $request->getCurrentTimestamp() + $lookAheadNSeconds);
232
233 return array($timeLookBack, $timeLookAhead);
234 }
235
236 /**
237 * @return array
238 */
239 private function getVisitorFieldsPersist()
240 {
241 if (is_null($this->visitFieldsToSelect)) {
242 $fields = array(
243 'idvisitor',
244 'idvisit',
245 'user_id',
246
247 'visit_exit_idaction_url',
248 'visit_exit_idaction_name',
249 'visitor_returning',
250 'visitor_seconds_since_first',
251 'visitor_seconds_since_order',
252 'visitor_count_visits',
253 'visit_goal_buyer',
254
255 'location_country',
256 'location_region',
257 'location_city',
258 'location_latitude',
259 'location_longitude',
260
261 'referer_name',
262 'referer_keyword',
263 'referer_type',
264 );
265
266 $dimensions = VisitDimension::getAllDimensions();
267
268 foreach ($dimensions as $dimension) {
269 if ($dimension->hasImplementedEvent('onExistingVisit') || $dimension->hasImplementedEvent('onAnyGoalConversion')) {
270 $fields[] = $dimension->getColumnName();
271 }
272
273 foreach ($dimension->getRequiredVisitFields() as $field) {
274 $fields[] = $field;
275 }
276 }
277
278 /**
279 * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading
280 * the existing visit. Properties that appear in this list will be available in other tracking
281 * events such as 'onExistingVisit'.
282 *
283 * Plugins can use this event to load additional visit entity properties for later use during tracking.
284 *
285 * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead.
286 *
287 * @deprecated
288 */
289 $this->eventDispatcher->postEvent('Tracker.getVisitFieldsToPersist', array(&$fields));
290
291 array_unshift($fields, 'visit_first_action_time');
292 array_unshift($fields, 'visit_last_action_time');
293
294 $this->visitFieldsToSelect = array_unique($fields);
295 }
296
297 return $this->visitFieldsToSelect;
298 }
299
300 public function getLastKnownVisit()
301 {
302 return $this->visitRow;
303 }
304 }
305