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