1<?php 2 3if(!defined('DOKU_INC')) die(); 4class helper_plugin_tagging extends DokuWiki_Plugin { 5 6 /** 7 * Gives access to the database 8 * 9 * Initializes the SQLite helper and register the CLEANTAG function 10 * 11 * @return helper_plugin_sqlite|bool false if initialization fails 12 */ 13 public function getDB() { 14 static $db = null; 15 if (!is_null($db)) { 16 return $db; 17 } 18 19 /** @var helper_plugin_sqlite $db */ 20 $db = plugin_load('helper', 'sqlite'); 21 if (is_null($db)) { 22 msg('The tagging plugin needs the sqlite plugin', -1); 23 return false; 24 } 25 $db->init('tagging',dirname(__FILE__).'/db/'); 26 $db->create_function('CLEANTAG', array($this, 'cleanTag'), 1); 27 return $db; 28 } 29 30 /** 31 * Canonicalizes the tag to its lower case nospace form 32 * 33 * @param $tag 34 * @return string 35 */ 36 public function cleanTag($tag) { 37 $tag = str_replace(' ', '', $tag); 38 $tag = utf8_strtolower($tag); 39 return $tag; 40 } 41 42 43 public function replaceTags($id, $user, $tags) { 44 $db = $this->getDB(); 45 $db->query('BEGIN TRANSACTION'); 46 $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user)); 47 foreach ($tags as $tag) { 48 $queries[] = array('INSERT INTO taggings (pid, tagger, tag) VALUES(?, ?, ?)', $id, $user, $tag); 49 } 50 51 foreach ($queries as $query) { 52 if (!call_user_func_array(array($db, 'query'), $query)) { 53 $db->query('ROLLBACK TRANSACTION'); 54 return false; 55 } 56 } 57 return $db->query('COMMIT TRANSACTION'); 58 } 59 60 /** 61 * Get a list of Tags or Pages matching search criteria 62 * 63 * @param array $filter What to search for array('field' => 'searchterm') 64 * @param string $type What field to return 'tag'|'pid' 65 * @return array associative array in form of value => count 66 */ 67 public function findItems($filter, $type) { 68 $db = $this->getDB(); 69 if(!$db) return array(); 70 71 // create WHERE clause 72 $where = '1=1'; 73 foreach($filter as $field => $value) { 74 // compare clean tags only 75 if ($field === 'tag') { 76 $field = 'CLEANTAG(tag)'; 77 $q = 'CLEANTAG(?)'; 78 } else { 79 $q = '?'; 80 } 81 // detect LIKE filters 82 if ($this->useLike($value)) { 83 $where .= " AND $field LIKE $q"; 84 } else { 85 $where .= " AND $field = $q"; 86 } 87 } 88 // group and order 89 if($type == 'tag') { 90 $groupby = 'CLEANTAG(tag)'; 91 $orderby = 'CLEANTAG(tag)'; 92 } else { 93 $groupby = $type; 94 $groupby = "cnt DESC, $type"; 95 } 96 97 // create SQL 98 $sql = "SELECT $type AS item, COUNT(*) AS cnt 99 FROM taggings 100 WHERE $where 101 GROUP BY $groupby 102 ORDER BY $orderby"; 103 104 // run query and turn into associative array 105 $res = $db->query($sql, array_values($filter)); 106 $res = $db->res2arr($res); 107 108 $ret = array(); 109 foreach ($res as $row) { 110 $ret[$row['item']] = $row['cnt']; 111 } 112 return $ret; 113 } 114 115 /** 116 * Check if the given string is a LIKE statement 117 * 118 * @param string $value 119 * @return bool 120 */ 121 private function useLike($value) { 122 return strpos($value, '%') === 0 || strrpos($value, '%') === strlen($value) - 1; 123 } 124 125 /** 126 * Constructs the URL to search for a tag 127 * 128 * @param $tag 129 * @return string 130 */ 131 public function getTagSearchURL($tag) { 132 return '?do=search&id=' . rawurlencode($tag); 133 } 134 135 public function cloudData($tags, $levels = 10) { 136 $min = min($tags); 137 $max = max($tags); 138 139 // calculate tresholds 140 $tresholds = array(); 141 for($i=0; $i<=$levels; $i++){ 142 $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min - 1; 143 } 144 145 // assign weights 146 foreach($tags as $tag => $cnt){ 147 foreach($tresholds as $tresh => $val){ 148 if($cnt <= $val){ 149 $tags[$tag] = $tresh; 150 break; 151 } 152 $tags[$tag] = $levels; 153 } 154 } 155 return $tags; 156 } 157 158 public function html_cloud($tags, $type, $func, $wrap = true, $return = false) { 159 $ret = ''; 160 if ($wrap) $ret .= '<ul class="tagging_cloud clearfix">'; 161 if (count($tags) === 0) { 162 // Produce valid XHTML (ul needs a child) 163 $this->setupLocale(); 164 $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 165 } else { 166 $tags = $this->cloudData($tags); 167 foreach ($tags as $val => $size) { 168 $ret .= '<li class="t' . $size . '"><div class="li">'; 169 $ret .= call_user_func($func, $val); 170 $ret .= '</div></li>'; 171 } 172 } 173 if ($wrap) $ret .= '</ul>'; 174 if ($return) return $ret; 175 echo $ret; 176 } 177 178 protected function linkToSearch($tag) { 179 return '<a href="' . hsc($this->getTagSearchURL($tag)) . '">' . 180 $tag . '</a>'; 181 } 182 183 public function tpl_tags() { 184 global $ID; 185 global $INFO; 186 global $lang; 187 $tags = $this->findItems(array('pid' => $ID), 'tag'); 188 $this->html_cloud($tags, 'tag', array($this, 'linkToSearch')); 189 190 if (isset($_SERVER['REMOTE_USER']) && $INFO['writable']) { 191 $lang['btn_tagging_edit'] = $lang['btn_secedit']; 192 echo html_btn('tagging_edit', $ID, '', array()); 193 $form = new Doku_Form(array('id' => 'tagging__edit')); 194 $form->addHidden('tagging[id]', $ID); 195 $form->addHidden('call', 'plugin_tagging_save'); 196 $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $ID, 'tagger' => $_SERVER['REMOTE_USER']), 'tag'))))); 197 $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save'))); 198 $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel'))); 199 $form->printForm(); 200 } 201 } 202 203 /** 204 * @return array 205 */ 206 public function getAllTags(){ 207 208 $db = $this->getDb(); 209 $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag'); 210 211 $tags_tmp = $db->res2arr($res); 212 $tags = array(); 213 foreach ($tags_tmp as $tag) { 214 $tid = $this->cleanTag($tag['tag']); 215 216 //$tags[$tid]['pid'][] = $tag['pid']; 217 218 if (isset($tags[$tid]['count'])) { 219 $tags[$tid]['count']++; 220 $tags[$tid]['tagger'][] = $tag['tagger']; 221 } else { 222 $tags[$tid]['count'] = 1; 223 $tags[$tid]['tagger'] = array($tag['tagger']); 224 } 225 } 226 return $tags; 227 } 228 229 /** 230 * Renames a tag 231 * 232 * @param string $formerTagName 233 * @param string $newTagName 234 */ 235 public function renameTag($formerTagName, $newTagName) { 236 237 if(empty($formerTagName) || empty($newTagName)) { 238 msg($this->getLang("admin enter tag names"), -1); 239 return; 240 } 241 242 $db = $this->getDb(); 243 244 $res = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName); 245 $check = $db->res2arr($res); 246 247 if (empty($check)) { 248 msg($this->getLang("admin tag does not exists"), -1); 249 return; 250 } 251 252 $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName); 253 $db->res2arr($res); 254 255 msg($this->getLang("admin saved"), 1); 256 return; 257 } 258 259} 260