| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\API; |
|
|
| use Exception; |
| use Piwik\API\DataTableManipulator\Flattener; |
| use Piwik\API\DataTableManipulator\LabelFilter; |
| use Piwik\API\DataTableManipulator\ReportTotalsCalculator; |
| use Piwik\Common; |
| use Piwik\DataTable; |
| use Piwik\DataTable\DataTableInterface; |
| use Piwik\DataTable\Filter\PivotByDimension; |
| use Piwik\Metrics\Formatter; |
| use Piwik\Piwik; |
| use Piwik\Plugin\ProcessedMetric; |
| use Piwik\Plugin\Report; |
| use Piwik\Plugin\ReportsProvider; |
| use Piwik\Plugins\API\Filter\DataComparisonFilter; |
| use Piwik\Plugins\CoreHome\Columns\Metrics\EvolutionMetric; |
| use Piwik\Plugins\PrivacyManager\DataRounding; |
| use Piwik\Request; |
|
|
| |
| |
| |
| |
| |
| class DataTablePostProcessor |
| { |
| public const PROCESSED_METRICS_COMPUTED_FLAG = 'processed_metrics_computed'; |
|
|
| |
| |
| |
| private $report; |
|
|
| |
| |
| |
| private $request; |
|
|
| |
| |
| |
| private $apiModule; |
|
|
| |
| |
| |
| private $apiMethod; |
|
|
| |
| |
| |
| private $apiInconsistencies; |
|
|
| |
| |
| |
| private $formatter; |
|
|
| private $callbackBeforeGenericFilters; |
| private $callbackAfterGenericFilters; |
|
|
| public function __construct($apiModule, $apiMethod, $request) |
| { |
| $this->apiModule = $apiModule; |
| $this->apiMethod = $apiMethod; |
| $this->setRequest($request); |
|
|
| $this->report = ReportsProvider::factory($apiModule, $apiMethod); |
| $this->apiInconsistencies = new Inconsistencies(); |
| $this->setFormatter(new Formatter()); |
| } |
|
|
| public function setFormatter(Formatter $formatter) |
| { |
| $this->formatter = $formatter; |
| } |
|
|
| public function setRequest($request) |
| { |
| $this->request = $request; |
| } |
|
|
| public function setCallbackBeforeGenericFilters($callbackBeforeGenericFilters) |
| { |
| $this->callbackBeforeGenericFilters = $callbackBeforeGenericFilters; |
| } |
|
|
| public function setCallbackAfterGenericFilters($callbackAfterGenericFilters) |
| { |
| $this->callbackAfterGenericFilters = $callbackAfterGenericFilters; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function process(DataTableInterface $dataTable) |
| { |
| |
| |
| |
| $dataTable = $this->applyPivotByFilter($dataTable); |
| $dataTable = $this->applyTotalsCalculator($dataTable); |
| $dataTable = $this->applyFlattener($dataTable); |
|
|
| if ($this->callbackBeforeGenericFilters) { |
| call_user_func($this->callbackBeforeGenericFilters, $dataTable); |
| } |
|
|
| $dataTable = $this->applyGenericFilters($dataTable); |
| $dataTable = $this->applyArchiveStateFilter($dataTable); |
| $this->applyComputeProcessedMetrics($dataTable); |
| $dataTable = $this->applyComparison($dataTable); |
|
|
| if ($this->callbackAfterGenericFilters) { |
| call_user_func($this->callbackAfterGenericFilters, $dataTable); |
| } |
|
|
| |
| $dataTable->queueFilter('SafeDecodeLabel'); |
|
|
| $dataTable = $this->convertSegmentValueToSegment($dataTable); |
| $dataTable = $this->applyQueuedFilters($dataTable); |
| $dataTable = $this->applyRequestedColumnDeletion($dataTable); |
| $dataTable = $this->applyLabelFilter($dataTable); |
|
|
| DataRounding::roundCountMetricsForRequest($dataTable, $this->request, $this->report); |
|
|
| $dataTable = $this->applyMetricsFormatting($dataTable); |
| return $dataTable; |
| } |
|
|
| private function convertSegmentValueToSegment(DataTableInterface $dataTable) |
| { |
| $dataTable->filter('AddSegmentBySegmentValue', array($this->report)); |
| $dataTable->filter('ColumnCallbackDeleteMetadata', array('segmentValue')); |
|
|
| return $dataTable; |
| } |
|
|
| public function applyArchiveStateFilter(DataTableInterface $dataTable): DataTableInterface |
| { |
| $fetchArchiveState = (new \Piwik\Request($this->request))->getBoolParameter('fetch_archive_state', false); |
|
|
| if (false === $fetchArchiveState) { |
| $dataTable->filter(function (DataTable $table) { |
| $table->deleteMetadata(DataTable::ARCHIVE_STATE_METADATA_NAME); |
| }); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| |
| |
| |
| public function applyPivotByFilter(DataTableInterface $dataTable) |
| { |
| $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request); |
| if (!empty($pivotBy)) { |
| $this->applyComputeProcessedMetrics($dataTable); |
| $dataTable = $this->convertSegmentValueToSegment($dataTable); |
|
|
| $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request); |
| $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request); |
|
|
| $dataTable->filter('PivotByDimension', array($this->report, $pivotBy, $pivotByColumn, $pivotByColumnLimit, |
| PivotByDimension::isSegmentFetchingEnabledInConfig())); |
| $dataTable->filter('ColumnCallbackDeleteMetadata', array('segment')); |
| } |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyFlattener($dataTable) |
| { |
| if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') { |
| |
| if ($this->report && !$this->report->supportsFlatten()) { |
| $dataTable->filter('RemoveSubtables'); |
| return $dataTable; |
| } |
|
|
| $flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request); |
| if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') { |
| $flattener->includeAggregateRows(); |
| } |
|
|
| $recursiveLabelSeparator = ' - '; |
| if ($this->report) { |
| $recursiveLabelSeparator = $this->report->getRecursiveLabelSeparator(); |
| } |
|
|
| $showDimensions = (new Request($this->request))->getBoolParameter('show_dimensions', false); |
|
|
| $dataTable = $flattener->flatten($dataTable, $recursiveLabelSeparator, $showDimensions); |
| } |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyTotalsCalculator($dataTable) |
| { |
| if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) { |
| $calculator = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request, $this->report); |
| $dataTable = $calculator->calculate($dataTable); |
| } |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyGenericFilters($dataTable) |
| { |
| |
| if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) { |
| $this->applyProcessedMetricsGenericFilters($dataTable); |
|
|
| $genericFilter = new DataTableGenericFilter($this->request, $this->report); |
|
|
| $self = $this; |
| $report = $this->report; |
| $dataTable->filter(function (DataTable $table) use ($genericFilter, $report, $self) { |
| $processedMetrics = Report::getProcessedMetricsForTable($table, $report); |
| if ($genericFilter->areProcessedMetricsNeededFor($processedMetrics)) { |
| $self->computeProcessedMetrics($table); |
| } |
| }); |
|
|
| $label = self::getLabelFromRequest($this->request); |
| if (!empty($label)) { |
| $genericFilter->disableFilters(array('Limit', 'Truncate')); |
| } |
|
|
| $genericFilter->filter($dataTable); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyProcessedMetricsGenericFilters($dataTable) |
| { |
| $addNormalProcessedMetrics = null; |
| try { |
| $addNormalProcessedMetrics = Common::getRequestVar( |
| 'filter_add_columns_when_show_all_columns', |
| null, |
| 'integer', |
| $this->request |
| ); |
| } catch (Exception $ex) { |
| |
| } |
|
|
| if ($addNormalProcessedMetrics !== null) { |
| $dataTable->filter('AddColumnsProcessedMetrics', array($addNormalProcessedMetrics)); |
| } |
|
|
| $addGoalProcessedMetrics = null; |
| try { |
| $addGoalProcessedMetrics = Common::getRequestVar( |
| 'filter_update_columns_when_show_all_goals', |
| false, |
| 'string', |
| $this->request |
| ); |
| if ( |
| (int) $addGoalProcessedMetrics === 0 |
| && $addGoalProcessedMetrics !== '0' |
| && $addGoalProcessedMetrics != Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER |
| && $addGoalProcessedMetrics != Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART |
| ) { |
| $addGoalProcessedMetrics = null; |
| } |
| } catch (Exception $ex) { |
| |
| } |
|
|
| $goalsToProcess = null; |
| try { |
| $goalsToProcess = Common::getRequestVar('filter_show_goal_columns_process_goals', null, 'string', $this->request); |
| $goalsToProcess = explode(',', $goalsToProcess); |
| $goalsToProcess = array_map('trim', $goalsToProcess); |
| $goalsToProcess = array_filter($goalsToProcess); |
| } catch (Exception $ex) { |
| |
| } |
|
|
| if ($addGoalProcessedMetrics !== null) { |
| |
| |
| |
| if ( |
| !empty($goalsToProcess) |
| && count($goalsToProcess) == 1 |
| && $goalsToProcess[0] !== '0' |
| && $goalsToProcess[0] !== 0 |
| ) { |
| $defaultIdGoal = $goalsToProcess[0]; |
| } else { |
| $defaultIdGoal = DataTable\Filter\AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW; |
| } |
|
|
| $idGoal = Common::getRequestVar('idGoal', $defaultIdGoal, 'string', $this->request); |
|
|
| $dataTable->filter('AddColumnsProcessedMetricsGoal', array($ignore = true, $idGoal, $goalsToProcess)); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyQueuedFilters($dataTable) |
| { |
| |
| if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) { |
| $dataTable->applyQueuedFilters(); |
| } |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| public function applyRequestedColumnDeletion($dataTable) |
| { |
| |
| |
| $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); |
| $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request); |
| $hideColumnsRecursively = Common::getRequestVar('hideColumnsRecursively', intval($this->report && $this->report->getModule() == 'Live'), 'int', $this->request); |
| $showRawMetrics = Common::getRequestVar('showRawMetrics', 0, 'int', $this->request); |
| if ( |
| !empty($hideColumns) |
| || !empty($showColumns) |
| ) { |
| $dataTable->filter('ColumnDelete', array($hideColumns, $showColumns, $deleteIfZeroOnly = false, $hideColumnsRecursively)); |
| } elseif ($showRawMetrics !== 1) { |
| $this->removeTemporaryMetrics($dataTable); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| public function removeTemporaryMetrics(DataTableInterface $dataTable) |
| { |
| $report = $this->report; |
| $dataTable->filter(function (DataTable $table) use ($report) { |
| $processedMetrics = Report::getProcessedMetricsForTable($table, $report); |
|
|
| $allTemporaryMetrics = array(); |
| foreach ($processedMetrics as $metric) { |
| $allTemporaryMetrics = array_merge($allTemporaryMetrics, $metric->getTemporaryMetrics()); |
| } |
|
|
| if (!empty($allTemporaryMetrics)) { |
| $table->filter('ColumnDelete', array($allTemporaryMetrics)); |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| public function applyLabelFilter($dataTable) |
| { |
| $label = self::getLabelFromRequest($this->request); |
|
|
| |
| if (!empty($label)) { |
| $addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $this->request) == 1; |
|
|
| $labelColumn = 'label'; |
| if ($this->report) { |
| |
| |
| $dataTableRowIdentifier = null; |
| $dataTable->filter(function (DataTable $table) use (&$dataTableRowIdentifier) { |
| $dataTableRowIdentifier = $dataTableRowIdentifier ?: $table->getMetadata(DataTable::ROW_IDENTIFIER_METADATA_NAME); |
| }); |
|
|
| $labelColumn = $dataTableRowIdentifier ?: $this->report->getRowIdentifier() ?: $labelColumn; |
| } |
|
|
| $filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request, $labelColumn); |
| $dataTable = $filter->filter($label, $dataTable, $addLabelIndex); |
| } |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| |
| public function applyMetricsFormatting($dataTable, bool $forceFormatting = false) |
| { |
| $formatMetrics = Common::getRequestVar('format_metrics', 0, 'string', $this->request); |
| if ($formatMetrics == '0' && $forceFormatting === false) { |
| return $dataTable; |
| } |
|
|
| |
| |
| $onlyFormatPercents = $forceFormatting === false && $formatMetrics === 'bc'; |
|
|
| $metricsToFormat = null; |
| if ($onlyFormatPercents) { |
| $metricsToFormat = $this->apiInconsistencies->getPercentMetricsToFormat(); |
| } |
|
|
| |
| |
| $formatAll = $forceFormatting === true || $formatMetrics === 'all'; |
|
|
| $dataTable->filter([$this->formatter, 'formatMetrics'], [$this->report, $metricsToFormat, $formatAll]); |
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public static function getLabelFromRequest($request) |
| { |
| $label = Common::getRequestVar('label', array(), 'array', $request); |
| if (empty($label)) { |
| $label = Common::getRequestVar('label', '', 'string', $request); |
| if (!empty($label)) { |
| $label = array($label); |
| } |
| } |
|
|
| $label = self::unsanitizeLabelParameter($label); |
| return $label; |
| } |
|
|
| public static function unsanitizeLabelParameter($label) |
| { |
| |
| |
| |
| $label = str_replace(htmlentities('>', ENT_COMPAT | ENT_HTML401, 'UTF-8'), '>', $label); |
| return $label; |
| } |
|
|
| public function computeProcessedMetrics(DataTable $dataTable) |
| { |
| if ($dataTable->getMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG)) { |
| return; |
| } |
|
|
| |
| $processedMetrics = Report::getProcessedMetricsForTable($dataTable, $this->report); |
| if (empty($processedMetrics)) { |
| return; |
| } |
|
|
| $dataTable->setMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG, true); |
|
|
| foreach ($processedMetrics as $name => $processedMetric) { |
| if (!$processedMetric->beforeCompute($this->report, $dataTable)) { |
| continue; |
| } |
|
|
| foreach ($dataTable->getRows() as $row) { |
| if ($row->hasColumn($name)) { |
| continue; |
| } |
|
|
| $computedValue = $processedMetric->compute($row); |
| if ($computedValue !== false) { |
| $row->addColumn($name, $computedValue); |
|
|
| |
| if ($processedMetric instanceof EvolutionMetric) { |
| $row->addColumn($processedMetric->getTrendName(), $processedMetric->getTrendValue($computedValue)); |
| } |
| } |
| } |
| } |
|
|
| foreach ($dataTable->getRows() as $row) { |
| $subtable = $row->getSubtable(); |
| if (!empty($subtable)) { |
| foreach ($processedMetrics as $name => $processedMetric) { |
| $processedMetric->beforeComputeSubtable($row); |
| } |
|
|
| $this->computeProcessedMetrics($subtable); |
|
|
| foreach ($processedMetrics as $name => $processedMetric) { |
| $processedMetric->afterComputeSubtable($row); |
| } |
| } |
| } |
| } |
|
|
| public function applyComputeProcessedMetrics(DataTableInterface $dataTable) |
| { |
| $dataTable->filter(array($this, 'computeProcessedMetrics')); |
| } |
|
|
| public function applyComparison(DataTableInterface $dataTable) |
| { |
| $compare = Common::getRequestVar('compare', '0', 'int', $this->request); |
| if ($compare != 1) { |
| return $dataTable; |
| } |
|
|
| $filter = new DataComparisonFilter($this->request, $this->report); |
| $filter->compare($dataTable); |
|
|
| $dataTable->filter(function (DataTable $table) { |
| foreach ($table->getRows() as $row) { |
| $comparisons = $row->getComparisons(); |
| if (!empty($comparisons)) { |
| $this->computeProcessedMetrics($comparisons); |
| } |
| } |
| }); |
|
|
| return $dataTable; |
| } |
| } |
|
|