diff options
Diffstat (limited to 'plugins/MultiSites/API.php')
-rwxr-xr-x | plugins/MultiSites/API.php | 930 |
1 files changed, 445 insertions, 485 deletions
diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php index 3f4176bbc0..2277291598 100755 --- a/plugins/MultiSites/API.php +++ b/plugins/MultiSites/API.php @@ -1,10 +1,10 @@ <?php /** * Piwik - Open source web analytics - * + * * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - * + * * @category Piwik_Plugins * @package Piwik_MultiSites */ @@ -14,487 +14,447 @@ */ class Piwik_MultiSites_API { - const METRIC_TRANSLATION_KEY = 'translation'; - const METRIC_EVOLUTION_COL_NAME_KEY = 'evolution_column_name'; - const METRIC_RECORD_NAME_KEY = 'record_name'; - const METRIC_IS_ECOMMERCE_KEY = 'is_ecommerce'; - - const NB_VISITS_METRIC = 'nb_visits'; - const NB_ACTIONS_METRIC = 'nb_actions'; - const NB_PAGEVIEWS_LABEL = 'nb_pageviews'; - const NB_PAGEVIEWS_METRIC = 'Actions_nb_pageviews'; - const GOAL_REVENUE_METRIC = 'revenue'; - const GOAL_CONVERSION_METRIC = 'nb_conversions'; - const ECOMMERCE_ORDERS_METRIC = 'orders'; - const ECOMMERCE_REVENUE_METRIC = 'ecommerce_revenue'; - - static private $baseMetrics = array( - self::NB_VISITS_METRIC => array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnNbVisits', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'visits_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_VISITS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ), - self::NB_ACTIONS_METRIC => array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnNbActions', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'actions_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_ACTIONS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ), - self::NB_PAGEVIEWS_LABEL => array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnPageviews', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'pageviews_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_PAGEVIEWS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ) - ); - - /** - * The singleton instance of this class. - */ - static private $instance = null; - - /** - * Returns the singleton instance of this class. The instance is created - * if it hasn't been already. - * - * @return Piwik_MultiSites_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - - return self::$instance; - } - - /** - * Returns a report displaying the total visits, actions and revenue, as - * well as the evolution of these values, of all existing sites over a - * specified period of time. - * - * If the specified period is not a 'range', this function will calculcate - * evolution metrics. Evolution metrics are metrics that display the - * percent increase/decrease of another metric since the last period. - * - * This function will merge the result of the archive query so each - * row in the result DataTable will correspond to the metrics of a single - * site. If a date range is specified, the result will be a - * DataTable_Array, but it will still be merged. - * - * @param string $period The period type to get data for. - * @param string $date The date(s) to get data for. - * @param bool|string $segment The segments to get data for. - * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username - * Only used when a scheduled task is running - * @param bool|string $enhanced When true, return additional goal & ecommerce metrics - * @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites - * @return Piwik_DataTable - */ - public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false) - { - Piwik::checkUserHasSomeViewAccess(); - - $idSites = $this->getSitesIdFromPattern($pattern); - - return $this->buildDataTable( - $idSites, - $period, - $date, - $segment, - $_restrictSitesToLogin, - $enhanced, - $multipleWebsitesRequested = true - ); - } - - /** - * Fetches the list of sites which names match the string pattern - * - * @param $pattern - * @return array|string - */ - private function getSitesIdFromPattern($pattern) - { - $idSites = 'all'; - if (!empty($pattern)) { - $sites = Piwik_API_Request::processRequest('SitesManager.getPatternMatchSites', - array('pattern' => $pattern, - // added because caller could overwrite these - 'serialize' => 0, - 'format' => 'original')); - if (!empty($sites)) { - $idSites = array(); - foreach ($sites as $site) { - $idSites[] = $site['idsite']; - } - } - } - return $idSites; - } - - /** - * Same as getAll but for a unique Piwik site - * @see Piwik_MultiSites_API::getAll() - * - * @param int $idSite Id of the Piwik site - * @param string $period The period type to get data for. - * @param string $date The date(s) to get data for. - * @param bool|string $segment The segments to get data for. - * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username - * Only used when a scheduled task is running - * @param bool|string $enhanced When true, return additional goal & ecommerce metrics - * @return Piwik_DataTable - */ - public function getOne($idSite, $period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) - { - Piwik::checkUserHasViewAccess($idSite); - return $this->buildDataTable( - $idSite, - $period, - $date, - $segment, - $_restrictSitesToLogin, - $enhanced, - $multipleWebsitesRequested = false - ); - } - - private function buildDataTable($sites, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested) - { - $allWebsitesRequested = ($sites == 'all'); - if($allWebsitesRequested) - { - if (Piwik::isUserIsSuperUser() - // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. - // This means this function would return ALL websites in all cases. - // Instead, we make sure that only the right set of data is returned - && !Piwik_TaskScheduler::isTaskBeingExecuted()) - { - Piwik_Site::setSites( - Piwik_SitesManager_API::getInstance()->getAllSites() - ); - } - else - { - Piwik_Site::setSitesFromArray( - Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin) - ); - } - } - - // build the archive type used to query archive data - $archive = Piwik_Archive::build( - $sites, - $period, - $date, - $segment, - $_restrictSitesToLogin - ); - - // determine what data will be displayed - $fieldsToGet = array(); - $columnNameRewrites = array(); - $apiECommerceMetrics = array(); - $apiMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced); - foreach($apiMetrics as $metricName => $metricSettings) - { - $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY]; - $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName; - - if($metricSettings[self::METRIC_IS_ECOMMERCE_KEY]) - { - $apiECommerceMetrics[$metricName] = $metricSettings; - } - } - - // get the data - // $dataTable instanceOf Piwik_DataTable_Array - $dataTable = $archive->getDataTableFromNumeric($fieldsToGet); - - // get rid of the DataTable_Array that is created by the IndexedBySite archive type - if($dataTable instanceof Piwik_DataTable_Array - && $multipleWebsitesRequested) - { - $dataTable = $dataTable->mergeChildren(); - } - else - { - if(!$dataTable instanceof Piwik_DataTable_Array) - { - $firstDataTableRow = $dataTable->getFirstRow(); - $firstDataTableRow->setColumn('label', $sites); - } - } - - // calculate total visits/actions/revenue - $this->setMetricsTotalsMetadata($dataTable, $apiMetrics); - - // if the period isn't a range & a lastN/previousN date isn't used, we get the same - // data for the last period to show the evolution of visits/actions/revenue - list($strLastDate, $lastPeriod) = Piwik_Period_Range::getLastDate($date, $period); - if ($strLastDate !== false) - { - if ($lastPeriod !== false) - { - // NOTE: no easy way to set last period date metadata when range of dates is requested. - // will be easier if DataTable_Array::metadata is removed, and metadata that is - // put there is put directly in Piwik_DataTable::metadata. - $dataTable->setMetadata(self::getLastPeriodMetadataName('date'), $lastPeriod); - } - - $pastArchive = Piwik_Archive::build('all', $period, $strLastDate, $segment, $_restrictSitesToLogin); - $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet); - - $pastData = $pastData->mergeChildren(); - - // use past data to calculate evolution percentages - $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); - - $this->setPastDataMetadata($dataTable, $pastData, $apiMetrics); - } - - // remove eCommerce related metrics on non eCommerce Piwik sites - // note: this is not optimal in terms of performance: those metrics should not be retrieved in the first place - if($enhanced) - { - // $dataTableRows instanceOf Piwik_DataTable_Row[] - $dataTableRows = $dataTable->getRows(); - - foreach($dataTableRows as $dataTableRow) - { - $siteId = $dataTableRow->getColumn('label'); - if(!Piwik_Site::isEcommerceEnabledFor($siteId)) - { - foreach($apiECommerceMetrics as $metricSettings) - { - $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]); - $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]); - } - } - } - } - - // move the site id to a metadata column - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite')); - - // set the label of each row to the site name - if($multipleWebsitesRequested) - { - $getNameFor = array('Piwik_Site', 'getNameFor'); - $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); - } - else - { - $dataTable->filter('ColumnDelete', array('label')); - } - - // replace record names with user friendly metric names - $dataTable->filter('ReplaceColumnNames', array($columnNameRewrites)); - - // Ensures data set sorted, for Metadata output - $dataTable->filter('Sort', array(self::NB_VISITS_METRIC, 'desc', $naturalSort = false)); - - // filter rows without visits - // note: if only one website is queried and there are no visits, we can not remove the row otherwise Piwik_API_ResponseBuilder throws 'Call to a member function getColumns() on a non-object' - if($multipleWebsitesRequested) - { - $dataTable->filter( - 'ColumnCallbackDeleteRow', - array( - self::NB_VISITS_METRIC, - create_function ( '$value', 'return $value != 0;') - ) - ); - } - - return $dataTable; - } - - /** - * Performs a binary filter of two - * DataTables in order to correctly calculate evolution metrics. - * - * @param Piwik_DataTable|Piwik_DataTable_Array $currentData - * @param Piwik_DataTable|Piwik_DataTable_Array $pastData - * @param array $fields The array of string fields to calculate evolution - * metrics for. - */ - private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) - { - if ($currentData instanceof Piwik_DataTable_Array) - { - $pastArray = $pastData->getArray(); - foreach ($currentData->getArray() as $subTable) - { - $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); - next($pastArray); - } - } - else - { - foreach ($apiMetrics as $metricSettings) - { - $currentData->filter( - 'CalculateEvolutionFilter', - array( - $pastData, - $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], - $metricSettings[self::METRIC_RECORD_NAME_KEY], - $quotientPrecision = 1) - ); - } - } - } - - /** - * Sets the total visits, actions & revenue for a DataTable returned by - * $this->buildDataTable. - * - * @param Piwik_DataTable $dataTable - * @param array $apiMetrics Metrics info. - * @return array Array of three values: total visits, total actions, total revenue - */ - private function setMetricsTotalsMetadata( $dataTable, $apiMetrics ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - foreach ($dataTable->getArray() as $table) - { - $this->setMetricsTotalsMetadata($table, $apiMetrics); - } - } - else - { - $revenueMetric = ''; - if (Piwik_Common::isGoalPluginEnabled()) - { - $revenueMetric = Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC); - } - - $totals = array(); - foreach ($apiMetrics as $label => $metricInfo) - { - $totalMetadataName = self::getTotalMetadataName($label); - $totals[$totalMetadataName] = 0; - } - - foreach ($dataTable->getRows() as $row) - { - foreach ($apiMetrics as $label => $metricInfo) - { - $totalMetadataName = self::getTotalMetadataName($label); - $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]); - } - } - - foreach ($totals as $name => $value) - { - $dataTable->setMetadata($name, $value); - } - } - } - - /** - * Sets the total evolution metadata for a datatable returned by $this->buildDataTable - * given data for the last period. - * - * @param Piwik_DataTable $dataTable - * @param Piwik_DataTable $pastData - * @param array $apiMetrics Metrics info. - */ - private function setPastDataMetadata( $dataTable, $pastData, $apiMetrics ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - $pastArray = $pastData->getArray(); - foreach ($dataTable->getArray() as $subTable) - { - $this->setPastDataMetadata($subTable, current($pastArray), $apiMetrics); - next($pastArray); - } - } - else - { - // calculate total visits/actions/revenue for past data - $this->setMetricsTotalsMetadata($pastData, $apiMetrics); - - foreach ($apiMetrics as $label => $metricInfo) - { - // get the names of metadata to set - $totalMetadataName = self::getTotalMetadataName($label); - $lastPeriodTotalMetadataName = self::getLastPeriodMetadataName($totalMetadataName); - $totalEvolutionMetadataName = - self::getTotalMetadataName($metricInfo[self::METRIC_EVOLUTION_COL_NAME_KEY]); - - // set last period total - $pastTotal = $pastData->getMetadata($totalMetadataName); - $dataTable->setMetadata($lastPeriodTotalMetadataName, $pastTotal); - - // calculate & set evolution - $currentTotal = $dataTable->getMetadata($totalMetadataName); - $evolution = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($currentTotal, $pastTotal); - $dataTable->setMetadata($totalEvolutionMetadataName, $evolution); - } - } - } - - /** - * @ignore - */ - public static function getApiMetrics($enhanced) - { - $metrics = self::$baseMetrics; - if (Piwik_Common::isGoalPluginEnabled()) - { - // goal revenue metric - $metrics[self::GOAL_REVENUE_METRIC] = array( - self::METRIC_TRANSLATION_KEY => 'Goals_ColumnRevenue', - self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_REVENUE_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC), - self::METRIC_IS_ECOMMERCE_KEY => false, - ); - - if($enhanced) - { - // number of goal conversions metric - $metrics[self::GOAL_CONVERSION_METRIC] = array( - self::METRIC_TRANSLATION_KEY => 'Goals_ColumnConversions', - self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_CONVERSION_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC), - self::METRIC_IS_ECOMMERCE_KEY => false, - ); - - // number of orders - $metrics[self::ECOMMERCE_ORDERS_METRIC] = array( - self::METRIC_TRANSLATION_KEY => 'General_EcommerceOrders', - self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_ORDERS_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC ,0), - self::METRIC_IS_ECOMMERCE_KEY => true, - ); - - // eCommerce revenue - $metrics[self::ECOMMERCE_REVENUE_METRIC] = array( - self::METRIC_TRANSLATION_KEY => 'General_ProductRevenue', - self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_REVENUE_METRIC . '_evolution', - self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC ,0), - self::METRIC_IS_ECOMMERCE_KEY => true, - ); - } - } - - return $metrics; - } - - private static function getTotalMetadataName( $name ) - { - return 'total_'.$name; - } - - private static function getLastPeriodMetadataName( $name ) - { - return 'last_period_'.$name; - } + const METRIC_TRANSLATION_KEY = 'translation'; + const METRIC_EVOLUTION_COL_NAME_KEY = 'evolution_column_name'; + const METRIC_RECORD_NAME_KEY = 'record_name'; + const METRIC_IS_ECOMMERCE_KEY = 'is_ecommerce'; + + const NB_VISITS_METRIC = 'nb_visits'; + const NB_ACTIONS_METRIC = 'nb_actions'; + const NB_PAGEVIEWS_LABEL = 'nb_pageviews'; + const NB_PAGEVIEWS_METRIC = 'Actions_nb_pageviews'; + const GOAL_REVENUE_METRIC = 'revenue'; + const GOAL_CONVERSION_METRIC = 'nb_conversions'; + const ECOMMERCE_ORDERS_METRIC = 'orders'; + const ECOMMERCE_REVENUE_METRIC = 'ecommerce_revenue'; + + static private $baseMetrics = array( + self::NB_VISITS_METRIC => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbVisits', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'visits_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_VISITS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + self::NB_ACTIONS_METRIC => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbActions', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'actions_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_ACTIONS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + self::NB_PAGEVIEWS_LABEL => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnPageviews', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'pageviews_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_PAGEVIEWS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ) + ); + + /** + * The singleton instance of this class. + */ + static private $instance = null; + + /** + * Returns the singleton instance of this class. The instance is created + * if it hasn't been already. + * + * @return Piwik_MultiSites_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * Returns a report displaying the total visits, actions and revenue, as + * well as the evolution of these values, of all existing sites over a + * specified period of time. + * + * If the specified period is not a 'range', this function will calculcate + * evolution metrics. Evolution metrics are metrics that display the + * percent increase/decrease of another metric since the last period. + * + * This function will merge the result of the archive query so each + * row in the result DataTable will correspond to the metrics of a single + * site. If a date range is specified, the result will be a + * DataTable_Array, but it will still be merged. + * + * @param string $period The period type to get data for. + * @param string $date The date(s) to get data for. + * @param bool|string $segment The segments to get data for. + * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username + * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics + * @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites + * @return Piwik_DataTable + */ + public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false) + { + Piwik::checkUserHasSomeViewAccess(); + + $idSites = $this->getSitesIdFromPattern($pattern); + + return $this->buildDataTable( + $idSites, + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced, + $multipleWebsitesRequested = true + ); + } + + /** + * Fetches the list of sites which names match the string pattern + * + * @param $pattern + * @return array|string + */ + private function getSitesIdFromPattern($pattern) + { + $idSites = 'all'; + if (!empty($pattern)) { + $sites = Piwik_API_Request::processRequest('SitesManager.getPatternMatchSites', + array('pattern' => $pattern, + // added because caller could overwrite these + 'serialize' => 0, + 'format' => 'original')); + if (!empty($sites)) { + $idSites = array(); + foreach ($sites as $site) { + $idSites[] = $site['idsite']; + } + } + } + return $idSites; + } + + /** + * Same as getAll but for a unique Piwik site + * @see Piwik_MultiSites_API::getAll() + * + * @param int $idSite Id of the Piwik site + * @param string $period The period type to get data for. + * @param string $date The date(s) to get data for. + * @param bool|string $segment The segments to get data for. + * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username + * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics + * @return Piwik_DataTable + */ + public function getOne($idSite, $period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) + { + Piwik::checkUserHasViewAccess($idSite); + return $this->buildDataTable( + $idSite, + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced, + $multipleWebsitesRequested = false + ); + } + + private function buildDataTable($sites, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested) + { + $allWebsitesRequested = ($sites == 'all'); + if ($allWebsitesRequested) { + if (Piwik::isUserIsSuperUser() + // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. + // This means this function would return ALL websites in all cases. + // Instead, we make sure that only the right set of data is returned + && !Piwik_TaskScheduler::isTaskBeingExecuted() + ) { + Piwik_Site::setSites( + Piwik_SitesManager_API::getInstance()->getAllSites() + ); + } else { + Piwik_Site::setSitesFromArray( + Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin) + ); + } + } + + // build the archive type used to query archive data + $archive = Piwik_Archive::build( + $sites, + $period, + $date, + $segment, + $_restrictSitesToLogin + ); + + // determine what data will be displayed + $fieldsToGet = array(); + $columnNameRewrites = array(); + $apiECommerceMetrics = array(); + $apiMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced); + foreach ($apiMetrics as $metricName => $metricSettings) { + $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY]; + $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName; + + if ($metricSettings[self::METRIC_IS_ECOMMERCE_KEY]) { + $apiECommerceMetrics[$metricName] = $metricSettings; + } + } + + // get the data + // $dataTable instanceOf Piwik_DataTable_Array + $dataTable = $archive->getDataTableFromNumeric($fieldsToGet); + + // get rid of the DataTable_Array that is created by the IndexedBySite archive type + if ($dataTable instanceof Piwik_DataTable_Array + && $multipleWebsitesRequested + ) { + $dataTable = $dataTable->mergeChildren(); + } else { + if (!$dataTable instanceof Piwik_DataTable_Array) { + $firstDataTableRow = $dataTable->getFirstRow(); + $firstDataTableRow->setColumn('label', $sites); + } + } + + // calculate total visits/actions/revenue + $this->setMetricsTotalsMetadata($dataTable, $apiMetrics); + + // if the period isn't a range & a lastN/previousN date isn't used, we get the same + // data for the last period to show the evolution of visits/actions/revenue + list($strLastDate, $lastPeriod) = Piwik_Period_Range::getLastDate($date, $period); + if ($strLastDate !== false) { + if ($lastPeriod !== false) { + // NOTE: no easy way to set last period date metadata when range of dates is requested. + // will be easier if DataTable_Array::metadata is removed, and metadata that is + // put there is put directly in Piwik_DataTable::metadata. + $dataTable->setMetadata(self::getLastPeriodMetadataName('date'), $lastPeriod); + } + + $pastArchive = Piwik_Archive::build('all', $period, $strLastDate, $segment, $_restrictSitesToLogin); + $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet); + + $pastData = $pastData->mergeChildren(); + + // use past data to calculate evolution percentages + $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); + + $this->setPastDataMetadata($dataTable, $pastData, $apiMetrics); + } + + // remove eCommerce related metrics on non eCommerce Piwik sites + // note: this is not optimal in terms of performance: those metrics should not be retrieved in the first place + if ($enhanced) { + // $dataTableRows instanceOf Piwik_DataTable_Row[] + $dataTableRows = $dataTable->getRows(); + + foreach ($dataTableRows as $dataTableRow) { + $siteId = $dataTableRow->getColumn('label'); + if (!Piwik_Site::isEcommerceEnabledFor($siteId)) { + foreach ($apiECommerceMetrics as $metricSettings) { + $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]); + $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]); + } + } + } + } + + // move the site id to a metadata column + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite')); + + // set the label of each row to the site name + if ($multipleWebsitesRequested) { + $getNameFor = array('Piwik_Site', 'getNameFor'); + $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); + } else { + $dataTable->filter('ColumnDelete', array('label')); + } + + // replace record names with user friendly metric names + $dataTable->filter('ReplaceColumnNames', array($columnNameRewrites)); + + // Ensures data set sorted, for Metadata output + $dataTable->filter('Sort', array(self::NB_VISITS_METRIC, 'desc', $naturalSort = false)); + + // filter rows without visits + // note: if only one website is queried and there are no visits, we can not remove the row otherwise Piwik_API_ResponseBuilder throws 'Call to a member function getColumns() on a non-object' + if ($multipleWebsitesRequested) { + $dataTable->filter( + 'ColumnCallbackDeleteRow', + array( + self::NB_VISITS_METRIC, + create_function('$value', 'return $value != 0;') + ) + ); + } + + return $dataTable; + } + + /** + * Performs a binary filter of two + * DataTables in order to correctly calculate evolution metrics. + * + * @param Piwik_DataTable|Piwik_DataTable_Array $currentData + * @param Piwik_DataTable|Piwik_DataTable_Array $pastData + * @param array $fields The array of string fields to calculate evolution + * metrics for. + */ + private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) + { + if ($currentData instanceof Piwik_DataTable_Array) { + $pastArray = $pastData->getArray(); + foreach ($currentData->getArray() as $subTable) { + $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); + next($pastArray); + } + } else { + foreach ($apiMetrics as $metricSettings) { + $currentData->filter( + 'CalculateEvolutionFilter', + array( + $pastData, + $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], + $metricSettings[self::METRIC_RECORD_NAME_KEY], + $quotientPrecision = 1) + ); + } + } + } + + /** + * Sets the total visits, actions & revenue for a DataTable returned by + * $this->buildDataTable. + * + * @param Piwik_DataTable $dataTable + * @param array $apiMetrics Metrics info. + * @return array Array of three values: total visits, total actions, total revenue + */ + private function setMetricsTotalsMetadata($dataTable, $apiMetrics) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + foreach ($dataTable->getArray() as $table) { + $this->setMetricsTotalsMetadata($table, $apiMetrics); + } + } else { + $revenueMetric = ''; + if (Piwik_Common::isGoalPluginEnabled()) { + $revenueMetric = Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC); + } + + $totals = array(); + foreach ($apiMetrics as $label => $metricInfo) { + $totalMetadataName = self::getTotalMetadataName($label); + $totals[$totalMetadataName] = 0; + } + + foreach ($dataTable->getRows() as $row) { + foreach ($apiMetrics as $label => $metricInfo) { + $totalMetadataName = self::getTotalMetadataName($label); + $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]); + } + } + + foreach ($totals as $name => $value) { + $dataTable->setMetadata($name, $value); + } + } + } + + /** + * Sets the total evolution metadata for a datatable returned by $this->buildDataTable + * given data for the last period. + * + * @param Piwik_DataTable $dataTable + * @param Piwik_DataTable $pastData + * @param array $apiMetrics Metrics info. + */ + private function setPastDataMetadata($dataTable, $pastData, $apiMetrics) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + $pastArray = $pastData->getArray(); + foreach ($dataTable->getArray() as $subTable) { + $this->setPastDataMetadata($subTable, current($pastArray), $apiMetrics); + next($pastArray); + } + } else { + // calculate total visits/actions/revenue for past data + $this->setMetricsTotalsMetadata($pastData, $apiMetrics); + + foreach ($apiMetrics as $label => $metricInfo) { + // get the names of metadata to set + $totalMetadataName = self::getTotalMetadataName($label); + $lastPeriodTotalMetadataName = self::getLastPeriodMetadataName($totalMetadataName); + $totalEvolutionMetadataName = + self::getTotalMetadataName($metricInfo[self::METRIC_EVOLUTION_COL_NAME_KEY]); + + // set last period total + $pastTotal = $pastData->getMetadata($totalMetadataName); + $dataTable->setMetadata($lastPeriodTotalMetadataName, $pastTotal); + + // calculate & set evolution + $currentTotal = $dataTable->getMetadata($totalMetadataName); + $evolution = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($currentTotal, $pastTotal); + $dataTable->setMetadata($totalEvolutionMetadataName, $evolution); + } + } + } + + /** + * @ignore + */ + public static function getApiMetrics($enhanced) + { + $metrics = self::$baseMetrics; + if (Piwik_Common::isGoalPluginEnabled()) { + // goal revenue metric + $metrics[self::GOAL_REVENUE_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'Goals_ColumnRevenue', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_REVENUE_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC), + self::METRIC_IS_ECOMMERCE_KEY => false, + ); + + if ($enhanced) { + // number of goal conversions metric + $metrics[self::GOAL_CONVERSION_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'Goals_ColumnConversions', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_CONVERSION_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC), + self::METRIC_IS_ECOMMERCE_KEY => false, + ); + + // number of orders + $metrics[self::ECOMMERCE_ORDERS_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'General_EcommerceOrders', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_ORDERS_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC, 0), + self::METRIC_IS_ECOMMERCE_KEY => true, + ); + + // eCommerce revenue + $metrics[self::ECOMMERCE_REVENUE_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'General_ProductRevenue', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_REVENUE_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC, 0), + self::METRIC_IS_ECOMMERCE_KEY => true, + ); + } + } + + return $metrics; + } + + private static function getTotalMetadataName($name) + { + return 'total_' . $name; + } + + private static function getLastPeriodMetadataName($name) + { + return 'last_period_' . $name; + } } |