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