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