PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.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 / TableLogAction.php
matomo / app / core / Tracker Last commit date
Config 5 months ago Db 2 weeks ago Handler 2 years ago Visit 2 weeks ago Action.php 2 weeks ago ActionPageview.php 2 years ago BotRequest.php 4 months ago BotRequestProcessor.php 2 months ago Cache.php 2 weeks ago Db.php 2 weeks ago Failures.php 8 months ago FingerprintSalt.php 2 weeks ago GoalManager.php 2 weeks ago Handler.php 2 years ago IgnoreCookie.php 1 year ago LogTable.php 2 weeks ago Model.php 2 weeks ago PageUrl.php 2 weeks ago Request.php 2 weeks ago RequestHandlerTrait.php 5 months ago RequestProcessor.php 2 months ago RequestSet.php 8 months ago Response.php 4 months ago ScheduledTasksRunner.php 1 year ago Settings.php 2 weeks ago TableLogAction.php 2 weeks ago TrackerCodeGenerator.php 2 weeks ago TrackerConfig.php 2 months ago Visit.php 2 weeks ago VisitExcluded.php 2 weeks ago VisitInterface.php 4 months ago Visitor.php 2 months ago VisitorNotFoundInDb.php 2 months ago VisitorRecognizer.php 2 weeks ago
TableLogAction.php
264 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 Piwik\Common;
12 use Piwik\Segment\SegmentExpression;
13 /**
14 * This class is used to query Action IDs from the log_action table.
15 *
16 * A pageview, outlink, download or site search are made of several "Action IDs"
17 * For example pageview is idaction_url and idaction_name.
18 *
19 */
20 class TableLogAction
21 {
22 /**
23 * This function will find the idaction from the lookup table log_action,
24 * given an Action name, type, and an optional URL Prefix.
25 *
26 * This is used to record Page URLs, Page Titles, Ecommerce items SKUs, item names, item categories
27 *
28 * If the action name does not exist in the lookup table, it will INSERT it
29 * @param array $actionsNameAndType Array of one or many (name,type)
30 * @return array Returns the an array (Field name => idaction)
31 */
32 public static function loadIdsAction($actionsNameAndType)
33 {
34 // Add url prefix if not set
35 foreach ($actionsNameAndType as &$action) {
36 if (2 == count($action)) {
37 $action[] = null;
38 }
39 }
40 $actionIds = self::queryIdsAction($actionsNameAndType);
41 [$queriedIds, $fieldNamesToInsert] = self::processIdsToInsert($actionsNameAndType, $actionIds);
42 $insertedIds = self::insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert);
43 $queriedIds = $queriedIds + $insertedIds;
44 return $queriedIds;
45 }
46 /**
47 * @param $matchType
48 * @param $actionType
49 * @return string
50 * @throws \Exception
51 */
52 private static function getSelectQueryWhereNameContains($matchType, $actionType)
53 {
54 // now, we handle the cases =@ (contains) and !@ (does not contain)
55 // build the expression based on the match type
56 $sql = 'SELECT idaction FROM `' . Common::prefixTable('log_action') . '` WHERE %s AND type = ' . $actionType . ' )';
57 switch ($matchType) {
58 case SegmentExpression::MATCH_CONTAINS:
59 // use concat to make sure, no %s occurs because some plugins use %s in their sql
60 $where = '( name LIKE CONCAT(\'%\', ?, \'%\') ';
61 break;
62 case SegmentExpression::MATCH_DOES_NOT_CONTAIN:
63 $where = '( name NOT LIKE CONCAT(\'%\', ?, \'%\') ';
64 break;
65 case SegmentExpression::MATCH_STARTS_WITH:
66 // use concat to make sure, no %s occurs because some plugins use %s in their sql
67 $where = '( name LIKE CONCAT(?, \'%\') ';
68 break;
69 case SegmentExpression::MATCH_ENDS_WITH:
70 // use concat to make sure, no %s occurs because some plugins use %s in their sql
71 $where = '( name LIKE CONCAT(\'%\', ?) ';
72 break;
73 default:
74 throw new \Exception("This match type {$matchType} is not available for action-segments.");
75 break;
76 }
77 $sql = sprintf($sql, $where);
78 return $sql;
79 }
80 private static function insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert)
81 {
82 // Then, we insert all new actions in the lookup table
83 $inserted = array();
84 foreach ($fieldNamesToInsert as $fieldName) {
85 [$name, $type, $urlPrefix] = $actionsNameAndType[$fieldName];
86 $actionId = self::getModel()->createNewIdAction($name, $type, $urlPrefix);
87 Common::printDebug("Recorded a new action (" . \Piwik\Tracker\Action::getTypeAsString($type) . ") in the lookup table: " . $name . " (idaction = " . $actionId . ")");
88 $inserted[$fieldName] = $actionId;
89 }
90 return $inserted;
91 }
92 private static function getModel()
93 {
94 return new \Piwik\Tracker\Model();
95 }
96 private static function queryIdsAction($actionsNameAndType)
97 {
98 $toQuery = array();
99 foreach ($actionsNameAndType as &$actionNameType) {
100 [$name, $type, $urlPrefix] = $actionNameType;
101 $toQuery[] = array('name' => $name, 'type' => $type);
102 }
103 $actionIds = self::getModel()->getIdsAction($toQuery);
104 return $actionIds;
105 }
106 private static function processIdsToInsert($actionsNameAndType, $actionIds)
107 {
108 // For the Actions found in the lookup table, add the idaction in the array,
109 // If not found in lookup table, queue for INSERT
110 $fieldNamesToInsert = $fieldNameToActionId = array();
111 foreach ($actionsNameAndType as $fieldName => &$actionNameType) {
112 @(list($name, $type, $urlPrefix) = $actionNameType);
113 if (empty($name)) {
114 $fieldNameToActionId[$fieldName] = \false;
115 continue;
116 }
117 $found = \false;
118 foreach ($actionIds as $row) {
119 if ($name == $row['name'] && $type == $row['type']) {
120 $found = \true;
121 $fieldNameToActionId[$fieldName] = $row['idaction'];
122 continue;
123 }
124 }
125 if (!$found) {
126 $fieldNamesToInsert[] = $fieldName;
127 }
128 }
129 return array($fieldNameToActionId, $fieldNamesToInsert);
130 }
131 /**
132 * Convert segment expression to an action ID or an SQL expression.
133 *
134 * This method is used as a sqlFilter-callback for the segments of this plugin.
135 * Usually, these callbacks only return a value that should be compared to the
136 * column in the database. In this case, that doesn't work since multiple IDs
137 * can match an expression (e.g. "pageUrl=@foo").
138 * @param string $valueToMatch
139 * @param string $sqlField
140 * @param string $matchType
141 * @param string $segmentName
142 * @return array|int|string|null
143 */
144 public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName)
145 {
146 if ($segmentName === 'actionType') {
147 $actionType = (int) $valueToMatch;
148 $valueToMatch = array();
149 $sql = 'SELECT idaction FROM `' . Common::prefixTable('log_action') . '` WHERE type = ' . $actionType . ' )';
150 } else {
151 $actionType = self::guessActionTypeFromSegment($segmentName);
152 $valueToMatch = self::removeProtocolIfSegmentStoredWithoutIt($valueToMatch, $actionType, $segmentName);
153 $unsanitizedValue = $valueToMatch;
154 $valueToMatch = self::normaliseActionString($actionType, $valueToMatch);
155 if ($matchType == SegmentExpression::MATCH_EQUAL || $matchType == SegmentExpression::MATCH_NOT_EQUAL) {
156 $idAction = self::getModel()->getIdActionMatchingNameAndType($valueToMatch, $actionType);
157 // If action can't be found normalized try search for it with original value
158 // This can eg happen for outlinks that contain a &amp; see https://github.com/matomo-org/matomo/issues/11806
159 if (empty($idAction)) {
160 $idAction = self::getModel()->getIdActionMatchingNameAndType($unsanitizedValue, $actionType);
161 // Action is not found (eg. &segment=pageTitle==Větrnásssssss)
162 if (empty($idAction)) {
163 $idAction = null;
164 }
165 }
166 return $idAction;
167 }
168 // "name contains $string" match can match several idaction so we cannot return yet an idaction
169 // special case
170 $sql = self::getSelectQueryWhereNameContains($matchType, $actionType);
171 }
172 return array(
173 // mark that the returned value is an sql-expression instead of a literal value
174 'SQL' => $sql,
175 'bind' => $valueToMatch,
176 );
177 }
178 /**
179 * @param $segmentName
180 * @return int
181 * @throws \Exception
182 */
183 private static function guessActionTypeFromSegment($segmentName)
184 {
185 $exactMatch = array('outlinkUrl' => \Piwik\Tracker\Action::TYPE_OUTLINK, 'downloadUrl' => \Piwik\Tracker\Action::TYPE_DOWNLOAD, 'eventUrl' => \Piwik\Tracker\Action::TYPE_EVENT, 'eventAction' => \Piwik\Tracker\Action::TYPE_EVENT_ACTION, 'eventCategory' => \Piwik\Tracker\Action::TYPE_EVENT_CATEGORY, 'eventName' => \Piwik\Tracker\Action::TYPE_EVENT_NAME, 'contentPiece' => \Piwik\Tracker\Action::TYPE_CONTENT_PIECE, 'contentTarget' => \Piwik\Tracker\Action::TYPE_CONTENT_TARGET, 'contentName' => \Piwik\Tracker\Action::TYPE_CONTENT_NAME, 'contentInteraction' => \Piwik\Tracker\Action::TYPE_CONTENT_INTERACTION, 'productName' => \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_NAME, 'productSku' => \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_SKU, 'productViewName' => \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_NAME, 'productViewSku' => \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_SKU);
186 if (!empty($exactMatch[$segmentName])) {
187 return $exactMatch[$segmentName];
188 }
189 if (stripos($segmentName, 'pageurl') !== \false) {
190 return \Piwik\Tracker\Action::TYPE_PAGE_URL;
191 } elseif (stripos($segmentName, 'pagetitle') !== \false) {
192 return \Piwik\Tracker\Action::TYPE_PAGE_TITLE;
193 } elseif (stripos($segmentName, 'sitesearch') !== \false) {
194 return \Piwik\Tracker\Action::TYPE_SITE_SEARCH;
195 } elseif (stripos($segmentName, 'productcategory') !== \false || stripos($segmentName, 'productviewcategory') !== \false) {
196 return \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_CATEGORY;
197 } else {
198 throw new \Exception("We cannot guess the action type from the segment {$segmentName}.");
199 }
200 }
201 /**
202 * This function will sanitize or not if it's needed for the specified action type
203 *
204 * URLs (Download URL, Outlink URL) are stored raw (unsanitized)
205 * while other action types are stored Sanitized
206 *
207 * @param $actionType
208 * @param $actionString
209 * @return string
210 */
211 private static function normaliseActionString($actionType, $actionString)
212 {
213 $actionString = Common::unsanitizeInputValue($actionString);
214 if (self::isActionTypeStoredUnsanitized($actionType)) {
215 return $actionString;
216 }
217 return Common::sanitizeInputValue($actionString);
218 }
219 /**
220 * @param $actionType
221 * @return bool
222 */
223 private static function isActionTypeStoredUnsanitized($actionType)
224 {
225 $actionsTypesStoredUnsanitized = array(\Piwik\Tracker\Action::TYPE_DOWNLOAD, \Piwik\Tracker\Action::TYPE_OUTLINK, \Piwik\Tracker\Action::TYPE_PAGE_URL, \Piwik\Tracker\Action::TYPE_CONTENT);
226 return in_array($actionType, $actionsTypesStoredUnsanitized);
227 }
228 public static function removeProtocolIfSegmentStoredWithoutIt($url, $actionType, $segmentName)
229 {
230 if ($actionType == \Piwik\Tracker\Action::TYPE_PAGE_URL || $segmentName == 'eventUrl') {
231 // for urls trim protocol and www because it is not recorded in the db
232 $url = preg_replace('@^http[s]?://(www\\.)?@i', '', $url);
233 }
234 return $url;
235 }
236 /**
237 * Returns an idaction value to match an idaction column by searching log_action, if $matchType is
238 * SegmentExpression::MATCH_EQUAL or SegmentExpression::MATCH_NOT_EQUAL. This method is used
239 * to optimize segment conditions involving idaction queries, avoiding a join by querying the log_action
240 * table beforehand.
241 *
242 * Should be used as the $sqlFilter property for idaction dimensions that use `ActionNameJoin`.
243 *
244 * @param string $value the value in the segment condition
245 * @param string $sqlField the table column of the segment condition
246 * @param string $matchType the SegmentExpression match type, eg, `SegmentExpression::MATCH_NOT_EQUAL`
247 * @param string $segmentName the name of the segment, ie, `pageUrl`
248 * @return array|null|string
249 */
250 public static function getOptimizedIdActionSqlMatch($value, $sqlField, $matchType, $segmentName)
251 {
252 if ($matchType == SegmentExpression::MATCH_EQUAL || $matchType == SegmentExpression::MATCH_NOT_EQUAL) {
253 $result = self::getIdActionFromSegment($value, $sqlField, $matchType, $segmentName);
254 if (is_numeric($result)) {
255 return ['value' => $result, 'joinTable' => \false];
256 }
257 return $result;
258 }
259 $actionType = self::guessActionTypeFromSegment($segmentName);
260 $value = self::removeProtocolIfSegmentStoredWithoutIt($value, $actionType, $segmentName);
261 return $value;
262 }
263 }
264