PageRenderTime 74ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/core/API/DataTableManipulator/ReportTotalsCalculator.php

https://github.com/CodeYellowBV/piwik
PHP | 247 lines | 151 code | 44 blank | 52 comment | 32 complexity | acad5eb71b3f21807139092e4a5dbdbd 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\API\DataTableManipulator;
  10. use Piwik\API\DataTableManipulator;
  11. use Piwik\DataTable;
  12. use Piwik\DataTable\Row;
  13. use Piwik\Metrics;
  14. use Piwik\Period;
  15. use Piwik\Plugins\API\API;
  16. /**
  17. * This class is responsible for setting the metadata property 'totals' on each dataTable if the report
  18. * has a dimension. 'Totals' means it tries to calculate the total report value for each metric. For each
  19. * the total number of visits, actions, ... for a given report / dataTable.
  20. */
  21. class ReportTotalsCalculator extends DataTableManipulator
  22. {
  23. /**
  24. * Cached report metadata array.
  25. * @var array
  26. */
  27. private static $reportMetadata = array();
  28. /**
  29. * @param DataTable $table
  30. * @return \Piwik\DataTable|\Piwik\DataTable\Map
  31. */
  32. public function calculate($table)
  33. {
  34. // apiModule and/or apiMethod is empty for instance in case when flat=1 is called. Basically whenever a
  35. // datamanipulator calls the API and wants the dataTable in return, see callApiAndReturnDataTable().
  36. // it is also not set for some settings API request etc.
  37. if (empty($this->apiModule) || empty($this->apiMethod)) {
  38. return $table;
  39. }
  40. try {
  41. return $this->manipulate($table);
  42. } catch(\Exception $e) {
  43. // eg. requests with idSubtable may trigger this exception
  44. // (where idSubtable was removed in
  45. // ?module=API&method=Events.getNameFromCategoryId&idSubtable=1&secondaryDimension=eventName&format=XML&idSite=1&period=day&date=yesterday&flat=0
  46. return $table;
  47. }
  48. }
  49. /**
  50. * Adds ratio metrics if possible.
  51. *
  52. * @param DataTable $dataTable
  53. * @return DataTable
  54. */
  55. protected function manipulateDataTable($dataTable)
  56. {
  57. $report = $this->findCurrentReport();
  58. if (!empty($report) && empty($report['dimension'])) {
  59. // we currently do not calculate the total value for reports having no dimension
  60. return $dataTable;
  61. }
  62. // Array [readableMetric] => [summed value]
  63. $totalValues = array();
  64. $firstLevelTable = $this->makeSureToWorkOnFirstLevelDataTable($dataTable);
  65. $metricsToCalculate = Metrics::getMetricIdsToProcessReportTotal();
  66. foreach ($metricsToCalculate as $metricId) {
  67. if (!$this->hasDataTableMetric($firstLevelTable, $metricId)) {
  68. continue;
  69. }
  70. foreach ($firstLevelTable->getRows() as $row) {
  71. $totalValues = $this->sumColumnValueToTotal($row, $metricId, $totalValues);
  72. }
  73. }
  74. $dataTable->setMetadata('totals', $totalValues);
  75. return $dataTable;
  76. }
  77. private function hasDataTableMetric(DataTable $dataTable, $metricId)
  78. {
  79. $firstRow = $dataTable->getFirstRow();
  80. if (empty($firstRow)) {
  81. return false;
  82. }
  83. if (false === $this->getColumn($firstRow, $metricId)) {
  84. return false;
  85. }
  86. return true;
  87. }
  88. /**
  89. * Returns column from a given row.
  90. * Will work with 2 types of datatable
  91. * - raw datatables coming from the archive DB, which columns are int indexed
  92. * - datatables processed resulting of API calls, which columns have human readable english names
  93. *
  94. * @param Row|array $row
  95. * @param int $columnIdRaw see consts in Metrics::
  96. * @return mixed Value of column, false if not found
  97. */
  98. private function getColumn($row, $columnIdRaw)
  99. {
  100. $columnIdReadable = Metrics::getReadableColumnName($columnIdRaw);
  101. if ($row instanceof Row) {
  102. $raw = $row->getColumn($columnIdRaw);
  103. if ($raw !== false) {
  104. return $raw;
  105. }
  106. return $row->getColumn($columnIdReadable);
  107. }
  108. return false;
  109. }
  110. private function makeSureToWorkOnFirstLevelDataTable($table)
  111. {
  112. if (!array_key_exists('idSubtable', $this->request)) {
  113. return $table;
  114. }
  115. $firstLevelReport = $this->findFirstLevelReport();
  116. if (empty($firstLevelReport)) {
  117. // it is not a subtable report
  118. $module = $this->apiModule;
  119. $action = $this->apiMethod;
  120. } else {
  121. $module = $firstLevelReport['module'];
  122. $action = $firstLevelReport['action'];
  123. }
  124. $request = $this->request;
  125. /** @var \Piwik\Period $period */
  126. $period = $table->getMetadata('period');
  127. if (!empty($period)) {
  128. // we want a dataTable, not a dataTable\map
  129. if (Period::isMultiplePeriod($request['date'], $request['period']) || 'range' == $period->getLabel()) {
  130. $request['date'] = $period->getRangeString();
  131. $request['period'] = 'range';
  132. } else {
  133. $request['date'] = $period->getDateStart()->toString();
  134. $request['period'] = $period->getLabel();
  135. }
  136. }
  137. return $this->callApiAndReturnDataTable($module, $action, $request);
  138. }
  139. private function sumColumnValueToTotal(Row $row, $metricId, $totalValues)
  140. {
  141. $value = $this->getColumn($row, $metricId);
  142. if (false === $value) {
  143. return $totalValues;
  144. }
  145. $metricName = Metrics::getReadableColumnName($metricId);
  146. if (array_key_exists($metricName, $totalValues)) {
  147. $totalValues[$metricName] += $value;
  148. } else {
  149. $totalValues[$metricName] = $value;
  150. }
  151. return $totalValues;
  152. }
  153. /**
  154. * Make sure to get all rows of the first level table.
  155. *
  156. * @param array $request
  157. */
  158. protected function manipulateSubtableRequest($request)
  159. {
  160. $request['totals'] = 0;
  161. $request['expanded'] = 0;
  162. $request['filter_limit'] = -1;
  163. $request['filter_offset'] = 0;
  164. $parametersToRemove = array('flat');
  165. if (!array_key_exists('idSubtable', $this->request)) {
  166. $parametersToRemove[] = 'idSubtable';
  167. }
  168. foreach ($parametersToRemove as $param) {
  169. if (array_key_exists($param, $request)) {
  170. unset($request[$param]);
  171. }
  172. }
  173. return $request;
  174. }
  175. private function getReportMetadata()
  176. {
  177. if (!empty(static::$reportMetadata)) {
  178. return static::$reportMetadata;
  179. }
  180. static::$reportMetadata = API::getInstance()->getReportMetadata();
  181. return static::$reportMetadata;
  182. }
  183. private function findCurrentReport()
  184. {
  185. foreach ($this->getReportMetadata() as $report) {
  186. if ($this->apiMethod == $report['action']
  187. && $this->apiModule == $report['module']) {
  188. return $report;
  189. }
  190. }
  191. }
  192. private function findFirstLevelReport()
  193. {
  194. foreach ($this->getReportMetadata() as $report) {
  195. if (!empty($report['actionToLoadSubTables'])
  196. && $this->apiMethod == $report['actionToLoadSubTables']
  197. && $this->apiModule == $report['module']
  198. ) {
  199. return $report;
  200. }
  201. }
  202. }
  203. }