xref: /plugin/struct/meta/SearchConfig.php (revision ba662a609884bbbecde8cffec91014be306b652b)
15511bd5bSAndreas Gohr<?php
25511bd5bSAndreas Gohr
3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
45511bd5bSAndreas Gohr
58b7e143cSAndreas Gohruse dokuwiki\File\PageResolver;
68b7e143cSAndreas Gohr
75511bd5bSAndreas Gohr/**
85511bd5bSAndreas Gohr * Class SearchConfig
95511bd5bSAndreas Gohr *
105511bd5bSAndreas Gohr * The same as @see Search but can be initialized by a configuration array
115511bd5bSAndreas Gohr *
12ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta
135511bd5bSAndreas Gohr */
14d6d97f60SAnna Dabrowskaclass SearchConfig extends Search
15d6d97f60SAnna Dabrowska{
1616b7d914SAndreas Gohr    /** @var int default aggregation caching (depends on last struct save) */
17d6d97f60SAnna Dabrowska    public static $CACHE_DEFAULT = 1;
1816b7d914SAndreas Gohr    /** @var int caching depends on current user */
19d6d97f60SAnna Dabrowska    public static $CACHE_USER = 2;
2016b7d914SAndreas Gohr    /** @var int caching depends on current date */
21d6d97f60SAnna Dabrowska    public static $CACHE_DATE = 4;
2216b7d914SAndreas Gohr
23668e4f8eSAndreas Gohr    /**
24668e4f8eSAndreas Gohr     * @var array hold the configuration as parsed and extended by dynamic params
25668e4f8eSAndreas Gohr     */
261a07b696SMichael Große    protected $config;
271a07b696SMichael Große
28668e4f8eSAndreas Gohr    /**
29668e4f8eSAndreas Gohr     * @var SearchConfigParameters manages dynamic parameters
30668e4f8eSAndreas Gohr     */
3100f6af48SAndreas Gohr    protected $dynamicParameters;
3200f6af48SAndreas Gohr
335511bd5bSAndreas Gohr    /**
3416b7d914SAndreas Gohr     * @var int the cache flag to use (binary flags)
3516b7d914SAndreas Gohr     */
3616b7d914SAndreas Gohr    protected $cacheFlag;
3716b7d914SAndreas Gohr
3816b7d914SAndreas Gohr    /**
395511bd5bSAndreas Gohr     * SearchConfig constructor.
4000f6af48SAndreas Gohr     * @param array $config The parsed configuration for this search
41b3a9db22SAndreas Gohr     * @param bool $dynamic Should dynamic parameters be applied?
425511bd5bSAndreas Gohr     */
43b3a9db22SAndreas Gohr    public function __construct($config, $dynamic = true)
44d6d97f60SAnna Dabrowska    {
455511bd5bSAndreas Gohr        parent::__construct();
465511bd5bSAndreas Gohr
473ad292a8SAndreas Gohr        // setup schemas and columns
480215a637SAndreas Gohr        if (!empty($config['schemas'])) foreach ($config['schemas'] as $schema) {
493ad292a8SAndreas Gohr            $this->addSchema($schema[0], $schema[1]);
503ad292a8SAndreas Gohr        }
510215a637SAndreas Gohr        if (!empty($config['cols'])) foreach ($config['cols'] as $col) {
523ad292a8SAndreas Gohr            $this->addColumn($col);
533ad292a8SAndreas Gohr        }
543ad292a8SAndreas Gohr
5516b7d914SAndreas Gohr        // cache flag setting
5616b7d914SAndreas Gohr        $this->cacheFlag = self::$CACHE_DEFAULT;
5716b7d914SAndreas Gohr        if (!empty($config['filters'])) $this->cacheFlag = $this->determineCacheFlag($config['filters']);
5816b7d914SAndreas Gohr
5900f6af48SAndreas Gohr        // configure search from configuration
6000f6af48SAndreas Gohr        if (!empty($config['filter'])) foreach ($config['filter'] as $filter) {
615625b985SAndreas Gohr            $this->addFilter($filter[0], $this->applyFilterVars($filter[2]), $filter[1], $filter[3]);
625511bd5bSAndreas Gohr        }
6300f6af48SAndreas Gohr
6400f6af48SAndreas Gohr        if (!empty($config['sort'])) foreach ($config['sort'] as $sort) {
65aa124708SAndreas Gohr            $this->addSort($sort[0], $sort[1]);
661a07b696SMichael Große        }
671a07b696SMichael Große
681a07b696SMichael Große        if (!empty($config['limit'])) {
691a07b696SMichael Große            $this->setLimit($config['limit']);
701a07b696SMichael Große        }
7100f6af48SAndreas Gohr
7200f6af48SAndreas Gohr        if (!empty($config['offset'])) {
73d2a8ce05SPaweł Czochański            $this->setOffset($config['offset']);
7400f6af48SAndreas Gohr        }
75668e4f8eSAndreas Gohr
76b3a9db22SAndreas Gohr        // prepare dynamic parameters
77b3a9db22SAndreas Gohr        $this->dynamicParameters = new SearchConfigParameters($this);
78b3a9db22SAndreas Gohr        if ($dynamic) {
79b3a9db22SAndreas Gohr            $this->dynamicParameters->apply();
80b3a9db22SAndreas Gohr        }
81b3a9db22SAndreas Gohr
82668e4f8eSAndreas Gohr        $this->config = $config;
831a07b696SMichael Große    }
845511bd5bSAndreas Gohr
8500f6af48SAndreas Gohr    /**
8616b7d914SAndreas Gohr     * Set the cache flag accordingly to the set filter placeholders
8716b7d914SAndreas Gohr     *
8816b7d914SAndreas Gohr     * @param array $filters
8916b7d914SAndreas Gohr     * @return int
9016b7d914SAndreas Gohr     */
91d6d97f60SAnna Dabrowska    protected function determineCacheFlag($filters)
92d6d97f60SAnna Dabrowska    {
9316b7d914SAndreas Gohr        $flags = self::$CACHE_DEFAULT;
9416b7d914SAndreas Gohr
9516b7d914SAndreas Gohr        foreach ($filters as $filter) {
9616b7d914SAndreas Gohr            if (is_array($filter)) $filter = $filter[2]; // this is the format we get fro the config parser
9716b7d914SAndreas Gohr
98*ba662a60SAndreas Gohr            if (str_contains($filter, '$USER$')) {
9916b7d914SAndreas Gohr                $flags |= self::$CACHE_USER;
100*ba662a60SAndreas Gohr            } elseif (str_contains($filter, '$TODAY$')) {
10116b7d914SAndreas Gohr                $flags |= self::$CACHE_DATE;
10216b7d914SAndreas Gohr            }
10316b7d914SAndreas Gohr        }
10416b7d914SAndreas Gohr
10516b7d914SAndreas Gohr        return $flags;
10616b7d914SAndreas Gohr    }
10716b7d914SAndreas Gohr
10816b7d914SAndreas Gohr    /**
1095625b985SAndreas Gohr     * Replaces placeholders in the given filter value by the proper value
1105625b985SAndreas Gohr     *
1115625b985SAndreas Gohr     * @param string $filter
11253528ecfSAndreas Gohr     * @return string|string[] Result may be an array when a multi column placeholder is used
1135625b985SAndreas Gohr     */
114d6d97f60SAnna Dabrowska    protected function applyFilterVars($filter)
115d6d97f60SAnna Dabrowska    {
116ecf2cba2SAndreas Gohr        global $INPUT;
11706fee43aSMichael Grosse        global $INFO;
1181ca21e17SAnna Dabrowska        if (!isset($INFO['id'])) {
1197fe2cdf2SAndreas Gohr            $INFO['id'] = '';
12034ea6e10SAnna Dabrowska        }
1218b7e143cSAndreas Gohr        $ns = getNS($INFO['id']);
1225625b985SAndreas Gohr
1235625b985SAndreas Gohr        // apply inexpensive filters first
1245625b985SAndreas Gohr        $filter = str_replace(
1257fe2cdf2SAndreas Gohr            [
1267fe2cdf2SAndreas Gohr                '$ID$',
1277fe2cdf2SAndreas Gohr                '$NS$',
1287fe2cdf2SAndreas Gohr                '$PAGE$',
1297fe2cdf2SAndreas Gohr                '$USER$',
1307fe2cdf2SAndreas Gohr                '$TODAY$'
1317fe2cdf2SAndreas Gohr            ],
1327fe2cdf2SAndreas Gohr            [
1337fe2cdf2SAndreas Gohr                $INFO['id'],
1348b7e143cSAndreas Gohr                $ns,
1357fe2cdf2SAndreas Gohr                noNS($INFO['id']),
1367fe2cdf2SAndreas Gohr                $INPUT->server->str('REMOTE_USER'),
1377fe2cdf2SAndreas Gohr                date('Y-m-d')
1387fe2cdf2SAndreas Gohr            ],
1395625b985SAndreas Gohr            $filter
1405625b985SAndreas Gohr        );
1415625b985SAndreas Gohr
1428b7e143cSAndreas Gohr        // apply namespace or id placeholder #712
1438b7e143cSAndreas Gohr        // returns the namespace for start pages, otherwise the ID
1448b7e143cSAndreas Gohr        if (preg_match('/\$NSORID\$/', $filter)) {
1458b7e143cSAndreas Gohr            $resolver = new PageResolver('');
1468b7e143cSAndreas Gohr
1478b7e143cSAndreas Gohr            $start = $resolver->resolveId($ns . ':');
1488b7e143cSAndreas Gohr            if ($start === $INFO['id']) {
1498b7e143cSAndreas Gohr                // This is a start page, we return the namespace
1508b7e143cSAndreas Gohr                $val = $ns;
1518b7e143cSAndreas Gohr            } else {
1528b7e143cSAndreas Gohr                // This is a normal page, we return the ID
1538b7e143cSAndreas Gohr                $val = $INFO['id'];
1548b7e143cSAndreas Gohr            }
1558b7e143cSAndreas Gohr            $filter = str_replace('$NSORID$', $val, $filter);
1568b7e143cSAndreas Gohr        }
1578b7e143cSAndreas Gohr
15853528ecfSAndreas Gohr        // apply struct column placeholder (we support only one!)
159888559e1Ssaggi-dw        // or apply date formula, given as strtotime
16053528ecfSAndreas Gohr        if (preg_match('/^(.*?)(?:\$STRUCT\.(.*?)\$)(.*?)$/', $filter, $match)) {
161e983bcdaSSzymon Olewniczak            $filter = $this->applyFilterVarsStruct($match);
162e983bcdaSSzymon Olewniczak        } elseif (preg_match('/^(.*?)(?:\$USER\.(.*?)\$)(.*?)$/', $filter, $match)) {
163e983bcdaSSzymon Olewniczak            $filter = $this->applyFilterVarsUser($match);
164888559e1Ssaggi-dw        } elseif (preg_match('/^(.*?)(?:\$DATE\((.*?)\)\$?)(.*?)$/', $filter, $match)) {
1657f610bd5Ssaggi-dw            $toparse = $match[2];
166888559e1Ssaggi-dw            if ($toparse == '') {
167888559e1Ssaggi-dw                $toparse = 'now';
168888559e1Ssaggi-dw            }
169888559e1Ssaggi-dw            $timestamp = strtotime($toparse);
170888559e1Ssaggi-dw            if ($timestamp === false) {
1717f610bd5Ssaggi-dw                throw new StructException('datefilter', hsc($toparse));
1727f610bd5Ssaggi-dw            } else {
1737f610bd5Ssaggi-dw                $filter = str_replace($filter, date('Y-m-d', $timestamp), $filter);
1747f610bd5Ssaggi-dw            }
175e983bcdaSSzymon Olewniczak        }
176e983bcdaSSzymon Olewniczak
177e983bcdaSSzymon Olewniczak        return $filter;
178e983bcdaSSzymon Olewniczak    }
179e983bcdaSSzymon Olewniczak
180e983bcdaSSzymon Olewniczak    /**
181e983bcdaSSzymon Olewniczak     * Replaces struct placeholders in the given filter value by the proper value
182e983bcdaSSzymon Olewniczak     *
183e983bcdaSSzymon Olewniczak     * @param string $match
184e983bcdaSSzymon Olewniczak     * @return string|string[] Result may be an array when a multi column placeholder is used
185e983bcdaSSzymon Olewniczak     */
186d6d97f60SAnna Dabrowska    protected function applyFilterVarsStruct($match)
187d6d97f60SAnna Dabrowska    {
188e983bcdaSSzymon Olewniczak        global $INFO;
189e983bcdaSSzymon Olewniczak
19053528ecfSAndreas Gohr        $key = $match[2];
191d19ba4b1SAndreas Gohr
1920280da9aSFrieder Schrempf        // we try to resolve the key via the assigned schemas first, otherwise take it literally
1930280da9aSFrieder Schrempf        $column = $this->findColumn($key, true);
1945625b985SAndreas Gohr        if ($column) {
1955625b985SAndreas Gohr            $label = $column->getLabel();
1965625b985SAndreas Gohr            $table = $column->getTable();
197aec9051bSAndreas Gohr        } else {
1987fe2cdf2SAndreas Gohr            [$table, $label] = sexplode('.', $key, 2, '');
199aec9051bSAndreas Gohr        }
200aec9051bSAndreas Gohr
201aec9051bSAndreas Gohr        // get the data from the current page
202aec9051bSAndreas Gohr        if ($table && $label) {
2034cd5cc28SAnna Dabrowska            $schemaData = AccessTable::getPageAccess($table, $INFO['id']);
2047717c082SMichael Große            $data = $schemaData->getData();
20534db2096SMichael Große            if (!isset($data[$label])) {
206e87d1e74SMichael Große                throw new StructException("column not in table", $label, $table);
20734db2096SMichael Große            }
2087717c082SMichael Große            $value = $data[$label]->getCompareValue();
209c75f25cfSAndreas Gohr
2107234bfb1Ssplitbrain            if (is_array($value) && $value === []) {
211c75f25cfSAndreas Gohr                $value = '';
212c75f25cfSAndreas Gohr            }
2135625b985SAndreas Gohr        } else {
2145625b985SAndreas Gohr            $value = '';
2155625b985SAndreas Gohr        }
2168b8243b2SAndreas Gohr
21753528ecfSAndreas Gohr        // apply any pre and postfixes, even when multi value
21853528ecfSAndreas Gohr        if (is_array($value)) {
2197234bfb1Ssplitbrain            $filter = [];
22053528ecfSAndreas Gohr            foreach ($value as $item) {
22153528ecfSAndreas Gohr                $filter[] = $match[1] . $item . $match[3];
22253528ecfSAndreas Gohr            }
22353528ecfSAndreas Gohr        } else {
22453528ecfSAndreas Gohr            $filter = $match[1] . $value . $match[3];
22553528ecfSAndreas Gohr        }
226e983bcdaSSzymon Olewniczak
227e983bcdaSSzymon Olewniczak        return $filter;
228e983bcdaSSzymon Olewniczak    }
229e983bcdaSSzymon Olewniczak
230e983bcdaSSzymon Olewniczak    /**
231e983bcdaSSzymon Olewniczak     * Replaces user placeholders in the given filter value by the proper value
232e983bcdaSSzymon Olewniczak     *
233e983bcdaSSzymon Olewniczak     * @param string $match
234e983bcdaSSzymon Olewniczak     * @return string|string[] String for name and mail, array for grps
235e983bcdaSSzymon Olewniczak     */
236d6d97f60SAnna Dabrowska    protected function applyFilterVarsUser($match)
237d6d97f60SAnna Dabrowska    {
238e983bcdaSSzymon Olewniczak        global $INFO;
239e983bcdaSSzymon Olewniczak
240daa4b09dSSzymon Olewniczak        $key = strtolower($match[2]);
241daa4b09dSSzymon Olewniczak
2427234bfb1Ssplitbrain        if (!in_array($key, ['name', 'mail', 'grps'])) {
243daa4b09dSSzymon Olewniczak            throw new StructException('"%s" is not a valid USER key', $key);
244daa4b09dSSzymon Olewniczak        }
245daa4b09dSSzymon Olewniczak
246daa4b09dSSzymon Olewniczak        if (empty($INFO['userinfo'])) {
247daa4b09dSSzymon Olewniczak            $filter = '';
248daa4b09dSSzymon Olewniczak        } else {
249daa4b09dSSzymon Olewniczak            $filter = $INFO['userinfo'][$key];
250daa4b09dSSzymon Olewniczak        }
2515625b985SAndreas Gohr
2525625b985SAndreas Gohr        return $filter;
2535625b985SAndreas Gohr    }
2545625b985SAndreas Gohr
2555625b985SAndreas Gohr    /**
25616b7d914SAndreas Gohr     * @return int cacheflag for this search
25716b7d914SAndreas Gohr     */
258d6d97f60SAnna Dabrowska    public function getCacheFlag()
259d6d97f60SAnna Dabrowska    {
26016b7d914SAndreas Gohr        return $this->cacheFlag;
26116b7d914SAndreas Gohr    }
26216b7d914SAndreas Gohr
26316b7d914SAndreas Gohr    /**
264fd9c77d3SAndreas Gohr     * Access the dynamic parameters of this search
26500f6af48SAndreas Gohr     *
266668e4f8eSAndreas Gohr     * Note: This call returns a clone of the parameters as they were initialized
26700f6af48SAndreas Gohr     *
26800f6af48SAndreas Gohr     * @return SearchConfigParameters
26900f6af48SAndreas Gohr     */
270d6d97f60SAnna Dabrowska    public function getDynamicParameters()
271d6d97f60SAnna Dabrowska    {
27200f6af48SAndreas Gohr        return clone $this->dynamicParameters;
2735511bd5bSAndreas Gohr    }
2745511bd5bSAndreas Gohr
27500f6af48SAndreas Gohr    /**
276fdf37115SAndreas Gohr     * Get the config this search was initialized with
277fdf37115SAndreas Gohr     *
278fdf37115SAndreas Gohr     * Note that the search may have been modified by dynamic parameters or additional member calls
279fdf37115SAndreas Gohr     *
280fdf37115SAndreas Gohr     * @return array
28100f6af48SAndreas Gohr     */
282d6d97f60SAnna Dabrowska    public function getConf()
283d6d97f60SAnna Dabrowska    {
2841a07b696SMichael Große        return $this->config;
2851a07b696SMichael Große    }
2865511bd5bSAndreas Gohr}
287