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