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