PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/API/ProcessedReport.php

https://github.com/CodeYellowBV/piwik
PHP | 735 lines | 440 code | 87 blank | 208 comment | 85 complexity | e56a2ef76f308786c64fd8604248bb12 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Plugins\API;
  10. use Exception;
  11. use Piwik\API\Request;
  12. use Piwik\Archive\DataTableFactory;
  13. use Piwik\Common;
  14. use Piwik\DataTable;
  15. use Piwik\DataTable\Row;
  16. use Piwik\DataTable\Simple;
  17. use Piwik\Date;
  18. use Piwik\Metrics;
  19. use Piwik\MetricsFormatter;
  20. use Piwik\Period;
  21. use Piwik\Piwik;
  22. use Piwik\Site;
  23. use Piwik\Timer;
  24. use Piwik\Url;
  25. class ProcessedReport
  26. {
  27. /**
  28. * Loads reports metadata, then return the requested one,
  29. * matching optional API parameters.
  30. */
  31. public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
  32. $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
  33. {
  34. $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports);
  35. foreach ($reportsMetadata as $report) {
  36. // See ArchiveProcessor/Aggregator.php - unique visitors are not processed for period != day
  37. if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) {
  38. unset($report['metrics']['nb_uniq_visitors']);
  39. }
  40. if ($report['module'] == $apiModule
  41. && $report['action'] == $apiAction
  42. ) {
  43. // No custom parameters
  44. if (empty($apiParameters)
  45. && empty($report['parameters'])
  46. ) {
  47. return array($report);
  48. }
  49. if (empty($report['parameters'])) {
  50. continue;
  51. }
  52. $diff = array_diff($report['parameters'], $apiParameters);
  53. if (empty($diff)) {
  54. return array($report);
  55. }
  56. }
  57. }
  58. return false;
  59. }
  60. /**
  61. * Verfies whether the given report exists for the given site.
  62. *
  63. * @param int $idSite
  64. * @param string $apiMethodUniqueId For example 'MultiSites_getAll'
  65. *
  66. * @return bool
  67. */
  68. public function isValidReportForSite($idSite, $apiMethodUniqueId)
  69. {
  70. $report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId);
  71. return !empty($report);
  72. }
  73. /**
  74. * Verfies whether the given metric belongs to the given report.
  75. *
  76. * @param int $idSite
  77. * @param string $metric For example 'nb_visits'
  78. * @param string $apiMethodUniqueId For example 'MultiSites_getAll'
  79. *
  80. * @return bool
  81. */
  82. public function isValidMetricForReport($metric, $idSite, $apiMethodUniqueId)
  83. {
  84. $translation = $this->translateMetric($metric, $idSite, $apiMethodUniqueId);
  85. return !empty($translation);
  86. }
  87. public function getReportMetadataByUniqueId($idSite, $apiMethodUniqueId)
  88. {
  89. $metadata = $this->getReportMetadata(array($idSite));
  90. foreach ($metadata as $report) {
  91. if ($report['uniqueId'] == $apiMethodUniqueId) {
  92. return $report;
  93. }
  94. }
  95. }
  96. /**
  97. * Translates the given metric in case the report exists and in case the metric acutally belongs to the report.
  98. *
  99. * @param string $metric For example 'nb_visits'
  100. * @param int $idSite
  101. * @param string $apiMethodUniqueId For example 'MultiSites_getAll'
  102. *
  103. * @return null|string
  104. */
  105. public function translateMetric($metric, $idSite, $apiMethodUniqueId)
  106. {
  107. $report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId);
  108. if (empty($report)) {
  109. return;
  110. }
  111. $properties = array('metrics', 'processedMetrics', 'processedMetricsGoal');
  112. foreach ($properties as $prop) {
  113. if (!empty($report[$prop]) && is_array($report[$prop]) && array_key_exists($metric, $report[$prop])) {
  114. return $report[$prop][$metric];
  115. }
  116. }
  117. }
  118. /**
  119. * Triggers a hook to ask plugins for available Reports.
  120. * Returns metadata information about each report (category, name, dimension, metrics, etc.)
  121. *
  122. * @param string $idSites Comma separated list of website Ids
  123. * @param bool|string $period
  124. * @param bool|Date $date
  125. * @param bool $hideMetricsDoc
  126. * @param bool $showSubtableReports
  127. * @return array
  128. */
  129. public function getReportMetadata($idSites, $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
  130. {
  131. $idSites = Site::getIdSitesFromIdSitesString($idSites);
  132. if (!empty($idSites)) {
  133. Piwik::checkUserHasViewAccess($idSites);
  134. }
  135. $parameters = array('idSites' => $idSites, 'period' => $period, 'date' => $date);
  136. $availableReports = array();
  137. /**
  138. * Triggered when gathering metadata for all available reports.
  139. *
  140. * Plugins that define new reports should use this event to make them available in via
  141. * the metadata API. By doing so, the report will become available in scheduled reports
  142. * as well as in the Piwik Mobile App. In fact, any third party app that uses the metadata
  143. * API will automatically have access to the new report.
  144. *
  145. * @param string &$availableReports The list of available reports. Append to this list
  146. * to make a report available.
  147. *
  148. * Every element of this array must contain the following
  149. * information:
  150. *
  151. * - **category**: A translated string describing the report's category.
  152. * - **name**: The translated display title of the report.
  153. * - **module**: The plugin of the report.
  154. * - **action**: The API method that serves the report.
  155. *
  156. * The following information is optional:
  157. *
  158. * - **dimension**: The report's [dimension](/guides/all-about-analytics-data#dimensions) if any.
  159. * - **metrics**: An array mapping metric names with their display names.
  160. * - **metricsDocumentation**: An array mapping metric names with their
  161. * translated documentation.
  162. * - **processedMetrics**: The array of metrics in the report that are
  163. * calculated using existing metrics. Can be set to
  164. * `false` if the report contains no processed
  165. * metrics.
  166. * - **order**: The order of the report in the list of reports
  167. * with the same category.
  168. *
  169. * @param array $parameters Contains the values of the sites and period we are
  170. * getting reports for. Some reports depend on this data.
  171. * For example, Goals reports depend on the site IDs being
  172. * requested. Contains the following information:
  173. *
  174. * - **idSites**: The array of site IDs we are getting reports for.
  175. * - **period**: The period type, eg, `'day'`, `'week'`, `'month'`,
  176. * `'year'`, `'range'`.
  177. * - **date**: A string date within the period or a date range, eg,
  178. * `'2013-01-01'` or `'2012-01-01,2013-01-01'`.
  179. *
  180. * TODO: put dimensions section in all about analytics data
  181. */
  182. Piwik::postEvent('API.getReportMetadata', array(&$availableReports, $parameters));
  183. foreach ($availableReports as &$availableReport) {
  184. if (!isset($availableReport['metrics'])) {
  185. $availableReport['metrics'] = Metrics::getDefaultMetrics();
  186. }
  187. if (!isset($availableReport['processedMetrics'])) {
  188. $availableReport['processedMetrics'] = Metrics::getDefaultProcessedMetrics();
  189. }
  190. if ($hideMetricsDoc) // remove metric documentation if it's not wanted
  191. {
  192. unset($availableReport['metricsDocumentation']);
  193. } else if (!isset($availableReport['metricsDocumentation'])) {
  194. // set metric documentation to default if it's not set
  195. $availableReport['metricsDocumentation'] = Metrics::getDefaultMetricsDocumentation();
  196. }
  197. }
  198. /**
  199. * Triggered after all available reports are collected.
  200. *
  201. * This event can be used to modify the report metadata of reports in other plugins. You
  202. * could, for example, add custom metrics to every report or remove reports from the list
  203. * of available reports.
  204. *
  205. * @param array &$availableReports List of all report metadata. Read the {@hook API.getReportMetadata}
  206. * docs to see what this array contains.
  207. * @param array $parameters Contains the values of the sites and period we are
  208. * getting reports for. Some report depend on this data.
  209. * For example, Goals reports depend on the site IDs being
  210. * request. Contains the following information:
  211. *
  212. * - **idSites**: The array of site IDs we are getting reports for.
  213. * - **period**: The period type, eg, `'day'`, `'week'`, `'month'`,
  214. * `'year'`, `'range'`.
  215. * - **date**: A string date within the period or a date range, eg,
  216. * `'2013-01-01'` or `'2012-01-01,2013-01-01'`.
  217. */
  218. Piwik::postEvent('API.getReportMetadata.end', array(&$availableReports, $parameters));
  219. // Sort results to ensure consistent order
  220. usort($availableReports, array($this, 'sort'));
  221. // Add the magic API.get report metadata aggregating all plugins API.get API calls automatically
  222. $this->addApiGetMetdata($availableReports);
  223. $knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics());
  224. foreach ($availableReports as &$availableReport) {
  225. // Ensure all metrics have a translation
  226. $metrics = $availableReport['metrics'];
  227. $cleanedMetrics = array();
  228. foreach ($metrics as $metricId => $metricTranslation) {
  229. // When simply the column name was given, ie 'metric' => array( 'nb_visits' )
  230. // $metricTranslation is in this case nb_visits. We look for a known translation.
  231. if (is_numeric($metricId)
  232. && isset($knownMetrics[$metricTranslation])
  233. ) {
  234. $metricId = $metricTranslation;
  235. $metricTranslation = $knownMetrics[$metricTranslation];
  236. }
  237. $cleanedMetrics[$metricId] = $metricTranslation;
  238. }
  239. $availableReport['metrics'] = $cleanedMetrics;
  240. // if hide/show columns specified, hide/show metrics & docs
  241. $availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']);
  242. if (isset($availableReport['processedMetrics'])) {
  243. $availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']);
  244. }
  245. if (isset($availableReport['metricsDocumentation'])) {
  246. $availableReport['metricsDocumentation'] =
  247. $this->hideShowMetrics($availableReport['metricsDocumentation']);
  248. }
  249. // Remove array elements that are false (to clean up API output)
  250. foreach ($availableReport as $attributeName => $attributeValue) {
  251. if (empty($attributeValue)) {
  252. unset($availableReport[$attributeName]);
  253. }
  254. }
  255. // when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum
  256. if (isset($availableReport['metricsGoal'])) {
  257. unset($availableReport['processedMetrics']['conversion_rate']);
  258. unset($availableReport['metricsGoal']['conversion_rate']);
  259. }
  260. // Processing a uniqueId for each report,
  261. // can be used by UIs as a key to match a given report
  262. $uniqueId = $availableReport['module'] . '_' . $availableReport['action'];
  263. if (!empty($availableReport['parameters'])) {
  264. foreach ($availableReport['parameters'] as $key => $value) {
  265. $uniqueId .= '_' . $key . '--' . $value;
  266. }
  267. }
  268. $availableReport['uniqueId'] = $uniqueId;
  269. // Order is used to order reports internally, but not meant to be used outside
  270. unset($availableReport['order']);
  271. }
  272. // remove subtable reports
  273. if (!$showSubtableReports) {
  274. foreach ($availableReports as $idx => $report) {
  275. if (isset($report['isSubtableReport']) && $report['isSubtableReport']) {
  276. unset($availableReports[$idx]);
  277. }
  278. }
  279. }
  280. return array_values($availableReports); // make sure array has contiguous key values
  281. }
  282. /**
  283. * API metadata are sorted by category/name,
  284. * with a little tweak to replicate the standard Piwik category ordering
  285. *
  286. * @param string $a
  287. * @param string $b
  288. * @return int
  289. */
  290. private function sort($a, $b)
  291. {
  292. static $order = null;
  293. if (is_null($order)) {
  294. $order = array(
  295. Piwik::translate('General_MultiSitesSummary'),
  296. Piwik::translate('VisitsSummary_VisitsSummary'),
  297. Piwik::translate('Goals_Ecommerce'),
  298. Piwik::translate('General_Actions'),
  299. Piwik::translate('Events_Events'),
  300. Piwik::translate('Actions_SubmenuSitesearch'),
  301. Piwik::translate('Referrers_Referrers'),
  302. Piwik::translate('Goals_Goals'),
  303. Piwik::translate('General_Visitors'),
  304. Piwik::translate('DevicesDetection_DevicesDetection'),
  305. Piwik::translate('UserSettings_VisitorSettings'),
  306. );
  307. }
  308. return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0
  309. ? (@$a['order'] < @$b['order'] ? -1 : 1)
  310. : $category;
  311. }
  312. /**
  313. * Add the metadata for the API.get report
  314. * In other plugins, this would hook on 'API.getReportMetadata'
  315. */
  316. private function addApiGetMetdata(&$availableReports)
  317. {
  318. $metadata = array(
  319. 'category' => Piwik::translate('General_API'),
  320. 'name' => Piwik::translate('General_MainMetrics'),
  321. 'module' => 'API',
  322. 'action' => 'get',
  323. 'metrics' => array(),
  324. 'processedMetrics' => array(),
  325. 'metricsDocumentation' => array(),
  326. 'order' => 1
  327. );
  328. $indexesToMerge = array('metrics', 'processedMetrics', 'metricsDocumentation');
  329. foreach ($availableReports as $report) {
  330. if ($report['action'] == 'get') {
  331. foreach ($indexesToMerge as $index) {
  332. if (isset($report[$index])
  333. && is_array($report[$index])
  334. ) {
  335. $metadata[$index] = array_merge($metadata[$index], $report[$index]);
  336. }
  337. }
  338. }
  339. }
  340. $availableReports[] = $metadata;
  341. }
  342. public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
  343. $apiParameters = false, $idGoal = false, $language = false,
  344. $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
  345. {
  346. $timer = new Timer();
  347. if (empty($apiParameters)) {
  348. $apiParameters = array();
  349. }
  350. if (!empty($idGoal)
  351. && empty($apiParameters['idGoal'])
  352. ) {
  353. $apiParameters['idGoal'] = $idGoal;
  354. }
  355. // Is this report found in the Metadata available reports?
  356. $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language,
  357. $period, $date, $hideMetricsDoc, $showSubtableReports = true);
  358. if (empty($reportMetadata)) {
  359. throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n");
  360. }
  361. $reportMetadata = reset($reportMetadata);
  362. // Generate Api call URL passing custom parameters
  363. $parameters = array_merge($apiParameters, array(
  364. 'method' => $apiModule . '.' . $apiAction,
  365. 'idSite' => $idSite,
  366. 'period' => $period,
  367. 'date' => $date,
  368. 'format' => 'original',
  369. 'serialize' => '0',
  370. 'language' => $language,
  371. 'idSubtable' => $idSubtable
  372. ));
  373. if (isset($reportMetadata['processedMetrics'])) {
  374. $deleteRowsWithNoVisit = '1';
  375. if (!empty($reportMetadata['constantRowsCount'])) {
  376. $deleteRowsWithNoVisit = '0';
  377. }
  378. $parameters['filter_add_columns_when_show_all_columns'] = $deleteRowsWithNoVisit;
  379. }
  380. if (!empty($segment)) $parameters['segment'] = $segment;
  381. $url = Url::getQueryStringFromParameters($parameters);
  382. $request = new Request($url);
  383. try {
  384. /** @var DataTable */
  385. $dataTable = $request->process();
  386. } catch (Exception $e) {
  387. throw new Exception("API returned an error: " . $e->getMessage() . " at " . basename($e->getFile()) . ":" . $e->getLine() . "\n");
  388. }
  389. list($newReport, $columns, $rowsMetadata, $totals) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, $showRawMetrics);
  390. foreach ($columns as $columnId => &$name) {
  391. $name = ucfirst($name);
  392. }
  393. $website = new Site($idSite);
  394. $period = Period\Factory::build($period, $date);
  395. $period = $period->getLocalizedLongString();
  396. $return = array(
  397. 'website' => $website->getName(),
  398. 'prettyDate' => $period,
  399. 'metadata' => $reportMetadata,
  400. 'columns' => $columns,
  401. 'reportData' => $newReport,
  402. 'reportMetadata' => $rowsMetadata,
  403. 'reportTotal' => $totals
  404. );
  405. if ($showTimer) {
  406. $return['timerMillis'] = $timer->getTimeMs(0);
  407. }
  408. return $return;
  409. }
  410. /**
  411. * Enhance a $dataTable using metadata :
  412. *
  413. * - remove metrics based on $reportMetadata['metrics']
  414. * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics']
  415. * - format metric values to a 'human readable' format
  416. * - extract row metadata to a separate Simple|Set : $rowsMetadata
  417. * - translate metric names to a separate array : $columns
  418. *
  419. * @param int $idSite enables monetary value formatting based on site currency
  420. * @param \Piwik\DataTable\Map|\Piwik\DataTable\Simple $dataTable
  421. * @param array $reportMetadata
  422. * @param bool $showRawMetrics
  423. * @return array Simple|Set $newReport with human readable format & array $columns list of translated column names & Simple|Set $rowsMetadata
  424. */
  425. private function handleTableReport($idSite, $dataTable, &$reportMetadata, $showRawMetrics = false)
  426. {
  427. $hasDimension = isset($reportMetadata['dimension']);
  428. $columns = $reportMetadata['metrics'];
  429. if ($hasDimension) {
  430. $columns = array_merge(
  431. array('label' => $reportMetadata['dimension']),
  432. $columns
  433. );
  434. if (isset($reportMetadata['processedMetrics'])) {
  435. $processedMetricsAdded = Metrics::getDefaultProcessedMetrics();
  436. foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) {
  437. // this processed metric can be displayed for this report
  438. if (isset($reportMetadata['processedMetrics'][$processedMetricId])) {
  439. $columns[$processedMetricId] = $processedMetricTranslation;
  440. }
  441. }
  442. }
  443. // Display the global Goal metrics
  444. if (isset($reportMetadata['metricsGoal'])) {
  445. $metricsGoalDisplay = array('revenue');
  446. // Add processed metrics to be displayed for this report
  447. foreach ($metricsGoalDisplay as $goalMetricId) {
  448. if (isset($reportMetadata['metricsGoal'][$goalMetricId])) {
  449. $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId];
  450. }
  451. }
  452. }
  453. }
  454. $columns = $this->hideShowMetrics($columns);
  455. $totals = array();
  456. // $dataTable is an instance of Set when multiple periods requested
  457. if ($dataTable instanceof DataTable\Map) {
  458. // Need a new Set to store the 'human readable' values
  459. $newReport = new DataTable\Map();
  460. $newReport->setKeyName("prettyDate");
  461. // Need a new Set to store report metadata
  462. $rowsMetadata = new DataTable\Map();
  463. $rowsMetadata->setKeyName("prettyDate");
  464. // Process each Simple entry
  465. foreach ($dataTable->getDataTables() as $label => $simpleDataTable) {
  466. $this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable);
  467. list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics);
  468. $enhancedSimpleDataTable->setAllTableMetadata($simpleDataTable->getAllTableMetadata());
  469. $period = $simpleDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedLongString();
  470. $newReport->addTable($enhancedSimpleDataTable, $period);
  471. $rowsMetadata->addTable($rowMetadata, $period);
  472. $totals = $this->aggregateReportTotalValues($simpleDataTable, $totals);
  473. }
  474. } else {
  475. $this->removeEmptyColumns($columns, $reportMetadata, $dataTable);
  476. list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics);
  477. $totals = $this->aggregateReportTotalValues($dataTable, $totals);
  478. }
  479. return array(
  480. $newReport,
  481. $columns,
  482. $rowsMetadata,
  483. $totals
  484. );
  485. }
  486. /**
  487. * Removes metrics from the list of columns and the report meta data if they are marked empty
  488. * in the data table meta data.
  489. */
  490. private function removeEmptyColumns(&$columns, &$reportMetadata, $dataTable)
  491. {
  492. $emptyColumns = $dataTable->getMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME);
  493. if (!is_array($emptyColumns)) {
  494. return;
  495. }
  496. $columns = $this->hideShowMetrics($columns, $emptyColumns);
  497. if (isset($reportMetadata['metrics'])) {
  498. $reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns);
  499. }
  500. if (isset($reportMetadata['metricsDocumentation'])) {
  501. $reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns);
  502. }
  503. }
  504. /**
  505. * Removes column names from an array based on the values in the hideColumns,
  506. * showColumns query parameters. This is a hack that provides the ColumnDelete
  507. * filter functionality in processed reports.
  508. *
  509. * @param array $columns List of metrics shown in a processed report.
  510. * @param array $emptyColumns Empty columns from the data table meta data.
  511. * @return array Filtered list of metrics.
  512. */
  513. private function hideShowMetrics($columns, $emptyColumns = array())
  514. {
  515. if (!is_array($columns)) {
  516. return $columns;
  517. }
  518. // remove columns if hideColumns query parameters exist
  519. $columnsToRemove = Common::getRequestVar('hideColumns', '');
  520. if ($columnsToRemove != '') {
  521. $columnsToRemove = explode(',', $columnsToRemove);
  522. foreach ($columnsToRemove as $name) {
  523. // if a column to remove is in the column list, remove it
  524. if (isset($columns[$name])) {
  525. unset($columns[$name]);
  526. }
  527. }
  528. }
  529. // remove columns if showColumns query parameters exist
  530. $columnsToKeep = Common::getRequestVar('showColumns', '');
  531. if ($columnsToKeep != '') {
  532. $columnsToKeep = explode(',', $columnsToKeep);
  533. $columnsToKeep[] = 'label';
  534. foreach ($columns as $name => $ignore) {
  535. // if the current column should not be kept, remove it
  536. $idx = array_search($name, $columnsToKeep);
  537. if ($idx === false) // if $name is not in $columnsToKeep
  538. {
  539. unset($columns[$name]);
  540. }
  541. }
  542. }
  543. // remove empty columns
  544. if (is_array($emptyColumns)) {
  545. foreach ($emptyColumns as $column) {
  546. if (isset($columns[$column])) {
  547. unset($columns[$column]);
  548. }
  549. }
  550. }
  551. return $columns;
  552. }
  553. /**
  554. * Enhance $simpleDataTable using metadata :
  555. *
  556. * - remove metrics based on $reportMetadata['metrics']
  557. * - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics']
  558. * - format metric values to a 'human readable' format
  559. * - extract row metadata to a separate Simple $rowsMetadata
  560. *
  561. * @param int $idSite enables monetary value formatting based on site currency
  562. * @param Simple $simpleDataTable
  563. * @param array $metadataColumns
  564. * @param boolean $hasDimension
  565. * @param bool $returnRawMetrics If set to true, the original metrics will be returned
  566. *
  567. * @return array DataTable $enhancedDataTable filtered metrics with human readable format & Simple $rowsMetadata
  568. */
  569. private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false)
  570. {
  571. // new DataTable to store metadata
  572. $rowsMetadata = new DataTable();
  573. // new DataTable to store 'human readable' values
  574. if ($hasDimension) {
  575. $enhancedDataTable = new DataTable();
  576. } else {
  577. $enhancedDataTable = new Simple();
  578. }
  579. foreach ($simpleDataTable->getRows() as $row) {
  580. $rowMetrics = $row->getColumns();
  581. // add missing metrics
  582. foreach ($metadataColumns as $id => $name) {
  583. if (!isset($rowMetrics[$id])) {
  584. $row->setColumn($id, 0);
  585. $rowMetrics[$id] = 0;
  586. }
  587. }
  588. $enhancedRow = new Row();
  589. $enhancedDataTable->addRow($enhancedRow);
  590. foreach ($rowMetrics as $columnName => $columnValue) {
  591. // filter metrics according to metadata definition
  592. if (isset($metadataColumns[$columnName])) {
  593. // generate 'human readable' metric values
  594. // if we handle MultiSites.getAll we do not always have the same idSite but different ones for
  595. // each site, see http://dev.piwik.org/trac/ticket/5006
  596. $idSiteForRow = $idSite;
  597. if ($row->getMetadata('idsite') && is_numeric($row->getMetadata('idsite'))) {
  598. $idSiteForRow = (int) $row->getMetadata('idsite');
  599. }
  600. $prettyValue = MetricsFormatter::getPrettyValue($idSiteForRow, $columnName, $columnValue, $htmlAllowed = false);
  601. $enhancedRow->addColumn($columnName, $prettyValue);
  602. } // For example the Maps Widget requires the raw metrics to do advanced datavis
  603. elseif ($returnRawMetrics) {
  604. if (!isset($columnValue)) {
  605. $columnValue = 0;
  606. }
  607. $enhancedRow->addColumn($columnName, $columnValue);
  608. }
  609. }
  610. // If report has a dimension, extract metadata into a distinct DataTable
  611. if ($hasDimension) {
  612. $rowMetadata = $row->getMetadata();
  613. $idSubDataTable = $row->getIdSubDataTable();
  614. // Create a row metadata only if there are metadata to insert
  615. if (count($rowMetadata) > 0 || !is_null($idSubDataTable)) {
  616. $metadataRow = new Row();
  617. $rowsMetadata->addRow($metadataRow);
  618. foreach ($rowMetadata as $metadataKey => $metadataValue) {
  619. $metadataRow->addColumn($metadataKey, $metadataValue);
  620. }
  621. if (!is_null($idSubDataTable)) {
  622. $metadataRow->addColumn('idsubdatatable', $idSubDataTable);
  623. }
  624. }
  625. }
  626. }
  627. return array(
  628. $enhancedDataTable,
  629. $rowsMetadata
  630. );
  631. }
  632. private function aggregateReportTotalValues($simpleDataTable, $totals)
  633. {
  634. $metadataTotals = $simpleDataTable->getMetadata('totals');
  635. if (empty($metadataTotals)) {
  636. return $totals;
  637. }
  638. $simpleTotals = $this->hideShowMetrics($metadataTotals);
  639. foreach ($simpleTotals as $metric => $value) {
  640. if (!array_key_exists($metric, $totals)) {
  641. $totals[$metric] = $value;
  642. } else {
  643. $totals[$metric] += $value;
  644. }
  645. }
  646. return $totals;
  647. }
  648. }