1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik\DataTable\Filter;
10
11use Piwik\DataTable\BaseFilter;
12use Piwik\DataTable;
13use Piwik\DataTable\Row;
14
15/**
16 * Replaces one or more column values in each row of a DataTable with the results
17 * of a callback.
18 *
19 * **Basic usage example**
20 *
21 *     $truncateString = function ($value, $truncateLength) {
22 *         if (strlen($value) > $truncateLength) {
23 *             return substr(0, $truncateLength);
24 *         } else {
25 *             return $value;
26 *         }
27 *     };
28 *
29 *     // label, url and truncate_length are columns in $dataTable
30 *     $dataTable->filter('ColumnCallbackReplace', array('label', 'url'), $truncateString, null, array('truncate_length'));
31 *
32 * @api
33 */
34class ColumnCallbackReplace extends BaseFilter
35{
36    private $columnsToFilter;
37    private $functionToApply;
38    private $functionParameters;
39    private $extraColumnParameters;
40
41    /**
42     * Constructor.
43     *
44     * @param DataTable $table The DataTable to filter.
45     * @param array|string $columnsToFilter The columns whose values should be passed to the callback
46     *                                      and then replaced with the callback's result.
47     * @param callable $functionToApply The function to execute. Must take the column value as a parameter
48     *                                  and return a value that will be used to replace the original.
49     * @param array|null $functionParameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php)
50     *                                       instead.
51     * @param array $extraColumnParameters Extra column values that should be passed to the callback, but
52     *                                     shouldn't be replaced.
53     */
54    public function __construct($table, $columnsToFilter, $functionToApply, $functionParameters = null,
55                                $extraColumnParameters = array())
56    {
57        parent::__construct($table);
58        $this->functionToApply    = $functionToApply;
59        $this->functionParameters = $functionParameters;
60
61        if (!is_array($columnsToFilter)) {
62            $columnsToFilter = array($columnsToFilter);
63        }
64
65        $this->columnsToFilter       = $columnsToFilter;
66        $this->extraColumnParameters = $extraColumnParameters;
67    }
68
69    /**
70     * See {@link ColumnCallbackReplace}.
71     *
72     * @param DataTable $table
73     */
74    public function filter($table)
75    {
76        foreach ($table->getRows() as $row) {
77            $extraColumnParameters = array();
78            foreach ($this->extraColumnParameters as $columnName) {
79                $extraColumnParameters[] = $row->getColumn($columnName);
80            }
81
82            foreach ($this->columnsToFilter as $column) {
83
84                // when a value is not defined, we set it to zero by default (rather than displaying '-')
85                $value = $this->getElementToReplace($row, $column);
86                if ($value === false) {
87                    $value = 0;
88                }
89
90                $parameters = array_merge(array($value), $extraColumnParameters);
91
92                if (!is_null($this->functionParameters)) {
93                    $parameters = array_merge($parameters, $this->functionParameters);
94                }
95
96                $newValue = call_user_func_array($this->functionToApply, $parameters);
97                $this->setElementToReplace($row, $column, $newValue);
98                $this->filterSubTable($row);
99            }
100        }
101
102        if (in_array('label', $this->columnsToFilter)) {
103            // we need to force rebuilding the index
104            $table->setLabelsHaveChanged();
105        }
106    }
107
108    /**
109     * Replaces the given column within given row with the given value
110     *
111     * @param Row $row
112     * @param string $columnToFilter
113     * @param mixed $newValue
114     */
115    protected function setElementToReplace($row, $columnToFilter, $newValue)
116    {
117        $row->setColumn($columnToFilter, $newValue);
118    }
119
120    /**
121     * Returns the element that should be replaced
122     *
123     * @param Row $row
124     * @param string $columnToFilter
125     * @return mixed
126     */
127    protected function getElementToReplace($row, $columnToFilter)
128    {
129        return $row->getColumn($columnToFilter);
130    }
131}
132