xref: /plugin/struct/types/Page.php (revision 7234bfb14e712ff548d9266ef32fdcc8eaf2d04e)
1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\File\PageResolver;
6use dokuwiki\plugin\struct\meta\QueryBuilder;
7use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
8use dokuwiki\Utf8\PhpString;
9
10/**
11 * Class Page
12 *
13 * Represents a single page in the wiki. Will be linked in output.
14 *
15 * @package dokuwiki\plugin\struct\types
16 */
17class Page extends AbstractMultiBaseType
18{
19    protected $config = ['usetitles' => false, 'autocomplete' => ['mininput' => 2, 'maxresult' => 5, 'namespace' => '', 'postfix' => '']];
20
21    /**
22     * Output the stored data
23     *
24     * @param string $value the value stored in the database - JSON when titles are used
25     * @param \Doku_Renderer $R the renderer currently used to render the data
26     * @param string $mode The mode the output is rendered in (eg. XHTML)
27     * @return bool true if $mode could be satisfied
28     */
29    public function renderValue($value, \Doku_Renderer $R, $mode)
30    {
31        if ($this->config['usetitles']) {
32            [$id, $title] = \helper_plugin_struct::decodeJson($value);
33        } else {
34            $id = $value;
35            $title = $id; // cannot be empty or internallink() might hijack %pageid% and use headings
36        }
37
38        if (!$id) return true;
39
40        $R->internallink(":$id", $title);
41        return true;
42    }
43
44    /**
45     * Cleans the link
46     *
47     * @param string $rawvalue
48     * @return string
49     */
50    public function validate($rawvalue)
51    {
52        [$page, $fragment] = array_pad(explode('#', $rawvalue, 2), 2, '');
53        return cleanID($page) . (strlen(cleanID($fragment)) > 0 ? '#' . cleanID($fragment) : '');
54    }
55
56    /**
57     * Autocompletion support for pages
58     *
59     * @return array
60     */
61    public function handleAjax()
62    {
63        global $INPUT;
64
65        // check minimum length
66        $lookup = trim($INPUT->str('search'));
67        if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return [];
68
69        // results wanted?
70        $max = $this->config['autocomplete']['maxresult'];
71        if ($max <= 0) return [];
72
73        // lookup with namespace and postfix applied
74        $namespace = $this->config['autocomplete']['namespace'];
75        if ($namespace) {
76            // namespace may be relative, resolve in current context
77            $namespace .= ':foo'; // resolve expects pageID
78            $resolver = new PageResolver($INPUT->str('ns') . ':foo'); // resolve relative to current namespace
79            $namespace = $resolver->resolveId($namespace);
80            $namespace = getNS($namespace);
81        }
82        $postfix = $this->config['autocomplete']['postfix'];
83        if ($namespace) $lookup .= ' @' . $namespace;
84
85        $data = ft_pageLookup($lookup, true, $this->config['usetitles']);
86        if ($data === []) return [];
87
88        // this basically duplicates what we do in ajax_qsearch()
89        $result = [];
90        $counter = 0;
91        foreach ($data as $id => $title) {
92            if ($this->config['usetitles']) {
93                $name = $title . ' (' . $id . ')';
94            } else {
95                $ns = getNS($id);
96                if ($ns) {
97                    $name = noNS($id) . ' (' . $ns . ')';
98                } else {
99                    $name = $id;
100                }
101            }
102
103            // check suffix
104            if ($postfix && substr($id, -1 * strlen($postfix)) != $postfix) {
105                continue; // page does not end in postfix, don't suggest it
106            }
107
108            $result[] = ['label' => $name, 'value' => $id];
109
110            $counter++;
111            if ($counter > $max) break;
112        }
113
114        return $result;
115    }
116
117    /**
118     * When using titles, we need ot join the titles table
119     *
120     * @param QueryBuilder $QB
121     * @param string $tablealias
122     * @param string $colname
123     * @param string $alias
124     */
125    public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
126    {
127        if (!$this->config['usetitles']) {
128            parent::select($QB, $tablealias, $colname, $alias);
129            return;
130        }
131        $rightalias = $QB->generateTableAlias();
132        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
133        $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $rightalias.title)", $alias);
134    }
135
136    /**
137     * When using titles, sort by them first
138     *
139     * @param QueryBuilder $QB
140     * @param string $tablealias
141     * @param string $colname
142     * @param string $order
143     */
144    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
145    {
146        if (!$this->config['usetitles']) {
147            parent::sort($QB, $tablealias, $colname, $order);
148            return;
149        }
150
151        $rightalias = $QB->generateTableAlias();
152        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
153        $QB->addOrderBy("$rightalias.title $order");
154        $QB->addOrderBy("$tablealias.$colname $order");
155    }
156
157    /**
158     * Return the pageid only
159     *
160     * @param string $value
161     * @return string
162     */
163    public function rawValue($value)
164    {
165        if ($this->config['usetitles']) {
166            [$value] = \helper_plugin_struct::decodeJson($value);
167        }
168        return $value;
169    }
170
171    /**
172     * Return the title only
173     *
174     * @param string $value
175     * @return string
176     */
177    public function displayValue($value)
178    {
179        if ($this->config['usetitles']) {
180            [$pageid, $value] = \helper_plugin_struct::decodeJson($value);
181            if (blank($value)) {
182                $value = $pageid;
183            }
184        }
185        return $value;
186    }
187
188    /**
189     * When using titles, we need to compare against the title table, too
190     *
191     * @param QueryBuilderWhere $add
192     * @param string $tablealias
193     * @param string $colname
194     * @param string $comp
195     * @param string $value
196     * @param string $op
197     */
198    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
199    {
200        if (!$this->config['usetitles']) {
201            parent::filter($add, $tablealias, $colname, $comp, $value, $op);
202            return;
203        }
204
205        $QB = $add->getQB();
206        $rightalias = $QB->generateTableAlias();
207        $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.$colname = $rightalias.pid");
208
209        // compare against page and title
210        $sub = $add->where($op);
211        $pl = $QB->addValue($value);
212        $sub->whereOr("$tablealias.$colname $comp $pl");
213        $pl = $QB->addValue($value);
214        $sub->whereOr("$rightalias.title $comp $pl");
215    }
216}
217