/Tracker/Action.php
PHP | 514 lines | 372 code | 54 blank | 88 comment | 29 complexity | 5d02238059ca3cee4edd02f89f5eecbf MD5 | raw file
- <?php
- /**
- * Piwik - Open source web analytics
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- * @version $Id: Action.php 4770 2011-05-22 21:26:47Z matt $
- *
- * @category Piwik
- * @package Piwik
- */
- /**
- * Interface of the Action object.
- * New Action classes can be defined in plugins and used instead of the default one.
- *
- * @package Piwik
- * @subpackage Piwik_Tracker
- */
- interface Piwik_Tracker_Action_Interface {
- const TYPE_ACTION_URL = 1;
- const TYPE_OUTLINK = 2;
- const TYPE_DOWNLOAD = 3;
- const TYPE_ACTION_NAME = 4;
- const TYPE_ECOMMERCE_ITEM_SKU = 5;
- const TYPE_ECOMMERCE_ITEM_NAME = 6;
- const TYPE_ECOMMERCE_ITEM_CATEGORY = 7;
-
- public function setRequest($requestArray);
- public function setIdSite( $idSite );
- public function init();
- public function getActionUrl();
- public function getActionName();
- public function getActionType();
- public function record( $idVisit, $visitorIdCookie, $idRefererActionUrl, $idRefererActionName, $timeSpentRefererAction );
- public function getIdActionUrl();
- public function getIdActionName();
- public function getIdLinkVisitAction();
- }
- /**
- * Handles an action (page view, download or outlink) by the visitor.
- * Parses the action name and URL from the request array, then records the action in the log table.
- *
- * @package Piwik
- * @subpackage Piwik_Tracker
- */
- class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface
- {
- private $request;
- private $idSite;
- private $timestamp;
- private $idLinkVisitAction;
- private $idActionName = false;
- private $idActionUrl = false;
- private $actionName;
- private $actionType;
- private $actionUrl;
-
- static private $queryParametersToExclude = array('phpsessid', 'jsessionid', 'sessionid', 'aspsessionid');
-
- public function setRequest($requestArray)
- {
- $this->request = $requestArray;
- }
- public function getRequest()
- {
- return $this->request;
- }
-
- /**
- * Returns URL of the page currently being tracked, or the file being downloaded, or the outlink being clicked
- * @return string
- */
- public function getActionUrl()
- {
- return $this->actionUrl;
- }
- public function getActionName()
- {
- return $this->actionName;
- }
- public function getActionType()
- {
- return $this->actionType;
- }
- public function getActionNameType()
- {
- $actionNameType = null;
- // we can add here action types for names of other actions than page views (like downloads, outlinks)
- switch( $this->getActionType() )
- {
- case Piwik_Tracker_Action_Interface::TYPE_ACTION_URL:
- $actionNameType = Piwik_Tracker_Action_Interface::TYPE_ACTION_NAME;
- break;
- }
- return $actionNameType;
- }
- public function getIdActionUrl()
- {
- return $this->idActionUrl;
- }
- public function getIdActionName()
- {
- return $this->idActionName;
- }
-
- protected function setActionName($name)
- {
- $name = $this->cleanupString($name);
- $this->actionName = $name;
- }
-
- protected function setActionType($type)
- {
- $this->actionType = $type;
- }
-
- protected function setActionUrl($url)
- {
- $url = self::excludeQueryParametersFromUrl($url, $this->idSite);
- $this->actionUrl = $url;
- }
-
- static public function excludeQueryParametersFromUrl($originalUrl, $idSite)
- {
- $website = Piwik_Common::getCacheWebsiteAttributes( $idSite );
- $originalUrl = Piwik_Common::unsanitizeInputValue($originalUrl);
- $originalUrl = self::cleanupString($originalUrl);
- $parsedUrl = @parse_url($originalUrl);
- if(empty($parsedUrl['query']))
- {
- return $originalUrl;
- }
- $campaignTrackingParameters = Piwik_Common::getCampaignParameters();
-
- $campaignTrackingParameters = array_merge(
- $campaignTrackingParameters[0], // campaign name parameters
- $campaignTrackingParameters[1] // campaign keyword parameters
- );
-
- $excludedParameters = isset($website['excluded_parameters'])
- ? $website['excluded_parameters']
- : array();
-
- $parametersToExclude = array_merge( $excludedParameters,
- self::$queryParametersToExclude,
- $campaignTrackingParameters);
-
- $parametersToExclude = array_map('strtolower', $parametersToExclude);
- $queryParameters = Piwik_Common::getArrayFromQueryString($parsedUrl['query']);
-
- $validQuery = '';
- $separator = '&';
- foreach($queryParameters as $name => $value)
- {
- if(!in_array(strtolower($name), $parametersToExclude))
- {
- if (is_array($value))
- {
- foreach ($value as $param)
- {
- if($param === false)
- {
- $validQuery .= $name.'[]'.$separator;
- }
- else
- {
- $validQuery .= $name.'[]='.$param.$separator;
- }
- }
- }
- else if($value === false)
- {
- $validQuery .= $name.$separator;
- }
- else
- {
- $validQuery .= $name.'='.$value.$separator;
- }
- }
- }
- $parsedUrl['query'] = substr($validQuery,0,-strlen($separator));
- $url = Piwik_Common::getParseUrlReverse($parsedUrl);
- printDebug('Excluding parameters "'.implode(',',$excludedParameters).'" from URL');
- if($originalUrl != $url)
- {
- printDebug(' Before was "'.$originalUrl.'"');
- printDebug(' After is "'.$url.'"');
- }
- return $url;
- }
-
- public function init()
- {
- $info = $this->extractUrlAndActionNameFromRequest();
- $this->setActionName($info['name']);
- $this->setActionType($info['type']);
- $this->setActionUrl($info['url']);
- }
-
- static public function getSqlSelectActionId()
- {
- $sql = "SELECT idaction, type, name
- FROM ".Piwik_Common::prefixTable('log_action')
- ." WHERE "
- ." ( hash = CRC32(?) AND name = ? AND type = ? ) ";
- return $sql;
- }
-
- /**
- * This function will find the idaction from the lookup table piwik_log_action,
- * given an Action name and type.
- *
- * 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
- * @param array $actionNamesAndTypes Array of one or many (name,type)
- * @return array Returns the input array, with the idaction appended ie. Array of one or many (name,type,idaction)
- */
- static public function loadActionId( $actionNamesAndTypes )
- {
- // First, we try and select the actions that are already recorded
- $sql = self::getSqlSelectActionId();
- $bind = array();
- $i = 0;
- foreach($actionNamesAndTypes as &$actionNameType)
- {
- list($name,$type) = $actionNameType;
- if(empty($name))
- {
- $actionNameType[] = false;
- continue;
- }
- if($i > 0)
- {
- $sql .= " OR ( hash = CRC32(?) AND name = ? AND type = ? ) ";
- }
- $bind[] = $name;
- $bind[] = $name;
- $bind[] = $type;
- $i++;
- }
- // Case URL & Title are empty
- if(empty($bind))
- {
- return $actionNamesAndTypes;
- }
- $actionIds = Piwik_Tracker::getDatabase()->fetchAll($sql, $bind);
-
- // For the Actions found in the lookup table, add the idaction in the array,
- // If not found in lookup table, queue for INSERT
- $actionsToInsert = array();
- foreach($actionNamesAndTypes as $index => &$actionNameType)
- {
- list($name,$type) = $actionNameType;
- if(empty($name)) { continue; }
- $found = false;
- foreach($actionIds as $row)
- {
- if($name == $row['name']
- && $type == $row['type'])
- {
- $found = true;
- $actionNameType[] = $row['idaction'];
- continue;
- }
- }
- if(!$found)
- {
- $actionsToInsert[] = $index;
- }
- }
-
- $sql = "INSERT INTO ". Piwik_Common::prefixTable('log_action').
- "( name, hash, type ) VALUES (?,CRC32(?),?)";
- // Then, we insert all new actions in the lookup table
- foreach($actionsToInsert as $actionToInsert)
- {
- list($name,$type) = $actionNamesAndTypes[$actionToInsert];
-
- Piwik_Tracker::getDatabase()->query($sql, array($name, $name, $type));
- $actionId = Piwik_Tracker::getDatabase()->lastInsertId();
- printDebug("Recorded a new action (".self::getActionTypeName($type).") in the lookup table: ". $name . " (idaction = ".$actionId.")");
-
- $actionNamesAndTypes[$actionToInsert][] = $actionId;
- }
- return $actionNamesAndTypes;
- }
-
- static public function getActionTypeName($type)
- {
- switch($type)
- {
- case self::TYPE_ACTION_URL: return 'Page URL'; break;
- case self::TYPE_OUTLINK: return 'Outlink URL'; break;
- case self::TYPE_DOWNLOAD: return 'Download URL'; break;
- case self::TYPE_ACTION_NAME: return 'Page Title'; break;
- case self::TYPE_ECOMMERCE_ITEM_SKU: return 'Ecommerce Item SKU'; break;
- case self::TYPE_ECOMMERCE_ITEM_NAME: return 'Ecommerce Item Name'; break;
- case self::TYPE_ECOMMERCE_ITEM_CATEGORY: return 'Ecommerce Item Category'; break;
- default: throw new Exception("Unexpected action type ".$type); break;
- }
- }
-
- /**
- * Loads the idaction of the current action name and the current action url.
- * These idactions are used in the visitor logging table to link the visit information
- * (entry action, exit action) to the actions.
- * These idactions are also used in the table that links the visits and their actions.
- *
- * The methods takes care of creating a new record(s) in the action table if the existing
- * action name and action url doesn't exist yet.
- */
- function loadIdActionNameAndUrl()
- {
- if( $this->idActionUrl !== false
- && $this->idActionName !== false )
- {
- return;
- }
- $actions = array();
- $action = array($this->getActionName(), $this->getActionNameType());
- if(!is_null($action[1]))
- {
- $actions[] = $action;
- }
- $action = array($this->getActionUrl(), $this->getActionType());
- if(!is_null($action[1]))
- {
- $actions[] = $action;
- }
- $loadedActionIds = self::loadActionId($actions);
-
- foreach($loadedActionIds as $loadedActionId)
- {
- list($name, $type, $actionId) = $loadedActionId;
- if($type == $this->getActionType())
- {
- $this->idActionUrl = $actionId;
- }
- elseif($type == $this->getActionNameType())
- {
- $this->idActionName = $actionId;
- }
- }
- }
-
- /**
- * @param int $idSite
- */
- function setIdSite($idSite)
- {
- $this->idSite = $idSite;
- }
-
- function setTimestamp($timestamp)
- {
- $this->timestamp = $timestamp;
- }
-
-
- /**
- * Records in the DB the association between the visit and this action.
- *
- * @param int idVisit is the ID of the current visit in the DB table log_visit
- * @param int idRefererActionUrl is the ID of the last action done by the current visit.
- * @param int timeSpentRefererAction is the number of seconds since the last action was done.
- * It is directly related to idRefererActionUrl.
- */
- public function record( $idVisit, $visitorIdCookie, $idRefererActionUrl, $idRefererActionName, $timeSpentRefererAction)
- {
- $this->loadIdActionNameAndUrl();
-
- $idActionName = in_array($this->getActionType(), array(Piwik_Tracker_Action::TYPE_ACTION_NAME, Piwik_Tracker_Action::TYPE_ACTION_URL))
- ? (int)$this->getIdActionName()
- : null;
- $insert = array(
- 'idvisit' => $idVisit,
- 'idsite' => $this->idSite,
- 'idvisitor' => $visitorIdCookie,
- 'server_time' => Piwik_Tracker::getDatetimeFromTimestamp($this->timestamp),
- 'idaction_url' => (int)$this->getIdActionUrl(),
- 'idaction_name' => $idActionName,
- 'idaction_url_ref' => $idRefererActionUrl,
- 'idaction_name_ref' => $idRefererActionName,
- 'time_spent_ref_action' => $timeSpentRefererAction
- );
- $customVariables = Piwik_Tracker_Visit::getCustomVariables($scope = 'page', $this->request);
- $insert = array_merge($insert, $customVariables);
- // Mysqli apparently does not like NULL inserts?
- $insertWithoutNulls = array();
- foreach($insert as $column => $value)
- {
- if(!is_null($value))
- {
- $insertWithoutNulls[$column] = $value;
- }
- }
-
- $fields = implode(", ", array_keys($insertWithoutNulls));
- $bind = array_values($insertWithoutNulls);
- $values = Piwik_Common::getSqlStringFieldsArray($insertWithoutNulls);
- $sql = "INSERT INTO ".Piwik_Common::prefixTable('log_link_visit_action'). " ($fields) VALUES ($values)";
- Piwik_Tracker::getDatabase()->query( $sql, $bind );
-
- $this->idLinkVisitAction = Piwik_Tracker::getDatabase()->lastInsertId();
-
- $info = array(
- 'idSite' => $this->idSite,
- 'idLinkVisitAction' => $this->idLinkVisitAction,
- 'idVisit' => $idVisit,
- 'idRefererActionUrl' => $idRefererActionUrl,
- 'idRefererActionName' => $idRefererActionName,
- 'timeSpentRefererAction' => $timeSpentRefererAction,
- );
- printDebug($insertWithoutNulls);
- /*
- * send the Action object ($this) and the list of ids ($info) as arguments to the event
- */
- Piwik_PostEvent('Tracker.Action.record', $this, $info);
- }
-
- /**
- * Returns the ID of the newly created record in the log_link_visit_action table
- *
- * @return int | false
- */
- public function getIdLinkVisitAction()
- {
- return $this->idLinkVisitAction;
- }
-
- /**
- * Generates the name of the action from the URL or the specified name.
- * Sets the name as $this->actionName
- *
- * @return array
- */
- protected function extractUrlAndActionNameFromRequest()
- {
- $actionName = null;
-
- // download?
- $downloadUrl = Piwik_Common::getRequestVar( 'download', '', 'string', $this->request);
- if(!empty($downloadUrl))
- {
- $actionType = self::TYPE_DOWNLOAD;
- $url = $downloadUrl;
- }
-
- // outlink?
- if(empty($actionType))
- {
- $outlinkUrl = Piwik_Common::getRequestVar( 'link', '', 'string', $this->request);
- if(!empty($outlinkUrl))
- {
- $actionType = self::TYPE_OUTLINK;
- $url = $outlinkUrl;
- }
- }
- // handle encoding
- $actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string', $this->request);
- // defaults to page view
- if(empty($actionType))
- {
- $actionType = self::TYPE_ACTION_URL;
- $url = Piwik_Common::getRequestVar( 'url', '', 'string', $this->request);
- // get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067)
- $actionCategoryDelimiter = isset(Piwik_Tracker_Config::getInstance()->General['action_category_delimiter'])
- ? Piwik_Tracker_Config::getInstance()->General['action_category_delimiter']
- : Piwik_Tracker_Config::getInstance()->General['action_url_category_delimiter'];
-
- // create an array of the categories delimited by the delimiter
- $split = explode($actionCategoryDelimiter, $actionName);
-
- // trim every category
- $split = array_map('trim', $split);
-
- // remove empty categories
- $split = array_filter($split, 'strlen');
-
- // rebuild the name from the array of cleaned categories
- $actionName = implode($actionCategoryDelimiter, $split);
- }
- $url = self::cleanupString($url);
- $actionName = self::cleanupString($actionName);
- return array(
- 'name' => empty($actionName) ? '' : $actionName,
- 'type' => $actionType,
- 'url' => $url,
- );
- }
-
- protected static function cleanupString($string)
- {
- $string = trim($string);
- $string = str_replace(array("\n", "\r"), "", $string);
- $limit = Piwik_Tracker_Config::getInstance()->Tracker['page_maximum_length'];
- return substr($string, 0, $limit);
- }
- }