Newer
Older
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Tracker;
use Piwik\Common;
use Piwik\Segment\SegmentExpression;
use Piwik\Tracker;
mattab
a validé
/**
* This class is used to query Action IDs from the log_action table.
*
* A pageview, outlink, download or site search are made of several "Action IDs"
* For example pageview is idaction_url and idaction_name.
*
*/
class TableLogAction
{
/**
* This function will find the idaction from the lookup table piwik_log_action,
*
* This is used to record Page URLs, Page Titles, Ecommerce items SKUs, item names, item categories
*
* If the action name does not exist in the lookup table, it will INSERT it
mattab
a validé
* @param array $actionsNameAndType Array of one or many (name,type)
* @return array Returns the an array (Field name => idaction)
mattab
a validé
public static function loadIdsAction($actionsNameAndType)
// Add url prefix if not set
foreach($actionsNameAndType as &$action) {
Thomas Steur
a validé
if (2 == count($action)) {
Thomas Steur
a validé
mattab
a validé
$actionIds = self::queryIdsAction($actionsNameAndType);
list($queriedIds, $fieldNamesToInsert) = self::processIdsToInsert($actionsNameAndType, $actionIds);
$insertedIds = self::insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert);
Thomas Steur
a validé
$queriedIds = $queriedIds + $insertedIds;
mattab
a validé
return $queriedIds;
}
/**
* @param $matchType
* @param $actionType
* @return string
* @throws \Exception
*/
private static function getSelectQueryWhereNameContains($matchType, $actionType)
{
// now, we handle the cases =@ (contains) and !@ (does not contain)
// build the expression based on the match type
diosmosis
a validé
$sql = 'SELECT MIN(idaction) AS idaction FROM ' . Common::prefixTable('log_action') . ' WHERE %s AND type = ' . $actionType . ' )'
diosmosis
a validé
. ' GROUP BY name, hash, type'; // group by is to avoid possible case of duplicates in log_action table
// (duplicates can exist if php tracker fails right after inserting a duplicate in
// Tracker\Model::insertNewAction())
Thomas Steur
a validé
switch ($matchType) {
case '=@':
// use concat to make sure, no %s occurs because some plugins use %s in their sql
$where = '( name LIKE CONCAT(\'%\', ?, \'%\') ';
break;
case '!@':
$where = '( name NOT LIKE CONCAT(\'%\', ?, \'%\') ';
break;
default:
throw new \Exception("This match type $matchType is not available for action-segments.");
break;
}
Thomas Steur
a validé
Thomas Steur
a validé
private static function insertNewIdsAction($actionsNameAndType, $fieldNamesToInsert)
mattab
a validé
{
// Then, we insert all new actions in the lookup table
$inserted = array();
Thomas Steur
a validé
mattab
a validé
foreach ($fieldNamesToInsert as $fieldName) {
list($name, $type, $urlPrefix) = $actionsNameAndType[$fieldName];
$actionId = self::getModel()->createNewIdAction($name, $type, $urlPrefix);
mattab
a validé
Common::printDebug("Recorded a new action (" . Action::getTypeAsString($type) . ") in the lookup table: " . $name . " (idaction = " . $actionId . ")");
$inserted[$fieldName] = $actionId;
mattab
a validé
}
Thomas Steur
a validé
mattab
a validé
return $inserted;
}
private static function getModel()
mattab
a validé
{
Thomas Steur
a validé
private static function queryIdsAction($actionsNameAndType)
{
$toQuery = array();
mattab
a validé
foreach ($actionsNameAndType as &$actionNameType) {
list($name, $type, $urlPrefix) = $actionNameType;
$toQuery[] = array('name' => $name, 'type' => $type);
Thomas Steur
a validé
$actionIds = self::getModel()->getIdsAction($toQuery);
mattab
a validé
return $actionIds;
}
private static function processIdsToInsert($actionsNameAndType, $actionIds)
mattab
a validé
{
// For the Actions found in the lookup table, add the idaction in the array,
// If not found in lookup table, queue for INSERT
mattab
a validé
$fieldNamesToInsert = $fieldNameToActionId = array();
Thomas Steur
a validé
mattab
a validé
foreach ($actionsNameAndType as $fieldName => &$actionNameType) {
@list($name, $type, $urlPrefix) = $actionNameType;
if (empty($name)) {
mattab
a validé
$fieldNameToActionId[$fieldName] = false;
continue;
}
$found = false;
foreach ($actionIds as $row) {
if ($name == $row['name']
&& $type == $row['type']
) {
$found = true;
mattab
a validé
$fieldNameToActionId[$fieldName] = $row['idaction'];
continue;
}
}
if (!$found) {
mattab
a validé
$fieldNamesToInsert[] = $fieldName;
Thomas Steur
a validé
mattab
a validé
return array($fieldNameToActionId, $fieldNamesToInsert);
/**
* Convert segment expression to an action ID or an SQL expression.
*
* This method is used as a sqlFilter-callback for the segments of this plugin.
* Usually, these callbacks only return a value that should be compared to the
* column in the database. In this case, that doesn't work since multiple IDs
* can match an expression (e.g. "pageUrl=@foo").
* @param string $valueToMatch
* @param string $sqlField
* @param string $matchType
* @param string $segmentName
* @throws \Exception
* @return array|int|string
*/
public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName)
{
$actionType = self::guessActionTypeFromSegment($segmentName);
if ($actionType == Action::TYPE_PAGE_URL) {
// for urls trim protocol and www because it is not recorded in the db
$valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch);
}
Thomas Steur
a validé
$valueToMatch = self::normaliseActionString($actionType, $valueToMatch);
if ($matchType == SegmentExpression::MATCH_EQUAL
|| $matchType == SegmentExpression::MATCH_NOT_EQUAL
) {
$idAction = self::getModel()->getIdActionMatchingNameAndType($valueToMatch, $actionType);
// if the action is not found, we hack -100 to ensure it tries to match against an integer
// otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Větrnásssssss)
if (empty($idAction)) {
$idAction = -100;
}
return $idAction;
}
// "name contains $string" match can match several idaction so we cannot return yet an idaction
// special case
$sql = TableLogAction::getSelectQueryWhereNameContains($matchType, $actionType);
Thomas Steur
a validé
return array(
// mark that the returned value is an sql-expression instead of a literal value
'SQL' => $sql,
'bind' => $valueToMatch,
);
}
/**
* @param $segmentName
* @return int
* @throws \Exception
*/
private static function guessActionTypeFromSegment($segmentName)
{
$exactMatch = array(
Thomas Steur
a validé
'eventAction' => Action::TYPE_EVENT_ACTION,
'eventCategory' => Action::TYPE_EVENT_CATEGORY,
'eventName' => Action::TYPE_EVENT_NAME,
'contentPiece' => Action::TYPE_CONTENT_PIECE,
'contentTarget' => Action::TYPE_CONTENT_TARGET,
'contentName' => Action::TYPE_CONTENT_NAME,
'contentInteraction' => Action::TYPE_CONTENT_INTERACTION,
);
Thomas Steur
a validé
if (!empty($exactMatch[$segmentName])) {
return $exactMatch[$segmentName];
}
if (stripos($segmentName, 'pageurl') !== false) {
$actionType = Action::TYPE_PAGE_URL;
return $actionType;
} elseif (stripos($segmentName, 'pagetitle') !== false) {
$actionType = Action::TYPE_PAGE_TITLE;
return $actionType;
} elseif (stripos($segmentName, 'sitesearch') !== false) {
$actionType = Action::TYPE_SITE_SEARCH;
return $actionType;
} else {
throw new \Exception("We cannot guess the action type from the segment $segmentName.");
/**
* This function will sanitize or not if it's needed for the specified action type
*
* URLs (Page URLs, Downloads, Outlinks) are stored raw (unsanitized)
* while other action types are stored Sanitized
*
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
* @param $actionType
* @param $actionString
* @return string
*/
private static function normaliseActionString($actionType, $actionString)
{
$actionString = Common::unsanitizeInputValue($actionString);
if (self::isActionTypeStoredSanitized($actionType)) {
return Common::sanitizeInputValue($actionString);
}
return $actionString;
}
/**
* @param $actionType
* @return bool
*/
private static function isActionTypeStoredSanitized($actionType)
{
$actionsTypesStoredUnsanitized = array(
$actionType == Action::TYPE_PAGE_URL,
$actionType == Action::TYPE_DOWNLOAD,
$actionType == Action::TYPE_OUTLINK,
);
$isStoredUnsanitized = in_array($actionType, $actionsTypesStoredUnsanitized);
return !$isStoredUnsanitized;
}