xref: /plugin/tagging/helper.php (revision 6dde8f5bbbc0c8a455c72247117545feb25a780b)
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    public function replaceTags($id, $user, $tags) {
43        $db = $this->getDB();
44        $db->query('BEGIN TRANSACTION');
45        $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user));
46        foreach($tags as $tag) {
47            $queries[] = array('INSERT INTO taggings (pid, tagger, tag) VALUES(?, ?, ?)', $id, $user, $tag);
48        }
49
50        foreach($queries as $query) {
51            if(!call_user_func_array(array($db, 'query'), $query)) {
52                $db->query('ROLLBACK TRANSACTION');
53                return false;
54            }
55        }
56        return $db->query('COMMIT TRANSACTION');
57    }
58
59    /**
60     * Get a list of Tags or Pages matching search criteria
61     *
62     * @param array  $filter What to search for array('field' => 'searchterm')
63     * @param string $type   What field to return 'tag'|'pid'
64     * @return array associative array in form of value => count
65     */
66    public function findItems($filter, $type) {
67        $db = $this->getDB();
68        if(!$db) return array();
69
70        // create WHERE clause
71        $where = '1=1';
72        foreach($filter as $field => $value) {
73            // compare clean tags only
74            if($field === 'tag') {
75                $field = 'CLEANTAG(tag)';
76                $q     = 'CLEANTAG(?)';
77            } else {
78                $q = '?';
79            }
80            // detect LIKE filters
81            if($this->useLike($value)) {
82                $where .= " AND $field LIKE $q";
83            } else {
84                $where .= " AND $field = $q";
85            }
86        }
87        // group and order
88        if($type == 'tag') {
89            $groupby = 'CLEANTAG(tag)';
90            $orderby = 'CLEANTAG(tag)';
91        } else {
92            $groupby = $type;
93            $orderby = "cnt DESC, $type";
94        }
95
96        // create SQL
97        $sql = "SELECT $type AS item, COUNT(*) AS cnt
98                  FROM taggings
99                 WHERE $where
100              GROUP BY $groupby
101              ORDER BY $orderby";
102
103        // run query and turn into associative array
104        $res = $db->query($sql, array_values($filter));
105        $res = $db->res2arr($res);
106
107        $ret = array();
108        foreach($res as $row) {
109            $ret[$row['item']] = $row['cnt'];
110        }
111        return $ret;
112    }
113
114    /**
115     * Check if the given string is a LIKE statement
116     *
117     * @param string $value
118     * @return bool
119     */
120    private function useLike($value) {
121        return strpos($value, '%') === 0 || strrpos($value, '%') === strlen($value) - 1;
122    }
123
124    /**
125     * Constructs the URL to search for a tag
126     *
127     * @param string $tag
128     * @param string $ns
129     * @return string
130     */
131    public function getTagSearchURL($tag, $ns = '') {
132        $ret = '?do=search&id=' . rawurlencode($tag);
133        if($ns) $ret .= rawurlencode(' @' . $ns);
134
135        return $ret;
136    }
137
138    /**
139     * Calculates the size levels for the given list of clouds
140     *
141     * Automatically determines sensible tresholds
142     *
143     * @param array $tags list of tags => count
144     * @param int   $levels
145     * @return mixed
146     */
147    public function cloudData($tags, $levels = 10) {
148        $min = min($tags);
149        $max = max($tags);
150
151        // calculate tresholds
152        $tresholds = array();
153        for($i = 0; $i <= $levels; $i++) {
154            $tresholds[$i] = pow($max - $min + 1, $i / $levels) + $min - 1;
155        }
156
157        // assign weights
158        foreach($tags as $tag => $cnt) {
159            foreach($tresholds as $tresh => $val) {
160                if($cnt <= $val) {
161                    $tags[$tag] = $tresh;
162                    break;
163                }
164                $tags[$tag] = $levels;
165            }
166        }
167        return $tags;
168    }
169
170    /**
171     * Display a tag cloud
172     *
173     * @param array    $tags   list of tags => count
174     * @param string   $type   'tag'
175     * @param Callable $func   The function to print the link (gets tag and ns)
176     * @param bool     $wrap   wrap cloud in UL tags?
177     * @param bool     $return returnn HTML instead of printing?
178     * @param string   $ns     Add this namespace to search links
179     * @return string
180     */
181    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') {
182        $ret = '';
183        if($wrap) $ret .= '<ul class="tagging_cloud clearfix">';
184        if(count($tags) === 0) {
185            // Produce valid XHTML (ul needs a child)
186            $this->setupLocale();
187            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
188        } else {
189            $tags = $this->cloudData($tags);
190            foreach($tags as $val => $size) {
191                $ret .= '<li class="t' . $size . '"><div class="li">';
192                $ret .= call_user_func($func, $val, $ns);
193                $ret .= '</div></li>';
194            }
195        }
196        if($wrap) $ret .= '</ul>';
197        if($return) return $ret;
198        echo $ret;
199        return '';
200    }
201
202    /**
203     * Get the link to a search for the given tag
204     *
205     * @param string $tag search for this tag
206     * @param string $ns  limit search to this namespace
207     * @return string
208     */
209    protected function linkToSearch($tag, $ns = '') {
210        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
211    }
212
213    public function tpl_tags() {
214        global $INFO;
215        global $lang;
216        $tags = $this->findItems(array('pid' => $INFO['id']), 'tag');
217        $this->html_cloud($tags, 'tag', array($this, 'linkToSearch'));
218
219        if(isset($_SERVER['REMOTE_USER']) && $INFO['writable']) {
220            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
221            echo html_btn('tagging_edit', $INFO['id'], '', array());
222            $form = new Doku_Form(array('id' => 'tagging__edit'));
223            $form->addHidden('tagging[id]', $INFO['id']);
224            $form->addHidden('call', 'plugin_tagging_save');
225            $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $INFO['id'], 'tagger' => $_SERVER['REMOTE_USER']), 'tag')))));
226            $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save')));
227            $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel')));
228            $form->printForm();
229        }
230    }
231
232    /**
233     * @return array
234     */
235    public function getAllTags() {
236
237        $db  = $this->getDb();
238        $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag');
239
240        $tags_tmp = $db->res2arr($res);
241        $tags     = array();
242        foreach($tags_tmp as $tag) {
243            $tid = $this->cleanTag($tag['tag']);
244
245            //$tags[$tid]['pid'][] = $tag['pid'];
246
247            if(isset($tags[$tid]['count'])) {
248                $tags[$tid]['count']++;
249                $tags[$tid]['tagger'][] = $tag['tagger'];
250            } else {
251                $tags[$tid]['count']  = 1;
252                $tags[$tid]['tagger'] = array($tag['tagger']);
253            }
254        }
255        return $tags;
256    }
257
258    /**
259     * Renames a tag
260     *
261     * @param string $formerTagName
262     * @param string $newTagName
263     */
264    public function renameTag($formerTagName, $newTagName) {
265
266        if(empty($formerTagName) || empty($newTagName)) {
267            msg($this->getLang("admin enter tag names"), -1);
268            return;
269        }
270
271        $db = $this->getDb();
272
273        $res   = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName);
274        $check = $db->res2arr($res);
275
276        if(empty($check)) {
277            msg($this->getLang("admin tag does not exists"), -1);
278            return;
279        }
280
281        $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName);
282        $db->res2arr($res);
283
284        msg($this->getLang("admin saved"), 1);
285        return;
286    }
287
288}
289