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 * 128302a38efSAndreas Gohr * @param $tag 129302a38efSAndreas Gohr * @return string 130302a38efSAndreas Gohr */ 131f61105deSAdrian Lang public function getTagSearchURL($tag) { 132302a38efSAndreas Gohr return '?do=search&id=' . rawurlencode($tag); 133f61105deSAdrian Lang } 134f61105deSAdrian Lang 135f61105deSAdrian Lang public function cloudData($tags, $levels = 10) { 136f61105deSAdrian Lang $min = min($tags); 137f61105deSAdrian Lang $max = max($tags); 138f61105deSAdrian Lang 139f61105deSAdrian Lang // calculate tresholds 140f61105deSAdrian Lang $tresholds = array(); 141f61105deSAdrian Lang for($i=0; $i<=$levels; $i++){ 142f61105deSAdrian Lang $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min - 1; 143f61105deSAdrian Lang } 144f61105deSAdrian Lang 145f61105deSAdrian Lang // assign weights 146f61105deSAdrian Lang foreach($tags as $tag => $cnt){ 147f61105deSAdrian Lang foreach($tresholds as $tresh => $val){ 148f61105deSAdrian Lang if($cnt <= $val){ 149f61105deSAdrian Lang $tags[$tag] = $tresh; 150f61105deSAdrian Lang break; 151f61105deSAdrian Lang } 152f61105deSAdrian Lang $tags[$tag] = $levels; 153f61105deSAdrian Lang } 154f61105deSAdrian Lang } 155f61105deSAdrian Lang return $tags; 156f61105deSAdrian Lang } 157f61105deSAdrian Lang 158f61105deSAdrian Lang public function html_cloud($tags, $type, $func, $wrap = true, $return = false) { 159f61105deSAdrian Lang $ret = ''; 1600dbdc3fcSAdrian Lang if ($wrap) $ret .= '<ul class="tagging_cloud clearfix">'; 161f61105deSAdrian Lang if (count($tags) === 0) { 162f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 163f61105deSAdrian Lang $this->setupLocale(); 164f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 165f61105deSAdrian Lang } else { 166f61105deSAdrian Lang $tags = $this->cloudData($tags); 167f61105deSAdrian Lang foreach ($tags as $val => $size) { 168f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 169f61105deSAdrian Lang $ret .= call_user_func($func, $val); 170f61105deSAdrian Lang $ret .= '</div></li>'; 171f61105deSAdrian Lang } 172f61105deSAdrian Lang } 173f61105deSAdrian Lang if ($wrap) $ret .= '</ul>'; 174f61105deSAdrian Lang if ($return) return $ret; 175f61105deSAdrian Lang echo $ret; 176f61105deSAdrian Lang } 177f61105deSAdrian Lang 178302a38efSAndreas Gohr protected function linkToSearch($tag) { 179f61105deSAdrian Lang return '<a href="' . hsc($this->getTagSearchURL($tag)) . '">' . 180f61105deSAdrian Lang $tag . '</a>'; 181f61105deSAdrian Lang } 182f61105deSAdrian Lang 183f61105deSAdrian Lang public function tpl_tags() { 184f61105deSAdrian Lang global $INFO; 185f61105deSAdrian Lang global $lang; 186*fbf38c5bSAndreas Gohr $tags = $this->findItems(array('pid' => $INFO['id']), 'tag'); 187f61105deSAdrian Lang $this->html_cloud($tags, 'tag', array($this, 'linkToSearch')); 188f61105deSAdrian Lang 189f61105deSAdrian Lang if (isset($_SERVER['REMOTE_USER']) && $INFO['writable']) { 190f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 191*fbf38c5bSAndreas Gohr echo html_btn('tagging_edit', $INFO['id'], '', array()); 192289f50bdSAndreas Gohr $form = new Doku_Form(array('id' => 'tagging__edit')); 193*fbf38c5bSAndreas Gohr $form->addHidden('tagging[id]', $INFO['id']); 194289f50bdSAndreas Gohr $form->addHidden('call', 'plugin_tagging_save'); 195*fbf38c5bSAndreas Gohr $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $INFO['id'], 'tagger' => $_SERVER['REMOTE_USER']), 'tag'))))); 196289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save'))); 197289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel'))); 198f61105deSAdrian Lang $form->printForm(); 199f61105deSAdrian Lang } 200f61105deSAdrian Lang } 201872edc7cSRené Corinth 2028a1a3846SAndreas Gohr /** 2038a1a3846SAndreas Gohr * @return array 2048a1a3846SAndreas Gohr */ 205872edc7cSRené Corinth public function getAllTags(){ 206872edc7cSRené Corinth 207872edc7cSRené Corinth $db = $this->getDb(); 2088a1a3846SAndreas Gohr $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag'); 209872edc7cSRené Corinth 210872edc7cSRené Corinth $tags_tmp = $db->res2arr($res); 211872edc7cSRené Corinth $tags = array(); 212872edc7cSRené Corinth foreach ($tags_tmp as $tag) { 213302a38efSAndreas Gohr $tid = $this->cleanTag($tag['tag']); 214872edc7cSRené Corinth 215302a38efSAndreas Gohr //$tags[$tid]['pid'][] = $tag['pid']; 216872edc7cSRené Corinth 217302a38efSAndreas Gohr if (isset($tags[$tid]['count'])) { 218302a38efSAndreas Gohr $tags[$tid]['count']++; 219302a38efSAndreas Gohr $tags[$tid]['tagger'][] = $tag['tagger']; 220872edc7cSRené Corinth } else { 221302a38efSAndreas Gohr $tags[$tid]['count'] = 1; 222302a38efSAndreas Gohr $tags[$tid]['tagger'] = array($tag['tagger']); 223872edc7cSRené Corinth } 224872edc7cSRené Corinth } 225872edc7cSRené Corinth return $tags; 226872edc7cSRené Corinth } 227872edc7cSRené Corinth 2288a1a3846SAndreas Gohr /** 2298a1a3846SAndreas Gohr * Renames a tag 2308a1a3846SAndreas Gohr * 2318a1a3846SAndreas Gohr * @param string $formerTagName 2328a1a3846SAndreas Gohr * @param string $newTagName 2338a1a3846SAndreas Gohr */ 234872edc7cSRené Corinth public function renameTag($formerTagName, $newTagName) { 235872edc7cSRené Corinth 236872edc7cSRené Corinth if(empty($formerTagName) || empty($newTagName)) { 2378a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 2388a1a3846SAndreas Gohr return; 239872edc7cSRené Corinth } 240872edc7cSRené Corinth 241872edc7cSRené Corinth $db = $this->getDb(); 242872edc7cSRené Corinth 243302a38efSAndreas Gohr $res = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName); 244872edc7cSRené Corinth $check = $db->res2arr($res); 245872edc7cSRené Corinth 246872edc7cSRené Corinth if (empty($check)) { 2478a1a3846SAndreas Gohr msg($this->getLang("admin tag does not exists"), -1); 2488a1a3846SAndreas Gohr return; 249872edc7cSRené Corinth } 250872edc7cSRené Corinth 251302a38efSAndreas Gohr $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName); 252872edc7cSRené Corinth $db->res2arr($res); 253872edc7cSRené Corinth 2548a1a3846SAndreas Gohr msg($this->getLang("admin saved"), 1); 2558a1a3846SAndreas Gohr return; 256872edc7cSRené Corinth } 257872edc7cSRené Corinth 258f61105deSAdrian Lang} 259