1 <?php
2 
3 namespace 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  */
12 class 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