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