From 722e8a6d6666d40c003dd2de9a249710cd09df22 Mon Sep 17 00:00:00 2001 From: mattpiwik <matthieu.aubry@gmail.com> Date: Thu, 22 Oct 2009 22:40:18 +0000 Subject: [PATCH] - Fixes #708 kudos to Maciej for his work on this one, and Anthon & Matt for the review. Changes from Maciej's patch: -- the old Actions stats are displayed in the Page titles section, ensure BC when users were specifying page titles, they would appear in the same report. The api Actions.getActions (now deprecated) is a proxy to Actions.getPageTitles) -- applied schema change to log_conversion -- empty action names and empty URLs are now accepted at tracking time. They will be replaced at archiving time by the strings defined in the config file. - API: if a method has a comment @deprecated, it won't be shown in the API listing page (used for the now deprecated Actions.getActions() API call) - Fixes #693 Visits generator now asks for user confirmation before inserting fake data in the DB TODO: - apply small modifs to the JS tracker (setCustomUrl() and action_name defaulting to document.title) - improve/fix unit tests around the expected behavior of Action naming git-svn-id: http://dev.piwik.org/svn/trunk@1530 59fd770c-687e-43c8-a1e3-f5a4ff64c105 --- config/global.ini.php | 11 +- core/API/Proxy.php | 4 +- core/ArchiveProcessing/Day.php | 2 +- core/Piwik.php | 14 +- core/Tracker/Action.php | 156 +++++++++------- core/Tracker/Generator.php | 11 +- core/Tracker/GoalManager.php | 35 ++-- core/Tracker/Visit.php | 38 ++-- core/Updates/0.5.php | 33 ++++ core/Version.php | 2 +- lang/en.php | 2 + misc/generateVisits.php | 26 ++- plugins/Actions/API.php | 19 +- plugins/Actions/Actions.php | 167 ++++++++++++++---- plugins/Actions/Controller.php | 54 +++--- plugins/Actions/tests/Actions.test.php | 92 ++++++++++ tests/core/Tracker/Action.test.php | 73 ++++++-- tests/resources/Tracker/Action.config.ini.php | 2 + .../plugins/Actions/Actions.config.ini.php | 5 + 19 files changed, 549 insertions(+), 197 deletions(-) create mode 100644 core/Updates/0.5.php create mode 100644 plugins/Actions/tests/Actions.test.php create mode 100644 tests/resources/plugins/Actions/Actions.config.ini.php diff --git a/config/global.ini.php b/config/global.ini.php index ffff48ea6b..14742744e7 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -72,6 +72,14 @@ enable_browser_archiving_triggering = 1 ; the page first-post in the subcategory development which belongs to the blog category action_category_delimiter = / +; this action name is used when the URL ends with a slash / +; it is useful to have an actual string to write in the UI +action_default_name = index + +; this action name is used when the URL has no page title or page URL defined +action_default_name_when_not_defined = "page title not defined" +action_default_url_when_not_defined = "page url not defined" + ; currency used by default when reporting money in Piwik ; the trailing space is required for php 5.2.x vs 5.3 compatibility default_currency = "$ " @@ -139,9 +147,6 @@ swfobject_version = 2.2 ; set to 0 if you want to stop tracking the visitors. Useful if you need to stop all the connections on the DB. record_statistics = 1 -; this action name is used when the javascript variable piwik_action_name is not specified in the piwik javascript code, and when the URL has no path. -default_action_name = index - ; length of a visit in seconds. If a visitor comes back on the website visit_standard_length seconds after his last page view, it will be recorded as a new visit visit_standard_length = 1800 diff --git a/core/API/Proxy.php b/core/API/Proxy.php index 0e83592378..26c11288c9 100644 --- a/core/API/Proxy.php +++ b/core/API/Proxy.php @@ -246,7 +246,9 @@ class Piwik_API_Proxy { if($method->isPublic() && !$method->isConstructor() - && $method->getName() != 'getInstance' ) + && $method->getName() != 'getInstance' + && false === strstr($method->getDocComment(), '@deprecated') + ) { $name = $method->getName(); $parameters = $method->getParameters(); diff --git a/core/ArchiveProcessing/Day.php b/core/ArchiveProcessing/Day.php index 051fedad18..2e3c0bbab7 100644 --- a/core/ArchiveProcessing/Day.php +++ b/core/ArchiveProcessing/Day.php @@ -28,7 +28,7 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing $this->db = Zend_Registry::get('db'); $this->debugAlwaysArchive = Zend_Registry::get('config')->Debug->always_archive_data_day; } - + /** * Main method to process logs for a day. The only logic done here is computing the number of visits, actions, etc. * All the other reports are computed inside plugins listening to the event 'ArchiveProcessing_Day.compute'. diff --git a/core/Piwik.php b/core/Piwik.php index 0d3595a452..09a6b8d109 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -609,9 +609,10 @@ class Piwik 'log_action' => "CREATE TABLE {$prefixTables}log_action ( idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, + hash INTEGER(10) UNSIGNED NOT NULL, type TINYINT UNSIGNED NULL, PRIMARY KEY(idaction), - INDEX index_type_name (type, name(15)) + INDEX index_type_hash (type, hash) ) DEFAULT CHARSET=utf8 ", @@ -624,8 +625,8 @@ class Piwik visit_first_action_time DATETIME NOT NULL, visit_last_action_time DATETIME NOT NULL, visit_server_date DATE NOT NULL, - visit_exit_idaction INTEGER(11) NOT NULL, - visit_entry_idaction INTEGER(11) NOT NULL, + visit_exit_idaction_url INTEGER(11) NOT NULL, + visit_entry_idaction_url INTEGER(11) NOT NULL, visit_total_actions SMALLINT(5) UNSIGNED NOT NULL, visit_total_time SMALLINT(5) UNSIGNED NOT NULL, visit_goal_converted TINYINT(1) NOT NULL, @@ -663,7 +664,7 @@ class Piwik `visitor_idcookie` char(32) NOT NULL, `server_time` datetime NOT NULL, `visit_server_date` date NOT NULL, - `idaction` int(11) default NULL, + `idaction_url` int(11) default NULL, `idlink_va` int(11) default NULL, `referer_idvisit` int(10) unsigned default NULL, `referer_visit_server_date` date default NULL, @@ -684,8 +685,9 @@ class Piwik 'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action ( idlink_va INTEGER(11) NOT NULL AUTO_INCREMENT, idvisit INTEGER(10) UNSIGNED NOT NULL, - idaction INTEGER(10) UNSIGNED NOT NULL, - idaction_ref INTEGER(11) UNSIGNED NOT NULL, + idaction_url INTEGER(10) UNSIGNED NOT NULL, + idaction_url_ref INTEGER(10) UNSIGNED NOT NULL, + idaction_name INTEGER(10) UNSIGNED, time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL, PRIMARY KEY(idlink_va), INDEX index_idvisit(idvisit) diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php index 5afe0738cf..bd0d924431 100644 --- a/core/Tracker/Action.php +++ b/core/Tracker/Action.php @@ -18,9 +18,10 @@ * @subpackage Piwik_Tracker */ interface Piwik_Tracker_Action_Interface { - const TYPE_ACTION = 1; + const TYPE_ACTION_URL = 1; const TYPE_OUTLINK = 2; const TYPE_DOWNLOAD = 3; + const TYPE_ACTION_NAME = 4; public function setRequest($requestArray); public function setIdSite( $idSite ); @@ -29,7 +30,8 @@ interface Piwik_Tracker_Action_Interface { public function getActionName(); public function getActionType(); public function record( $idVisit, $idRefererAction, $timeSpentRefererAction ); - public function getIdAction(); + public function getIdActionUrl(); + public function getIdActionName(); public function getIdLinkVisitAction(); } @@ -44,10 +46,6 @@ interface Piwik_Tracker_Action_Interface { * - An action is defined by a name. * - The name can be specified in the JS Code in the variable 'action_name' * For example you can decide to use the javascript value document.title as an action_name - * - If the name is not specified, we use the URL(path+query) to build a default name. - * For example for "http://piwik.org/test/my_page/test.html" - * the name would be "test/my_page/test.html" - * - If the name is empty we set it to default_action_name found in global.ini.php * - Handling UTF8 in the action name * PLUGIN_IDEA - An action is associated to URLs and link to the URL from the reports (currently actions do not link to the url of the pages) * PLUGIN_IDEA - An action hit by a visitor is associated to the HTML title of the page that triggered the action and this HTML title is displayed in the interface @@ -60,16 +58,12 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface private $request; private $idSite; private $idLinkVisitAction; - private $idAction = null; + private $idActionName = null; + private $idActionUrl = null; private $actionName; private $actionType; - private $url; - - protected function getDefaultActionName() - { - return Piwik_Tracker_Config::getInstance()->Tracker['default_action_name']; - } + private $actionUrl; public function setRequest($requestArray) { @@ -92,6 +86,29 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface { 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) { @@ -115,44 +132,65 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface } /** - * Returns the idaction of the current action name. - * This idaction is used in the visitor logging table to link the visit information + * 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. - * This idaction is also used in the table that links the visits and their 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 in the action table if the existing - * action name doesn't exist yet. + * 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. * - * @return int Id action that is associated to this action name in the Actions table lookup */ - function getIdAction() + function loadIdActionNameAndUrl() { - if(!is_null($this->idAction)) + if( !is_null($this->idActionUrl) && !is_null($this->idActionName) ) { - return $this->idAction; + return; } - $idAction = Piwik_Tracker::getDatabase()->fetch("/* SHARDING_ID_SITE = ".$this->idSite." */ SELECT idaction + $idAction = Piwik_Tracker::getDatabase()->fetchAll("/* SHARDING_ID_SITE = ".$this->idSite." */ + SELECT idaction, type FROM ".Piwik_Common::prefixTable('log_action') - ." WHERE name = ? AND type = ?", - array($this->getActionName(), $this->getActionType()) + ." WHERE " + ." ( hash = CRC32(?) AND name = ? AND type = ? ) " + ." OR " + ." ( hash = CRC32(?) AND name = ? AND type = ? ) ", + array($this->getActionName(), $this->getActionName(), $this->getActionNameType(), + $this->getActionUrl(), $this->getActionUrl(), $this->getActionType()) ); - - // the action name has not been found, create it - if($idAction === false || $idAction == '') + + if( $idAction !== false ) { - Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ".$this->idSite." */ - INSERT INTO ". Piwik_Common::prefixTable('log_action'). " ( name, type ) - VALUES (?,?)", - array($this->getActionName(),$this->getActionType()) - ); - $idAction = Piwik_Tracker::getDatabase()->lastInsertId(); + foreach($idAction as $row) + { + if( $row['type'] == Piwik_Tracker_Action_Interface::TYPE_ACTION_NAME ) + { + $this->idActionName = $row['idaction']; + } + else + { + $this->idActionUrl = $row['idaction']; + } + } } - else + + $sql = "/* SHARDING_ID_SITE = ".$this->idSite." */ + INSERT INTO ". Piwik_Common::prefixTable('log_action'). + "( name, hash, type ) VALUES (?,CRC32(?),?)"; + + if( is_null($this->idActionName) + && !is_null($this->getActionNameType()) ) { - $idAction = $idAction['idaction']; + Piwik_Tracker::getDatabase()->query($sql, + array($this->getActionName(), $this->getActionName(), $this->getActionNameType())); + $this->idActionName = Piwik_Tracker::getDatabase()->lastInsertId(); + } + + if( is_null($this->idActionUrl) ) + { + Piwik_Tracker::getDatabase()->query($sql, + array($this->getActionUrl(), $this->getActionUrl(), $this->getActionType())); + $this->idActionUrl = Piwik_Tracker::getDatabase()->lastInsertId(); } - $this->idAction = $idAction; - return $this->idAction; } /** @@ -174,9 +212,11 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface */ public function record( $idVisit, $idRefererAction, $timeSpentRefererAction) { - Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ".$this->idSite." */ INSERT INTO ".Piwik_Common::prefixTable('log_link_visit_action') - ." (idvisit, idaction, idaction_ref, time_spent_ref_action) VALUES (?,?,?,?)", - array($idVisit, $this->getIdAction(), $idRefererAction, $timeSpentRefererAction) + $this->loadIdActionNameAndUrl(); + + Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ".$this->idSite." */ INSERT INTO ".Piwik_Common::prefixTable('log_link_visit_action') + ." (idvisit, idaction_url, idaction_name, idaction_url_ref, time_spent_ref_action) VALUES (?,?,?,?,?)", + array($idVisit, $this->getIdActionUrl(), $this->getIdActionName(), $idRefererAction, $timeSpentRefererAction) ); $this->idLinkVisitAction = Piwik_Tracker::getDatabase()->lastInsertId(); @@ -209,9 +249,13 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface /** * 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)) @@ -230,26 +274,15 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface $url = $outlinkUrl; } } - + + $actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string', $this->request); + // defaults to page view if(empty($actionType)) { - $actionType = self::TYPE_ACTION; + $actionType = self::TYPE_ACTION_URL; $url = Piwik_Common::getRequestVar( 'url', '', 'string', $this->request); - $actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string', $this->request); - if( empty($actionName) ) - { - $cleanedUrl = str_replace(array("\n", "\r", "\t"), "", $url); - $actionName = Piwik_Common::getPathAndQueryFromUrl($cleanedUrl); - // in case the $actionName is empty or ending with a slash, - // we append the defaultActionName: a/b/ becomes a/b/index - if(empty($actionName) - || substr($actionName, -1) == '/') - { - $actionName .= $this->getDefaultActionName(); - } - } - + // get the delimiter, by default '/' $actionCategoryDelimiter = Piwik_Tracker_Config::getInstance()->General['action_category_delimiter']; @@ -268,13 +301,12 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface $url = trim($url); $url = str_replace(array("\n", "\r"), "", $url); - if(empty($actionName)) - { - $actionName = $url; - } + + $actionName = trim($actionName); + $actionName = str_replace(array("\n", "\r"), "", $actionName); return array( - 'name' => $actionName, + 'name' => empty($actionName) ? '' : $actionName, 'type' => $actionType, 'url' => $url, ); diff --git a/core/Tracker/Generator.php b/core/Tracker/Generator.php index d0e5f016d2..7a6dfdfe21 100644 --- a/core/Tracker/Generator.php +++ b/core/Tracker/Generator.php @@ -220,11 +220,11 @@ class Piwik_Tracker_Generator */ public function end() { - Piwik_Tracker::disconnectDatabase(); if($this->profiling) { Piwik::printSqlProfilingReportTracker(); } + Piwik_Tracker::disconnectDatabase(); } /** @@ -446,19 +446,12 @@ class Piwik_Tracker_Generator // add the parameter to the url $this->setCurrentRequest( $GETParamToAdd , $urlValue); } - - // if we didn't set any campaign NOR any download click - // then we sometimes set a special action name to the current action - elseif(rand(0,2)==1) - { - $this->setCurrentRequest( 'action_name' , $this->getRandomString(1,1)); - } } $this->setCurrentRequest( 'url' ,$url); // setup the title of the page - $this->setCurrentRequest( 'title',$this->getRandomString(15,5)); + $this->setCurrentRequest( 'action_name',$this->getRandomString(15,5)); } /** diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php index 4dc148d86c..f1ff810891 100644 --- a/core/Tracker/GoalManager.php +++ b/core/Tracker/GoalManager.php @@ -17,7 +17,7 @@ class Piwik_Tracker_GoalManager { /** - * @var Piwik_Cookie + * @var Piwik_Cookie */ protected $cookie = null; /** @@ -41,7 +41,7 @@ class Piwik_Tracker_GoalManager } return array(); } - + static public function getGoalDefinition( $idSite, $idGoal ) { $goals = self::getGoalDefinitions( $idSite ); @@ -54,7 +54,7 @@ class Piwik_Tracker_GoalManager } throw new Exception("The goal id = $idGoal couldn't be found."); } - + static public function getGoalIds( $idSite ) { $goals = self::getGoalDefinitions( $idSite ); @@ -65,13 +65,13 @@ class Piwik_Tracker_GoalManager } return $goalIds; } - + private function isGoalPluginEnabled() { return Piwik_PluginsManager::getInstance()->isPluginActivated('Goals'); } - - //TODO does this code work for manually triggered goals, with custom revenue? + + //TODO does this code work for manually triggered goals, with custom revenue? function detectGoalsMatchingUrl($idSite, $action) { if(!$this->isGoalPluginEnabled()) @@ -85,7 +85,7 @@ class Piwik_Tracker_GoalManager { $attribute = $goal['match_attribute']; // if the attribute to match is not the type of the current action - if( ($actionType == Piwik_Tracker_Action::TYPE_ACTION && $attribute != 'url') + if( ($actionType == Piwik_Tracker_Action::TYPE_ACTION_URL && $attribute != 'url') || ($actionType == Piwik_Tracker_Action::TYPE_DOWNLOAD && $attribute != 'file') || ($actionType == Piwik_Tracker_Action::TYPE_OUTLINK && $attribute != 'external_website') || ($attribute == 'manually') @@ -93,9 +93,9 @@ class Piwik_Tracker_GoalManager { continue; } - + $pattern_type = $goal['pattern_type']; - + switch($pattern_type) { case 'regex': @@ -141,7 +141,7 @@ class Piwik_Tracker_GoalManager // var_dump($this->convertedGoals);exit; return count($this->convertedGoals) > 0; } - + function detectGoalId($idSite, $idGoal, $request) { if(!$this->isGoalPluginEnabled()) @@ -159,12 +159,12 @@ class Piwik_Tracker_GoalManager $this->convertedGoals[] = $goal; return true; } - + function recordGoals($visitorInformation, $action) { $location_country = isset($visitorInformation['location_country']) ? $visitorInformation['location_country'] : Piwik_Common::getCountry(Piwik_Common::getBrowserLanguage(), $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess']); $location_continent = isset($visitorInformation['location_continent']) ? $visitorInformation['location_continent'] : Piwik_Common::getContinent($location_country); - + $goal = array( 'idvisit' => $visitorInformation['idvisit'], 'idsite' => $visitorInformation['idsite'], @@ -197,18 +197,18 @@ class Piwik_Tracker_GoalManager $newGoal['revenue'] = $convertedGoal['revenue']; if(!is_null($action)) { - $newGoal['idaction'] = $action->getIdAction(); + $newGoal['idaction_url'] = $action->getIdActionUrl(); $newGoal['idlink_va'] = $action->getIdLinkVisitAction(); } printDebug($newGoal); - + $fields = implode(", ", array_keys($newGoal)); $bindFields = substr(str_repeat( "?,",count($newGoal)),0,-1); - + try { Piwik_Tracker::getDatabase()->query( - "INSERT INTO " . Piwik_Common::prefixTable('log_conversion') . " ($fields) - VALUES ($bindFields) ", array_values($newGoal) + "INSERT INTO " . Piwik_Common::prefixTable('log_conversion') . " ($fields) + VALUES ($bindFields) ", array_values($newGoal) ); } catch( Exception $e) { if(Piwik_Tracker::isErrNo($e, '1062')) @@ -221,7 +221,6 @@ class Piwik_Tracker_GoalManager throw $e; } } - //$idlog_goal = Piwik_Tracker::getDatabase()->lastInsertId(); } } } diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 323a720806..2b50bd1baf 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -95,7 +95,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $goalManager = new Piwik_Tracker_GoalManager(); $someGoalsConverted = false; - $actionId = 0; + $actionUrlId = 0; $action = null; $idGoal = Piwik_Common::getRequestVar('idgoal', 0, 'int', $this->request); @@ -116,7 +116,9 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $action = $this->newAction(); $this->handleAction($action); $someGoalsConverted = $goalManager->detectGoalsMatchingUrl($this->idsite, $action); - $actionId = $action->getIdAction(); + + $action->loadIdActionNameAndUrl(); + $actionUrlId = $action->getIdActionUrl(); } // the visitor and session @@ -133,9 +135,9 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface if( $this->isVisitorKnown() && $isLastActionInTheSameVisit) { - $idActionReferer = $this->visitorInfo['visit_exit_idaction']; + $idActionReferer = $this->visitorInfo['visit_exit_idaction_url']; try { - $this->handleKnownVisit($actionId, $someGoalsConverted); + $this->handleKnownVisit($actionUrlId, $someGoalsConverted); if(!is_null($action)) { $action->record( $this->visitorInfo['idvisit'], @@ -156,7 +158,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface if(!$this->isVisitorKnown() || !$isLastActionInTheSameVisit) { - $this->handleNewVisit($actionId, $someGoalsConverted); + $this->handleNewVisit($actionUrlId, $someGoalsConverted); if(!is_null($action)) { $action->record( $this->visitorInfo['idvisit'], 0, 0 ); @@ -189,7 +191,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) { switch($action->getActionType()) { - case Piwik_Tracker_Action::TYPE_ACTION: + case Piwik_Tracker_Action::TYPE_ACTION_URL: $type = "normal page view"; break; case Piwik_Tracker_Action::TYPE_DOWNLOAD: @@ -211,7 +213,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface * * 2) Update the visit information */ - protected function handleKnownVisit($actionId, $someGoalsConverted) + protected function handleKnownVisit($actionUrlId, $someGoalsConverted) { $serverTime = $this->getCurrentTimestamp(); $datetimeServer = Piwik_Tracker::getDatetimeFromTimestamp($serverTime); @@ -224,13 +226,12 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface } $sqlActionIdUpdate = ''; - if(!empty($actionId)) + if(!empty($actionUrlId)) { - $sqlActionIdUpdate = "visit_exit_idaction = ". $actionId .", + $sqlActionIdUpdate = "visit_exit_idaction_url = ". $actionUrlId .", visit_total_actions = visit_total_actions + 1, "; - $this->visitorInfo['visit_exit_idaction'] = $actionId; + $this->visitorInfo['visit_exit_idaction_url'] = $actionUrlId; } - $result = Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ". $this->idsite ." */ UPDATE ". Piwik_Common::prefixTable('log_visit')." SET $sqlActionIdUpdate @@ -244,7 +245,6 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->visitorInfo['idvisit'], $this->visitorInfo['visitor_idcookie'] ) ); - if(Piwik_Tracker::getDatabase()->rowCount($result) == 0) { throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase("The visitor with visitor_idcookie=".$this->visitorInfo['visitor_idcookie']." and idvisit=".$this->visitorInfo['idvisit']." wasn't found in the DB, we fallback to a new visitor"); @@ -267,7 +267,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface * * 2) Insert the visit information */ - protected function handleNewVisit($actionId, $someGoalsConverted) + protected function handleNewVisit($actionUrlId, $someGoalsConverted) { printDebug("New Visit."); @@ -297,8 +297,8 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime), 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime), 'visit_server_date' => $serverDate, - 'visit_entry_idaction' => $actionId, - 'visit_exit_idaction' => $actionId, + 'visit_entry_idaction_url' => $actionUrlId, + 'visit_exit_idaction_url' => $actionUrlId, 'visit_total_actions' => 1, 'visit_total_time' => $defaultTimeOnePageVisit, 'visit_goal_converted' => $someGoalsConverted ? 1: 0, @@ -531,7 +531,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->visitorInfo['visit_last_action_time'] = $timestampLastAction; $this->visitorInfo['visit_first_action_time'] = $timestampFirstAction; $this->visitorInfo['idvisit'] = $idVisit; - $this->visitorInfo['visit_exit_idaction'] = $idLastAction; + $this->visitorInfo['visit_exit_idaction_url'] = $idLastAction; $this->visitorKnown = true; @@ -569,7 +569,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->visitorInfo['visit_last_action_time'] = $visitRow['visit_last_action_time']; $this->visitorInfo['visit_first_action_time'] = $visitRow['visit_first_action_time']; $this->visitorInfo['idvisit'] = $visitRow['idvisit']; - $this->visitorInfo['visit_exit_idaction'] = $visitRow['visit_exit_idaction']; + $this->visitorInfo['visit_exit_idaction_url'] = $visitRow['visit_exit_idaction_url']; $this->visitorKnown = true; @@ -719,10 +719,10 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->visitorInfo['idvisit'] ); // the last action ID is the current exit idaction - if(isset($this->visitorInfo['visit_exit_idaction'] )) + if(isset($this->visitorInfo['visit_exit_idaction_url'] )) { $this->cookie->set( Piwik_Tracker::COOKIE_INDEX_ID_LAST_ACTION, - $this->visitorInfo['visit_exit_idaction'] ); + $this->visitorInfo['visit_exit_idaction_url'] ); } // for a new visit, we flag the visit with visitor_returning diff --git a/core/Updates/0.5.php b/core/Updates/0.5.php new file mode 100644 index 0000000000..108e651617 --- /dev/null +++ b/core/Updates/0.5.php @@ -0,0 +1,33 @@ +<?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$ + * + * @category Piwik + * @package Updates + */ + +/** + * @package Updates + */ +class Piwik_Updates_0_5 implements Piwik_iUpdate +{ + static function update() + { + Piwik_Updater::updateDatabase(__FILE__, array( + 'ALTER TABLE ' . Piwik::prefixTable('log_action'). ' ADD COLUMN `hash` INTEGER(10) UNSIGNED NOT NULL AFTER `name`;' => false, + 'UPDATE '. Piwik::prefixTable('log_action'). ' SET `hash` = CRC32(name);' => false, + 'CREATE INDEX index_type_hash ON '. Piwik::prefixTable('log_action') .' (type, hash);' => false, + 'DROP INDEX index_type_name ON '. Piwik::prefixTable('log_action') .';' => false, + 'ALTER TABLE '. Piwik::prefixTable('log_visit') .' CHANGE visit_exit_idaction visit_exit_idaction_url INTEGER(11) NOT NULL;' => false, + 'ALTER TABLE '. Piwik::prefixTable('log_visit') .' CHANGE visit_entry_idaction visit_entry_idaction_url INTEGER(11) NOT NULL;' => false, + 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' CHANGE `idaction_ref` `idaction_url_ref` INTEGER(10) UNSIGNED NOT NULL;' => false, + 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' CHANGE `idaction` `idaction_url` INTEGER(10) UNSIGNED NOT NULL;' => false, + 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' ADD COLUMN `idaction_name` INTEGER(10) UNSIGNED AFTER `idaction_url_ref`;' => false, + 'ALTER TABLE ' . Piwik::prefixTable('log_conversion'). ' CHANGE `idaction` `idaction_url` INTEGER(11) UNSIGNED NOT NULL;' => false, + )); + } +} diff --git a/core/Version.php b/core/Version.php index 824001fe86..26e24c4dfc 100644 --- a/core/Version.php +++ b/core/Version.php @@ -17,5 +17,5 @@ */ final class Piwik_Version { - const VERSION = '0.4.5'; + const VERSION = '0.5'; } diff --git a/lang/en.php b/lang/en.php index ba611f0103..c393c91e43 100644 --- a/lang/en.php +++ b/lang/en.php @@ -200,6 +200,7 @@ $translations = array( 'CoreUpdater_ExceptionArchiveIncomplete' => 'Archive is incomplete: some files are missing (eg. %s).', 'Actions_Actions' => 'Actions', 'Actions_SubmenuPages' => 'Pages', + 'Actions_SubmenuPageTitles' => 'Page titles', 'Actions_SubmenuOutlinks' => 'Outlinks', 'Actions_SubmenuDownloads' => 'Downloads', 'Actions_ColumnClicks' => 'Clicks', @@ -207,6 +208,7 @@ $translations = array( 'Actions_ColumnDownloads' => 'Downloads', 'Actions_ColumnUniqueDownloads' => 'Unique Downloads', 'Actions_ColumnPageName' => 'Page Name', + 'Actions_ColumnPageURL' => 'Page URL', 'Actions_ColumnClickedURL' => 'Clicked URL', 'Actions_ColumnDownloadURL' => 'Download URL', 'Dashboard_Dashboard' => 'Dashboard', diff --git a/misc/generateVisits.php b/misc/generateVisits.php index a2d2689129..35dec2be44 100644 --- a/misc/generateVisits.php +++ b/misc/generateVisits.php @@ -9,12 +9,29 @@ if(file_exists('../bootstrap.php')) require_once '../bootstrap.php'; } +if(empty($_GET['choice']) || $_GET['choice'] != 'yes') { + echo "<div style='color:red;font-size:large'>WARNING!</div> <br>You are about to generate fake visits which will be recorded in your Piwik database. + <br>It will <b>not</b> be possible to easily delete these visits from the piwik logs. + <br><br>Are you sure you want to generate fake visits? + <br><br> + <a href='../index.php'><b>NO</b>, I do not want to generate fake visits</a> + <br><br> + <a href='?choice=yes'><b>YES</b>, I want to generate fake visits</a> + <br><br> + Note: you can edit the source code of this file to specify how many visits to generate, how many days, etc. + "; + return; +} + + // TODO - generator should generate pages with slash, then test that period archiving doesn't show the unique page view // TODO - should generate goals with keyword or referer that are not found for this day, to simulate a referer 5 days ago and conversion today -$minVisitors = 200; -$maxVisitors = 200; +$minVisitors = 20; +$maxVisitors = 100; $nbActions = 10; -$daysToCompute = 5; +$daysToCompute = 1; +$idSite = 1; + //----------------------------------------------------------------------------- error_reporting(E_ALL|E_NOTICE); @@ -42,8 +59,7 @@ require_once PIWIK_INCLUDE_PATH . "/index.php"; require_once "FrontController.php"; Piwik::setMaxExecutionTime(0); - -$idSite = Piwik_Common::getRequestVar('idSite', 1, 'int'); +$idSite = Piwik_Common::getRequestVar('idSite', $idSite, 'int'); try { Piwik_FrontController::getInstance()->init(); diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 8a9851cf30..658d5da13f 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -50,10 +50,25 @@ class Piwik_Actions_API $dataTable->queueFilter('ReplaceSummaryRowLabel'); return $dataTable; } - + + /** + * Backward compatibility. Fallsback to getPageTitles() instead. + * @deprecated Deprecated since Piwik 0.5 + */ public function getActions( $idSite, $period, $date, $expanded = false, $idSubtable = false ) { - return $this->getDataTable('Actions_actions', $idSite, $period, $date, $expanded, $idSubtable ); + return $this->getPageTitles( $idSite, $period, $date, $expanded, $idSubtable ); + } + + public function getPageUrls( $idSite, $period, $date, $expanded = false, $idSubtable = false ) + { + return $this->getDataTable('Actions_actions_url', $idSite, $period, $date, $expanded, $idSubtable ); + } + + public function getPageTitles( $idSite, $period, $date, $expanded = false, $idSubtable = false) + { + $dataTable = $this->getDataTable('Actions_actions', $idSite, $period, $date, $expanded, $idSubtable); + return $dataTable; } public function getDownloads( $idSite, $period, $date, $expanded = false, $idSubtable = false ) diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 1fc9d2c19e..199726675a 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -20,6 +20,9 @@ class Piwik_Actions extends Piwik_Plugin { static protected $actionCategoryDelimiter = null; + static protected $defaultActionName = null; + static protected $defaultActionNameWhenNotDefined = null; + static protected $defaultActionUrlWhenNotDefined = null; static protected $limitLevelSubCategory = 10; // must be less than Piwik_DataTable::MAXIMUM_DEPTH_LEVEL_ALLOWED protected $maximumRowsInDataTableLevelZero; protected $maximumRowsInSubDataTable; @@ -47,9 +50,13 @@ class Piwik_Actions extends Piwik_Plugin ); return $hooks; } + public function __construct() { self::$actionCategoryDelimiter = Zend_Registry::get('config')->General->action_category_delimiter; + self::$defaultActionName = Zend_Registry::get('config')->General->action_default_name; + self::$defaultActionNameWhenNotDefined = Zend_Registry::get('config')->General->action_default_name_when_not_defined; + self::$defaultActionUrlWhenNotDefined = Zend_Registry::get('config')->General->action_default_url_when_not_defined; $this->columnToSortByBeforeTruncation = 'nb_visits'; $this->maximumRowsInDataTableLevelZero = Zend_Registry::get('config')->General->datatable_archiving_maximum_rows_actions; $this->maximumRowsInSubDataTable = Zend_Registry::get('config')->General->datatable_archiving_maximum_rows_subtable_actions; @@ -57,16 +64,18 @@ class Piwik_Actions extends Piwik_Plugin function addWidgets() { - Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuPages', 'Actions', 'getActions'); + Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuPages', 'Actions', 'getPageUrls'); + Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuPageTitles', 'Actions', 'getPageTitles'); Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuOutlinks', 'Actions', 'getOutlinks'); Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuDownloads', 'Actions', 'getDownloads'); } function addMenus() { - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPages', array('module' => 'Actions', 'action' => 'getActions')); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPages', array('module' => 'Actions', 'action' => 'getPageUrls')); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPageTitles', array('module' => 'Actions', 'action' => 'getPageTitles')); Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuOutlinks', array('module' => 'Actions', 'action' => 'getOutlinks')); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuDownloads', array('module' => 'Actions', 'action' => 'getDownloads')); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuDownloads', array('module' => 'Actions', 'action' => 'getDownloads')); } static protected $invalidSummedColumnNameToRenamedNameForPeriodArchive = array( @@ -88,6 +97,7 @@ class Piwik_Actions extends Piwik_Plugin 'Actions_actions', 'Actions_downloads', 'Actions_outlink', + 'Actions_actions_url', ); $archiveProcessing->archiveDataTable($dataTableToSum, self::$invalidSummedColumnNameToRenamedNameForPeriodArchive, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); } @@ -106,9 +116,10 @@ class Piwik_Actions extends Piwik_Plugin $archiveProcessing = $notification->getNotificationObject(); $this->actionsTablesByType = array( - Piwik_Tracker_Action::TYPE_ACTION => array(), + Piwik_Tracker_Action::TYPE_ACTION_URL => array(), Piwik_Tracker_Action::TYPE_DOWNLOAD => array(), Piwik_Tracker_Action::TYPE_OUTLINK => array(), + Piwik_Tracker_Action::TYPE_ACTION_NAME => array(), ); // This row is used in the case where an action is know as an exit_action @@ -122,7 +133,7 @@ class Piwik_Actions extends Piwik_Plugin ))); /* - * Actions global information + * Actions urls global information */ $query = "SELECT name, type, @@ -131,7 +142,25 @@ class Piwik_Actions extends Piwik_Plugin count(*) as nb_hits FROM (".$archiveProcessing->logTable." as t1 LEFT JOIN ".$archiveProcessing->logVisitActionTable." as t2 USING (idvisit)) - LEFT JOIN ".$archiveProcessing->logActionTable." as t3 USING (idaction) + LEFT JOIN ".$archiveProcessing->logActionTable." as t3 ON (t2.idaction_url = t3.idaction) + WHERE visit_server_date = ? + AND idsite = ? + GROUP BY t3.idaction + ORDER BY nb_hits DESC"; + $query = $archiveProcessing->db->query($query, array( $archiveProcessing->strDateStart, $archiveProcessing->idsite )); + $modified = $this->updateActionsTableWithRowQuery($query); + + /* + * Actions names global information + */ + $query = "SELECT name, + type, + count(distinct t1.idvisit) as nb_visits, + count(distinct visitor_idcookie) as nb_uniq_visitors, + count(*) as nb_hits + FROM (".$archiveProcessing->logTable." as t1 + LEFT JOIN ".$archiveProcessing->logVisitActionTable." as t2 USING (idvisit)) + LEFT JOIN ".$archiveProcessing->logActionTable." as t3 ON (t2.idaction_name = t3.idaction) WHERE visit_server_date = ? AND idsite = ? GROUP BY t3.idaction @@ -139,7 +168,6 @@ class Piwik_Actions extends Piwik_Plugin $query = $archiveProcessing->db->query($query, array( $archiveProcessing->strDateStart, $archiveProcessing->idsite )); $modified = $this->updateActionsTableWithRowQuery($query); - /* * Entry actions */ @@ -151,10 +179,10 @@ class Piwik_Actions extends Piwik_Plugin sum(visit_total_time) as entry_sum_visit_length, sum(case visit_total_actions when 1 then 1 else 0 end) as entry_bounce_count FROM ".$archiveProcessing->logTable." - JOIN ".$archiveProcessing->logActionTable." ON (visit_entry_idaction = idaction) + JOIN ".$archiveProcessing->logActionTable." ON (visit_entry_idaction_url = idaction) WHERE visit_server_date = ? AND idsite = ? - GROUP BY visit_entry_idaction + GROUP BY visit_entry_idaction_url "; $query = $archiveProcessing->db->query($query, array( $archiveProcessing->strDateStart, $archiveProcessing->idsite )); $modified = $this->updateActionsTableWithRowQuery($query); @@ -169,10 +197,10 @@ class Piwik_Actions extends Piwik_Plugin count(*) as exit_nb_visits, sum(case visit_total_actions when 1 then 1 else 0 end) as exit_bounce_count FROM ".$archiveProcessing->logTable." - JOIN ".$archiveProcessing->logActionTable." ON (visit_exit_idaction = idaction) + JOIN ".$archiveProcessing->logActionTable." ON (visit_exit_idaction_url = idaction) WHERE visit_server_date = ? AND idsite = ? - GROUP BY visit_exit_idaction + GROUP BY visit_exit_idaction_url "; $query = $archiveProcessing->db->query($query, array( $archiveProcessing->strDateStart, $archiveProcessing->idsite )); $modified = $this->updateActionsTableWithRowQuery($query); @@ -185,10 +213,10 @@ class Piwik_Actions extends Piwik_Plugin sum(time_spent_ref_action) as sum_time_spent FROM (".$archiveProcessing->logTable." log_visit JOIN ".$archiveProcessing->logVisitActionTable." log_link_visit_action USING (idvisit)) - JOIN ".$archiveProcessing->logActionTable." log_action ON (log_action.idaction = log_link_visit_action.idaction_ref) + JOIN ".$archiveProcessing->logActionTable." log_action ON (log_action.idaction = log_link_visit_action.idaction_url_ref) WHERE visit_server_date = ? AND idsite = ? - GROUP BY idaction_ref + GROUP BY idaction_url_ref "; $query = $archiveProcessing->db->query($query, array( $archiveProcessing->strDateStart, $archiveProcessing->idsite )); $modified = $this->updateActionsTableWithRowQuery($query); @@ -197,10 +225,10 @@ class Piwik_Actions extends Piwik_Plugin protected function archiveDayRecordInDatabase($archiveProcessing) { - $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION]); + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_URL]); $this->deleteInvalidSummedColumnsFromDataTable($dataTable); $s = $dataTable->getSerialized( $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation ); - $archiveProcessing->insertBlobRecord('Actions_actions', $s); + $archiveProcessing->insertBlobRecord('Actions_actions_url', $s); destroy($dataTable); $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_DOWNLOAD]); @@ -215,6 +243,12 @@ class Piwik_Actions extends Piwik_Plugin $archiveProcessing->insertBlobRecord('Actions_outlink', $s); destroy($dataTable); + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_NAME]); + $this->deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized( $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation ); + $archiveProcessing->insertBlobRecord('Actions_actions', $s); + destroy($dataTable); + unset($this->actionsTablesByType); } @@ -232,30 +266,84 @@ class Piwik_Actions extends Piwik_Plugin } } } + + /** + * Explodes action name into an array of elements. + * + * for downloads: + * we explode link http://piwik.org/some/path/piwik.zip into an array( 'piwik.org', '/some/path/piwik.zip' ); + * + * for outlinks: + * we explode link http://dev.piwik.org/some/path into an array( 'dev.piwik.org', '/some/path' ); + * + * for action urls: + * we explode link http://piwik.org/some/path into an array( 'some', 'path' ); + * + * for action names: + * we explode name 'Piwik / Category 1 / Category 2' into an array('Piwik', 'Category 1', 'Category 2'); + * + * @param string action name + * @param int action type + * @return array of exploded elements from $name + */ static public function getActionExplodedNames($name, $type) { + $matches = array(); + $isUrl = false; + + preg_match('@^http[s]?://([^/]+)[/]?([^#]*)[#]?(.*)$@i', $name, $matches); + + if( count($matches) ) + { + $isUrl = true; + $urlHost = $matches[1]; + $urlPath = $matches[2]; + $urlAnchor = $matches[3]; + } + if($type == Piwik_Tracker_Action::TYPE_DOWNLOAD || $type == Piwik_Tracker_Action::TYPE_OUTLINK) { - $matches = $split_arr = array(); - //TODO optimize with substring count rather than preg_match - if(preg_match("#://[^/]+(/)#", $name, $matches, PREG_OFFSET_CAPTURE)) + if( $isUrl ) + { + return array($urlHost, '/' . $urlPath); + } + } + + if( $isUrl ) + { + $name = $urlPath; + + if( empty($name) || substr($name, -1) == '/' ) { - $host = substr($name, 0, $matches[1][1]); - return array($host, substr($name, strlen($host))); + $name .= self::$defaultActionName; } - return array($name, "/"); } + if(empty(self::$actionCategoryDelimiter)) { - return array($name); + return array( trim($name) ); + } + + $split = explode(self::$actionCategoryDelimiter, $name, self::$limitLevelSubCategory); + + // trim every category and remove empty categories + $split = array_map('trim', $split); + $split = array_filter($split, 'strlen'); + + if( empty($split) ) + { + if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) { + $defaultName = self::$defaultActionNameWhenNotDefined; + } else { + $defaultName = self::$defaultActionUrlWhenNotDefined; + } + return array( $defaultName ); } - return explode( self::$actionCategoryDelimiter, - $name, - self::$limitLevelSubCategory); + + return array_values( $split ); } - protected function updateActionsTableWithRowQuery($query) { $rowsProcessed = 0; @@ -265,7 +353,7 @@ class Piwik_Actions extends Piwik_Plugin // we work on the root table of the given TYPE (either ACTION or DOWNLOAD or OUTLINK etc.) $currentTable =& $this->actionsTablesByType[$row['type']]; - + // go to the level of the subcategory $end = count($actionExplodedNames)-1; for($level = 0 ; $level < $end; $level++) @@ -274,15 +362,19 @@ class Piwik_Actions extends Piwik_Plugin $currentTable =& $currentTable[$actionCategory]; } $actionName = $actionExplodedNames[$end]; - - // we are careful to prefix the pageName with some value + + // we are careful to prefix the page URL / name with some value // so that if a page has the same name as a category // we don't merge both entries - if($row['type'] == Piwik_Tracker_Action::TYPE_ACTION) + if($row['type'] == Piwik_Tracker_Action::TYPE_ACTION_URL ) { $actionName = '/' . $actionName; } - + else if( $row['type'] == Piwik_Tracker_Action::TYPE_ACTION_NAME ) + { + $actionName = ' ' . $actionName; + } + // currentTable is now the array element corresponding the the action // at this point we may be for example at the 4th level of depth in the hierarchy $currentTable =& $currentTable[$actionName]; @@ -290,10 +382,19 @@ class Piwik_Actions extends Piwik_Plugin // add the row to the matching sub category subtable if(!($currentTable instanceof Piwik_DataTable_Row)) { - $currentTable = new Piwik_DataTable_Row(array( + if( $row['type'] == Piwik_Tracker_Action::TYPE_ACTION_NAME ) + { + $currentTable = new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => array('label' => (string)$actionName), + )); + } + else + { + $currentTable = new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label' => (string)$actionName), Piwik_DataTable_Row::METADATA => array('url' => (string)$row['name']), )); + } } foreach($row as $name => $value) @@ -325,7 +426,7 @@ class Piwik_Actions extends Piwik_Plugin if($currentTable->getColumn('nb_hits') === false) { // to test this code: delete the entries in log_link_action_visit for - // a given exit_idaction + // a given exit_idaction_url foreach($this->defaultRow->getColumns() as $name => $value) { $currentTable->addColumn($name, $value); diff --git a/plugins/Actions/Controller.php b/plugins/Actions/Controller.php index 6943e3d531..64974199b4 100644 --- a/plugins/Actions/Controller.php +++ b/plugins/Actions/Controller.php @@ -17,41 +17,52 @@ */ class Piwik_Actions_Controller extends Piwik_Controller { - public function index() + public function getPageUrls($fetch = false) { - $view = Piwik_View::factory('index'); - - /* Actions, Downloads, Outlinks */ - $view->dataTableActions = $this->getActions( true ); - $view->dataTableDownloads = $this->getDownloads( true ); - $view->dataTableOutlinks = $this->getOutlinks( true ); - - echo $view->render(); + $view = Piwik_ViewDataTable::factory(); + $view->init( $this->pluginName, + __FUNCTION__, + 'Actions.getPageUrls', + 'getPageUrlsSubDataTable' ); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageURL')); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); } - public function getActions($fetch = false) + public function getPageUrlsSubDataTable($fetch = false) { $view = Piwik_ViewDataTable::factory(); $view->init( $this->pluginName, __FUNCTION__, - 'Actions.getActions', - 'getActionsSubDataTable' ); + 'Actions.getPageUrls', + 'getActionsSubDataTable' ); $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + public function getPageTitles($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init( $this->pluginName, + __FUNCTION__, + 'Actions.getPageTitles', + 'getPageTitlesSubDataTable' ); $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageName')); + $this->configureViewActions($view); return $this->renderView($view, $fetch); } - - public function getActionsSubDataTable($fetch = false) + + public function getPageTitlesSubDataTable($fetch = false) { $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, + $view->init( $this->pluginName, __FUNCTION__, - 'Actions.getActions', - 'getActionsSubDataTable' ); + 'Actions.getPageTitles', + 'getPageTitlesSubDataTable' ); $this->configureViewActions($view); return $this->renderView($view, $fetch); } - + public function getDownloads($fetch = false) { $view = Piwik_ViewDataTable::factory(); @@ -76,7 +87,7 @@ class Piwik_Actions_Controller extends Piwik_Controller $view->disableSearchBox(); return $this->renderView($view, $fetch); } - + public function getOutlinks($fetch = false) { $view = Piwik_ViewDataTable::factory(); @@ -101,7 +112,7 @@ class Piwik_Actions_Controller extends Piwik_Controller $view->disableSearchBox(); return $this->renderView($view, $fetch); } - + protected function configureViewActions($view) { $view->setTemplate('CoreHome/templates/datatable_actions.tpl'); @@ -123,7 +134,6 @@ class Piwik_Actions_Controller extends Piwik_Controller $view->setLimit( 100 ); $view->setColumnsToDisplay( array('label','nb_hits','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageName')); $view->setColumnTranslation('nb_hits', Piwik_Translate('General_ColumnPageviews')); $view->setColumnTranslation('nb_visits', Piwik_Translate('General_ColumnUniquePageviews')); @@ -171,7 +181,7 @@ class Piwik_Actions_Controller extends Piwik_Controller $view->disableExcludeLowPopulation(); $view->setLimit( 15 ); } - + protected function getArrayFromRecursiveDataTable( $dataTable, $depth = 0 ) { $table = array(); diff --git a/plugins/Actions/tests/Actions.test.php b/plugins/Actions/tests/Actions.test.php new file mode 100644 index 0000000000..08f11e81ef --- /dev/null +++ b/plugins/Actions/tests/Actions.test.php @@ -0,0 +1,92 @@ +<?php +if(!defined("PIWIK_PATH_TEST_TO_ROOT")) { + define('PIWIK_PATH_TEST_TO_ROOT', getcwd().'/../../..'); +} +if(!defined('PIWIK_CONFIG_TEST_INCLUDED')) +{ + require_once PIWIK_PATH_TEST_TO_ROOT . "/tests/config_test.php"; +} + +require_once 'Actions/Actions.php'; +require_once 'Tracker/Action.php'; +require_once 'Tracker/Config.php'; + +class Test_Piwik_Actions extends UnitTestCase +{ + function test_getActionExplodedNames() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/plugins/Actions/Actions.config.ini.php'; + + Piwik::createConfigObject($userFile); + + $action = new Test_Piwik_Actions_getActionExplodedNames(); + + $tests = array( + array( + 'params' => array( 'name' => 'http://example.org/', 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + 'expected' => array('index' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/path/', 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + 'expected' => array( 'path', 'index' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/test/path', 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + 'expected' => array( 'test', 'path' ), + ), + array( + 'params' => array( 'name' => 'Test / Path', 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + 'expected' => array( 'Test', 'Path' ), + ), + array( + 'params' => array( 'name' => ' Test trim ', 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + 'expected' => array( 'Test trim' ), + ), + array( + 'params' => array( 'name' => 'Category / Subcategory', 'type' => Piwik_Tracker_Action::TYPE_ACTION_NAME), + 'expected' => array( 'Category', 'Subcategory' ), + ), + array( + 'params' => array( 'name' => '/path/index.php?var=test', 'type' => Piwik_Tracker_Action::TYPE_ACTION_NAME), + 'expected' => array( 'path', 'index.php?var=test' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/path/Default.aspx#anchor', 'type' => Piwik_Tracker_Action::TYPE_ACTION_NAME), + 'expected' => array( 'path', 'Default.aspx' ), + ), + array( + 'params' => array( 'name' => '', 'type' => Piwik_Tracker_Action::TYPE_ACTION_NAME), + 'expected' => array( 'index' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/download.zip', 'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD), + 'expected' => array( 'example.org', '/download.zip' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/download/1/', 'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD), + 'expected' => array( 'example.org', '/download/1/' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/link', 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), + 'expected' => array( 'example.org', '/link' ), + ), + array( + 'params' => array( 'name' => 'http://example.org/some/path/', 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), + 'expected' => array( 'example.org', '/some/path/' ), + ), + + ); + foreach($tests as $test) { + $params = $test['params']; + $expected = $test['expected']; + $this->assertEqual($action->public_getActionExplodedNames($params['name'],$params['type']), $expected); + } + } +} + +class Test_Piwik_Actions_getActionExplodedNames extends Piwik_Actions { + public function public_getActionExplodedNames($name, $type) + { + return self::getActionExplodedNames($name, $type); + } +} \ No newline at end of file diff --git a/tests/core/Tracker/Action.test.php b/tests/core/Tracker/Action.test.php index 5f6b510f9d..2cc1fba216 100644 --- a/tests/core/Tracker/Action.test.php +++ b/tests/core/Tracker/Action.test.php @@ -23,29 +23,53 @@ class Test_Piwik_TrackerAction extends UnitTestCase // outlinks array( 'request' => array( 'link' => 'http://example.org'), - 'expected' => array( 'name' => 'http://example.org', + 'expected' => array( 'name' => null, + 'url' => 'http://example.org', + 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), + ), + // outlinks with custom name + array( + 'request' => array( 'link' => 'http://example.org', 'action_name' => 'Example.org'), + 'expected' => array( 'name' => 'Example.org', 'url' => 'http://example.org', 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), ), // keep the case in urls, but trim array( 'request' => array( 'link' => ' http://example.org/Category/Test/ '), - 'expected' => array( 'name' => 'http://example.org/Category/Test/', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/Category/Test/', 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), ), - + + // trim the custom name + array( + 'request' => array( 'link' => ' http://example.org/Category/Test/ ', 'action_name' => ' Example dot org '), + 'expected' => array( 'name' => 'Example dot org', + 'url' => 'http://example.org/Category/Test/', + 'type' => Piwik_Tracker_Action::TYPE_OUTLINK), + ), + // downloads array( 'request' => array( 'download' => 'http://example.org/*$test.zip'), - 'expected' => array( 'name' => 'http://example.org/*$test.zip', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/*$test.zip', 'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD), ), + + // downloads with custom name + array( + 'request' => array( 'download' => 'http://example.org/*$test.zip', 'action_name' => 'Download test.zip'), + 'expected' => array( 'name' => 'Download test.zip', + 'url' => 'http://example.org/*$test.zip', + 'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD), + ), + // keep the case and multiple / in urls array( 'request' => array( 'download' => 'http://example.org/CATEGORY/test///test.pdf'), - 'expected' => array( 'name' => 'http://example.org/CATEGORY/test///test.pdf', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/CATEGORY/test///test.pdf', 'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD), ), @@ -53,43 +77,62 @@ class Test_Piwik_TrackerAction extends UnitTestCase // page view array( 'request' => array( 'url' => 'http://example.org/'), - 'expected' => array( 'name' => 'index', + 'expected' => array( 'name' => null, + 'url' => 'http://example.org/', + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + ), + array( + 'request' => array( 'url' => 'http://example.org/', 'action_name' => 'Example.org Website'), + 'expected' => array( 'name' => 'Example.org Website', 'url' => 'http://example.org/', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), array( 'request' => array( 'url' => 'http://example.org/CATEGORY/'), - 'expected' => array( 'name' => 'CATEGORY/index', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/CATEGORY/', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + ), + array( + 'request' => array( 'url' => 'http://example.org/CATEGORY/TEST', 'action_name' => 'Example.org / Category / test /'), + 'expected' => array( 'name' => 'Example.org/Category/test', + 'url' => 'http://example.org/CATEGORY/TEST', + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), + ), + + // empty request + array( + 'request' => array(), + 'expected' => array( 'name' => null, 'url' => '/', + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), array( 'request' => array( 'url' => 'http://example.org/category/', 'action_name' => 'custom name with/one delimiter/two delimiters/'), 'expected' => array( 'name' => 'custom name with/one delimiter/two delimiters', 'url' => 'http://example.org/category/', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), array( 'request' => array( 'url' => 'http://example.org/category/', 'action_name' => 'http://custom action name look like url/'), 'expected' => array( 'name' => 'http:/custom action name look like url', 'url' => 'http://example.org/category/', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), // testing: delete tab, trimmed, not strtolowered array( 'request' => array( 'url' => "http://example.org/category/test///test wOw "), - 'expected' => array( 'name' => 'category/test/test wOw', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/category/test///test wOw', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), // testing: inclusion of zero values in action name array( 'request' => array( 'url' => "http://example.org/category/1/0/t/test"), - 'expected' => array( 'name' => 'category/1/0/t/test', + 'expected' => array( 'name' => null, 'url' => 'http://example.org/category/1/0/t/test', - 'type' => Piwik_Tracker_Action::TYPE_ACTION), + 'type' => Piwik_Tracker_Action::TYPE_ACTION_URL), ), ); foreach($tests as $test) { diff --git a/tests/resources/Tracker/Action.config.ini.php b/tests/resources/Tracker/Action.config.ini.php index a5f8cbd30a..2f3bf5d2c2 100644 --- a/tests/resources/Tracker/Action.config.ini.php +++ b/tests/resources/Tracker/Action.config.ini.php @@ -1,2 +1,4 @@ [Tracker] action_category_delimiter = / +default_action_name = index +default_action_url = / \ No newline at end of file diff --git a/tests/resources/plugins/Actions/Actions.config.ini.php b/tests/resources/plugins/Actions/Actions.config.ini.php new file mode 100644 index 0000000000..f1e96d26d6 --- /dev/null +++ b/tests/resources/plugins/Actions/Actions.config.ini.php @@ -0,0 +1,5 @@ +[General] +action_category_delimiter = / + +[Tracker] +default_action_name = index -- GitLab