xref: /plugin/tagging/helper.php (revision b12334e15c17310b0c6ed6ebeb5b6a21ccb63336)
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