PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Tracker/TableLogAction.php

https://github.com/CodeYellowBV/piwik
PHP | 257 lines | 161 code | 28 blank | 68 comment | 23 complexity | 64f7d54ce8f71e26f069e6a352e8994c MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Tracker;
  10. use Piwik\Common;
  11. use Piwik\SegmentExpression;
  12. use Piwik\Tracker;
  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 piwik_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(count($action) == 2) {
  37. $action[] = null;
  38. }
  39. }
  40. $actionIds = self::queryIdsAction($actionsNameAndType);
  41. list($queriedIds, $fieldNamesToInsert) = self::processIdsToInsert($actionsNameAndType, $actionIds);
  42. $insertedIds = self::insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert);
  43. $queriedIds = $queriedIds + $insertedIds;
  44. return $queriedIds;
  45. }
  46. /**
  47. * @param $name
  48. * @param $type
  49. * @return string
  50. */
  51. private static function getIdActionMatchingNameAndType($name, $type)
  52. {
  53. $sql = TableLogAction::getSqlSelectActionId();
  54. $bind = array($name, $name, $type);
  55. $idAction = \Piwik\Db::fetchOne($sql, $bind);
  56. return $idAction;
  57. }
  58. /**
  59. * @param $matchType
  60. * @param $actionType
  61. * @return string
  62. * @throws \Exception
  63. */
  64. private static function getSelectQueryWhereNameContains($matchType, $actionType)
  65. {
  66. // now, we handle the cases =@ (contains) and !@ (does not contain)
  67. // build the expression based on the match type
  68. $sql = 'SELECT idaction FROM ' . Common::prefixTable('log_action') . ' WHERE %s AND type = ' . $actionType . ' )';
  69. switch ($matchType) {
  70. case '=@':
  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. case '!@':
  75. $where = '( name NOT LIKE CONCAT(\'%\', ?, \'%\') ';
  76. break;
  77. default:
  78. throw new \Exception("This match type $matchType is not available for action-segments.");
  79. break;
  80. }
  81. $sql = sprintf($sql, $where);
  82. return $sql;
  83. }
  84. private static function getSqlSelectActionId()
  85. {
  86. $sql = "SELECT idaction, type, name
  87. FROM " . Common::prefixTable('log_action')
  88. . " WHERE "
  89. . " ( hash = CRC32(?) AND name = ? AND type = ? ) ";
  90. return $sql;
  91. }
  92. private static function insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert)
  93. {
  94. $sql = "INSERT INTO " . Common::prefixTable('log_action') .
  95. "( name, hash, type, url_prefix ) VALUES (?,CRC32(?),?,?)";
  96. // Then, we insert all new actions in the lookup table
  97. $inserted = array();
  98. foreach ($fieldNamesToInsert as $fieldName) {
  99. list($name, $type, $urlPrefix) = $actionsNameAndType[$fieldName];
  100. Tracker::getDatabase()->query($sql, array($name, $name, $type, $urlPrefix));
  101. $actionId = Tracker::getDatabase()->lastInsertId();
  102. $inserted[$fieldName] = $actionId;
  103. Common::printDebug("Recorded a new action (" . Action::getTypeAsString($type) . ") in the lookup table: " . $name . " (idaction = " . $actionId . ")");
  104. }
  105. return $inserted;
  106. }
  107. private static function queryIdsAction($actionsNameAndType)
  108. {
  109. $sql = TableLogAction::getSqlSelectActionId();
  110. $bind = array();
  111. $i = 0;
  112. foreach ($actionsNameAndType as &$actionNameType) {
  113. list($name, $type, $urlPrefix) = $actionNameType;
  114. if (empty($name)) {
  115. continue;
  116. }
  117. if ($i > 0) {
  118. $sql .= " OR ( hash = CRC32(?) AND name = ? AND type = ? ) ";
  119. }
  120. $bind[] = $name;
  121. $bind[] = $name;
  122. $bind[] = $type;
  123. $i++;
  124. }
  125. // Case URL & Title are empty
  126. if (empty($bind)) {
  127. return false;
  128. }
  129. $actionIds = Tracker::getDatabase()->fetchAll($sql, $bind);
  130. return $actionIds;
  131. }
  132. private static function processIdsToInsert($actionsNameAndType, $actionIds)
  133. {
  134. // For the Actions found in the lookup table, add the idaction in the array,
  135. // If not found in lookup table, queue for INSERT
  136. $fieldNamesToInsert = $fieldNameToActionId = array();
  137. foreach ($actionsNameAndType as $fieldName => &$actionNameType) {
  138. @list($name, $type, $urlPrefix) = $actionNameType;
  139. if (empty($name)) {
  140. $fieldNameToActionId[$fieldName] = false;
  141. continue;
  142. }
  143. $found = false;
  144. foreach ($actionIds as $row) {
  145. if ($name == $row['name']
  146. && $type == $row['type']
  147. ) {
  148. $found = true;
  149. $fieldNameToActionId[$fieldName] = $row['idaction'];
  150. continue;
  151. }
  152. }
  153. if (!$found) {
  154. $fieldNamesToInsert[] = $fieldName;
  155. }
  156. }
  157. return array($fieldNameToActionId, $fieldNamesToInsert);
  158. }
  159. /**
  160. * Convert segment expression to an action ID or an SQL expression.
  161. *
  162. * This method is used as a sqlFilter-callback for the segments of this plugin.
  163. * Usually, these callbacks only return a value that should be compared to the
  164. * column in the database. In this case, that doesn't work since multiple IDs
  165. * can match an expression (e.g. "pageUrl=@foo").
  166. * @param string $valueToMatch
  167. * @param string $sqlField
  168. * @param string $matchType
  169. * @param string $segmentName
  170. * @throws \Exception
  171. * @return array|int|string
  172. */
  173. public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName)
  174. {
  175. $actionType = self::guessActionTypeFromSegment($segmentName);
  176. if ($actionType == Action::TYPE_PAGE_URL) {
  177. // for urls trim protocol and www because it is not recorded in the db
  178. $valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch);
  179. }
  180. $valueToMatch = Common::sanitizeInputValue(Common::unsanitizeInputValue($valueToMatch));
  181. if ($matchType == SegmentExpression::MATCH_EQUAL
  182. || $matchType == SegmentExpression::MATCH_NOT_EQUAL
  183. ) {
  184. $idAction = self::getIdActionMatchingNameAndType($valueToMatch, $actionType);
  185. // if the action is not found, we hack -100 to ensure it tries to match against an integer
  186. // otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Vฤ›trnรกsssssss)
  187. if (empty($idAction)) {
  188. $idAction = -100;
  189. }
  190. return $idAction;
  191. }
  192. // "name contains $string" match can match several idaction so we cannot return yet an idaction
  193. // special case
  194. $sql = TableLogAction::getSelectQueryWhereNameContains($matchType, $actionType);
  195. return array(
  196. // mark that the returned value is an sql-expression instead of a literal value
  197. 'SQL' => $sql,
  198. 'bind' => $valueToMatch,
  199. );
  200. }
  201. /**
  202. * @param $segmentName
  203. * @return int
  204. * @throws \Exception
  205. */
  206. private static function guessActionTypeFromSegment($segmentName)
  207. {
  208. $exactMatch = array(
  209. 'eventAction' => Action::TYPE_EVENT_ACTION,
  210. 'eventCategory' => Action::TYPE_EVENT_CATEGORY,
  211. 'eventName' => Action::TYPE_EVENT_NAME,
  212. );
  213. if(!empty($exactMatch[$segmentName])) {
  214. return $exactMatch[$segmentName];
  215. }
  216. if (stripos($segmentName, 'pageurl') !== false) {
  217. $actionType = Action::TYPE_PAGE_URL;
  218. return $actionType;
  219. } elseif (stripos($segmentName, 'pagetitle') !== false) {
  220. $actionType = Action::TYPE_PAGE_TITLE;
  221. return $actionType;
  222. } elseif (stripos($segmentName, 'sitesearch') !== false) {
  223. $actionType = Action::TYPE_SITE_SEARCH;
  224. return $actionType;
  225. } else {
  226. throw new \Exception("We cannot guess the action type from the segment $segmentName.");
  227. }
  228. }
  229. }