1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Gina Haeussge <gina@foosel.net> 5 */ 6 7/** 8 * Delivers tag related functions 9 */ 10class helper_plugin_blogtng_tags extends DokuWiki_Plugin { 11 12 /** @var helper_plugin_blogtng_sqlite */ 13 private $sqlitehelper; 14 15 private $tags = array(); 16 17 private $pid = null; 18 19 /** 20 * Constructor, loads the sqlite helper plugin 21 */ 22 public function __construct() { 23 $this->sqlitehelper = plugin_load('helper', 'blogtng_sqlite'); 24 } 25 26 /** 27 * Set pid of page 28 * 29 * @param string $pid 30 */ 31 public function setPid($pid) { 32 $this->pid = trim($pid); 33 } 34 35 /** 36 * Set tags, filtered and unique 37 * 38 * @param array $tags 39 */ 40 public function setTags($tags) { 41 $this->tags = array_unique(array_filter(array_map('trim', $tags))); 42 } 43 44 /** 45 * Return the tag array 46 * 47 * @return array tags 48 */ 49 public function getTags() { 50 return $this->tags; 51 } 52 53 /** 54 * Load tags for specified pid 55 * 56 * @param $pid 57 * @return bool 58 */ 59 public function load($pid) { 60 $this->setPid($pid); 61 62 if(!$this->sqlitehelper->ready()) { 63 msg('blogtng plugin: failed to load sqlite helper plugin!', -1); 64 $this->tags = []; 65 return false; 66 } 67 $query = 'SELECT tag 68 FROM tags 69 WHERE pid = ? 70 ORDER BY tag ASC'; 71 $resid = $this->sqlitehelper->getDB()->query($query, $this->pid); 72 if ($resid === false) { 73 msg('blogtng plugin: failed to load tags!', -1); 74 $this->tags = []; 75 return false; 76 } 77 if ($this->sqlitehelper->getDB()->res2count($resid) == 0) { 78 $this->tags = []; 79 return true; 80 } 81 82 $tags_from_db = $this->sqlitehelper->getDB()->res2arr($resid); 83 $tags = []; 84 foreach ($tags_from_db as $tag_from_db) { 85 $tags[] = $tag_from_db['tag']; 86 } 87 $this->tags = $tags; 88 return true; 89 } 90 91 /** 92 * Count tags for specified pid 93 * 94 * @param $pid 95 * @return int 96 */ 97 public function count($pid) { 98 if(!$this->sqlitehelper->ready()) { 99 msg('BlogTNG plugin: failed to load tags. (sqlite helper plugin not available)', -1); 100 return 0; 101 } 102 103 $pid = trim($pid); 104 $query = 'SELECT COUNT(tag) AS tagcount 105 FROM tags 106 WHERE pid = ?'; 107 108 $resid = $this->sqlitehelper->getDB()->query($query, $pid); 109 if ($resid === false) { 110 msg('BlogTNG plugin: failed to load tags!', -1); 111 return 0; 112 } 113 114 $tagcount = $this->sqlitehelper->getDB()->res2row($resid, 0); 115 return (int)$tagcount['tagcount']; 116 } 117 118 /** 119 * Load tags for a specified blog 120 * 121 * @param $blogs 122 * @return array|bool 123 */ 124 public function load_by_blog($blogs) { 125 if(!$this->sqlitehelper->ready()) return false; 126 127 $query = 'SELECT tags.tag AS tag, tags.pid AS pid 128 FROM tags, entries 129 WHERE tags.pid = entries.pid 130 AND entries.blog IN ("' . implode('","', $blogs) . '") 131 AND GETACCESSLEVEL(page) >= '.AUTH_READ; 132 133 $resid = $this->sqlitehelper->getDB()->query($query); 134 if($resid) { 135 return $this->sqlitehelper->getDB()->res2arr($resid); 136 } 137 return false; 138 } 139 140 /** 141 * Save tags 142 */ 143 public function save() { 144 if (!$this->sqlitehelper->ready()) return; 145 146 $query = 'BEGIN TRANSACTION'; 147 if (!$this->sqlitehelper->getDB()->query($query)) { 148 $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION'); 149 return; 150 } 151 $query = 'DELETE FROM tags WHERE pid = ?'; 152 if (!$this->sqlitehelper->getDB()->query($query, $this->pid)) { 153 $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION'); 154 return; 155 } 156 foreach ($this->tags as $tag) { 157 $query = 'INSERT INTO tags (pid, tag) VALUES (?, ?)'; 158 if (!$this->sqlitehelper->getDB()->query($query, $this->pid, $tag)) { 159 $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION'); 160 return; 161 } 162 } 163 $query = 'END TRANSACTION'; 164 if (!$this->sqlitehelper->getDB()->query($query)) { 165 $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION'); 166 } 167 } 168 169 /** 170 * Parses query string to a where clause for use in a query 171 * in the query string: 172 * - tags are space separated 173 * - prefix + is AND 174 * - prefix - is NOT 175 * - no prefix is OR 176 * 177 * @param string $tagquery query string to be parsed 178 * @return null|string 179 */ 180 public function parse_tag_query($tagquery) { 181 if (!$tagquery) { 182 return null; 183 } 184 if(!$this->sqlitehelper->ready()) return null; 185 186 $tags = array_map('trim', explode(' ', $tagquery)); 187 $tag_clauses = array( 188 'OR' => array(), 189 'AND' => array(), 190 'NOT' => array(), 191 ); 192 foreach ($tags as $tag) { 193 if ($tag[0] == '+') { 194 $tag_clauses['AND'][] = 'tag = ' . $this->sqlitehelper->getDB()->quote_string(substr($tag, 1)); 195 } else if ($tag[0] == '-') { 196 $tag_clauses['NOT'][] = 'tag != ' . $this->sqlitehelper->getDB()->quote_string(substr($tag, 1)); 197 } else { 198 $tag_clauses['OR'][] = 'tag = ' . $this->sqlitehelper->getDB()->quote_string($tag); 199 } 200 } 201 $tag_clauses = array_map('array_unique', $tag_clauses); 202 203 $where = ''; 204 if ($tag_clauses['OR']) { 205 $where .= '('.join(' OR ', $tag_clauses['OR']).')'; 206 } 207 if ($tag_clauses['AND']) { 208 $where .= (!empty($where) ? ' AND ' : '').join(' AND ', $tag_clauses['AND']); 209 } 210 if ($tag_clauses['NOT']) { 211 $where .= (!empty($where) ? ' AND ' : '').join(' AND ', $tag_clauses['NOT']); 212 } 213 return $where; 214 } 215 216 /** 217 * Print a list of tags 218 * 219 * @param string $target - tag links will point to this page, tag is passed as parameter 220 */ 221 public function tpl_tags($target){ 222 $prepared = array(); 223 foreach ($this->tags as $tag) { 224 $prepared[] = '<li><div class="li">' . $this->_format_tag_link($tag, $target) . '</div></li>'; 225 } 226 $html = '<ul class="blogtng_tags">'.DOKU_LF.join(DOKU_LF, $prepared).'</ul>'.DOKU_LF; 227 echo $html; 228 } 229 230 /** 231 * Print the joined tags as a string 232 * 233 * @param string $target - tag links will point to this page 234 * @param string $separator 235 */ 236 public function tpl_tagstring($target, $separator) { 237 echo join($separator, array_map(array($this, '_format_tag_link'), $this->tags, array_fill(0, count($this->tags), $target))); 238 } 239 240 /** 241 * Displays a tag cloud 242 * 243 * @author Michael Klier <chi@chimeric.de> 244 * 245 * @param $conf 246 * @return string 247 */ 248 public function xhtml_tagcloud($conf) { 249 $tags = $this->load_by_blog($conf['blog']); 250 if(!$tags) return ''; 251 $cloud = array(); 252 foreach($tags as $tag) { 253 if(!isset($cloud[$tag['tag']])) { 254 $cloud[$tag['tag']] = 1; 255 } else { 256 $cloud[$tag['tag']]++; 257 } 258 //$cloud[$tag['tag']][] = $tag['pid']; 259 } 260 asort($cloud); 261 $cloud = array_slice(array_reverse($cloud), 0, $conf['limit']); 262 $this->_cloud_weight($cloud, min($cloud), max($cloud), 5); 263 ksort($cloud); 264 $output = ""; 265 foreach($cloud as $tag => $weight) { 266 $output .= '<a href="' . wl($conf['target'], ['post-tags'=>$tag]) 267 . '" class="tag cloud_weight' . $weight 268 . '" title="' . $tag . '">' . $tag . "</a>\n"; 269 } 270 return $output; 271 } 272 273 /** 274 * Happily stolen (and slightly modified) from 275 * 276 * http://www.splitbrain.org/blog/2007-01/03-tagging_splitbrain 277 * 278 * @param $tags 279 * @param $min 280 * @param $max 281 * @param $levels 282 */ 283 private function _cloud_weight(&$tags,$min,$max,$levels){ 284 // calculate tresholds 285 $tresholds = array(); 286 $tresholds[0]= $min; // lowest treshold should always be min 287 for($i=1; $i<=$levels; $i++){ 288 $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min; 289 } 290 291 // assign weights 292 foreach($tags as $tag => $cnt){ 293 foreach($tresholds as $tresh => $val){ 294 if($cnt <= $val){ 295 $tags[$tag] = $tresh; 296 break; 297 } 298 $tags[$tag] = $levels; 299 } 300 } 301 } 302 303 /** 304 * Create html of url to target page for given tag 305 * 306 * @param string $tag 307 * @param string $target pageid 308 * @return string html of url 309 */ 310 private function _format_tag_link($tag, $target) { 311 return '<a href="'.wl($target,array('post-tags'=>$tag)).'" class="tag">'.hsc($tag).'</a>'; 312 } 313} 314