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