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 / TableLogAction.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
TableLogAction.php
265 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 * @throws \Exception
143 * @return array|int|string
144 */
145 public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName)
146 {
147 if ($segmentName === 'actionType') {
148 $actionType = (int) $valueToMatch;
149 $valueToMatch = array();
150 $sql = 'SELECT idaction FROM `' . Common::prefixTable('log_action') . '` WHERE type = ' . $actionType . ' )';
151 } else {
152 $actionType = self::guessActionTypeFromSegment($segmentName);
153 $valueToMatch = self::removeProtocolIfSegmentStoredWithoutIt($valueToMatch, $actionType, $segmentName);
154 $unsanitizedValue = $valueToMatch;
155 $valueToMatch = self::normaliseActionString($actionType, $valueToMatch);
156 if ($matchType == SegmentExpression::MATCH_EQUAL || $matchType == SegmentExpression::MATCH_NOT_EQUAL) {
157 $idAction = self::getModel()->getIdActionMatchingNameAndType($valueToMatch, $actionType);
158 // If action can't be found normalized try search for it with original value
159 // This can eg happen for outlinks that contain a &amp; see https://github.com/matomo-org/matomo/issues/11806
160 if (empty($idAction)) {
161 $idAction = self::getModel()->getIdActionMatchingNameAndType($unsanitizedValue, $actionType);
162 // Action is not found (eg. &segment=pageTitle==Větrnásssssss)
163 if (empty($idAction)) {
164 $idAction = null;
165 }
166 }
167 return $idAction;
168 }
169 // "name contains $string" match can match several idaction so we cannot return yet an idaction
170 // special case
171 $sql = self::getSelectQueryWhereNameContains($matchType, $actionType);
172 }
173 return array(
174 // mark that the returned value is an sql-expression instead of a literal value
175 'SQL' => $sql,
176 'bind' => $valueToMatch,
177 );
178 }
179 /**
180 * @param $segmentName
181 * @return int
182 * @throws \Exception
183 */
184 private static function guessActionTypeFromSegment($segmentName)
185 {
186 $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);
187 if (!empty($exactMatch[$segmentName])) {
188 return $exactMatch[$segmentName];
189 }
190 if (stripos($segmentName, 'pageurl') !== \false) {
191 return \Piwik\Tracker\Action::TYPE_PAGE_URL;
192 } elseif (stripos($segmentName, 'pagetitle') !== \false) {
193 return \Piwik\Tracker\Action::TYPE_PAGE_TITLE;
194 } elseif (stripos($segmentName, 'sitesearch') !== \false) {
195 return \Piwik\Tracker\Action::TYPE_SITE_SEARCH;
196 } elseif (stripos($segmentName, 'productcategory') !== \false || stripos($segmentName, 'productviewcategory') !== \false) {
197 return \Piwik\Tracker\Action::TYPE_ECOMMERCE_ITEM_CATEGORY;
198 } else {
199 throw new \Exception("We cannot guess the action type from the segment {$segmentName}.");
200 }
201 }
202 /**
203 * This function will sanitize or not if it's needed for the specified action type
204 *
205 * URLs (Download URL, Outlink URL) are stored raw (unsanitized)
206 * while other action types are stored Sanitized
207 *
208 * @param $actionType
209 * @param $actionString
210 * @return string
211 */
212 private static function normaliseActionString($actionType, $actionString)
213 {
214 $actionString = Common::unsanitizeInputValue($actionString);
215 if (self::isActionTypeStoredUnsanitized($actionType)) {
216 return $actionString;
217 }
218 return Common::sanitizeInputValue($actionString);
219 }
220 /**
221 * @param $actionType
222 * @return bool
223 */
224 private static function isActionTypeStoredUnsanitized($actionType)
225 {
226 $actionsTypesStoredUnsanitized = array(\Piwik\Tracker\Action::TYPE_DOWNLOAD, \Piwik\Tracker\Action::TYPE_OUTLINK, \Piwik\Tracker\Action::TYPE_PAGE_URL, \Piwik\Tracker\Action::TYPE_CONTENT);
227 return in_array($actionType, $actionsTypesStoredUnsanitized);
228 }
229 public static function removeProtocolIfSegmentStoredWithoutIt($url, $actionType, $segmentName)
230 {
231 if ($actionType == \Piwik\Tracker\Action::TYPE_PAGE_URL || $segmentName == 'eventUrl') {
232 // for urls trim protocol and www because it is not recorded in the db
233 $url = preg_replace('@^http[s]?://(www\\.)?@i', '', $url);
234 }
235 return $url;
236 }
237 /**
238 * Returns an idaction value to match an idaction column by searching log_action, if $matchType is
239 * SegmentExpression::MATCH_EQUAL or SegmentExpression::MATCH_NOT_EQUAL. This method is used
240 * to optimize segment conditions involving idaction queries, avoiding a join by querying the log_action
241 * table beforehand.
242 *
243 * Should be used as the $sqlFilter property for idaction dimensions that use `ActionNameJoin`.
244 *
245 * @param string $value the value in the segment condition
246 * @param string $sqlField the table column of the segment condition
247 * @param string $matchType the SegmentExpression match type, eg, `SegmentExpression::MATCH_NOT_EQUAL`
248 * @param string $segmentName the name of the segment, ie, `pageUrl`
249 * @return array|null|string
250 */
251 public static function getOptimizedIdActionSqlMatch($value, $sqlField, $matchType, $segmentName)
252 {
253 if ($matchType == SegmentExpression::MATCH_EQUAL || $matchType == SegmentExpression::MATCH_NOT_EQUAL) {
254 $result = self::getIdActionFromSegment($value, $sqlField, $matchType, $segmentName);
255 if (is_numeric($result)) {
256 return ['value' => $result, 'joinTable' => \false];
257 }
258 return $result;
259 }
260 $actionType = self::guessActionTypeFromSegment($segmentName);
261 $value = self::removeProtocolIfSegmentStoredWithoutIt($value, $actionType, $segmentName);
262 return $value;
263 }
264 }
265