1f61105deSAdrian Lang<?php 2f61105deSAdrian Lang 3f61105deSAdrian Langif(!defined('DOKU_INC')) die(); 4e4543b6dSAdrian Langclass helper_plugin_tagging extends DokuWiki_Plugin { 5f61105deSAdrian Lang 6289f50bdSAndreas Gohr /** 7*b12334e1SAndreas Gohr * Gives access to the database 8*b12334e1SAndreas Gohr * 9*b12334e1SAndreas Gohr * Initializes the SQLite helper and register the CLEANTAG function 10*b12334e1SAndreas Gohr * 11*b12334e1SAndreas 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 /** 61*b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 620a518a11SAndreas Gohr * 63*b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 64*b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 650a518a11SAndreas Gohr * @return array associative array in form of value => count 660a518a11SAndreas Gohr */ 67*b12334e1SAndreas Gohr public function findItems($filter, $type) { 68f61105deSAdrian Lang $db = $this->getDB(); 69*b12334e1SAndreas Gohr if(!$db) return array(); 70f61105deSAdrian Lang 71*b12334e1SAndreas Gohr // create WHERE clause 72*b12334e1SAndreas Gohr $where = '1=1'; 73*b12334e1SAndreas Gohr foreach($filter as $field => $value) { 74*b12334e1SAndreas Gohr // compare clean tags only 75*b12334e1SAndreas Gohr if ($field === 'tag') { 76*b12334e1SAndreas Gohr $field = 'CLEANTAG(tag)'; 77*b12334e1SAndreas Gohr $q = 'CLEANTAG(?)'; 78*b12334e1SAndreas Gohr } else { 79*b12334e1SAndreas Gohr $q = '?'; 80*b12334e1SAndreas Gohr } 81*b12334e1SAndreas Gohr // detect LIKE filters 82*b12334e1SAndreas Gohr if ($this->useLike($value)) { 83*b12334e1SAndreas Gohr $where .= " AND $field LIKE $q"; 84*b12334e1SAndreas Gohr } else { 85*b12334e1SAndreas Gohr $where .= " AND $field = $q"; 86*b12334e1SAndreas Gohr } 87*b12334e1SAndreas Gohr } 88*b12334e1SAndreas Gohr // group and order 89*b12334e1SAndreas Gohr if($type == 'tag') { 90*b12334e1SAndreas Gohr $groupby = 'CLEANTAG(tag)'; 91*b12334e1SAndreas Gohr $orderby = 'CLEANTAG(tag)'; 92*b12334e1SAndreas Gohr } else { 93*b12334e1SAndreas Gohr $groupby = $type; 94*b12334e1SAndreas Gohr $groupby = "cnt DESC, $type"; 95*b12334e1SAndreas Gohr } 96*b12334e1SAndreas Gohr 97*b12334e1SAndreas Gohr // create SQL 98*b12334e1SAndreas Gohr $sql = "SELECT $type AS item, COUNT(*) AS cnt 99*b12334e1SAndreas Gohr FROM taggings 100*b12334e1SAndreas Gohr WHERE $where 101*b12334e1SAndreas Gohr GROUP BY $groupby 102*b12334e1SAndreas Gohr ORDER BY $orderby"; 103*b12334e1SAndreas Gohr 104*b12334e1SAndreas Gohr // run query and turn into associative array 105*b12334e1SAndreas Gohr $res = $db->query($sql, array_values($filter)); 106f61105deSAdrian Lang $res = $db->res2arr($res); 107*b12334e1SAndreas Gohr 108f61105deSAdrian Lang $ret = array(); 109*b12334e1SAndreas Gohr foreach ($res as $row) { 110*b12334e1SAndreas Gohr $ret[$row['item']] = $row['cnt']; 111f61105deSAdrian Lang } 112f61105deSAdrian Lang return $ret; 113f61105deSAdrian Lang } 114f61105deSAdrian Lang 115*b12334e1SAndreas Gohr /** 116*b12334e1SAndreas Gohr * Check if the given string is a LIKE statement 117*b12334e1SAndreas Gohr * 118*b12334e1SAndreas Gohr * @param string $value 119*b12334e1SAndreas Gohr * @return bool 120*b12334e1SAndreas Gohr */ 121*b12334e1SAndreas Gohr private function useLike($value) { 122*b12334e1SAndreas 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 $ID; 185f61105deSAdrian Lang global $INFO; 186f61105deSAdrian Lang global $lang; 187*b12334e1SAndreas Gohr $tags = $this->findItems(array('pid' => $ID), 'tag'); 188f61105deSAdrian Lang $this->html_cloud($tags, 'tag', array($this, 'linkToSearch')); 189f61105deSAdrian Lang 190f61105deSAdrian Lang if (isset($_SERVER['REMOTE_USER']) && $INFO['writable']) { 191f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 192f61105deSAdrian Lang echo html_btn('tagging_edit', $ID, '', array()); 193289f50bdSAndreas Gohr $form = new Doku_Form(array('id' => 'tagging__edit')); 194e4543b6dSAdrian Lang $form->addHidden('tagging[id]', $ID); 195289f50bdSAndreas Gohr $form->addHidden('call', 'plugin_tagging_save'); 196*b12334e1SAndreas Gohr $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $ID, 'tagger' => $_SERVER['REMOTE_USER']), 'tag'))))); 197289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save'))); 198289f50bdSAndreas Gohr $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel'))); 199f61105deSAdrian Lang $form->printForm(); 200f61105deSAdrian Lang } 201f61105deSAdrian Lang } 202872edc7cSRené Corinth 2038a1a3846SAndreas Gohr /** 2048a1a3846SAndreas Gohr * @return array 2058a1a3846SAndreas Gohr */ 206872edc7cSRené Corinth public function getAllTags(){ 207872edc7cSRené Corinth 208872edc7cSRené Corinth $db = $this->getDb(); 2098a1a3846SAndreas Gohr $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag'); 210872edc7cSRené Corinth 211872edc7cSRené Corinth $tags_tmp = $db->res2arr($res); 212872edc7cSRené Corinth $tags = array(); 213872edc7cSRené Corinth foreach ($tags_tmp as $tag) { 214302a38efSAndreas Gohr $tid = $this->cleanTag($tag['tag']); 215872edc7cSRené Corinth 216302a38efSAndreas Gohr //$tags[$tid]['pid'][] = $tag['pid']; 217872edc7cSRené Corinth 218302a38efSAndreas Gohr if (isset($tags[$tid]['count'])) { 219302a38efSAndreas Gohr $tags[$tid]['count']++; 220302a38efSAndreas Gohr $tags[$tid]['tagger'][] = $tag['tagger']; 221872edc7cSRené Corinth } else { 222302a38efSAndreas Gohr $tags[$tid]['count'] = 1; 223302a38efSAndreas Gohr $tags[$tid]['tagger'] = array($tag['tagger']); 224872edc7cSRené Corinth } 225872edc7cSRené Corinth } 226872edc7cSRené Corinth return $tags; 227872edc7cSRené Corinth } 228872edc7cSRené Corinth 2298a1a3846SAndreas Gohr /** 2308a1a3846SAndreas Gohr * Renames a tag 2318a1a3846SAndreas Gohr * 2328a1a3846SAndreas Gohr * @param string $formerTagName 2338a1a3846SAndreas Gohr * @param string $newTagName 2348a1a3846SAndreas Gohr */ 235872edc7cSRené Corinth public function renameTag($formerTagName, $newTagName) { 236872edc7cSRené Corinth 237872edc7cSRené Corinth if(empty($formerTagName) || empty($newTagName)) { 2388a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 2398a1a3846SAndreas Gohr return; 240872edc7cSRené Corinth } 241872edc7cSRené Corinth 242872edc7cSRené Corinth $db = $this->getDb(); 243872edc7cSRené Corinth 244302a38efSAndreas Gohr $res = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName); 245872edc7cSRené Corinth $check = $db->res2arr($res); 246872edc7cSRené Corinth 247872edc7cSRené Corinth if (empty($check)) { 2488a1a3846SAndreas Gohr msg($this->getLang("admin tag does not exists"), -1); 2498a1a3846SAndreas Gohr return; 250872edc7cSRené Corinth } 251872edc7cSRené Corinth 252302a38efSAndreas Gohr $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName); 253872edc7cSRené Corinth $db->res2arr($res); 254872edc7cSRené Corinth 2558a1a3846SAndreas Gohr msg($this->getLang("admin saved"), 1); 2568a1a3846SAndreas Gohr return; 257872edc7cSRené Corinth } 258872edc7cSRené Corinth 259f61105deSAdrian Lang} 260