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