<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */
namespace Piwik\API;

use Exception;
use Piwik\Common;
use Piwik\DataTable;
use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugin\Report;
class DataTableGenericFilter
{
    /**
     * List of filter names not to run.
     *
     * @var string[]
     */
    private $disabledFilters = array();
    /**
     * @var Report
     */
    private $report;
    /**
     * @var array
     */
    private $request;
    /**
     * @param array $request
     * @param Report $report
     */
    public function __construct($request, $report)
    {
        $this->request = $request;
        $this->report = $report;
    }
    /**
     * Filters the given data table
     *
     * @param DataTable $table
     */
    public function filter($table)
    {
        $this->applyGenericFilters($table);
    }
    /**
     * Makes sure a set of filters are not run.
     *
     * @param string[] $filterNames The name of each filter to disable.
     */
    public function disableFilters($filterNames)
    {
        $this->disabledFilters = array_unique(array_merge($this->disabledFilters, $filterNames));
    }
    /**
     * Returns an array containing the information of the generic Filter
     * to be applied automatically to the data resulting from the API calls.
     *
     * Order to apply the filters:
     * 1 - Filter that remove filtered rows
     * 2 - Filter that sort the remaining rows
     * 3 - Filter that keep only a subset of the results
     * 4 - Presentation filters
     *
     * @return array  See the code for spec
     */
    public static function getGenericFiltersInformation()
    {
        return array(array('Pattern', array('filter_column' => array('string', 'label'), 'filter_pattern' => array('string'))), array('PatternRecursive', array('filter_column_recursive' => array('string', 'label'), 'filter_pattern_recursive' => array('string'))), array('ExcludeLowPopulation', array('filter_excludelowpop' => array('string'), 'filter_excludelowpop_value' => array('float', '0'))), array('Sort', array('filter_sort_column' => array('string'), 'filter_sort_order' => array('string', 'desc'), $naturalSort = \true, $recursiveSort = \true, 'filter_sort_column_secondary' => \true)), array('Truncate', array('filter_truncate' => array('integer'))), array('Limit', array('filter_offset' => array('integer', '0'), 'filter_limit' => array('integer'), 'keep_summary_row' => array('integer', '0'))));
    }
    private function getGenericFiltersHavingDefaultValues()
    {
        $filters = self::getGenericFiltersInformation();
        if ($this->report && $this->report->getDefaultSortColumn()) {
            foreach ($filters as $index => $filter) {
                if ($filter[0] === 'Sort') {
                    $filters[$index][1]['filter_sort_column'] = array('string', $this->report->getDefaultSortColumn());
                    $filters[$index][1]['filter_sort_order'] = array('string', $this->report->getDefaultSortOrder());
                    $callback = $this->report->getSecondarySortColumnCallback();
                    if (is_callable($callback)) {
                        $filters[$index][1]['filter_sort_column_secondary'] = $callback;
                    }
                }
            }
        }
        return $filters;
    }
    /**
     * Apply generic filters to the DataTable object resulting from the API Call.
     * Disable this feature by setting the parameter disable_generic_filters to 1 in the API call request.
     *
     * @param DataTable $datatable
     * @return bool|null
     */
    protected function applyGenericFilters($datatable)
    {
        if ($datatable instanceof DataTable\Map) {
            $tables = $datatable->getDataTables();
            foreach ($tables as $table) {
                $this->applyGenericFilters($table);
            }
            return null;
        }
        $tableDisabledFilters = $datatable->getMetadata(DataTable::GENERIC_FILTERS_TO_DISABLE_METADATA_NAME) ?: [];
        $genericFilters = $this->getGenericFiltersHavingDefaultValues();
        $filterApplied = \false;
        foreach ($genericFilters as $filterMeta) {
            $filterName = $filterMeta[0];
            $filterParams = $filterMeta[1];
            $filterParameters = array();
            $exceptionRaised = \false;
            if (in_array($filterName, $this->disabledFilters) || in_array($filterName, $tableDisabledFilters)) {
                continue;
            }
            foreach ($filterParams as $name => $info) {
                if (!is_array($info)) {
                    // hard coded value that cannot be changed via API, see eg $naturalSort = true in 'Sort'
                    $filterParameters[] = $info;
                } else {
                    // parameter type to cast to
                    $type = $info[0];
                    // default value if specified, when the parameter doesn't have a value
                    $defaultValue = null;
                    if (isset($info[1])) {
                        $defaultValue = $info[1];
                    }
                    try {
                        $value = Common::getRequestVar($name, $defaultValue, $type, $this->request);
                        settype($value, $type);
                        $filterParameters[] = $value;
                    } catch (Exception $e) {
                        $exceptionRaised = \true;
                        break;
                    }
                }
            }
            if (!$exceptionRaised) {
                $datatable->filter($filterName, $filterParameters);
                $filterApplied = \true;
            }
        }
        return $filterApplied;
    }
    public function areProcessedMetricsNeededFor($metrics)
    {
        $columnQueryParameters = array('filter_column', 'filter_column_recursive', 'filter_excludelowpop', 'filter_sort_column');
        foreach ($columnQueryParameters as $queryParamName) {
            $queryParamValue = Common::getRequestVar($queryParamName, \false, $type = null, $this->request);
            if (!empty($queryParamValue) && $this->containsProcessedMetric($metrics, $queryParamValue)) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * @param ProcessedMetric[] $metrics
     * @param string $name
     * @return bool
     */
    private function containsProcessedMetric($metrics, $name)
    {
        foreach ($metrics as $metric) {
            if ($metric instanceof ProcessedMetric && $metric->getName() == $name) {
                return \true;
            }
        }
        return \false;
    }
}
