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 / Action.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
Action.php
467 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
10 namespace Piwik\Tracker;
11
12 use Exception;
13 use Piwik\Common;
14 use Piwik\Container\StaticContainer;
15 use Piwik\Plugin\Dimension\ActionDimension;
16 use Piwik\Plugin\Manager;
17 use Psr\Log\LoggerInterface;
18
19 /**
20 * An action
21 *
22 */
23 abstract class Action
24 {
25 const TYPE_PAGE_URL = 1;
26 const TYPE_OUTLINK = 2;
27 const TYPE_DOWNLOAD = 3;
28 const TYPE_PAGE_TITLE = 4;
29 const TYPE_ECOMMERCE_ITEM_SKU = 5;
30 const TYPE_ECOMMERCE_ITEM_NAME = 6;
31 const TYPE_ECOMMERCE_ITEM_CATEGORY = 7;
32 const TYPE_SITE_SEARCH = 8;
33
34 const TYPE_EVENT = 10; // Alias TYPE_EVENT_CATEGORY
35 const TYPE_EVENT_CATEGORY = 10;
36 const TYPE_EVENT_ACTION = 11;
37 const TYPE_EVENT_NAME = 12;
38
39 const TYPE_CONTENT = 13; // Alias TYPE_CONTENT_NAME
40 const TYPE_CONTENT_NAME = 13;
41 const TYPE_CONTENT_PIECE = 14;
42 const TYPE_CONTENT_TARGET = 15;
43 const TYPE_CONTENT_INTERACTION = 16;
44
45 const DB_COLUMN_CUSTOM_FLOAT = 'custom_float';
46
47 private static $factoryPriority = array(
48 self::TYPE_PAGE_URL,
49 self::TYPE_SITE_SEARCH,
50 self::TYPE_CONTENT,
51 self::TYPE_EVENT,
52 self::TYPE_OUTLINK,
53 self::TYPE_DOWNLOAD
54 );
55
56 /**
57 * Public so that events listener can access it
58 *
59 * @var Request
60 */
61 public $request;
62
63 private $idLinkVisitAction;
64 private $actionIdsCached = array();
65 private $customFields = array();
66 private $actionName;
67 private $actionType;
68
69 /**
70 * URL with excluded Query parameters
71 */
72 private $actionUrl;
73
74 /**
75 * Raw URL (will contain excluded URL query parameters)
76 */
77 private $rawActionUrl;
78
79 /**
80 * @var mixed|LoggerInterface
81 */
82 private $logger;
83
84 /**
85 * Makes the correct Action object based on the request.
86 *
87 * @param Request $request
88 * @return Action
89 */
90 public static function factory(Request $request)
91 {
92 /** @var Action[] $actions */
93 $actions = self::getAllActions($request);
94
95 foreach ($actions as $actionType) {
96 if (empty($action)) {
97 $action = $actionType;
98 continue;
99 }
100
101 $posPrevious = self::getPriority($action);
102 $posCurrent = self::getPriority($actionType);
103
104 if ($posCurrent > $posPrevious) {
105 $action = $actionType;
106 }
107 }
108
109 if (!empty($action)) {
110 return $action;
111 }
112
113 if (self::isCustomActionRequest($request)) {
114 throw new Exception('Request was meant for a plugin which is no longer activated. Request needs to be ignored.');
115 }
116
117 return new ActionPageview($request);
118 }
119
120 /**
121 * Returns true if the tracking request was meant for some action that isn't the page view. See
122 * https://github.com/matomo-org/matomo/pull/16570 for more details. Basically, plugins that implement a tracker
123 * action should send a `ca=1` tracking parameter along the request so it doesn't get executed should the plugin
124 * be disabled but the JS tracker is still cached and keeps on sending these requests.
125 *
126 * @param Request $request
127 * @return bool
128 * @throws Exception
129 */
130 public static function isCustomActionRequest(Request $request)
131 {
132 return $request->hasParam('ca') && $request->getParam('ca');
133 }
134
135 private static function getPriority(Action $actionType)
136 {
137 $key = array_search($actionType->getActionType(), self::$factoryPriority);
138
139 if (false === $key) {
140 return -1;
141 }
142
143 return $key;
144 }
145
146 public static function shouldHandle(Request $request)
147 {
148 return false;
149 }
150
151 private static function getAllActions(Request $request)
152 {
153 static $actions;
154
155 if (is_null($actions)) {
156 $actions = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action');
157 }
158
159 $instances = array();
160
161 foreach ($actions as $action) {
162 /** @var \Piwik\Tracker\Action $action */
163 if ($action::shouldHandle($request)) {
164 $instances[] = new $action($request);
165 }
166 }
167
168 return $instances;
169 }
170
171 public function __construct($type, Request $request)
172 {
173 $this->actionType = $type;
174 $this->request = $request;
175 $this->logger = StaticContainer::get(LoggerInterface::class);
176 }
177
178 /**
179 * Returns URL of the page currently being tracked, or the file being downloaded, or the outlink being clicked
180 *
181 * @return string
182 */
183 public function getActionUrl()
184 {
185 return $this->actionUrl;
186 }
187
188 /**
189 * Returns URL of page being tracked, including all original Query parameters
190 */
191 public function getActionUrlRaw()
192 {
193 return $this->rawActionUrl;
194 }
195
196 public function getActionName()
197 {
198 return $this->actionName;
199 }
200
201 public function getActionType()
202 {
203 return $this->actionType;
204 }
205
206 // custom_float column
207 public function getCustomFloatValue()
208 {
209 return false;
210 }
211
212 protected function setActionName($name)
213 {
214 $this->actionName = PageUrl::cleanupString((string)$name);
215 }
216
217 protected function setActionUrl($url)
218 {
219 $this->rawActionUrl = PageUrl::getUrlIfLookValid($url);
220 $url2 = PageUrl::excludeQueryParametersFromUrl($url, $this->request->getIdSite());
221
222 $this->actionUrl = PageUrl::getUrlIfLookValid($url2);
223
224 if ($url != $this->rawActionUrl) {
225 $this->logger->debug(' Before was "{rawActionUrl}"', [
226 'rawActionUrl' => $this->rawActionUrl,
227 ]);
228 $this->logger->debug(' After is "{url2}"', [
229 'url2' => $url2,
230 ]);
231 }
232 }
233
234 protected function setActionUrlWithoutExcludingParameters($url)
235 {
236 $url = PageUrl::getUrlIfLookValid($url);
237 $this->rawActionUrl = $url;
238 $this->actionUrl = $url;
239 }
240
241 abstract protected function getActionsToLookup();
242
243 protected function getUrlAndType()
244 {
245 $url = $this->getActionUrl();
246
247 if (!empty($url)) {
248 // normalize urls by stripping protocol and www
249 $url = PageUrl::normalizeUrl($url);
250 return array($url['url'], self::TYPE_PAGE_URL, $url['prefixId']);
251 }
252
253 return false;
254 }
255
256 public function setCustomField($field, $value)
257 {
258 $this->customFields[$field] = $value;
259 }
260
261 public function getCustomField($field)
262 {
263 if (isset($this->customFields[$field])) {
264 return $this->customFields[$field];
265 }
266 }
267
268 public function getCustomFields()
269 {
270 return $this->customFields;
271 }
272
273 public function getIdActionUrl()
274 {
275 $idUrl = isset($this->actionIdsCached['idaction_url']) ? $this->actionIdsCached['idaction_url'] : 0;
276 // note; idaction_url = 0 is displayed as "Page URL Not Defined"
277 return (int)$idUrl;
278 }
279
280 public function getIdActionUrlForEntryAndExitIds()
281 {
282 return false;
283 }
284
285 public function getIdActionNameForEntryAndExitIds()
286 {
287 return false;
288 }
289
290 public function getIdActionName()
291 {
292 if (!isset($this->actionIdsCached['idaction_name'])) {
293 return false;
294 }
295
296 return $this->actionIdsCached['idaction_name'];
297 }
298
299 /**
300 * Returns the ID of the newly created record in the log_link_visit_action table
301 *
302 * @return int
303 */
304 public function getIdLinkVisitAction()
305 {
306 return $this->idLinkVisitAction;
307 }
308
309 public static function getTypeAsString($type)
310 {
311 $class = new \ReflectionClass("\\Piwik\\Tracker\\Action");
312 $constants = $class->getConstants();
313
314 $typeId = array_search($type, $constants);
315
316 if (false === $typeId) {
317 return $type;
318 }
319
320 return str_replace('TYPE_', '', $typeId);
321 }
322
323 /**
324 * Loads the idaction of the current action name and the current action url.
325 * These idactions are used in the visitor logging table to link the visit information
326 * (entry action, exit action) to the actions.
327 * These idactions are also used in the table that links the visits and their actions.
328 *
329 * The methods takes care of creating a new record(s) in the action table if the existing
330 * action name and action url doesn't exist yet.
331 */
332 public function loadIdsFromLogActionTable()
333 {
334 if (!empty($this->actionIdsCached)) {
335 return;
336 }
337
338 /** @var ActionDimension[] $dimensions */
339 $dimensions = ActionDimension::getAllDimensions();
340 $actions = $this->getActionsToLookup();
341
342 foreach ($dimensions as $dimension) {
343 $value = $dimension->onLookupAction($this->request, $this);
344
345 if (false !== $value) {
346 if (is_float($value)) {
347 $value = Common::forceDotAsSeparatorForDecimalPoint($value);
348 }
349
350 $field = $dimension->getColumnName();
351
352 if (empty($field)) {
353 $dimensionClass = get_class($dimension);
354 throw new Exception('Dimension ' . $dimensionClass . ' does not define a field name');
355 }
356
357 $actionId = $dimension->getActionId();
358 $actions[$field] = array($value, $actionId);
359 $this->logger->debug("$field = $value");
360 }
361 }
362
363 $actions = array_filter($actions);
364
365 if (empty($actions)) {
366 return;
367 }
368
369 $loadedActionIds = TableLogAction::loadIdsAction($actions);
370
371 $this->actionIdsCached = $loadedActionIds;
372 return $this->actionIdsCached;
373 }
374
375 /**
376 * Records in the DB the association between the visit and this action.
377 *
378 * @param int $idReferrerActionUrl is the ID of the last action done by the current visit.
379 * @param $idReferrerActionName
380 * @param Visitor $visitor
381 */
382 public function record(Visitor $visitor, $idReferrerActionUrl, $idReferrerActionName)
383 {
384 $this->loadIdsFromLogActionTable();
385
386 $visitAction = array(
387 'idvisit' => $visitor->getVisitorColumn('idvisit'),
388 'idsite' => $this->request->getIdSite(),
389 'idvisitor' => $visitor->getVisitorColumn('idvisitor'),
390 'idaction_url' => $this->getIdActionUrl(),
391 'idaction_url_ref' => $idReferrerActionUrl,
392 'idaction_name_ref' => $idReferrerActionName
393 );
394
395 /** @var ActionDimension[] $dimensions */
396 $dimensions = ActionDimension::getAllDimensions();
397
398 foreach ($dimensions as $dimension) {
399 $value = $dimension->onNewAction($this->request, $visitor, $this);
400
401 if ($value !== false) {
402 if (is_float($value)) {
403 $value = Common::forceDotAsSeparatorForDecimalPoint($value);
404 }
405
406 $visitAction[$dimension->getColumnName()] = $value;
407 }
408 }
409
410 // idaction_name is NULLable. we only set it when applicable
411 if ($this->isActionHasActionName()) {
412 $visitAction['idaction_name'] = (int)$this->getIdActionName();
413 }
414
415 foreach ($this->actionIdsCached as $field => $idAction) {
416 $visitAction[$field] = ($idAction === false) ? 0 : $idAction;
417 }
418
419 $customValue = $this->getCustomFloatValue();
420 if ($customValue !== false && $customValue !== null && $customValue !== '') {
421 $visitAction[self::DB_COLUMN_CUSTOM_FLOAT] = Common::forceDotAsSeparatorForDecimalPoint($customValue);
422 }
423
424 $visitAction = array_merge($visitAction, $this->customFields);
425
426 $this->idLinkVisitAction = $this->getModel()->createAction($visitAction);
427
428 $visitAction['idlink_va'] = $this->idLinkVisitAction;
429
430 $visitActionDebug = $visitAction;
431 $visitActionDebug['idvisitor'] = bin2hex($visitActionDebug['idvisitor']);
432 $this->logger->debug("Inserted new action: {action}", [
433 'action' => var_export($visitActionDebug, true),
434 ]);
435 }
436
437 public function writeDebugInfo()
438 {
439 $type = self::getTypeAsString($this->getActionType());
440 $name = $this->getActionName();
441 $url = $this->getActionUrl();
442
443 $this->logger->debug('Action is a {type}, Action name = {name}, Action URL = {url}', [
444 'type' => $type,
445 'name' => $name,
446 'url' => $url,
447 ]);
448
449 return true;
450 }
451
452 private function getModel()
453 {
454 return new Model();
455 }
456
457 /**
458 * @return bool
459 */
460 private function isActionHasActionName()
461 {
462 $types = array(self::TYPE_PAGE_TITLE, self::TYPE_PAGE_URL, self::TYPE_SITE_SEARCH);
463
464 return in_array($this->getActionType(), $types);
465 }
466 }
467