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