diff options
author | mattpiwik <matthieu.aubry@gmail.com> | 2012-09-26 12:40:42 +0400 |
---|---|---|
committer | mattpiwik <matthieu.aubry@gmail.com> | 2012-09-26 12:40:42 +0400 |
commit | c1bd36b2aa1b80e6af95e4e39dbb1d0ceccd59b2 (patch) | |
tree | 3e3733030aa52f08123a97c9f307d60995732af0 | |
parent | 443934a802cc5dc94b278430b29b2e73dac67a5a (diff) |
Refs #3330 Refactoring of the Actions.php and moving code to: Archiving and ArchivingHelper
git-svn-id: http://dev.piwik.org/svn/trunk@7065 59fd770c-687e-43c8-a1e3-f5a4ff64c105
-rw-r--r-- | core/DataTable.php | 25 | ||||
-rw-r--r-- | plugins/Actions/API.php | 3 | ||||
-rw-r--r-- | plugins/Actions/Actions.php | 831 | ||||
-rw-r--r-- | plugins/Actions/Archiving.php | 425 | ||||
-rw-r--r-- | plugins/Actions/ArchivingHelper.php | 533 | ||||
-rw-r--r-- | plugins/CoreHome/templates/header.tpl | 2 | ||||
-rw-r--r-- | plugins/Transitions/API.php | 3 | ||||
-rwxr-xr-x | tests/PHPUnit/Integration/BlobReportLimitingTest.php | 2 | ||||
-rw-r--r-- | tests/PHPUnit/Plugins/ActionsTest.php | 12 |
9 files changed, 980 insertions, 856 deletions
diff --git a/core/DataTable.php b/core/DataTable.php index a2da7075ec..b697fad2c9 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -140,13 +140,12 @@ class Piwik_DataTable { /** Name for metadata that describes when a report was archived. */ const ARCHIVED_DATE_METADATA_NAME = 'archived_date'; - + const MAX_DEPTH_DEFAULT = 15; + /** - * Maximum nesting level - * - * @var int + * Maximum nesting level. */ - static private $maximumDepthLevelAllowed = 15; + static private $maximumDepthLevelAllowed = self::MAX_DEPTH_DEFAULT; /** * Array of Piwik_DataTable_Row @@ -1006,7 +1005,7 @@ class Piwik_DataTable if($depth > self::$maximumDepthLevelAllowed) { $depth = 0; - throw new Exception("Maximum recursion level of ".self::$maximumDepthLevelAllowed. " reached. You have probably set a DataTable_Row with an associated DataTable which belongs already to its parent hierarchy."); + throw new Exception("Maximum recursion level of ".self::$maximumDepthLevelAllowed. " reached. Maybe you have set a DataTable_Row with an associated DataTable belonging already to one of its parent tables?"); } if( !is_null($maximumRowsInDataTable) ) { @@ -1303,17 +1302,6 @@ class Piwik_DataTable } /** - * Gets the maximum nesting level for datatables. - * - * @return int - */ - static public function getMaximumDepthLevelAllowed() - { - return self::$maximumDepthLevelAllowed; - } - - - /** * Sets the maximum nesting level to at least a certain value. If the current value is * greater than the supplied level, the maximum nesting level is not changed. * @@ -1322,6 +1310,9 @@ class Piwik_DataTable static public function setMaximumDepthLevelAllowedAtLeast( $atLeastLevel ) { self::$maximumDepthLevelAllowed = max($atLeastLevel, self::$maximumDepthLevelAllowed); + if(self::$maximumDepthLevelAllowed < 1) { + self::$maximumDepthLevelAllowed = 1; + } } /** diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 35b06710a2..3587c3f70c 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -246,7 +246,8 @@ class Piwik_Actions_API $searchedString = $search; } } - $searchTree = Piwik_Actions::getActionExplodedNames($searchedString, $actionType); + Piwik_Actions_ArchivingHelper::reloadConfig(); + $searchTree = Piwik_Actions_ArchivingHelper::getActionExplodedNames($searchedString, $actionType); } if ($table === false) diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 9192858ec7..cae4d15401 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -19,17 +19,6 @@ */ class Piwik_Actions extends Piwik_Plugin { - static protected $actionUrlCategoryDelimiter = null; - static protected $actionTitleCategoryDelimiter = null; - static protected $defaultActionName = null; - static protected $defaultActionNameWhenNotDefined = null; - static protected $defaultActionUrlWhenNotDefined = null; - protected $maximumRowsInDataTableLevelZero; - protected $maximumRowsInSubDataTable; - protected $columnToSortByBeforeTruncation; - - const OTHERS_ROW_KEY = ''; - public function getInformation() { $info = array( @@ -129,7 +118,7 @@ class Piwik_Actions extends Piwik_Plugin * @throws Exception * @return array|int|string */ - function getIdActionFromSegment($string, $sqlField, $matchType='==') + public function getIdActionFromSegment($string, $sqlField, $matchType='==') { // Field is visit_*_idaction_url or visit_*_idaction_name $actionType = strpos($sqlField, '_name') === false @@ -427,47 +416,6 @@ class Piwik_Actions extends Piwik_Plugin Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuOutlinks', array('module' => 'Actions', 'action' => 'indexOutlinks'), true, 5); Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuDownloads', array('module' => 'Actions', 'action' => 'indexDownloads'), true, 6); } - - static protected $invalidSummedColumnNameToRenamedNameForPeriodArchive = array( - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS, - ); - - protected static $invalidSummedColumnNameToDeleteFromDayArchive = array( - Piwik_Archive::INDEX_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, - Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, - ); - - public function __construct() - { - $this->reloadConfig(); - } - - public function reloadConfig() - { - // for BC, we read the old style delimiter first (see #1067) - $actionDelimiter = @Piwik_Config::getInstance()->General['action_category_delimiter']; - if(empty($actionDelimiter)) - { - self::$actionUrlCategoryDelimiter = Piwik_Config::getInstance()->General['action_url_category_delimiter']; - self::$actionTitleCategoryDelimiter = Piwik_Config::getInstance()->General['action_title_category_delimiter']; - } - else - { - self::$actionUrlCategoryDelimiter = self::$actionTitleCategoryDelimiter = $actionDelimiter; - } - - self::$defaultActionName = Piwik_Config::getInstance()->General['action_default_name']; - $this->columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; - $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_actions']; - $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_actions']; - $rankingQueryLimit = Piwik_Config::getInstance()->General['archiving_ranking_query_row_limit']; - - // Piwik_DataTable::MAXIMUM_DEPTH_LEVEL_ALLOWED must be greater than the category level limit - Piwik_DataTable::setMaximumDepthLevelAllowedAtLeast(self::getSubCategoryLevelLimit() + 1); - } /** * @param Piwik_Event_Notification $notification notification object @@ -478,23 +426,9 @@ class Piwik_Actions extends Piwik_Plugin $archiveProcessing = $notification->getNotificationObject(); if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $dataTableToSum = array( - 'Actions_actions', - 'Actions_downloads', - 'Actions_outlink', - 'Actions_actions_url', - ); - $archiveProcessing->archiveDataTable($dataTableToSum, self::$invalidSummedColumnNameToRenamedNameForPeriodArchive, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation); - - $archiveProcessing->archiveNumericValuesSum(array( - 'Actions_nb_pageviews', - 'Actions_nb_uniq_pageviews', - 'Actions_nb_downloads', - 'Actions_nb_uniq_downloads', - 'Actions_nb_outlinks', - 'Actions_nb_uniq_outlinks' - )); + + $actionsArchiving = new Piwik_Actions_Archiving; + return $actionsArchiving->archivePeriod($archiveProcessing); } /** @@ -511,762 +445,9 @@ class Piwik_Actions extends Piwik_Plugin $archiveProcessing = $notification->getNotificationObject(); if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $this->actionsTablesByType = 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 - // but this action was not properly recorded when it was hit in the first place - // so we add this fake row information to make sure there is a nb_hits, etc. column for every action - $this->defaultRow = new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => array( - Piwik_Archive::INDEX_NB_VISITS => 1, - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 1, - Piwik_Archive::INDEX_PAGE_NB_HITS => 1, - ))); - - $rankingQueryLimit = self::getRankingQueryLimit(); - $rankingQuery = false; - - /* - * Page URLs and Page names, general stats - */ - - $select = "log_action.name, - log_action.type, - log_action.idaction, - log_action.url_prefix, - count(distinct log_link_visit_action.idvisit) as `". Piwik_Archive::INDEX_NB_VISITS ."`, - count(distinct log_link_visit_action.idvisitor) as `". Piwik_Archive::INDEX_NB_UNIQ_VISITORS ."`, - count(*) as `". Piwik_Archive::INDEX_PAGE_NB_HITS ."`"; - - $from = array( - "log_link_visit_action", - array( - "table" => "log_action", - "joinOn" => "log_link_visit_action.%s = log_action.idaction" - ) - ); - - $where = "log_link_visit_action.server_time >= ? - AND log_link_visit_action.server_time <= ? - AND log_link_visit_action.idsite = ? - AND log_link_visit_action.%s IS NOT NULL"; - - $groupBy = "log_action.idaction"; - $orderBy = "`". Piwik_Archive::INDEX_PAGE_NB_HITS ."` DESC, name ASC"; - - if ($rankingQueryLimit > 0) - { - $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); - $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); - $rankingQuery->addLabelColumn(array('idaction', 'name')); - $rankingQuery->addColumn(array('url_prefix', Piwik_Archive::INDEX_NB_UNIQ_VISITORS)); - $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_NB_HITS, Piwik_Archive::INDEX_NB_VISITS), 'sum'); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - } - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_url", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_name", $archiveProcessing, $rankingQuery); - - /* - * Entry actions for Page URLs and Page names - */ - if ($rankingQueryLimit > 0) - { - $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); - $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); - $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS); - $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS, - Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS, - Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, - Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT), 'sum'); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = 'log_action.type, log_action.name,'; - $from = array( - "log_visit", - array( - "table" => "log_action", - "joinOn" => "log_visit.%s = log_action.idaction" - ) - ); - $orderBy = "`".Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS."` DESC, log_action.name ASC"; - } - else - { - $extraSelects = false; - $from = "log_visit"; - $orderBy = false; - } - - $select = "log_visit.%s as idaction, $extraSelects - count(distinct log_visit.idvisitor) as `". Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS ."`, - count(*) as `". Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS ."`, - sum(log_visit.visit_total_actions) as `". Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS ."`, - sum(log_visit.visit_total_time) as `". Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH ."`, - sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `". Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT ."`"; - - $where = "log_visit.visit_last_action_time >= ? - AND log_visit.visit_last_action_time <= ? - AND log_visit.idsite = ? - AND log_visit.%s > 0"; - - $groupBy = "log_visit.%s, idaction"; - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_entry_idaction_url", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_entry_idaction_name", $archiveProcessing, $rankingQuery); - - /* - * Exit actions - */ - if ($rankingQueryLimit > 0) - { - $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); - $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); - $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS, 'sum'); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = 'log_action.type, log_action.name,'; - $from = array( - "log_visit", - array( - "table" => "log_action", - "joinOn" => "log_visit.%s = log_action.idaction" - ) - ); - $orderBy = "`".Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS."` DESC, log_action.name ASC"; - } - else - { - $extraSelects = false; - $from = "log_visit"; - $orderBy = false; - } - - $select = "log_visit.%s as idaction, $extraSelects - count(distinct log_visit.idvisitor) as `". Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS ."`, - count(*) as `". Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS ."`"; - - $where = "log_visit.visit_last_action_time >= ? - AND log_visit.visit_last_action_time <= ? - AND log_visit.idsite = ? - AND log_visit.%s > 0"; - - $groupBy = "log_visit.%s, idaction"; - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_url", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_name", $archiveProcessing, $rankingQuery); - - /* - * Time per action - */ - if ($rankingQueryLimit > 0) - { - $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); - $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); - $rankingQuery->addLabelColumn('idaction'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT, 'sum'); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = "log_action.type, log_action.name, count(*) as `".Piwik_Archive::INDEX_PAGE_NB_HITS."`,"; - $from = array( - "log_link_visit_action", - array( - "table" => "log_action", - "joinOn" => "log_link_visit_action.%s = log_action.idaction" - ) - ); - $orderBy = "`".Piwik_Archive::INDEX_PAGE_NB_HITS."` DESC, log_action.name ASC"; - } - else - { - $extraSelects = false; - $from = "log_link_visit_action"; - $orderBy = false; - } - - $select = "log_link_visit_action.%s as idaction, $extraSelects - sum(log_link_visit_action.time_spent_ref_action) as `".Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT."`"; - - $where = "log_link_visit_action.server_time >= ? - AND log_link_visit_action.server_time <= ? - AND log_link_visit_action.idsite = ? - AND log_link_visit_action.time_spent_ref_action > 0 - AND log_link_visit_action.%s > 0"; - - $groupBy = "log_link_visit_action.%s, idaction"; - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_url_ref", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_name_ref", $archiveProcessing, $rankingQuery); - - // Empty static cache - self::$cacheParsedAction = array(); - - // Record the final datasets - $this->archiveDayRecordInDatabase($archiveProcessing); - } - - /** - * @param $select - * @param $from - * @param $where - * @param $orderBy - * @param $groupBy - * @param $sprintfField - * @param Piwik_ArchiveProcessing $archiveProcessing - * @param Piwik_RankingQuery|false $rankingQuery - * @return int - */ - protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - $sprintfField, $archiveProcessing, $rankingQuery = false) - { - // idaction field needs to be set in select clause before calling getSelectQuery(). - // if a complex segmentation join is needed, the field needs to be propagated - // to the outer select. therefore, $segment needs to know about it. - $select = sprintf($select, $sprintfField); - - // get query with segmentation - $bind = array(); - $query = $archiveProcessing->getSegment()->getSelectQuery( - $select, $from, $where, $bind, $orderBy, $groupBy); - - // replace the rest of the %s - $querySql = str_replace("%s", $sprintfField, $query['sql']); - - // apply ranking query - if ($rankingQuery) - { - $querySql = $rankingQuery->generateQuery($querySql); - } - - // extend bindings - $bind = array_merge(array($archiveProcessing->getStartDatetimeUTC(), - $archiveProcessing->getEndDatetimeUTC(), $archiveProcessing->idsite), $query['bind']); - - // get result - $resultSet = $archiveProcessing->db->query($querySql, $bind); - $modified = $this->updateActionsTableWithRowQuery($resultSet, $sprintfField); - return $modified; - } - - protected function archiveDayRecordInDatabase($archiveProcessing) - { - $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_url', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - destroy($dataTable); - $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_DOWNLOAD]); - $this->deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized($this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation ); - $archiveProcessing->insertBlobRecord('Actions_downloads', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - destroy($dataTable); - - $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_OUTLINK]); - $this->deleteInvalidSummedColumnsFromDataTable($dataTable); - $s = $dataTable->getSerialized( $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable, $this->columnToSortByBeforeTruncation ); - $archiveProcessing->insertBlobRecord('Actions_outlink', $s); - $archiveProcessing->insertNumericRecord('Actions_nb_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); - $archiveProcessing->insertNumericRecord('Actions_nb_uniq_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); - 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); - - destroy($this->actionsTablesByType); - } - - protected function deleteInvalidSummedColumnsFromDataTable($dataTable) - { - foreach($dataTable->getRows() as $id => $row) - { - if(($idSubtable = $row->getIdSubDataTable()) !== null || $id === Piwik_DataTable::ID_SUMMARY_ROW) - { - foreach(self::$invalidSummedColumnNameToDeleteFromDayArchive as $name) - { - $row->deleteColumn($name); - } - - if ($idSubtable !== null) - { - $subtable = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable); - $this->deleteInvalidSummedColumnsFromDataTable($subtable); - } - } - } - } - - /** - * 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 - * @param int url prefix (only used for TYPE_ACTION_URL) - * @return array of exploded elements from $name - */ - static public function getActionExplodedNames($name, $type, $urlPrefix=null) - { - $matches = array(); - $isUrl = false; - $name = str_replace("\n", "", $name); - - $urlRegexAfterDomain = '([^/]+)[/]?([^#]*)[#]?(.*)'; - if ($urlPrefix === null) - { - // match url with protocol (used for outlinks / downloads) - $urlRegex = '@^http[s]?://'.$urlRegexAfterDomain.'$@i'; - } - else - { - // the name is a url that does not contain protocol and www anymore - // we know that normalization has been done on db level because $urlPrefix is set - $urlRegex = '@^'.$urlRegexAfterDomain.'$@i'; - } - - preg_match($urlRegex, $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) - { - if( $isUrl ) - { - return array(trim($urlHost), '/' . trim($urlPath)); - } - } - - if( $isUrl ) - { - $name = $urlPath; - - if( $name === '' || substr($name, -1) == '/' ) - { - $name .= self::$defaultActionName; - } - } - - if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) - { - $categoryDelimiter = self::$actionTitleCategoryDelimiter; - } - else - { - $categoryDelimiter = self::$actionUrlCategoryDelimiter; - } - - if(empty($categoryDelimiter)) - { - return array( trim($name) ); - } - - $split = explode($categoryDelimiter, $name, self::getSubCategoryLevelLimit()); - - // trim every category and remove empty categories - $split = array_map('trim', $split); - $split = array_filter($split, 'strlen'); - - // forces array key to start at 0 - $split = array_values($split); - - if( empty($split) ) - { - $defaultName = self::getUnknownActionName($type); - return array( trim($defaultName) ); - } - - $lastPageName = end($split); - // 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($type != Piwik_Tracker_Action::TYPE_ACTION_NAME ) - { - $lastPageName = '/' . $lastPageName; - } - else - { - $lastPageName = ' ' . $lastPageName; - } - $split[count($split)-1] = $lastPageName; - return array_values( $split ); - } - - static protected function getUnknownActionName($type) - { - if(empty(self::$defaultActionNameWhenNotDefined)) - { - self::$defaultActionNameWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageName')); - self::$defaultActionUrlWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')); - } - if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) { - return self::$defaultActionNameWhenNotDefined; - } - return self::$defaultActionUrlWhenNotDefined; - } - - const CACHE_PARSED_INDEX_NAME = 0; - const CACHE_PARSED_INDEX_TYPE = 1; - static $cacheParsedAction = array(); - - /** - * Get cached action row by id & type. If $idAction is set to -1, the 'Others' row - * for the specific action type will be returned. - * - * @param int $idAction - * @param int $actionType - * @return Piwik_DataTable_Row|false - */ - private static function getCachedActionRow( $idAction, $actionType ) - { - $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); - - if (!isset(self::$cacheParsedAction[$cacheLabel])) - { - // This can happen when - // - We select an entry page ID that was only seen yesterday, so wasn't selected in the first query - // - We count time spent on a page, when this page was only seen yesterday - return false; - } - - return self::$cacheParsedAction[$cacheLabel]; - } - - /** - * Set cached action row for an id & type. - * - * @param int $idAction - * @param int $actionType - * @param Piwik_DataTable_Row - */ - private static function setCachedActionRow( $idAction, $actionType, $actionRow ) - { - $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); - self::$cacheParsedAction[$cacheLabel] = $actionRow; - } - - /** - * Gets the key for the cache of action rows from an action ID and type. - * - * @param int $idAction - * @param int $actionType - * @return string|int - */ - private static function getCachedActionRowKey( $idAction, $actionType ) - { - return $idAction == Piwik_DataTable::LABEL_SUMMARY_ROW ? $actionType.'_others' : $idAction; - } - - /** - * @param Zend_Db_Statement|PDOStatement $query - * @param string|bool $fieldQueried - * @return int - */ - protected function updateActionsTableWithRowQuery($query, $fieldQueried = false) - { - $rowsProcessed = 0; - while( $row = $query->fetch() ) - { - if(empty($row['idaction'])) - { - $row['type'] = ($fieldQueried == 'idaction_url' ? Piwik_Tracker_Action::TYPE_ACTION_URL : Piwik_Tracker_Action::TYPE_ACTION_NAME); - // This will be replaced with 'X not defined' later - $row['name'] = ''; - // Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc. - $row['idaction'] = -$row['type']; - } - // Only the first query will contain the name and type of actions, for performance reasons - if(isset($row['name']) - && isset($row['type'])) - { - $actionName = $row['name']; - $actionType = $row['type']; - $urlPrefix = $row['url_prefix']; - $idaction = $row['idaction']; - - // in some unknown case, the type field is NULL, as reported in #1082 - we ignore this page view - if(empty($actionType)) - { - if ($idaction != Piwik_DataTable::LABEL_SUMMARY_ROW) - { - self::setCachedActionRow($idaction, $actionType, false); - } - - continue; - } - - $currentTable = $this->parseActionNameCategoriesInDataTable($actionName, $actionType, $urlPrefix); - - self::setCachedActionRow($idaction, $actionType, $currentTable); - } - else - { - $currentTable = self::getCachedActionRow($row['idaction'], $row['type']); - - // Action processed as "to skip" for some reasons - if($currentTable === false) - { - continue; - } - } - - unset($row['name']); - unset($row['type']); - unset($row['idaction']); - unset($row['url_prefix']); - - if (is_null($currentTable)) - { - continue; - } - - foreach($row as $name => $value) - { - // in some edge cases, we have twice the same action name with 2 different idaction - // this happens when 2 visitors visit the same new page at the same time, there is a SELECT and an INSERT for each new page, - // and in between the two the other visitor comes. - // here we handle the case where there is already a row for this action name, if this is the case we add the value - if(($alreadyValue = $currentTable->getColumn($name)) !== false) - { - $currentTable->setColumn($name, $alreadyValue+$value); - } - else - { - $currentTable->addColumn($name, $value); - } - } - - // if the exit_action was not recorded properly in the log_link_visit_action - // there would be an error message when getting the nb_hits column - // we must fake the record and add the columns - if($currentTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS) === false) - { - // to test this code: delete the entries in log_link_action_visit for - // a given exit_idaction_url - foreach($this->defaultRow->getColumns() as $name => $value) - { - $currentTable->addColumn($name, $value); - } - } - $rowsProcessed++; - } - - // just to make sure php copies the last $currentTable in the $parentTable array - $currentTable =& $this->actionsTablesByType; - return $rowsProcessed; - } - - /** - * Given a page name and type, builds a recursive datatable where - * each level of the tree is a category, based on the page name split by a delimiter (slash / by default) - * - * @param string $actionName - * @param int $actionType - * @param int $urlPrefix - * @return Piwik_DataTable - */ - protected function parseActionNameCategoriesInDataTable($actionName, $actionType, $urlPrefix=null) - { - // we work on the root table of the given TYPE (either ACTION_URL or DOWNLOAD or OUTLINK etc.) - $currentTable =& $this->actionsTablesByType[$actionType]; - - // check for ranking query cut-off - if ($actionName == Piwik_DataTable::LABEL_SUMMARY_ROW) - { - return $this->createOthersRow($currentTable, $actionType); - } - - // go to the level of the subcategory - $actionExplodedNames = $this->getActionExplodedNames($actionName, $actionType, $urlPrefix); - $end = count($actionExplodedNames)-1; - $maxRows = $this->maximumRowsInDataTableLevelZero; - for($level = 0 ; $level < $end; $level++) - { - $actionCategory = $actionExplodedNames[$level]; - - $othersRow = $this->getOthersRowIfTableFull($currentTable, $actionCategory, $actionType, $maxRows); - if ($othersRow) - { - return $othersRow; - } - - $currentTable =& $currentTable[$actionCategory]; - $maxRows = $this->maximumRowsInSubDataTable; - } - $actionShortName = $actionExplodedNames[$end]; - - $othersRow = $this->getOthersRowIfTableFull($currentTable, $actionShortName, $actionType, $maxRows); - if ($othersRow) - { - return $othersRow; - } - - // 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[$actionShortName]; - - // add the row to the matching sub category subtable - if(!($currentTable instanceof Piwik_DataTable_Row)) - { - $currentTable = $this->createActionsTableRow( - (string)$actionShortName, $actionType, $actionName, $urlPrefix); - } - return $currentTable; - } - - /** - * Checks if the given table is full (has the maximum number of rows allowed in config) - * and if so, returns an 'Others' summary row. Returns false if the table is not full. - * - * @param array $currentTable Array of Piwik_DataTable_Rows. - * @param string $actionCategory The current table key. - * @param int $actionType The action type. - * @param int $maxRows The maximum number of rows allowed in $currentTable. - * @return Piwik_DataTable_Row|false - */ - private function getOthersRowIfTableFull( &$currentTable, $actionCategory, $actionType, $maxRows ) - { - if (!isset($currentTable[$actionCategory])) - { - if (count($currentTable) == $maxRows - 1) - { - return $this->createOthersRow($currentTable, $actionType); - } - else if (count($currentTable) >= $maxRows) - { - return $currentTable[self::OTHERS_ROW_KEY]; // return existing 'others' row - } - } - - return false; - } - - /** - * Create an 'Others' action row. The row created by this function is used - * as the summary row in a truncated DataTable. - * - * @param array $currentTable The array of rows to add the 'Others' row to. - * @param int $actionType The type of actions the row will hold stats for. - * @return Piwik_DataTable_Row - */ - private function createOthersRow( &$currentTable, $actionType ) - { - // create other row and return it - $othersRow = $this->createActionsTableRow(Piwik_DataTable::LABEL_SUMMARY_ROW, $actionType); - $othersRow->setMetadata('issummaryrow', true); - - $currentTable[self::OTHERS_ROW_KEY] = $othersRow; - return $othersRow; - } - - /** - * Creates a new empty row for DataTables created by this plugin. - * - * @param string $label The row label. - * @param int $actionType The action type of the action the row will describe. - * @param string $actionName - * @param string $urlPrefix - * @return Piwik_DataTable_Row - */ - private function createActionsTableRow( $label, $actionType, $actionName = null, $urlPrefix = null ) - { - $defaultColumnsNewRow = array( - 'label' => $label, - Piwik_Archive::INDEX_NB_VISITS => 0, - Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 0, - Piwik_Archive::INDEX_PAGE_NB_HITS => 0, - Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT => 0, - ); - if( $actionType == Piwik_Tracker_Action::TYPE_ACTION_NAME ) - { - return new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => $defaultColumnsNewRow, - )); - } - else - { - $metadata = array(); - if (!is_null($actionName)) - { - $url = Piwik_Tracker_Action::reconstructNormalizedUrl((string)$actionName, $urlPrefix); - $metadata['url'] = $url; - } - - return new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => $defaultColumnsNewRow, - Piwik_DataTable_Row::METADATA => $metadata, - )); - } - } - - /** - * Returns the configured sub-category level limit. - * - * @return int - */ - public static function getSubCategoryLevelLimit() - { - return Piwik_Config::getInstance()->General['action_category_level_limit']; - } - - /** - * Returns the limit to use with RankingQuery for this plugin. - * - * @return int - */ - public static function getRankingQueryLimit() - { - $configGeneral = Piwik_Config::getInstance()->General; - $configLimit = $configGeneral['archiving_ranking_query_row_limit']; - return $configLimit == 0 ? 0 : max( - $configLimit, - $configGeneral['datatable_archiving_maximum_rows_actions'], - $configGeneral['datatable_archiving_maximum_rows_subtable_actions'] - ); + $actionsArchiving = new Piwik_Actions_Archiving; + return $actionsArchiving->archiveDay($archiveProcessing); } } diff --git a/plugins/Actions/Archiving.php b/plugins/Actions/Archiving.php new file mode 100644 index 0000000000..728685b15f --- /dev/null +++ b/plugins/Actions/Archiving.php @@ -0,0 +1,425 @@ +<?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: Actions.php 6986 2012-09-15 03:42:26Z capedfuzz $ + * + * @category Piwik_Plugins + * @package Piwik_Actions + */ + +/** + * Class encapsulating logic to process Day/Period Archiving for the Actions reports + * + * @package Piwik_Actions + */ +class Piwik_Actions_Archiving +{ + protected $actionsTablesByType = 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(), + ); + + + static protected $invalidSummedColumnNameToRenamedNameFromPeriodArchive = array( + Piwik_Archive::INDEX_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, + Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS, + Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Piwik_Archive::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS, + ); + + static protected $invalidSummedColumnNameToDeleteFromDayArchive = array( + Piwik_Archive::INDEX_NB_UNIQ_VISITORS, + Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS, + Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS, + ); + + /** + * Archives Actions reports for a Period + * @param Piwik_ArchiveProcessing $archiveProcessing + * @return bool + */ + public function archivePeriod(Piwik_ArchiveProcessing $archiveProcessing) + { + Piwik_Actions_ArchivingHelper::reloadConfig(); + $dataTableToSum = array( + 'Actions_actions', + 'Actions_downloads', + 'Actions_outlink', + 'Actions_actions_url', + ); + $archiveProcessing->archiveDataTable($dataTableToSum, + self::$invalidSummedColumnNameToRenamedNameFromPeriodArchive, + Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, + Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, + Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation); + + $archiveProcessing->archiveNumericValuesSum(array( + 'Actions_nb_pageviews', + 'Actions_nb_uniq_pageviews', + 'Actions_nb_downloads', + 'Actions_nb_uniq_downloads', + 'Actions_nb_outlinks', + 'Actions_nb_uniq_outlinks' + )); + + return true; + } + + /** + * Archives Actions reports for a Day + * + * @param Piwik_ArchiveProcessing $archiveProcessing + * @return bool + */ + public function archiveDay(Piwik_ArchiveProcessing $archiveProcessing) + { + $rankingQueryLimit = self::getRankingQueryLimit(); + Piwik_Actions_ArchivingHelper::reloadConfig(); + + $this->archiveDayActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayEntryActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayExitActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayActionsTime($archiveProcessing, $rankingQueryLimit); + + // Record the final datasets + $this->archiveDayRecordInDatabase($archiveProcessing); + + return true; + } + + /* + * Page URLs and Page names, general stats + */ + protected function archiveDayActions($archiveProcessing, $rankingQueryLimit) + { + $select = "log_action.name, + log_action.type, + log_action.idaction, + log_action.url_prefix, + count(distinct log_link_visit_action.idvisit) as `" . Piwik_Archive::INDEX_NB_VISITS . "`, + count(distinct log_link_visit_action.idvisitor) as `" . Piwik_Archive::INDEX_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`"; + + $from = array( + "log_link_visit_action", + array( + "table" => "log_action", + "joinOn" => "log_link_visit_action.%s = log_action.idaction" + ) + ); + + $where = "log_link_visit_action.server_time >= ? + AND log_link_visit_action.server_time <= ? + AND log_link_visit_action.idsite = ? + AND log_link_visit_action.%s IS NOT NULL"; + + $groupBy = "log_action.idaction"; + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, name ASC"; + + $rankingQuery = false; + if ($rankingQueryLimit > 0) { + $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); + $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); + $rankingQuery->addLabelColumn(array('idaction', 'name')); + $rankingQuery->addColumn(array('url_prefix', Piwik_Archive::INDEX_NB_UNIQ_VISITORS)); + $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_NB_HITS, Piwik_Archive::INDEX_NB_VISITS), 'sum'); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + } + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_url", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_name", $archiveProcessing, $rankingQuery); + } + + + /** + * Entry actions for Page URLs and Page names + */ + protected function archiveDayEntryActions($archiveProcessing, $rankingQueryLimit) + { + $rankingQuery = false; + if ($rankingQueryLimit > 0) { + $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); + $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); + $rankingQuery->addLabelColumn('idaction'); + $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS); + $rankingQuery->addColumn(array(Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS, + Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS, + Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH, + Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT), 'sum'); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = 'log_action.type, log_action.name,'; + $from = array( + "log_visit", + array( + "table" => "log_action", + "joinOn" => "log_visit.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_visit"; + $orderBy = false; + } + + $select = "log_visit.%s as idaction, $extraSelects + count(distinct log_visit.idvisitor) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_VISITS . "`, + sum(log_visit.visit_total_actions) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "`, + sum(log_visit.visit_total_time) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH . "`, + sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Piwik_Archive::INDEX_PAGE_ENTRY_BOUNCE_COUNT . "`"; + + $where = "log_visit.visit_last_action_time >= ? + AND log_visit.visit_last_action_time <= ? + AND log_visit.idsite = ? + AND log_visit.%s > 0"; + + $groupBy = "log_visit.%s, idaction"; + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_entry_idaction_url", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_entry_idaction_name", $archiveProcessing, $rankingQuery); + } + + + /** + * Time per action + */ + protected function archiveDayActionsTime($archiveProcessing, $rankingQueryLimit) + { + $rankingQuery = false; + if ($rankingQueryLimit > 0) + { + $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); + $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); + $rankingQuery->addLabelColumn('idaction'); + $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT, 'sum'); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = "log_action.type, log_action.name, count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`,"; + $from = array( + "log_link_visit_action", + array( + "table" => "log_action", + "joinOn" => "log_link_visit_action.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_link_visit_action"; + $orderBy = false; + } + + $select = "log_link_visit_action.%s as idaction, $extraSelects + sum(log_link_visit_action.time_spent_ref_action) as `" . Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT . "`"; + + $where = "log_link_visit_action.server_time >= ? + AND log_link_visit_action.server_time <= ? + AND log_link_visit_action.idsite = ? + AND log_link_visit_action.time_spent_ref_action > 0 + AND log_link_visit_action.%s > 0"; + + $groupBy = "log_link_visit_action.%s, idaction"; + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_url_ref", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_name_ref", $archiveProcessing, $rankingQuery); + } + + /** + * Exit actions + */ + public function archiveDayExitActions($archiveProcessing, $rankingQueryLimit) + { + $rankingQuery = false; + if ($rankingQueryLimit > 0) { + $rankingQuery = new Piwik_RankingQuery($rankingQueryLimit); + $rankingQuery->setOthersLabel(Piwik_DataTable::LABEL_SUMMARY_ROW); + $rankingQuery->addLabelColumn('idaction'); + $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS); + $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS, 'sum'); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = 'log_action.type, log_action.name,'; + $from = array( + "log_visit", + array( + "table" => "log_action", + "joinOn" => "log_visit.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_visit"; + $orderBy = false; + } + + $select = "log_visit.%s as idaction, $extraSelects + count(distinct log_visit.idvisitor) as `" . Piwik_Archive::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS . "`, + count(*) as `" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "`"; + + $where = "log_visit.visit_last_action_time >= ? + AND log_visit.visit_last_action_time <= ? + AND log_visit.idsite = ? + AND log_visit.%s > 0"; + + $groupBy = "log_visit.%s, idaction"; + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_exit_idaction_url", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_exit_idaction_name", $archiveProcessing, $rankingQuery); + return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy); + } + + + /** + * Records in the DB the archived reports for Page views, Downloads, Outlinks, and Page titles + * + * @param $archiveProcessing + */ + protected function archiveDayRecordInDatabase($archiveProcessing) + { + Piwik_Actions_ArchivingHelper::clearActionsCache(); + + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_URL]); + self::deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized( Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation ); + $archiveProcessing->insertBlobRecord('Actions_actions_url', $s); + $archiveProcessing->insertNumericRecord('Actions_nb_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); + $archiveProcessing->insertNumericRecord('Actions_nb_uniq_pageviews', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); + destroy($dataTable); + + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_DOWNLOAD]); + self::deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized(Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation ); + $archiveProcessing->insertBlobRecord('Actions_downloads', $s); + $archiveProcessing->insertNumericRecord('Actions_nb_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); + $archiveProcessing->insertNumericRecord('Actions_nb_uniq_downloads', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); + destroy($dataTable); + + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_OUTLINK]); + self::deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized( Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation ); + $archiveProcessing->insertBlobRecord('Actions_outlink', $s); + $archiveProcessing->insertNumericRecord('Actions_nb_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS))); + $archiveProcessing->insertNumericRecord('Actions_nb_uniq_outlinks', array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS))); + destroy($dataTable); + + $dataTable = Piwik_ArchiveProcessing_Day::generateDataTable($this->actionsTablesByType[Piwik_Tracker_Action::TYPE_ACTION_NAME]); + self::deleteInvalidSummedColumnsFromDataTable($dataTable); + $s = $dataTable->getSerialized( Piwik_Actions_ArchivingHelper::$maximumRowsInDataTableLevelZero, Piwik_Actions_ArchivingHelper::$maximumRowsInSubDataTable, Piwik_Actions_ArchivingHelper::$columnToSortByBeforeTruncation ); + $archiveProcessing->insertBlobRecord('Actions_actions', $s); + destroy($dataTable); + + destroy($this->actionsTablesByType); + } + + /** + * Returns the limit to use with RankingQuery for this plugin. + * + * @return int + */ + private static function getRankingQueryLimit() + { + $configGeneral = Piwik_Config::getInstance()->General; + $configLimit = $configGeneral['archiving_ranking_query_row_limit']; + return $configLimit == 0 ? 0 : max( + $configLimit, + $configGeneral['datatable_archiving_maximum_rows_actions'], + $configGeneral['datatable_archiving_maximum_rows_subtable_actions'] + ); + } + + + /** + * @param $select + * @param $from + * @param $where + * @param $orderBy + * @param $groupBy + * @param $sprintfField + * @param Piwik_ArchiveProcessing $archiveProcessing + * @param Piwik_RankingQuery|false $rankingQuery + * @return int + */ + protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + $sprintfField, $archiveProcessing, $rankingQuery = false) + { + // idaction field needs to be set in select clause before calling getSelectQuery(). + // if a complex segmentation join is needed, the field needs to be propagated + // to the outer select. therefore, $segment needs to know about it. + $select = sprintf($select, $sprintfField); + + // get query with segmentation + $bind = array(); + $query = $archiveProcessing->getSegment()->getSelectQuery( + $select, $from, $where, $bind, $orderBy, $groupBy); + + // replace the rest of the %s + $querySql = str_replace("%s", $sprintfField, $query['sql']); + + // apply ranking query + if ($rankingQuery) + { + $querySql = $rankingQuery->generateQuery($querySql); + } + + // extend bindings + $bind = array_merge(array( $archiveProcessing->getStartDatetimeUTC(), + $archiveProcessing->getEndDatetimeUTC(), + $archiveProcessing->idsite + ), + $query['bind'] + ); + + // get result + $resultSet = $archiveProcessing->db->query($querySql, $bind); + $modified = Piwik_Actions_ArchivingHelper::updateActionsTableWithRowQuery($resultSet, $sprintfField, $this->actionsTablesByType); + return $modified; + } + + + /** + * For rows which have subtables (eg. directories with sub pages), + * deletes columns which don't make sense when all values of sub pages are summed. + * + * @param $dataTable Piwik_DataTable + */ + static public function deleteInvalidSummedColumnsFromDataTable($dataTable) + { + foreach($dataTable->getRows() as $id => $row) + { + if(($idSubtable = $row->getIdSubDataTable()) !== null + || $id === Piwik_DataTable::ID_SUMMARY_ROW) + { + foreach(self::$invalidSummedColumnNameToDeleteFromDayArchive as $name) + { + $row->deleteColumn($name); + } + + if ($idSubtable !== null) + { + $subtable = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable); + self::deleteInvalidSummedColumnsFromDataTable($subtable); + } + } + } + } + +} diff --git a/plugins/Actions/ArchivingHelper.php b/plugins/Actions/ArchivingHelper.php new file mode 100644 index 0000000000..8aa2baeb58 --- /dev/null +++ b/plugins/Actions/ArchivingHelper.php @@ -0,0 +1,533 @@ +<?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: Actions.php 6986 2012-09-15 03:42:26Z capedfuzz $ + * + * @category Piwik_Plugins + * @package Piwik_Actions + */ + +/** + * This static class provides: + * - logic to parse/cleanup Action names, + * - logic to efficiently process aggregate the array data during Archiving + * + * @package Piwik_Actions + */ + +class Piwik_Actions_ArchivingHelper +{ + const OTHERS_ROW_KEY = ''; + + /** + * @param Zend_Db_Statement|PDOStatement $query + * @param string|bool $fieldQueried + * @param array $actionsTablesByType + * @return int + */ + static public function updateActionsTableWithRowQuery($query, $fieldQueried, & $actionsTablesByType) + { + $rowsProcessed = 0; + while( $row = $query->fetch() ) + { + if(empty($row['idaction'])) + { + $row['type'] = ($fieldQueried == 'idaction_url' ? Piwik_Tracker_Action::TYPE_ACTION_URL : Piwik_Tracker_Action::TYPE_ACTION_NAME); + // This will be replaced with 'X not defined' later + $row['name'] = ''; + // Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc. + $row['idaction'] = -$row['type']; + } + // Only the first query will contain the name and type of actions, for performance reasons + if(isset($row['name']) + && isset($row['type'])) + { + $actionName = $row['name']; + $actionType = $row['type']; + $urlPrefix = $row['url_prefix']; + $idaction = $row['idaction']; + + // in some unknown case, the type field is NULL, as reported in #1082 - we ignore this page view + if(empty($actionType)) + { + if ($idaction != Piwik_DataTable::LABEL_SUMMARY_ROW) + { + self::setCachedActionRow($idaction, $actionType, false); + } + + continue; + } + + $currentTable = self::parseActionNameCategoriesInDataTable($actionName, $actionType, $urlPrefix, $actionsTablesByType); + + self::setCachedActionRow($idaction, $actionType, $currentTable); + } + else + { + $currentTable = self::getCachedActionRow($row['idaction'], $row['type']); + + // Action processed as "to skip" for some reasons + if($currentTable === false) + { + continue; + } + } + + unset($row['name']); + unset($row['type']); + unset($row['idaction']); + unset($row['url_prefix']); + + if (is_null($currentTable)) + { + continue; + } + + foreach($row as $name => $value) + { + // in some edge cases, we have twice the same action name with 2 different idaction + // this happens when 2 visitors visit the same new page at the same time, there is a SELECT and an INSERT for each new page, + // and in between the two the other visitor comes. + // here we handle the case where there is already a row for this action name, if this is the case we add the value + if(($alreadyValue = $currentTable->getColumn($name)) !== false) + { + $currentTable->setColumn($name, $alreadyValue+$value); + } + else + { + $currentTable->addColumn($name, $value); + } + } + + // if the exit_action was not recorded properly in the log_link_visit_action + // there would be an error message when getting the nb_hits column + // we must fake the record and add the columns + if($currentTable->getColumn(Piwik_Archive::INDEX_PAGE_NB_HITS) === false) + { + // to test this code: delete the entries in log_link_action_visit for + // a given exit_idaction_url + foreach(self::getDefaultRow()->getColumns() as $name => $value) + { + $currentTable->addColumn($name, $value); + } + } + $rowsProcessed++; + } + + // just to make sure php copies the last $currentTable in the $parentTable array + $currentTable =& $actionsTablesByType; + return $rowsProcessed; + } + + static public $maximumRowsInDataTableLevelZero; + static public $maximumRowsInSubDataTable; + static public $columnToSortByBeforeTruncation; + + static protected $actionUrlCategoryDelimiter = null; + static protected $actionTitleCategoryDelimiter = null; + static protected $defaultActionName = null; + static protected $defaultActionNameWhenNotDefined = null; + static protected $defaultActionUrlWhenNotDefined = null; + + static public function reloadConfig() + { + // for BC, we read the old style delimiter first (see #1067) + $actionDelimiter = @Piwik_Config::getInstance()->General['action_category_delimiter']; + if(empty($actionDelimiter)) + { + self::$actionUrlCategoryDelimiter = Piwik_Config::getInstance()->General['action_url_category_delimiter']; + self::$actionTitleCategoryDelimiter = Piwik_Config::getInstance()->General['action_title_category_delimiter']; + } + else + { + self::$actionUrlCategoryDelimiter = self::$actionTitleCategoryDelimiter = $actionDelimiter; + } + + self::$defaultActionName = Piwik_Config::getInstance()->General['action_default_name']; + self::$columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS; + self::$maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_actions']; + self::$maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_actions']; + + Piwik_DataTable::setMaximumDepthLevelAllowedAtLeast(self::getSubCategoryLevelLimit() + 1); + } + + + /** + * The default row is used when archiving, if data is inconsistent in the DB, + * there could be pages that have exit/entry hits, but don't yet + * have a record in the table (or the record was truncated). + * + * @return Piwik_DataTable_Row + */ + static private function getDefaultRow() + { + static $row = false; + if($row === false) { + // This row is used in the case where an action is know as an exit_action + // but this action was not properly recorded when it was hit in the first place + // so we add this fake row information to make sure there is a nb_hits, etc. column for every action + $row = new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => array( + Piwik_Archive::INDEX_NB_VISITS => 1, + Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 1, + Piwik_Archive::INDEX_PAGE_NB_HITS => 1, + ))); + } + return $row; + } + + /** + * Given a page name and type, builds a recursive datatable where + * each level of the tree is a category, based on the page name split by a delimiter (slash / by default) + * + * @param string $actionName + * @param int $actionType + * @param int $urlPrefix + * @param array $actionsTablesByType + * @return Piwik_DataTable + */ + protected function parseActionNameCategoriesInDataTable($actionName, $actionType, $urlPrefix=null, &$actionsTablesByType) + { + // we work on the root table of the given TYPE (either ACTION_URL or DOWNLOAD or OUTLINK etc.) + $currentTable =& $actionsTablesByType[$actionType]; + + // check for ranking query cut-off + if ($actionName == Piwik_DataTable::LABEL_SUMMARY_ROW) + { + return self::createOthersRow($currentTable, $actionType); + } + + // go to the level of the subcategory + $actionExplodedNames = self::getActionExplodedNames($actionName, $actionType, $urlPrefix); + $end = count($actionExplodedNames)-1; + $maxRows = self::$maximumRowsInDataTableLevelZero; + for($level = 0 ; $level < $end; $level++) + { + $actionCategory = $actionExplodedNames[$level]; + + $othersRow = self::getOthersRowIfTableFull($currentTable, $actionCategory, $actionType, $maxRows); + if ($othersRow) + { + return $othersRow; + } + + $currentTable =& $currentTable[$actionCategory]; + $maxRows = self::$maximumRowsInSubDataTable; + } + $actionShortName = $actionExplodedNames[$end]; + + $othersRow = self::getOthersRowIfTableFull($currentTable, $actionShortName, $actionType, $maxRows); + if ($othersRow) + { + return $othersRow; + } + + // 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[$actionShortName]; + + // add the row to the matching sub category subtable + if(!($currentTable instanceof Piwik_DataTable_Row)) + { + $currentTable = self::createActionsTableRow( + (string)$actionShortName, $actionType, $actionName, $urlPrefix); + } + return $currentTable; + } + + /** + * Explodes action name into an array of elements. + * + * NOTE: before calling this function make sure Piwik_Actions_ArchivingHelper::reloadConfig(); is called + * + * 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 + * @param int url prefix (only used for TYPE_ACTION_URL) + * @return array of exploded elements from $name + */ + static public function getActionExplodedNames($name, $type, $urlPrefix=null) + { + $matches = array(); + $isUrl = false; + $name = str_replace("\n", "", $name); + + $urlRegexAfterDomain = '([^/]+)[/]?([^#]*)[#]?(.*)'; + if ($urlPrefix === null) + { + // match url with protocol (used for outlinks / downloads) + $urlRegex = '@^http[s]?://'.$urlRegexAfterDomain.'$@i'; + } + else + { + // the name is a url that does not contain protocol and www anymore + // we know that normalization has been done on db level because $urlPrefix is set + $urlRegex = '@^'.$urlRegexAfterDomain.'$@i'; + } + + preg_match($urlRegex, $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) + { + if( $isUrl ) + { + return array(trim($urlHost), '/' . trim($urlPath)); + } + } + + if( $isUrl ) + { + $name = $urlPath; + + if( $name === '' || substr($name, -1) == '/' ) + { + $name .= self::$defaultActionName; + } + } + + if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) + { + $categoryDelimiter = self::$actionTitleCategoryDelimiter; + } + else + { + $categoryDelimiter = self::$actionUrlCategoryDelimiter; + } + + if(empty($categoryDelimiter)) + { + return array( trim($name) ); + } + + $split = explode($categoryDelimiter, $name, self::getSubCategoryLevelLimit()); + + // trim every category and remove empty categories + $split = array_map('trim', $split); + $split = array_filter($split, 'strlen'); + + // forces array key to start at 0 + $split = array_values($split); + + if( empty($split) ) + { + $defaultName = self::getUnknownActionName($type); + return array( trim($defaultName) ); + } + + $lastPageName = end($split); + // 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($type != Piwik_Tracker_Action::TYPE_ACTION_NAME ) + { + $lastPageName = '/' . $lastPageName; + } + else + { + $lastPageName = ' ' . $lastPageName; + } + $split[count($split)-1] = $lastPageName; + return array_values( $split ); + } + + /** + * Gets the key for the cache of action rows from an action ID and type. + * + * @param int $idAction + * @param int $actionType + * @return string|int + */ + private static function getCachedActionRowKey( $idAction, $actionType ) + { + return $idAction == Piwik_DataTable::LABEL_SUMMARY_ROW + ? $actionType.'_others' + : $idAction; + } + + /** + * Returns the configured sub-category level limit. + * + * @return int + */ + public static function getSubCategoryLevelLimit() + { + return Piwik_Config::getInstance()->General['action_category_level_limit']; + } + + /** + * Returns default label for the action type + * + * @param $type + * @return string + */ + static protected function getUnknownActionName($type) + { + if(empty(self::$defaultActionNameWhenNotDefined)) + { + self::$defaultActionNameWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageName')); + self::$defaultActionUrlWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')); + } + if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) + { + return self::$defaultActionNameWhenNotDefined; + } + return self::$defaultActionUrlWhenNotDefined; + } + + + + /** + * Checks if the given table is full (has the maximum number of rows allowed in config) + * and if so, returns an 'Others' summary row. Returns false if the table is not full. + * + * @param array $currentTable Array of Piwik_DataTable_Rows. + * @param string $actionCategory The current table key. + * @param int $actionType The action type. + * @param int $maxRows The maximum number of rows allowed in $currentTable. + * @return Piwik_DataTable_Row|false + */ + static private function getOthersRowIfTableFull( &$currentTable, $actionCategory, $actionType, $maxRows ) + { + if (!isset($currentTable[$actionCategory])) + { + if (count($currentTable) == $maxRows - 1) + { + return self::createOthersRow($currentTable, $actionType); + } + else if (count($currentTable) >= $maxRows) + { + return $currentTable[self::OTHERS_ROW_KEY]; // return existing 'others' row + } + } + + return false; + } + + /** + * Create an 'Others' action row. The row created by this function is used + * as the summary row in a truncated DataTable. + * + * @param array $currentTable The array of rows to add the 'Others' row to. + * @param int $actionType The type of actions the row will hold stats for. + * @return Piwik_DataTable_Row + */ + static private function createOthersRow( &$currentTable, $actionType ) + { + // create other row and return it + $othersRow = self::createActionsTableRow(Piwik_DataTable::LABEL_SUMMARY_ROW, $actionType); + $othersRow->setMetadata('issummaryrow', true); + + $currentTable[self::OTHERS_ROW_KEY] = $othersRow; + return $othersRow; + } + + /** + * Creates a new empty datatable row for storing Actions . + * + * @param string $label The row label. + * @param int $actionType The action type of the action the row will describe. + * @param string $actionName + * @param string $urlPrefix + * @return Piwik_DataTable_Row + */ + static private function createActionsTableRow( $label, $actionType, $actionName = null, $urlPrefix = null ) + { + $defaultColumnsNewRow = array( + 'label' => $label, + Piwik_Archive::INDEX_NB_VISITS => 0, + Piwik_Archive::INDEX_NB_UNIQ_VISITORS => 0, + Piwik_Archive::INDEX_PAGE_NB_HITS => 0, + Piwik_Archive::INDEX_PAGE_SUM_TIME_SPENT => 0, + ); + if( $actionType == Piwik_Tracker_Action::TYPE_ACTION_NAME ) + { + return new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => $defaultColumnsNewRow, + )); + } + else + { + $metadata = array(); + if (!is_null($actionName)) + { + $url = Piwik_Tracker_Action::reconstructNormalizedUrl((string)$actionName, $urlPrefix); + $metadata['url'] = $url; + } + + return new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => $defaultColumnsNewRow, + Piwik_DataTable_Row::METADATA => $metadata, + )); + } + } + + + /** + * Static cache to store Rows during processing + */ + static protected $cacheParsedAction = array(); + + public static function clearActionsCache() + { + self::$cacheParsedAction = array(); + } + + /** + * Get cached action row by id & type. If $idAction is set to -1, the 'Others' row + * for the specific action type will be returned. + * + * @param int $idAction + * @param int $actionType + * @return Piwik_DataTable_Row|false + */ + private static function getCachedActionRow( $idAction, $actionType ) + { + $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); + + if (!isset(self::$cacheParsedAction[$cacheLabel])) + { + // This can happen when + // - We select an entry page ID that was only seen yesterday, so wasn't selected in the first query + // - We count time spent on a page, when this page was only seen yesterday + return false; + } + + return self::$cacheParsedAction[$cacheLabel]; + } + + /** + * Set cached action row for an id & type. + * + * @param int $idAction + * @param int $actionType + * @param Piwik_DataTable_Row + */ + private static function setCachedActionRow( $idAction, $actionType, $actionRow ) + { + $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); + self::$cacheParsedAction[$cacheLabel] = $actionRow; + } + +} diff --git a/plugins/CoreHome/templates/header.tpl b/plugins/CoreHome/templates/header.tpl index fd4fe450a5..405bcc6e1e 100644 --- a/plugins/CoreHome/templates/header.tpl +++ b/plugins/CoreHome/templates/header.tpl @@ -1,7 +1,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> -<title>{if !$isCustomLogo}Piwik › {/if} {'CoreHome_WebAnalyticsReports'|translate} - {$siteName}</title> +<title>{$siteName} - {if !$isCustomLogo}Piwik › {/if} {'CoreHome_WebAnalyticsReports'|translate}</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="Piwik - Open Source Web Analytics" /> <meta name="description" content="Web Analytics report for '{$siteName}' - Piwik" /> diff --git a/plugins/Transitions/API.php b/plugins/Transitions/API.php index 507f259d5c..8db9d3bccb 100644 --- a/plugins/Transitions/API.php +++ b/plugins/Transitions/API.php @@ -79,7 +79,8 @@ class Piwik_Transitions_API */ private function addMainPageMetricsToReport(&$report, $pageUrl, $idSite, $period, $date, $segment) { - $label = Piwik_Actions::getActionExplodedNames($pageUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); + Piwik_Actions_ArchivingHelper::reloadConfig(); + $label = Piwik_Actions_ArchivingHelper::getActionExplodedNames($pageUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); if (count($label) == 1) { $label = $label[0]; diff --git a/tests/PHPUnit/Integration/BlobReportLimitingTest.php b/tests/PHPUnit/Integration/BlobReportLimitingTest.php index 3ba5916613..ca12499737 100755 --- a/tests/PHPUnit/Integration/BlobReportLimitingTest.php +++ b/tests/PHPUnit/Integration/BlobReportLimitingTest.php @@ -73,7 +73,7 @@ class Test_Piwik_Integration_BlobReportLimitingTest extends IntegrationTestCase $generalConfig['datatable_archiving_maximum_rows_subtable_actions'] = 4; $generalConfig['datatable_archiving_maximum_rows_standard'] = 4; Piwik_Config::getInstance()->General['archiving_ranking_query_row_limit'] = 3; - Piwik_PluginsManager::getInstance()->getLoadedPlugin('Actions')->reloadConfig(); + Piwik_Actions_ArchivingHelper::reloadConfig(); foreach ($this->getApiForTesting() as $pair) { diff --git a/tests/PHPUnit/Plugins/ActionsTest.php b/tests/PHPUnit/Plugins/ActionsTest.php index 7c7ee058c0..fc3778192f 100644 --- a/tests/PHPUnit/Plugins/ActionsTest.php +++ b/tests/PHPUnit/Plugins/ActionsTest.php @@ -113,17 +113,9 @@ class ActionsTests extends PHPUnit_Framework_TestCase */ public function testGetActionExplodedNames($params, $expected) { - $action = new Test_Piwik_Actions_getActionExplodedNames(); - - $processed = $action->public_getActionExplodedNames($params['name'], $params['type'], (isset($params['urlPrefix'])?$params['urlPrefix']:null)); + Piwik_Actions_ArchivingHelper::reloadConfig(); + $processed = Piwik_Actions_ArchivingHelper::getActionExplodedNames($params['name'], $params['type'], (isset($params['urlPrefix'])?$params['urlPrefix']:null)); $this->assertEquals($expected, $processed); } } -class Test_Piwik_Actions_getActionExplodedNames extends Piwik_Actions -{ - public function public_getActionExplodedNames($name, $type, $urlPrefix) - { - return self::getActionExplodedNames($name, $type, $urlPrefix); - } -} |