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