xref: /plugin/tagging/helper/querybuilder.php (revision 4a7da0a5bb06936be030da8735839af485bfe649)
1<?php
2/**
3 * Tagging Plugin (helper component)
4 */
5class helper_plugin_tagging_querybuilder extends DokuWiki_Plugin {
6
7    /** @var string */
8    protected $field;
9    /** @var bool */
10    protected $logicalAnd = false;
11    /** @var array */
12    protected $tags = [];
13    /** @var array  */
14    protected $ns = [];
15    /** @var array */
16    protected $notns = [];
17    /** @var string */
18    protected $pid;
19    /** @var string */
20    protected $tagger = '';
21    /** @var string */
22    protected $limit = '';
23    /** @var string */
24    protected $where;
25    /** @var string */
26    protected $orderby;
27    /** @var string */
28    protected $groupby;
29    /** @var string */
30    protected $having = '';
31    /** @var array */
32    protected $values = [];
33
34    /**
35     * Shorthand method: calls the appropriate getter deduced from $this->field
36     *
37     * @return helper_plugin_tagging_querybuilder
38     */
39    public function getQuery()
40    {
41        if (!$this->field) {
42            throw new \RuntimeException('Failed to build a query, no field specified');
43        }
44        return ($this->field === 'pid') ? $this->getPages() : $this->getTags();
45    }
46
47    /**
48     * Processes all parts of the query for fetching tagged pages
49     *
50     * Returns the query builder object
51     *
52     * @return helper_plugin_tagging_querybuilder
53     */
54    public function getPages()
55    {
56        $this->where = $this->getWhere();
57        $this->groupby = 'pid';
58        $this->orderby = "cnt DESC, pid";
59        if ($this->tags && $this->logicalAnd) $this->having = ' HAVING cnt = ' . count($this->tags);
60
61        return $this;
62    }
63
64    /**
65     * Processes all parts of the query for fetching tags
66     *
67     * Returns the query builder object
68     *
69     * @return helper_plugin_tagging_querybuilder
70     */
71    public function getTags()
72    {
73        $this->where = $this->getWhere();
74        $this->groupby = 'CLEANTAG(tag)';
75        $this->orderby = 'CLEANTAG(tag)';
76
77        return $this;
78    }
79
80    /**
81     * Returns the SQL string
82     * @return string
83     */
84    public function getSql()
85    {
86        return $this->composeSql();
87    }
88
89    /**
90     * Returns the parameter values
91     * @return array
92     */
93    public function getParameterValues()
94    {
95        return $this->values;
96    }
97
98    /**
99     * Tags to search for
100     * @param array $tags
101     */
102    public function setTags($tags)
103    {
104        $this->tags = $tags;
105    }
106
107    /**
108     * Namespaces to limit search to
109     * @param array $ns
110     */
111    public function includeNS($ns)
112    {
113        $this->ns = $this->globNS($ns);
114    }
115
116    /**
117     * Namespaces to exclude from search
118     * @param array $ns
119     */
120    public function excludeNS($ns)
121    {
122        $this->notns = $this->globNS($ns);
123    }
124
125    /**
126     * Sets the logical operator used in tag search to AND
127     * @param bool $and
128     */
129    public function setLogicalAnd($and)
130    {
131        $this->logicalAnd = $and;
132    }
133
134    /**
135     * Result limit
136     * @param string $limit
137     */
138    public function setLimit($limit)
139    {
140        $this->limit = $limit ? " LIMIT $limit" : '';
141    }
142
143    /**
144     * Database field to select
145     * @param string $field
146     */
147    public function setField($field)
148    {
149        $this->field = $field;
150    }
151
152    /**
153     * Limit search to this page id
154     * @param string $pid
155     */
156    public function setPid($pid)
157    {
158        $this->pid = $pid;
159    }
160
161    /**
162     * Limit results to this tagger
163     * @param string $tagger
164     */
165    public function setTagger($tagger)
166    {
167        $this->tagger = $tagger;
168    }
169
170    /**
171     * Returns full query SQL
172     * @return string
173     */
174    protected function composeSql()
175    {
176        $sql = "SELECT $this->field AS item, COUNT(*) AS cnt
177                  FROM taggings
178                 WHERE $this->where
179              GROUP BY $this->groupby
180              $this->having
181              ORDER BY $this->orderby
182                $this->limit
183              ";
184
185        return $sql;
186    }
187
188    /**
189     * Builds the WHERE part of query string
190     * @return string
191     */
192    protected function getWhere()
193    {
194        $where = '1=1';
195
196        if ($this->pid) {
197            $where .= ' AND pid = ?';
198            $this->values[] = $this->pid;
199        }
200
201        if ($this->tagger) {
202            $where .= ' AND tagger = ?';
203            $this->values[] = $this->tagger;
204        }
205
206        if ($this->ns) {
207            $where .= ' AND ';
208
209            $nsCnt = count($this->ns);
210            $i = 0;
211            foreach ($this->ns as $ns) {
212                $where .= ' pid';
213                $where .= ' GLOB';
214                $where .= ' ?';
215                if (++$i < $nsCnt) $where .= ' OR';
216                $this->values[] = $ns;
217            }
218        }
219
220        if ($this->notns) {
221            $where .= ' AND ';
222
223            $nsCnt = count($this->notns);
224            $i = 0;
225            foreach ($this->notns as $notns) {
226                $where .= ' pid';
227                $where .= ' NOT GLOB';
228                $where .= ' ?';
229                if (++$i < $nsCnt) $where .= ' AND';
230                $this->values[] = $notns;
231            }
232        }
233
234        if ($this->tags) {
235            $where .= ' AND ';
236
237            $tagCnt = count($this->tags);
238            $i = 0;
239            foreach ($this->tags as $tag) {
240                $where .= ' CLEANTAG(tag)';
241                $where .= $this->useLike($tag) ? ' GLOB' : ' =';
242                $where .= ' CLEANTAG(?)';
243                if (++$i < $tagCnt) $where .= ' OR';
244                $this->values[] = $tag;
245            }
246        }
247
248        $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_READ;
249
250
251        return $where;
252    }
253
254    /**
255     * Check if the given string is a LIKE statement
256     *
257     * @param string $value
258     * @return bool
259     */
260    protected function useLike($value) {
261        return strpos($value, '*') === 0 || strrpos($value, '*') === strlen($value) - 1;
262    }
263
264    /**
265     * Converts namespaces into a wildcard form suitable for SQL queries
266     *
267     * @param array $item
268     * @return array
269     */
270    protected function globNS(array $item)
271    {
272        return array_map(function($ns) {
273            return cleanId($ns) . '*';
274        }, $item);
275    }
276
277}
278