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