1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4
5/**
6 * Class SearchConfig
7 *
8 * The same as @see Search but can be initialized by a configuration array
9 *
10 * @package dokuwiki\plugin\struct\meta
11 */
12class SearchConfig extends Search
13{
14    /** @var int default aggregation caching (depends on last struct save) */
15    public static $CACHE_DEFAULT = 1;
16    /** @var int caching depends on current user */
17    public static $CACHE_USER = 2;
18    /** @var int caching depends on current date */
19    public static $CACHE_DATE = 4;
20
21    /**
22     * @var array hold the configuration as parsed and extended by dynamic params
23     */
24    protected $config;
25
26    /**
27     * @var SearchConfigParameters manages dynamic parameters
28     */
29    protected $dynamicParameters;
30
31    /**
32     * @var int the cache flag to use (binary flags)
33     */
34    protected $cacheFlag;
35
36    /**
37     * SearchConfig constructor.
38     * @param array $config The parsed configuration for this search
39     * @param bool $dynamic Should dynamic parameters be applied?
40     */
41    public function __construct($config, $dynamic = true)
42    {
43        parent::__construct();
44
45        // setup schemas and columns
46        if (!empty($config['schemas'])) foreach ($config['schemas'] as $schema) {
47            $this->addSchema($schema[0], $schema[1]);
48        }
49        if (!empty($config['cols'])) foreach ($config['cols'] as $col) {
50            $this->addColumn($col);
51        }
52
53        // cache flag setting
54        $this->cacheFlag = self::$CACHE_DEFAULT;
55        if (!empty($config['filters'])) $this->cacheFlag = $this->determineCacheFlag($config['filters']);
56
57        // configure search from configuration
58        if (!empty($config['filter'])) foreach ($config['filter'] as $filter) {
59            $this->addFilter($filter[0], $this->applyFilterVars($filter[2]), $filter[1], $filter[3]);
60        }
61
62        if (!empty($config['sort'])) foreach ($config['sort'] as $sort) {
63            $this->addSort($sort[0], $sort[1]);
64        }
65
66        if (!empty($config['limit'])) {
67            $this->setLimit($config['limit']);
68        }
69
70        if (!empty($config['offset'])) {
71            $this->setOffset($config['offset']);
72        }
73
74        // prepare dynamic parameters
75        $this->dynamicParameters = new SearchConfigParameters($this);
76        if ($dynamic) {
77            $this->dynamicParameters->apply();
78        }
79
80        $this->config = $config;
81    }
82
83    /**
84     * Set the cache flag accordingly to the set filter placeholders
85     *
86     * @param array $filters
87     * @return int
88     */
89    protected function determineCacheFlag($filters)
90    {
91        $flags = self::$CACHE_DEFAULT;
92
93        foreach ($filters as $filter) {
94            if (is_array($filter)) $filter = $filter[2]; // this is the format we get fro the config parser
95
96            if (strpos($filter, '$USER$') !== false) {
97                $flags |= self::$CACHE_USER;
98            } elseif (strpos($filter, '$TODAY$') !== false) {
99                $flags |= self::$CACHE_DATE;
100            }
101        }
102
103        return $flags;
104    }
105
106    /**
107     * Replaces placeholders in the given filter value by the proper value
108     *
109     * @param string $filter
110     * @return string|string[] Result may be an array when a multi column placeholder is used
111     */
112    protected function applyFilterVars($filter)
113    {
114        global $INPUT;
115        global $INFO;
116        if (!isset($INFO['id'])) {
117            $INFO['id'] = '';
118        }
119
120        // apply inexpensive filters first
121        $filter = str_replace(
122            [
123                '$ID$',
124                '$NS$',
125                '$PAGE$',
126                '$USER$',
127                '$TODAY$'
128            ],
129            [
130                $INFO['id'],
131                getNS($INFO['id']),
132                noNS($INFO['id']),
133                $INPUT->server->str('REMOTE_USER'),
134                date('Y-m-d')
135            ],
136            $filter
137        );
138
139        // apply struct column placeholder (we support only one!)
140        // or apply date formula, given as strtotime
141        if (preg_match('/^(.*?)(?:\$STRUCT\.(.*?)\$)(.*?)$/', $filter, $match)) {
142            $filter = $this->applyFilterVarsStruct($match);
143        } elseif (preg_match('/^(.*?)(?:\$USER\.(.*?)\$)(.*?)$/', $filter, $match)) {
144            $filter = $this->applyFilterVarsUser($match);
145        } elseif (preg_match('/^(.*?)(?:\$DATE\((.*?)\)\$?)(.*?)$/', $filter, $match)) {
146            $toparse = $match[2];
147            if ($toparse == '') {
148                $toparse = 'now';
149            }
150            $timestamp = strtotime($toparse);
151            if ($timestamp === false) {
152                throw new StructException('datefilter', hsc($toparse));
153            } else {
154                $filter = str_replace($filter, date('Y-m-d', $timestamp), $filter);
155            }
156        }
157
158        return $filter;
159    }
160
161    /**
162     * Replaces struct placeholders in the given filter value by the proper value
163     *
164     * @param string $match
165     * @return string|string[] Result may be an array when a multi column placeholder is used
166     */
167    protected function applyFilterVarsStruct($match)
168    {
169        global $INFO;
170
171        $key = $match[2];
172
173        // we try to resolve the key via the assigned schemas first, otherwise take it literally
174        $column = $this->findColumn($key, true);
175        if ($column) {
176            $label = $column->getLabel();
177            $table = $column->getTable();
178        } else {
179            [$table, $label] = sexplode('.', $key, 2, '');
180        }
181
182        // get the data from the current page
183        if ($table && $label) {
184            $schemaData = AccessTable::getPageAccess($table, $INFO['id']);
185            $data = $schemaData->getData();
186            if (!isset($data[$label])) {
187                throw new StructException("column not in table", $label, $table);
188            }
189            $value = $data[$label]->getCompareValue();
190
191            if (is_array($value) && $value === []) {
192                $value = '';
193            }
194        } else {
195            $value = '';
196        }
197
198        // apply any pre and postfixes, even when multi value
199        if (is_array($value)) {
200            $filter = [];
201            foreach ($value as $item) {
202                $filter[] = $match[1] . $item . $match[3];
203            }
204        } else {
205            $filter = $match[1] . $value . $match[3];
206        }
207
208        return $filter;
209    }
210
211    /**
212     * Replaces user placeholders in the given filter value by the proper value
213     *
214     * @param string $match
215     * @return string|string[] String for name and mail, array for grps
216     */
217    protected function applyFilterVarsUser($match)
218    {
219        global $INFO;
220
221        $key = strtolower($match[2]);
222
223        if (!in_array($key, ['name', 'mail', 'grps'])) {
224            throw new StructException('"%s" is not a valid USER key', $key);
225        }
226
227        if (empty($INFO['userinfo'])) {
228            $filter = '';
229        } else {
230            $filter = $INFO['userinfo'][$key];
231        }
232
233        return $filter;
234    }
235
236    /**
237     * @return int cacheflag for this search
238     */
239    public function getCacheFlag()
240    {
241        return $this->cacheFlag;
242    }
243
244    /**
245     * Access the dynamic parameters of this search
246     *
247     * Note: This call returns a clone of the parameters as they were initialized
248     *
249     * @return SearchConfigParameters
250     */
251    public function getDynamicParameters()
252    {
253        return clone $this->dynamicParameters;
254    }
255
256    /**
257     * Get the config this search was initialized with
258     *
259     * Note that the search may have been modified by dynamic parameters or additional member calls
260     *
261     * @return array
262     */
263    public function getConf()
264    {
265        return $this->config;
266    }
267}
268