diff --git a/core/API/DataTableGenericFilter.php b/core/API/DataTableGenericFilter.php index 2dc9ee82c16182f7e159512f49d186147833b8b6..a87f3305b0244a6df18705d0d6ec8c887c3642f6 100644 --- a/core/API/DataTableGenericFilter.php +++ b/core/API/DataTableGenericFilter.php @@ -12,7 +12,6 @@ use Exception; use Piwik\Common; use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal; use Piwik\DataTable; -use Piwik\Plugin\ProcessedMetric; use Piwik\Plugin\Report; class DataTableGenericFilter @@ -24,22 +23,14 @@ class DataTableGenericFilter */ private $disabledFilters = array(); - /** - * TODO - * - * @var Report|null- - */ - private $report; - /** * Constructor * * @param $request */ - function __construct($request, $report = null) + function __construct($request) { $this->request = $request; - $this->report = $report; } /** @@ -178,27 +169,7 @@ class DataTableGenericFilter return $filterApplied; } - public function computeProcessedMetricsIfNeeded(DataTable $dataTable) - { - if (!$this->doesColumnQueryParamReferenceProcessedMetric()) { - return false; - } - - $this->computeProcessedMetrics($dataTable); - - return true; - } - - public function computeProcessedMetrics(DataTable $dataTable) - { - if (empty($this->report)) { - return; - } - - $this->report->computeProcessedMetrics($dataTable); - } - - private function doesColumnQueryParamReferenceProcessedMetric() + public function areProcessedMetricsNeededFor(Report $report) { $columnQueryParameters = array( 'filter_column', @@ -208,9 +179,9 @@ class DataTableGenericFilter ); foreach ($columnQueryParameters as $queryParamName) { - $queryParamValue = Common::getRequestVar($queryParamName, false); + $queryParamValue = Common::getRequestVar($queryParamName, false, $type = null, $this->request); if (!empty($queryParamValue) - && $this->report->hasProcessedMetric($queryParamValue) + && $report->hasProcessedMetric($queryParamValue) ) { return true; } diff --git a/core/API/DataTablePostProcessor.php b/core/API/DataTablePostProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..6b80082f69679166d362b7ca0774f7f3fb979733 --- /dev/null +++ b/core/API/DataTablePostProcessor.php @@ -0,0 +1,336 @@ +<?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'; + + /** + * 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, $report, $request, $applyFormatting = true) + { + $label = self::getLabelFromRequest($request); + + $dataTable = $this->applyPivotByFilter($dataTable, $report, $request); + $dataTable = $this->applyFlattener($dataTable, $report, $request); + $dataTable = $this->applyTotalsCalculator($dataTable, $report, $request); + $dataTable = $this->applyGenericFilters($label, $dataTable, $report, $request); + + $dataTable->filter(array($this, 'computeProcessedMetrics'), array($report)); + + // we automatically safe decode all datatable labels (against xss) + $dataTable->queueFilter('SafeDecodeLabel'); + + $dataTable = $this->applyQueuedFilters($dataTable, $request); + $dataTable = $this->applyRequestedColumnDeletion($dataTable, $request); + $dataTable = $this->applyLabelFilter($label, $dataTable, $report, $request); + $dataTable = $this->applyProcessedMetricsFormatting($dataTable, $report, $applyFormatting); + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param Report|null $report + * @param $request + * @return DataTableInterface + */ + private function applyPivotByFilter(DataTableInterface $dataTable, $report, $request) + { + $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $request); + if (!empty($pivotBy)) { + $reportId = $report->getModule() . '.' . $report->getAction(); + $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $request); + $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $request); + + $dataTable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit, + PivotByDimension::isSegmentFetchingEnabledInConfig())); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param Report|null $report + * @param $request + * @return DataTable|DataTableInterface|DataTable\Map + */ + private function applyFlattener($dataTable, $report, $request) + { + if (Common::getRequestVar('flat', '0', 'string', $request) == '1') { + $flattener = new Flattener($report->getModule(), $report->getAction(), $request); + if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $request) == '1') { + $flattener->includeAggregateRows(); + } + $dataTable = $flattener->flatten($dataTable); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param Report|null $report + * @param $request + * @return DataTableInterface + */ + private function applyTotalsCalculator($dataTable, $report, $request) + { + if (1 == Common::getRequestVar('totals', '1', 'integer', $request)) { + $reportTotalsCalculator = new ReportTotalsCalculator($report->getModule(), $report->getAction(), $request); + $dataTable = $reportTotalsCalculator->calculate($dataTable); + } + return $dataTable; + } + + /** + * @param string $label + * @param DataTableInterface $dataTable + * @param Report|null $report + * @param $request + * @return DataTableInterface + */ + private function applyGenericFilters($label, $dataTable, $report, $request) + { + // if the flag disable_generic_filters is defined we skip the generic filters + if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $request)) { + $self = $this; + + $genericFilter = new DataTableGenericFilter($request, $report); + + if ($genericFilter->areProcessedMetricsNeededFor($report)) { + $dataTable->filter(function (DataTable $table) use ($self, $report) { + $self->computeProcessedMetrics($table, $report); + }); + } + + if (!empty($label)) { + $genericFilter->disableFilters(array('Limit', 'Truncate')); + } + + $genericFilter->filter($dataTable); + } + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param $request + * @return DataTableInterface + */ + private function applyQueuedFilters($dataTable, $request) + { + // if the flag disable_queued_filters is defined we skip the filters that were queued + if (Common::getRequestVar('disable_queued_filters', 0, 'int', $request) == 0) { + $dataTable->applyQueuedFilters(); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param $request + * @return DataTableInterface + */ + private function applyRequestedColumnDeletion($dataTable, $request) + { + // 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', $request); + $showColumns = Common::getRequestVar('showColumns', '', 'string', $request); + 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', $request); + } + + if (!empty($hideColumns) + || !empty($showColumns) + ) { + $dataTable->filter('ColumnDelete', array($hideColumns, $showColumns)); + } + + return $dataTable; + } + + /** + * @param string $label + * @param DataTableInterface $dataTable + * @param Report $report + * @return DataTableInterface + */ + private function applyLabelFilter($label, $dataTable, $report, $request) + { + // 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', $request) == 1; + + $filter = new LabelFilter($report->getModule(), $report->getAction(), $request); + $dataTable = $filter->filter($label, $dataTable, $addLabelIndex); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @param Report $report + * @return DataTableInterface + */ + private function applyProcessedMetricsFormatting($dataTable, $report, $applyFormatting) + { + if ($applyFormatting) { + $dataTable->filter(array($this, 'formatProcessedMetrics'), array($report)); + } else { + $dataTable->queueFilter(array($this, 'formatProcessedMetrics'), array($report)); // TODO: queuing does not always work. + } + + 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; + } + + private function computeProcessedMetrics(DataTable $dataTable, $report) + { + if ($dataTable->getMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG)) { + return; + } + + $dataTable->setMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG, true); + + $processedMetrics = $this->getProcessedMetricsFor($dataTable, $report); + if (empty($processedMetrics)) { + return; + } + + foreach ($processedMetrics as $name => $processedMetric) { + if (!$processedMetric->beforeCompute($this, $dataTable)) { + 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, $report); + } + } + } + } + } + + /** + * public for use as callback. + */ + public function formatProcessedMetrics(DataTable $dataTable, $report) + { + if ($dataTable->getMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG)) { + return; + } + + $dataTable->setMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG, true); + + $processedMetrics = $this->getProcessedMetricsFor($dataTable, $report); + 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, $report); + } + } + } + } + + /** + * @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->processedMetrics ?: array()); + } + + $result = array(); + foreach ($processedMetrics as $metric) { + if (!($metric instanceof ProcessedMetric)) { + continue; + } + + $result[$metric->getName()] = $metric; + } + return $result; + } +} \ No newline at end of file diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index 248c4401aea27f94e9dc15224169445d8456fbde..dbf79b1ec8a306894edf4a1e6e0a654e54b24a5d 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -9,12 +9,8 @@ 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\Filter\PivotByDimension; use Piwik\DataTable\Renderer; use Piwik\DataTable\DataTableInterface; use Piwik\DataTable\Filter\ColumnDelete; @@ -33,6 +29,8 @@ class ResponseBuilder private $apiModule = false; private $apiMethod = false; + private $postProcessor; + /** * @param string $outputFormat * @param array $request @@ -42,6 +40,7 @@ class ResponseBuilder $this->outputFormat = $outputFormat; $this->request = $request; $this->apiRenderer = ApiRenderer::factory($outputFormat, $request); + $this->postProcessor = new DataTablePostProcessor(); } public function disableSendHeader() @@ -168,90 +167,10 @@ class ResponseBuilder private function handleDataTable(DataTableInterface $datatable) { - $label = $this->getLabelFromRequest($this->request); $report = Report::factory($this->apiModule, $this->apiMethod); + $applyFormatting = !($this->apiRenderer instanceof Original); - $genericFilter = new DataTableGenericFilter($this->request, $report); - - // handle pivot by dimension filter - $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request); - if (!empty($pivotBy)) { - $reportId = $this->apiModule . '.' . $this->apiMethod; - $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request); - $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request); - - $datatable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit, - PivotByDimension::isSegmentFetchingEnabledInConfig())); - } - - // if requested, flatten nested tables - 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') { - $flattener->includeAggregateRows(); - } - $datatable = $flattener->flatten($datatable); - } - - if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) { - $reportTotalsCalculator = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request); - $datatable = $reportTotalsCalculator->calculate($datatable); - } - - // if the flag disable_generic_filters is defined we skip the generic filters - if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) { - $datatable->filter(function (DataTable $table) use ($genericFilter) { - $genericFilter->computeProcessedMetricsIfNeeded($table); - }); - - if (!empty($label)) { - $genericFilter->disableFilters(array('Limit', 'Truncate')); - } - - $genericFilter->filter($datatable); - } - - $datatable->filter(function (DataTable $table) use ($genericFilter) { - $genericFilter->computeProcessedMetrics($table); - }); - - // we automatically safe decode all datatable labels (against xss) - $datatable->queueFilter('SafeDecodeLabel'); - - // 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) { - $datatable->applyQueuedFilters(); - } - - // 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); - if (empty($showColumns)) { - $showColumns = Common::getRequestVar('columns', '', 'string', $this->request); // TODO: note backwards compatibility - } - - if (!empty($hideColumns) - || !empty($showColumns) - ) { - $datatable->filter('ColumnDelete', array($hideColumns, $showColumns)); - } - - // 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; - - $filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request); - $datatable = $filter->filter($label, $datatable, $addLabelIndex); - } - - if (!empty($report)) { - if (!($this->apiRenderer instanceof Original)) { - $datatable->filter(array($report, 'formatProcessedMetrics')); - } else { - $datatable->queueFilter(array($report, 'formatProcessedMetrics')); // TODO: queuing does not always work. - } - } + $datatable = $this->postProcessor->process($datatable, $report, $this->request, $applyFormatting); return $this->apiRenderer->renderDataTable($datatable); } @@ -286,36 +205,6 @@ class ResponseBuilder return $this->apiRenderer->renderArray($array); } - /** - * 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; - } - private function sendHeaderIfEnabled() { if ($this->sendHeader) { diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php index fb009f06a25b1cef9c8edd8d356f6fd624dd148e..11b50fc3efd2305b2768cce9bcfe9afc99cf8cfa 100644 --- a/core/Plugin/Report.php +++ b/core/Plugin/Report.php @@ -121,10 +121,8 @@ class Report * Eg `array('avg_time_on_site', 'nb_actions_per_visit', ...)` * @var array|false * @api - * - * TODO: shouldn't be public */ - public $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate'); + protected $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate'); // for a little performance improvement we avoid having to call Metrics::getDefaultProcessedMetrics for each report /** @@ -361,7 +359,18 @@ class Report } /** - * TODO + * Returns the list of metrics required at minimum for a report factoring in the columns requested by + * the report requester. + * + * This will return all the metrics requested (or all the metrics in the report if nothing is requested) + * **plus** the metrics required to calculate the requested processed metrics. + * + * This method should be used in **Plugin.get** API methods. + * + * @param string[]|null $allColumns The list of all available columns. Defaults to this report's metrics + * and processed metrics. + * @param string[]|null $restrictToColumns The requested columns. + * @return string[] */ public function getMetricsRequiredForReport($allColumns = null, $restrictToColumns = null) { @@ -697,97 +706,13 @@ class Report } /** - * TODO + * Returns true if this report has a processed metric with the `$name` name. * - * @return ProcessedMetric[] - */ - public function getProcessedMetricsFor(DataTable $dataTable) - { - $dataTableProcessedMetrics = $dataTable->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME) ?: array(); - - $processedMetrics = $this->processedMetrics ?: array(); - $processedMetrics = array_merge($processedMetrics, $dataTableProcessedMetrics); - - $result = array(); - foreach ($processedMetrics as $metric) { - if (!($metric instanceof ProcessedMetric)) { - continue; - } - - $result[$metric->getName()] = $metric; - } - return $result; - } - - /** - * TODO + * Will only search through {@link Piwik\Plugin\ProcessedMetric} instances, so string entries + * in {@link $processedMetrics} will be ignored. * - * TODO: put in new non-filter class. do not mark w/ @api. - */ - public function computeProcessedMetrics(DataTable $dataTable) - { - if ($dataTable->getMetadata('processed_metrics_computed')) { - return; - } - - $dataTable->setMetadata('processed_metrics_computed', true); // TODO: metadataname should be const - - $processedMetrics = $this->getProcessedMetricsFor($dataTable); - if (empty($processedMetrics)) { - return; - } - - foreach ($processedMetrics as $name => $processedMetric) { - if (!$processedMetric->beforeCompute($this, $dataTable)) { - 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); - } - } - } - } - } - - /** - * TODO - */ - public function formatProcessedMetrics(DataTable $dataTable) - { - if ($dataTable->getMetadata('processed_metrics_formatted')) { - return; - } - - $dataTable->setMetadata('processed_metrics_formatted', true); // TODO: metadataname should be const - - $processedMetrics = $this->getProcessedMetricsFor($dataTable); - 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); - } - } - } - } - - /** - * TODO + * @param string $name + * @return bool */ public function hasProcessedMetric($name) { diff --git a/plugins/API/RowEvolution.php b/plugins/API/RowEvolution.php index b57a788050d54e553497ee5cf85e1fc1c228866e..c4d96fb9126c842a90f36456481e0096d35f165e 100644 --- a/plugins/API/RowEvolution.php +++ b/plugins/API/RowEvolution.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\API; use Exception; use Piwik\API\DataTableManipulator\LabelFilter; +use Piwik\API\DataTablePostProcessor; use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Common; @@ -48,7 +49,7 @@ class RowEvolution throw new Exception("Row evolutions can not be processed with this combination of \'date\' and \'period\' parameters."); } - $label = ResponseBuilder::unsanitizeLabelParameter($label); + $label = DataTablePostProcessor::unsanitizeLabelParameter($label); $labels = Piwik::getArrayFromApiParameter($label); $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php index 6f2db7ab9dbb1476ac6f936922a6743164087f19..2e2631203a2f9e48378887e5384cfd6587a360d3 100644 --- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php +++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\CoreHome\DataTableRowAction; use Exception; +use Piwik\API\DataTablePostProcessor; use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Common; @@ -85,7 +86,7 @@ class RowEvolution $this->apiMethod = Common::getRequestVar('apiMethod', '', 'string'); if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set."); - $this->label = ResponseBuilder::getLabelFromRequest($_GET); + $this->label = DataTablePostProcessor::getLabelFromRequest($_GET); if (!is_array($this->label)) { throw new Exception("Expected label to be an array, got instead: " . $this->label); }