1f61105deSAdrian Lang<?php 2f61105deSAdrian Lang 3f61105deSAdrian Langif(!defined('DOKU_INC')) die(); 4e4543b6dSAdrian Langclass helper_plugin_tagging extends DokuWiki_Plugin { 5f61105deSAdrian Lang 6289f50bdSAndreas Gohr /** 7b12334e1SAndreas Gohr * Gives access to the database 8b12334e1SAndreas Gohr * 9b12334e1SAndreas Gohr * Initializes the SQLite helper and register the CLEANTAG function 10b12334e1SAndreas Gohr * 11b12334e1SAndreas Gohr * @return helper_plugin_sqlite|bool false if initialization fails 12289f50bdSAndreas Gohr */ 13289f50bdSAndreas Gohr public function getDB() { 14302a38efSAndreas Gohr static $db = null; 15f61105deSAdrian Lang if (!is_null($db)) { 16f61105deSAdrian Lang return $db; 17f61105deSAdrian Lang } 18f61105deSAdrian Lang 19302a38efSAndreas Gohr /** @var helper_plugin_sqlite $db */ 20f61105deSAdrian Lang $db = plugin_load('helper', 'sqlite'); 21f61105deSAdrian Lang if (is_null($db)) { 22f61105deSAdrian Lang msg('The tagging plugin needs the sqlite plugin', -1); 23f61105deSAdrian Lang return false; 24f61105deSAdrian Lang } 257e6afce6SAndreas Gohr $db->init('tagging',dirname(__FILE__).'/db/'); 26302a38efSAndreas Gohr $db->create_function('CLEANTAG', array($this, 'cleanTag'), 1); 27f61105deSAdrian Lang return $db; 28f61105deSAdrian Lang } 29f61105deSAdrian Lang 30302a38efSAndreas Gohr /** 31302a38efSAndreas Gohr * Canonicalizes the tag to its lower case nospace form 32302a38efSAndreas Gohr * 33302a38efSAndreas Gohr * @param $tag 34302a38efSAndreas Gohr * @return string 35302a38efSAndreas Gohr */ 36302a38efSAndreas Gohr public function cleanTag($tag) { 37302a38efSAndreas Gohr $tag = str_replace(' ', '', $tag); 38302a38efSAndreas Gohr $tag = utf8_strtolower($tag); 39302a38efSAndreas Gohr return $tag; 40302a38efSAndreas Gohr } 41302a38efSAndreas Gohr 42302a38efSAndreas Gohr 43f61105deSAdrian Lang public function replaceTags($id, $user, $tags) { 44f61105deSAdrian Lang $db = $this->getDB(); 45f61105deSAdrian Lang $db->query('BEGIN TRANSACTION'); 46f61105deSAdrian Lang $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user)); 47f61105deSAdrian Lang foreach ($tags as $tag) { 48f61105deSAdrian Lang $queries[] = array('INSERT INTO taggings (pid, tagger, tag) VALUES(?, ?, ?)', $id, $user, $tag); 49f61105deSAdrian Lang } 50f61105deSAdrian Lang 51f61105deSAdrian Lang foreach ($queries as $query) { 52f61105deSAdrian Lang if (!call_user_func_array(array($db, 'query'), $query)) { 53f61105deSAdrian Lang $db->query('ROLLBACK TRANSACTION'); 54f61105deSAdrian Lang return false; 55f61105deSAdrian Lang } 56f61105deSAdrian Lang } 57f61105deSAdrian Lang return $db->query('COMMIT TRANSACTION'); 58f61105deSAdrian Lang } 59f61105deSAdrian Lang 600a518a11SAndreas Gohr /** 61b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 620a518a11SAndreas Gohr * 63b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 64b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 650a518a11SAndreas Gohr * @return array associative array in form of value => count 660a518a11SAndreas Gohr */ 67b12334e1SAndreas Gohr public function findItems($filter, $type) { 68f61105deSAdrian Lang $db = $this->getDB(); 69b12334e1SAndreas Gohr if(!$db) return array(); 70f61105deSAdrian Lang 71b12334e1SAndreas Gohr // create WHERE clause 72b12334e1SAndreas Gohr $where = '1=1'; 73b12334e1SAndreas Gohr foreach($filter as $field => $value) { 74b12334e1SAndreas Gohr // compare clean tags only 75b12334e1SAndreas Gohr if ($field === 'tag') { 76b12334e1SAndreas Gohr $field = 'CLEANTAG(tag)'; 77b12334e1SAndreas Gohr $q = 'CLEANTAG(?)'; 78b12334e1SAndreas Gohr } else { 79b12334e1SAndreas Gohr $q = '?'; 80b12334e1SAndreas Gohr } 81b12334e1SAndreas Gohr // detect LIKE filters 82b12334e1SAndreas Gohr if ($this->useLike($value)) { 83b12334e1SAndreas Gohr $where .= " AND $field LIKE $q"; 84b12334e1SAndreas Gohr } else { 85b12334e1SAndreas Gohr $where .= " AND $field = $q"; 86b12334e1SAndreas Gohr } 87b12334e1SAndreas Gohr } 88b12334e1SAndreas Gohr // group and order 89b12334e1SAndreas Gohr if($type == 'tag') { 90b12334e1SAndreas Gohr $groupby = 'CLEANTAG(tag)'; 91b12334e1SAndreas Gohr $orderby = 'CLEANTAG(tag)'; 92b12334e1SAndreas Gohr } else { 93b12334e1SAndreas Gohr $groupby = $type; 94466524f5SAndreas Gohr $orderby = "cnt DESC, $type"; 95b12334e1SAndreas Gohr } 96b12334e1SAndreas Gohr 97b12334e1SAndreas Gohr // create SQL 98b12334e1SAndreas Gohr $sql = "SELECT $type AS item, COUNT(*) AS cnt 99b12334e1SAndreas Gohr FROM taggings 100b12334e1SAndreas Gohr WHERE $where 101b12334e1SAndreas Gohr GROUP BY $groupby 102b12334e1SAndreas Gohr ORDER BY $orderby"; 103b12334e1SAndreas Gohr 104b12334e1SAndreas Gohr // run query and turn into associative array 105b12334e1SAndreas Gohr $res = $db->query($sql, array_values($filter)); 106f61105deSAdrian Lang $res = $db->res2arr($res); 107b12334e1SAndreas Gohr 108f61105deSAdrian Lang $ret = array(); 109b12334e1SAndreas Gohr foreach ($res as $row) { 110b12334e1SAndreas Gohr $ret[$row['item']] = $row['cnt']; 111f61105deSAdrian Lang } 112f61105deSAdrian Lang return $ret; 113f61105deSAdrian Lang } 114f61105deSAdrian Lang 115b12334e1SAndreas Gohr /** 116b12334e1SAndreas Gohr * Check if the given string is a LIKE statement 117b12334e1SAndreas Gohr * 118b12334e1SAndreas Gohr * @param string $value 119b12334e1SAndreas Gohr * @return bool 120b12334e1SAndreas Gohr */ 121b12334e1SAndreas Gohr private function useLike($value) { 122b12334e1SAndreas Gohr return strpos($value, '%') === 0 || strrpos($value, '%') === strlen($value) - 1; 123b2073787SDominik Eckelmann } 124b2073787SDominik Eckelmann 125302a38efSAndreas Gohr /** 126302a38efSAndreas Gohr * Constructs the URL to search for a tag 127302a38efSAndreas Gohr * 128*5540f91dSAndreas Gohr * @param string $tag 129*5540f91dSAndreas Gohr * @param string $ns 130302a38efSAndreas Gohr * @return string 131302a38efSAndreas Gohr */ 132*5540f91dSAndreas Gohr public function getTagSearchURL($tag, $ns = '') { 133*5540f91dSAndreas Gohr $ret = '?do=search&id=' . rawurlencode($tag); 134*5540f91dSAndreas Gohr if($ns) $ret .= rawurlencode(' @'.$ns); 135*5540f91dSAndreas Gohr 136*5540f91dSAndreas Gohr return $ret; 137f61105deSAdrian Lang } 138f61105deSAdrian Lang 139*5540f91dSAndreas Gohr /** 140*5540f91dSAndreas Gohr * Calculates the size levels for the given list of clouds 141*5540f91dSAndreas Gohr * 142*5540f91dSAndreas Gohr * Automatically determines sensible tresholds 143*5540f91dSAndreas Gohr * 144*5540f91dSAndreas Gohr * @param array $tags list of tags => count 145*5540f91dSAndreas Gohr * @param int $levels 146*5540f91dSAndreas Gohr * @return mixed 147*5540f91dSAndreas Gohr */ 148f61105deSAdrian Lang public function cloudData($tags, $levels = 10) { 149f61105deSAdrian Lang $min = min($tags); 150f61105deSAdrian Lang $max = max($tags); 151f61105deSAdrian Lang 152f61105deSAdrian Lang // calculate tresholds 153f61105deSAdrian Lang $tresholds = array(); 154f61105deSAdrian Lang for($i=0; $i<=$levels; $i++){ 155f61105deSAdrian Lang $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min - 1; 156f61105deSAdrian Lang } 157f61105deSAdrian Lang 158f61105deSAdrian Lang // assign weights 159f61105deSAdrian Lang foreach($tags as $tag => $cnt){ 160f61105deSAdrian Lang foreach($tresholds as $tresh => $val){ 161f61105deSAdrian Lang if($cnt <= $val){ 162f61105deSAdrian Lang $tags[$tag] = $tresh; 163f61105deSAdrian Lang break; 164f61105deSAdrian Lang } 165f61105deSAdrian Lang $tags[$tag] = $levels; 166f61105deSAdrian Lang } 167f61105deSAdrian Lang } 168f61105deSAdrian Lang return $tags; 169f61105deSAdrian Lang } 170f61105deSAdrian Lang 171*5540f91dSAndreas Gohr /** 172*5540f91dSAndreas Gohr * Display a tag cloud 173*5540f91dSAndreas Gohr * 174*5540f91dSAndreas Gohr * @param array $tags list of tags => count 175*5540f91dSAndreas Gohr * @param string $type 'tag' 176*5540f91dSAndreas Gohr * @param Callable $func The function to print the link (gets tag and ns) 177*5540f91dSAndreas Gohr * @param bool $wrap wrap cloud in UL tags? 178*5540f91dSAndreas Gohr * @param bool $return returnn HTML instead of printing? 179*5540f91dSAndreas Gohr * @param string $ns Add this namespace to search links 180*5540f91dSAndreas Gohr * @return string 181*5540f91dSAndreas Gohr */ 182*5540f91dSAndreas Gohr public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') { 183f61105deSAdrian Lang $ret = ''; 1840dbdc3fcSAdrian Lang if($wrap) $ret .= '<ul class="tagging_cloud clearfix">'; 185f61105deSAdrian Lang if(count($tags) === 0) { 186f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 187f61105deSAdrian Lang $this->setupLocale(); 188f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 189f61105deSAdrian Lang } else { 190f61105deSAdrian Lang $tags = $this->cloudData($tags); 191f61105deSAdrian Lang foreach($tags as $val => $size) { 192f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 193*5540f91dSAndreas Gohr $ret .= call_user_func($func, $val, $ns); 194f61105deSAdrian Lang $ret .= '</div></li>'; 195f61105deSAdrian Lang } 196f61105deSAdrian Lang } 197f61105deSAdrian Lang if($wrap) $ret .= '</ul>'; 198f61105deSAdrian Lang if($return) return $ret; 199f61105deSAdrian Lang echo $ret; 200*5540f91dSAndreas Gohr return ''; 201f61105deSAdrian Lang } 202f61105deSAdrian Lang 203*5540f91dSAndreas Gohr /** 204*5540f91dSAndreas Gohr * Get the link to a search for the given tag 205*5540f91dSAndreas Gohr * 206*5540f91dSAndreas Gohr * @param string $tag search for this tag 207*5540f91dSAndreas Gohr * @param string $ns limit search to this namespace 208*5540f91dSAndreas Gohr * @return string 209*5540f91dSAndreas Gohr */ 210*5540f91dSAndreas Gohr protected function linkToSearch($tag, $ns='') { 211*5540f91dSAndreas Gohr return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>'; 212f61105deSAdrian Lang } 213f61105deSAdrian Lang 214f61105deSAdrian Lang public function tpl_tags() { 215f61105deSAdrian Lang global $INFO; 216f61105deSAdrian Lang global $lang; 217fbf38c5bSAndreas Gohr $tags = $this->findItems(array('pid' => $INFO['id']), 'tag'); 218f61105deSAdrian Lang $this->html_cloud($tags, 'tag', array($this, 'linkToSearch')); 219f61105deSAdrian Lang 220f61105deSAdrian Lang if (isset($_SERVER['REMOTE_USER']) && $INFO['writable']) { 221f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 222fbf38c5bSAndreas Gohr echo html_btn('tagging_edit', $INFO['id'], '', array()); 223289f50bdSAndreas Gohr $form = new Doku_Form(array('id' => 'tagging__edit')); 224fbf38c5bSAndreas Gohr $form->addHidden('tagging[id]', $INFO['id']); 225289f50bdSAndreas Gohr $form->addHidden('call', 'plugin_tagging_save'); 226fbf38c5bSAndreas Gohr $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $INFO['id'], 'tagger' => $_SERVER['REMOTE_USER']), 'tag'))))); 227289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save'))); 228289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel'))); 229f61105deSAdrian Lang $form->printForm(); 230f61105deSAdrian Lang } 231f61105deSAdrian Lang } 232872edc7cSRené Corinth 2338a1a3846SAndreas Gohr /** 2348a1a3846SAndreas Gohr * @return array 2358a1a3846SAndreas Gohr */ 236872edc7cSRené Corinth public function getAllTags(){ 237872edc7cSRené Corinth 238872edc7cSRené Corinth $db = $this->getDb(); 2398a1a3846SAndreas Gohr $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag'); 240872edc7cSRené Corinth 241872edc7cSRené Corinth $tags_tmp = $db->res2arr($res); 242872edc7cSRené Corinth $tags = array(); 243872edc7cSRené Corinth foreach ($tags_tmp as $tag) { 244302a38efSAndreas Gohr $tid = $this->cleanTag($tag['tag']); 245872edc7cSRené Corinth 246302a38efSAndreas Gohr //$tags[$tid]['pid'][] = $tag['pid']; 247872edc7cSRené Corinth 248302a38efSAndreas Gohr if (isset($tags[$tid]['count'])) { 249302a38efSAndreas Gohr $tags[$tid]['count']++; 250302a38efSAndreas Gohr $tags[$tid]['tagger'][] = $tag['tagger']; 251872edc7cSRené Corinth } else { 252302a38efSAndreas Gohr $tags[$tid]['count'] = 1; 253302a38efSAndreas Gohr $tags[$tid]['tagger'] = array($tag['tagger']); 254872edc7cSRené Corinth } 255872edc7cSRené Corinth } 256872edc7cSRené Corinth return $tags; 257872edc7cSRené Corinth } 258872edc7cSRené Corinth 2598a1a3846SAndreas Gohr /** 2608a1a3846SAndreas Gohr * Renames a tag 2618a1a3846SAndreas Gohr * 2628a1a3846SAndreas Gohr * @param string $formerTagName 2638a1a3846SAndreas Gohr * @param string $newTagName 2648a1a3846SAndreas Gohr */ 265872edc7cSRené Corinth public function renameTag($formerTagName, $newTagName) { 266872edc7cSRené Corinth 267872edc7cSRené Corinth if(empty($formerTagName) || empty($newTagName)) { 2688a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 2698a1a3846SAndreas Gohr return; 270872edc7cSRené Corinth } 271872edc7cSRené Corinth 272872edc7cSRené Corinth $db = $this->getDb(); 273872edc7cSRené Corinth 274302a38efSAndreas Gohr $res = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName); 275872edc7cSRené Corinth $check = $db->res2arr($res); 276872edc7cSRené Corinth 277872edc7cSRené Corinth if (empty($check)) { 2788a1a3846SAndreas Gohr msg($this->getLang("admin tag does not exists"), -1); 2798a1a3846SAndreas Gohr return; 280872edc7cSRené Corinth } 281872edc7cSRené Corinth 282302a38efSAndreas Gohr $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName); 283872edc7cSRené Corinth $db->res2arr($res); 284872edc7cSRené Corinth 2858a1a3846SAndreas Gohr msg($this->getLang("admin saved"), 1); 2868a1a3846SAndreas Gohr return; 287872edc7cSRené Corinth } 288872edc7cSRené Corinth 289f61105deSAdrian Lang} 290