xref: /plugin/tagging/helper.php (revision 5540f91db869f8f784ab842fffcbc41c0da27fe6)
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            $orderby = "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 string $tag
129     * @param string $ns
130     * @return string
131     */
132    public function getTagSearchURL($tag, $ns = '') {
133        $ret = '?do=search&id=' . rawurlencode($tag);
134        if($ns) $ret .= rawurlencode(' @'.$ns);
135
136        return $ret;
137    }
138
139    /**
140     * Calculates the size levels for the given list of clouds
141     *
142     * Automatically determines sensible tresholds
143     *
144     * @param array $tags list of tags => count
145     * @param int $levels
146     * @return mixed
147     */
148    public function cloudData($tags, $levels = 10) {
149        $min = min($tags);
150        $max = max($tags);
151
152        // calculate tresholds
153        $tresholds = array();
154        for($i=0; $i<=$levels; $i++){
155            $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min - 1;
156        }
157
158        // assign weights
159        foreach($tags as $tag => $cnt){
160            foreach($tresholds as $tresh => $val){
161                if($cnt <= $val){
162                    $tags[$tag] = $tresh;
163                    break;
164                }
165                $tags[$tag] = $levels;
166            }
167        }
168        return $tags;
169    }
170
171    /**
172     * Display a tag cloud
173     *
174     * @param array    $tags   list of tags => count
175     * @param string   $type   'tag'
176     * @param Callable $func   The function to print the link (gets tag and ns)
177     * @param bool     $wrap   wrap cloud in UL tags?
178     * @param bool     $return returnn HTML instead of printing?
179     * @param string   $ns     Add this namespace to search links
180     * @return string
181     */
182    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') {
183        $ret = '';
184        if($wrap) $ret .= '<ul class="tagging_cloud clearfix">';
185        if(count($tags) === 0) {
186            // Produce valid XHTML (ul needs a child)
187            $this->setupLocale();
188            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
189        } else {
190            $tags = $this->cloudData($tags);
191            foreach($tags as $val => $size) {
192                $ret .= '<li class="t' . $size . '"><div class="li">';
193                $ret .= call_user_func($func, $val, $ns);
194                $ret .= '</div></li>';
195            }
196        }
197        if($wrap) $ret .= '</ul>';
198        if($return) return $ret;
199        echo $ret;
200        return '';
201    }
202
203    /**
204     * Get the link to a search for the given tag
205     *
206     * @param string $tag search for this tag
207     * @param string $ns limit search to this namespace
208     * @return string
209     */
210    protected function linkToSearch($tag, $ns='') {
211        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
212    }
213
214    public function tpl_tags() {
215        global $INFO;
216        global $lang;
217        $tags = $this->findItems(array('pid' => $INFO['id']), 'tag');
218        $this->html_cloud($tags, 'tag', array($this, 'linkToSearch'));
219
220        if (isset($_SERVER['REMOTE_USER']) && $INFO['writable']) {
221            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
222            echo html_btn('tagging_edit', $INFO['id'], '', array());
223            $form = new Doku_Form(array('id' => 'tagging__edit'));
224            $form->addHidden('tagging[id]', $INFO['id']);
225            $form->addHidden('call', 'plugin_tagging_save');
226            $form->addElement(form_makeTextField('tagging[tags]', implode(', ', array_keys($this->findItems(array('pid' => $INFO['id'], 'tagger' => $_SERVER['REMOTE_USER']), 'tag')))));
227            $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id' => 'tagging__edit_save')));
228            $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('id' => 'tagging__edit_cancel')));
229            $form->printForm();
230        }
231    }
232
233    /**
234     * @return array
235     */
236    public function getAllTags(){
237
238        $db = $this->getDb();
239        $res = $db->query('SELECT pid, tag, tagger FROM taggings ORDER BY tag');
240
241        $tags_tmp = $db->res2arr($res);
242        $tags = array();
243        foreach ($tags_tmp as $tag) {
244            $tid = $this->cleanTag($tag['tag']);
245
246            //$tags[$tid]['pid'][] = $tag['pid'];
247
248            if (isset($tags[$tid]['count'])) {
249                $tags[$tid]['count']++;
250                $tags[$tid]['tagger'][] = $tag['tagger'];
251            } else {
252                $tags[$tid]['count'] = 1;
253                $tags[$tid]['tagger'] = array($tag['tagger']);
254            }
255        }
256        return $tags;
257    }
258
259    /**
260     * Renames a tag
261     *
262     * @param string $formerTagName
263     * @param string $newTagName
264     */
265    public function renameTag($formerTagName, $newTagName) {
266
267        if(empty($formerTagName) || empty($newTagName)) {
268            msg($this->getLang("admin enter tag names"), -1);
269            return;
270        }
271
272        $db = $this->getDb();
273
274        $res = $db->query('SELECT pid FROM taggings WHERE tag= ?', $formerTagName);
275        $check = $db->res2arr($res);
276
277        if (empty($check)) {
278            msg($this->getLang("admin tag does not exists"), -1);
279            return;
280        }
281
282        $res = $db->query("UPDATE taggings SET tag = ? WHERE tag = ?", $newTagName, $formerTagName);
283        $db->res2arr($res);
284
285        msg($this->getLang("admin saved"), 1);
286        return;
287    }
288
289}
290