Newer
Older
diosmosis
a validé
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\API;
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\Plugin\ProcessedMetric;
use Piwik\Plugin\Report;
/**
* Processes DataTables that should be served through Piwik's APIs. This processing handles
* special query parameters and computes processed metrics. It does not included rendering to
* output formates (eg, 'xml').
*/
class DataTablePostProcessor
{
const PROCESSED_METRICS_FORMATTED_FLAG = 'processed_metrics_formatted';
const PROCESSED_METRICS_COMPUTED_FLAG = 'processed_metrics_computed';
/**
* @var null|Report
*/
private $report;
/**
* @var string[]
*/
private $request;
/**
* @var string
*/
private $apiModule;
/**
* @var string
*/
private $apiMethod;
/**
* Constructor.
*/
public function __construct($apiModule, $apiMethod, $request)
{
$this->apiModule = $apiModule;
$this->apiMethod = $apiMethod;
$this->request = $request;
$this->report = Report::factory($apiModule, $apiMethod);
}
diosmosis
a validé
/**
* Apply post-processing logic to a DataTable of a report for an API request.
*
* @param DataTableInterface $dataTable The data table to process.
* @param Report|null $report The Report metadata class for the DataTable's report, or null if
* there is none.
* @param string[] $request The API request that
* @param bool $applyFormatting Whether to format processed metrics or not.
* @return DataTableInterface A new data table.
*/
public function process(DataTableInterface $dataTable, $applyFormatting = true)
diosmosis
a validé
{
diosmosis
a validé
$dataTable = $this->applyPivotByFilter($dataTable);
$dataTable = $this->applyFlattener($dataTable);
$dataTable = $this->applyTotalsCalculator($dataTable);
$dataTable = $this->applyGenericFilters($label, $dataTable);
diosmosis
a validé
diosmosis
a validé
// we automatically safe decode all datatable labels (against xss)
$dataTable->queueFilter('SafeDecodeLabel');
$dataTable = $this->applyQueuedFilters($dataTable);
$dataTable = $this->applyRequestedColumnDeletion($dataTable);
$dataTable = $this->applyLabelFilter($label, $dataTable);
$dataTable = $this->applyProcessedMetricsFormatting($dataTable, $applyFormatting);
diosmosis
a validé
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
private function applyPivotByFilter(DataTableInterface $dataTable)
diosmosis
a validé
{
$pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request);
diosmosis
a validé
if (!empty($pivotBy)) {
$this->applyComputeProcessedMetrics($dataTable);
$reportId = $this->apiModule . '.' . $this->apiMethod;
$pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request);
$pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request);
diosmosis
a validé
$dataTable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit,
PivotByDimension::isSegmentFetchingEnabledInConfig()));
}
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTable|DataTableInterface|DataTable\Map
*/
diosmosis
a validé
{
if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') {
$flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request);
if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') {
diosmosis
a validé
$flattener->includeAggregateRows();
}
$dataTable = $flattener->flatten($dataTable);
}
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
diosmosis
a validé
{
if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) {
$reportTotalsCalculator = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request);
diosmosis
a validé
$dataTable = $reportTotalsCalculator->calculate($dataTable);
}
return $dataTable;
}
/**
* @param string $label
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
private function applyGenericFilters($label, $dataTable)
diosmosis
a validé
{
// if the flag disable_generic_filters is defined we skip the generic filters
if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) {
$genericFilter = new DataTableGenericFilter($this->request);
diosmosis
a validé
$self = $this;
$report = $this->report;
$dataTable->filter(function (DataTable $table) use ($genericFilter, $report, $self) {
$processedMetrics = $this->getProcessedMetricsFor($table, $report);
if ($genericFilter->areProcessedMetricsNeededFor($processedMetrics)) {
diosmosis
a validé
if (!empty($label)) {
$genericFilter->disableFilters(array('Limit', 'Truncate'));
}
$genericFilter->filter($dataTable);
}
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
diosmosis
a validé
{
// if the flag disable_queued_filters is defined we skip the filters that were queued
if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) {
diosmosis
a validé
$dataTable->applyQueuedFilters();
}
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
private function applyRequestedColumnDeletion($dataTable)
diosmosis
a validé
{
// use the ColumnDelete filter if hideColumns/showColumns is provided (must be done
// after queued filters are run so processed metrics can be removed, too)
$hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request);
$showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request);
diosmosis
a validé
if (empty($showColumns)) {
// if 'columns' is used, we remove all temporary metrics by showing only the columns specified in
// 'columns'
$showColumns = Common::getRequestVar('columns', '', 'string', $this->request);
diosmosis
a validé
}
if (!empty($hideColumns)
|| !empty($showColumns)
) {
$dataTable->filter('ColumnDelete', array($hideColumns, $showColumns));
}
return $dataTable;
}
/**
* @param string $label
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
diosmosis
a validé
{
// apply label filter: only return rows matching the label parameter (more than one if more than one label)
if (!empty($label)) {
$addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $this->request) == 1;
diosmosis
a validé
$filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request);
diosmosis
a validé
$dataTable = $filter->filter($label, $dataTable, $addLabelIndex);
}
return $dataTable;
}
/**
* @param DataTableInterface $dataTable
* @return DataTableInterface
*/
private function applyProcessedMetricsFormatting($dataTable, $applyFormatting)
diosmosis
a validé
{
if ($applyFormatting) {
$dataTable->filter(array($this, 'formatProcessedMetrics'));
diosmosis
a validé
} else {
$dataTable->queueFilter(array($this, 'formatProcessedMetrics'));
diosmosis
a validé
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
}
return $dataTable;
}
/**
* Returns the value for the label query parameter which can be either a string
* (ie, label=...) or array (ie, label[]=...).
*
* @param array $request
* @return array
*/
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)
{
// this is needed because Proxy uses Common::getRequestVar which in turn
// uses Common::sanitizeInputValue. This causes the > that separates recursive labels
// to become > and we need to undo that here.
$label = Common::unsanitizeInputValues($label);
return $label;
}
public function computeProcessedMetrics(DataTable $dataTable)
diosmosis
a validé
{
if ($dataTable->getMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG)) {
return;
}
$dataTable->setMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG, true);
$processedMetrics = $this->getProcessedMetricsFor($dataTable, $this->report);
diosmosis
a validé
if (empty($processedMetrics)) {
return;
}
foreach ($processedMetrics as $name => $processedMetric) {
if (!$processedMetric->beforeCompute($this->report, $dataTable)) {
diosmosis
a validé
continue;
}
foreach ($dataTable->getRows() as $row) {
if ($row->getColumn($name) === false) { // do not compute the metric if it has been computed already
$row->addColumn($name, $processedMetric->compute($row));
$subtable = $row->getSubtable();
if (!empty($subtable)) {
$this->computeProcessedMetrics($subtable, $this->report);
diosmosis
a validé
}
}
}
}
}
/**
* public for use as callback.
*/
public function formatProcessedMetrics(DataTable $dataTable)
diosmosis
a validé
{
if ($dataTable->getMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG)) {
return;
}
$dataTable->setMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG, true);
$processedMetrics = $this->getProcessedMetricsFor($dataTable, $this->report);
diosmosis
a validé
if (empty($processedMetrics)) {
return;
}
foreach ($dataTable->getRows() as $row) {
foreach ($processedMetrics as $name => $processedMetric) {
$columnValue = $row->getColumn($name);
if ($columnValue !== false) {
$row->setColumn($name, $processedMetric->format($columnValue));
}
$subtable = $row->getSubtable();
if (!empty($subtable)) {
$this->formatProcessedMetrics($subtable, $this->report);
diosmosis
a validé
}
}
}
}
/**
* @param DataTable $dataTable
* @param Report $report
* @return ProcessedMetric[]
*/
private function getProcessedMetricsFor(DataTable $dataTable, $report)
{
$processedMetrics = $dataTable->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME) ?: array();
if (!empty($report)) {
$processedMetrics = array_merge($processedMetrics, $report->getProcessedMetricsById());
diosmosis
a validé
}
$result = array();
foreach ($processedMetrics as $metric) {
if (!($metric instanceof ProcessedMetric)) {
continue;
}
$result[$metric->getName()] = $metric;
}
return $result;
}
private function applyComputeProcessedMetrics(DataTableInterface $dataTable)
{
$dataTable->filter(array($this, 'computeProcessedMetrics'));
}