xref: /plugin/struct/types/Page.php (revision f0be5277609f2aab8c4274bc0f35256ec3368b52)
10234787dSAndreas Gohr<?php
2d6d97f60SAnna Dabrowska
3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\types;
494603e42SAndreas Gohr
56a819106SAndreas Gohruse dokuwiki\File\PageResolver;
6a827267cSAndreas Gohruse dokuwiki\plugin\struct\meta\QueryBuilder;
7af993d55SMichael Grosseuse dokuwiki\plugin\struct\meta\QueryBuilderWhere;
8996125f5SAnna Dabrowskause dokuwiki\plugin\struct\meta\StructException;
96a819106SAndreas Gohruse dokuwiki\Utf8\PhpString;
100234787dSAndreas Gohr
110234787dSAndreas Gohr/**
120234787dSAndreas Gohr * Class Page
130234787dSAndreas Gohr *
140234787dSAndreas Gohr * Represents a single page in the wiki. Will be linked in output.
150234787dSAndreas Gohr *
16ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\types
170234787dSAndreas Gohr */
18d6d97f60SAnna Dabrowskaclass Page extends AbstractMultiBaseType
19d6d97f60SAnna Dabrowska{
207fe2cdf2SAndreas Gohr    protected $config = [
217fe2cdf2SAndreas Gohr        'usetitles' => false,
227fe2cdf2SAndreas Gohr        'autocomplete' => [
237fe2cdf2SAndreas Gohr            'mininput' => 2,
247fe2cdf2SAndreas Gohr            'maxresult' => 5,
259c075503SAnna Dabrowska            'filter' => '',
267fe2cdf2SAndreas Gohr        ]
277fe2cdf2SAndreas Gohr    ];
2816a4ba5bSAndreas Gohr
290234787dSAndreas Gohr    /**
300234787dSAndreas Gohr     * Output the stored data
310234787dSAndreas Gohr     *
32a827267cSAndreas Gohr     * @param string $value the value stored in the database - JSON when titles are used
33797f0dfeSAndreas Gohr     * @param \Doku_Renderer $R the renderer currently used to render the data
34797f0dfeSAndreas Gohr     * @param string $mode The mode the output is rendered in (eg. XHTML)
35797f0dfeSAndreas Gohr     * @return bool true if $mode could be satisfied
360234787dSAndreas Gohr     */
37d6d97f60SAnna Dabrowska    public function renderValue($value, \Doku_Renderer $R, $mode)
38d6d97f60SAnna Dabrowska    {
39a827267cSAndreas Gohr        if ($this->config['usetitles']) {
407234bfb1Ssplitbrain            [$id, $title] = \helper_plugin_struct::decodeJson($value);
41a827267cSAndreas Gohr        } else {
42a827267cSAndreas Gohr            $id = $value;
439dc9943dSAnna Dabrowska            $title = $id; // cannot be empty or internallink() might hijack %pageid% and use headings
44a827267cSAndreas Gohr        }
454d19af58SAndreas Gohr
46a827267cSAndreas Gohr        if (!$id) return true;
47a827267cSAndreas Gohr
48a827267cSAndreas Gohr        $R->internallink(":$id", $title);
49797f0dfeSAndreas Gohr        return true;
500234787dSAndreas Gohr    }
510234787dSAndreas Gohr
5216a4ba5bSAndreas Gohr    /**
5306bdb2c2SAndreas Gohr     * Cleans the link
5406bdb2c2SAndreas Gohr     *
5523169abeSAndreas Gohr     * @param string $rawvalue
5606bdb2c2SAndreas Gohr     * @return string
5706bdb2c2SAndreas Gohr     */
58d6d97f60SAnna Dabrowska    public function validate($rawvalue)
59d6d97f60SAnna Dabrowska    {
607234bfb1Ssplitbrain        [$page, $fragment] = array_pad(explode('#', $rawvalue, 2), 2, '');
61eeac4e77SMichael Grosse        return cleanID($page) . (strlen(cleanID($fragment)) > 0 ? '#' . cleanID($fragment) : '');
6206bdb2c2SAndreas Gohr    }
6306bdb2c2SAndreas Gohr
6406bdb2c2SAndreas Gohr    /**
6516a4ba5bSAndreas Gohr     * Autocompletion support for pages
6616a4ba5bSAndreas Gohr     *
6716a4ba5bSAndreas Gohr     * @return array
6816a4ba5bSAndreas Gohr     */
69d6d97f60SAnna Dabrowska    public function handleAjax()
70d6d97f60SAnna Dabrowska    {
7116a4ba5bSAndreas Gohr        global $INPUT;
7216a4ba5bSAndreas Gohr
7316a4ba5bSAndreas Gohr        // check minimum length
7416a4ba5bSAndreas Gohr        $lookup = trim($INPUT->str('search'));
757234bfb1Ssplitbrain        if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return [];
7616a4ba5bSAndreas Gohr
7716a4ba5bSAndreas Gohr        // results wanted?
7816a4ba5bSAndreas Gohr        $max = $this->config['autocomplete']['maxresult'];
797234bfb1Ssplitbrain        if ($max <= 0) return [];
8016a4ba5bSAndreas Gohr
8185716d37SAndreas Gohr        $data = ft_pageLookup($lookup, true, $this->config['usetitles']);
827234bfb1Ssplitbrain        if ($data === []) return [];
8316a4ba5bSAndreas Gohr
849c075503SAnna Dabrowska        $filter = $this->config['autocomplete']['filter'];
85c0f1a2d1SAnna Dabrowska
869c075503SAnna Dabrowska        // this basically duplicates what we do in ajax_qsearch() but with a filter
877234bfb1Ssplitbrain        $result = [];
8816a4ba5bSAndreas Gohr        $counter = 0;
8916a4ba5bSAndreas Gohr        foreach ($data as $id => $title) {
909c075503SAnna Dabrowska            if (!empty($filter) && !$this->filterMatch($id, $filter)) {
91c0f1a2d1SAnna Dabrowska                continue;
92c0f1a2d1SAnna Dabrowska            }
9385716d37SAndreas Gohr            if ($this->config['usetitles']) {
9494603e42SAndreas Gohr                $name = $title . ' (' . $id . ')';
9516a4ba5bSAndreas Gohr            } else {
9616a4ba5bSAndreas Gohr                $ns = getNS($id);
9716a4ba5bSAndreas Gohr                if ($ns) {
9816a4ba5bSAndreas Gohr                    $name = noNS($id) . ' (' . $ns . ')';
9916a4ba5bSAndreas Gohr                } else {
10016a4ba5bSAndreas Gohr                    $name = $id;
10116a4ba5bSAndreas Gohr                }
10216a4ba5bSAndreas Gohr            }
1037067bf5aSAndreas Gohr
1047fe2cdf2SAndreas Gohr            $result[] = [
1057fe2cdf2SAndreas Gohr                'label' => $name,
1067fe2cdf2SAndreas Gohr                'value' => $id
1077fe2cdf2SAndreas Gohr            ];
10816a4ba5bSAndreas Gohr
10916a4ba5bSAndreas Gohr            $counter++;
11016a4ba5bSAndreas Gohr            if ($counter > $max) break;
11116a4ba5bSAndreas Gohr        }
11216a4ba5bSAndreas Gohr
11316a4ba5bSAndreas Gohr        return $result;
11416a4ba5bSAndreas Gohr    }
115a827267cSAndreas Gohr
116a827267cSAndreas Gohr    /**
117a827267cSAndreas Gohr     * When using titles, we need ot join the titles table
118a827267cSAndreas Gohr     *
119a827267cSAndreas Gohr     * @param QueryBuilder $QB
120a827267cSAndreas Gohr     * @param string $tablealias
121a827267cSAndreas Gohr     * @param string $colname
122a827267cSAndreas Gohr     * @param string $alias
123a827267cSAndreas Gohr     */
124d6d97f60SAnna Dabrowska    public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
125d6d97f60SAnna Dabrowska    {
126a827267cSAndreas Gohr        if (!$this->config['usetitles']) {
127a827267cSAndreas Gohr            parent::select($QB, $tablealias, $colname, $alias);
128a827267cSAndreas Gohr            return;
129a827267cSAndreas Gohr        }
130a827267cSAndreas Gohr        $rightalias = $QB->generateTableAlias();
131a827267cSAndreas Gohr        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
1320e72ef50SAndreas Gohr        $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $rightalias.title)", $alias);
133a827267cSAndreas Gohr    }
134a827267cSAndreas Gohr
135a827267cSAndreas Gohr    /**
136694968d6SAndreas Gohr     * When using titles, sort by them first
137694968d6SAndreas Gohr     *
138694968d6SAndreas Gohr     * @param QueryBuilder $QB
139694968d6SAndreas Gohr     * @param string $tablealias
140694968d6SAndreas Gohr     * @param string $colname
141694968d6SAndreas Gohr     * @param string $order
142694968d6SAndreas Gohr     */
143d6d97f60SAnna Dabrowska    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
144d6d97f60SAnna Dabrowska    {
145694968d6SAndreas Gohr        if (!$this->config['usetitles']) {
146694968d6SAndreas Gohr            parent::sort($QB, $tablealias, $colname, $order);
147694968d6SAndreas Gohr            return;
148694968d6SAndreas Gohr        }
149694968d6SAndreas Gohr
150694968d6SAndreas Gohr        $rightalias = $QB->generateTableAlias();
151694968d6SAndreas Gohr        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
152*f0be5277SAnna Dabrowska        $QB->addOrderBy("$rightalias.title COLLATE NOCASE $order");
153694968d6SAndreas Gohr        $QB->addOrderBy("$tablealias.$colname $order");
154694968d6SAndreas Gohr    }
155694968d6SAndreas Gohr
156694968d6SAndreas Gohr    /**
157e7ee2b64SAndreas Gohr     * Return the pageid only
158e7ee2b64SAndreas Gohr     *
159e7ee2b64SAndreas Gohr     * @param string $value
160e7ee2b64SAndreas Gohr     * @return string
161e7ee2b64SAndreas Gohr     */
162d6d97f60SAnna Dabrowska    public function rawValue($value)
163d6d97f60SAnna Dabrowska    {
164e7ee2b64SAndreas Gohr        if ($this->config['usetitles']) {
1657234bfb1Ssplitbrain            [$value] = \helper_plugin_struct::decodeJson($value);
166e7ee2b64SAndreas Gohr        }
167e7ee2b64SAndreas Gohr        return $value;
168e7ee2b64SAndreas Gohr    }
169e7ee2b64SAndreas Gohr
170e7ee2b64SAndreas Gohr    /**
1715241ca30SAndreas Gohr     * Return the title only
1725241ca30SAndreas Gohr     *
1735241ca30SAndreas Gohr     * @param string $value
1745241ca30SAndreas Gohr     * @return string
1755241ca30SAndreas Gohr     */
176d6d97f60SAnna Dabrowska    public function displayValue($value)
177d6d97f60SAnna Dabrowska    {
1785241ca30SAndreas Gohr        if ($this->config['usetitles']) {
1797234bfb1Ssplitbrain            [$pageid, $value] = \helper_plugin_struct::decodeJson($value);
180faa6f42cSMichael Grosse            if (blank($value)) {
181faa6f42cSMichael Grosse                $value = $pageid;
182faa6f42cSMichael Grosse            }
1835241ca30SAndreas Gohr        }
1845241ca30SAndreas Gohr        return $value;
1855241ca30SAndreas Gohr    }
1865241ca30SAndreas Gohr
1875241ca30SAndreas Gohr    /**
188fab65495SAndreas Gohr     * When using titles, we need to compare against the title table, too
189a827267cSAndreas Gohr     *
190af993d55SMichael Grosse     * @param QueryBuilderWhere $add
191a827267cSAndreas Gohr     * @param string $tablealias
192a827267cSAndreas Gohr     * @param string $colname
193a827267cSAndreas Gohr     * @param string $comp
194a827267cSAndreas Gohr     * @param string $value
195a827267cSAndreas Gohr     * @param string $op
196a827267cSAndreas Gohr     */
197d6d97f60SAnna Dabrowska    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
198d6d97f60SAnna Dabrowska    {
199a827267cSAndreas Gohr        if (!$this->config['usetitles']) {
200af993d55SMichael Grosse            parent::filter($add, $tablealias, $colname, $comp, $value, $op);
201a827267cSAndreas Gohr            return;
202a827267cSAndreas Gohr        }
203a827267cSAndreas Gohr
204af993d55SMichael Grosse        $QB = $add->getQB();
205a827267cSAndreas Gohr        $rightalias = $QB->generateTableAlias();
206a827267cSAndreas Gohr        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
207a827267cSAndreas Gohr
208fab65495SAndreas Gohr        // compare against page and title
209af993d55SMichael Grosse        $sub = $add->where($op);
210a827267cSAndreas Gohr        $pl = $QB->addValue($value);
211fab65495SAndreas Gohr        $sub->whereOr("$tablealias.$colname $comp $pl");
212fab65495SAndreas Gohr        $pl = $QB->addValue($value);
213fab65495SAndreas Gohr        $sub->whereOr("$rightalias.title $comp $pl");
214a827267cSAndreas Gohr    }
215c0f1a2d1SAnna Dabrowska
216c0f1a2d1SAnna Dabrowska    /**
2179c075503SAnna Dabrowska     * Check if the given id matches a configured filter pattern
218c0f1a2d1SAnna Dabrowska     *
219c0f1a2d1SAnna Dabrowska     * @param string $id
2209c075503SAnna Dabrowska     * @param string $filter
221c0f1a2d1SAnna Dabrowska     * @return bool
222c0f1a2d1SAnna Dabrowska     */
2239c075503SAnna Dabrowska    public function filterMatch($id, $filter)
224c0f1a2d1SAnna Dabrowska    {
225c0f1a2d1SAnna Dabrowska        // absolute namespace?
2269c075503SAnna Dabrowska        if (PhpString::substr($filter, 0, 1) === ':') {
2279c075503SAnna Dabrowska            $filter = '^' . $filter;
228c0f1a2d1SAnna Dabrowska        }
229c0f1a2d1SAnna Dabrowska
230996125f5SAnna Dabrowska        try {
231996125f5SAnna Dabrowska            $check = preg_match('/' . $filter . '/', ':' . $id, $matches);
232996125f5SAnna Dabrowska        } catch (\Exception $e) {
233996125f5SAnna Dabrowska            throw new StructException("Error processing regular expression '$filter'");
234996125f5SAnna Dabrowska        }
235996125f5SAnna Dabrowska        return (bool)$check;
2369c075503SAnna Dabrowska    }
2379c075503SAnna Dabrowska
2389c075503SAnna Dabrowska    /**
2399c075503SAnna Dabrowska     * Merge the current config with the base config of the type.
2409c075503SAnna Dabrowska     *
2419c075503SAnna Dabrowska     * In contrast to parent, this method does not throw away unknown keys.
2429c075503SAnna Dabrowska     * Required to migrate deprecated / obsolete options, no longer part of type config.
2439c075503SAnna Dabrowska     *
2449c075503SAnna Dabrowska     * @param array $current Current configuration
2459c075503SAnna Dabrowska     * @param array $config Base Type configuration
2469c075503SAnna Dabrowska     */
2479c075503SAnna Dabrowska    protected function mergeConfig($current, &$config)
2489c075503SAnna Dabrowska    {
2499c075503SAnna Dabrowska        foreach ($current as $key => $value) {
2509c075503SAnna Dabrowska            if (isset($config[$key]) && is_array($config[$key])) {
2519c075503SAnna Dabrowska                $this->mergeConfig($value, $config[$key]);
2529c075503SAnna Dabrowska            } else {
2539c075503SAnna Dabrowska                $config[$key] = $value;
2549c075503SAnna Dabrowska            }
2559c075503SAnna Dabrowska        }
25645c5f0a9SAnna Dabrowska
25745c5f0a9SAnna Dabrowska        // migrate autocomplete options 'namespace' and 'postfix' to 'filter'
25845c5f0a9SAnna Dabrowska        if (empty($config['autocomplete']['filter'])) {
25945c5f0a9SAnna Dabrowska            if (!empty($config['autocomplete']['namespace'])) {
26045c5f0a9SAnna Dabrowska                $config['autocomplete']['filter'] = $config['autocomplete']['namespace'];
26145c5f0a9SAnna Dabrowska                unset($config['autocomplete']['namespace']);
26245c5f0a9SAnna Dabrowska            }
26345c5f0a9SAnna Dabrowska            if (!empty($config['autocomplete']['postfix'])) {
26445c5f0a9SAnna Dabrowska                $config['autocomplete']['filter'] .= '.+?' . $config['autocomplete']['postfix'] . '$';
26545c5f0a9SAnna Dabrowska                unset($config['autocomplete']['postfix']);
26645c5f0a9SAnna Dabrowska            }
26745c5f0a9SAnna Dabrowska        }
268c0f1a2d1SAnna Dabrowska    }
2690234787dSAndreas Gohr}
270