1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Gina Haeussge <gina@foosel.net>
5 */
6// must be run within Dokuwiki
7if(!defined('DOKU_INC')) die();
8
9/**
10 * Delivers tag related functions
11 */
12class helper_plugin_blogtng_tags extends DokuWiki_Plugin {
13
14    /** @var helper_plugin_blogtng_sqlite */
15    private $sqlitehelper = null;
16
17    private $tags = array();
18
19    private $pid = null;
20
21    /**
22     * Constructor, loads the sqlite helper plugin
23     */
24    public function __construct() {
25        $this->sqlitehelper = plugin_load('helper', 'blogtng_sqlite');
26    }
27
28    /**
29     * Set pid of page
30     *
31     * @param string $pid
32     */
33    public function setPid($pid) {
34        $this->pid = trim($pid);
35    }
36
37    /**
38     * Set tags, filtered and unique
39     *
40     * @param array $tags
41     */
42    public function setTags($tags) {
43        $this->tags = array_unique(array_filter(array_map('trim', $tags)));
44    }
45
46    /**
47     * Return the tag array
48     *
49     * @return array tags
50     */
51    public function getTags() {
52        return $this->tags;
53    }
54
55    /**
56     * Load tags for specified pid
57     *
58     * @param $pid
59     * @return bool
60     */
61    public function load($pid) {
62        $this->setPid($pid);
63
64        if(!$this->sqlitehelper->ready()) {
65            msg('blogtng plugin: failed to load sqlite helper plugin!', -1);
66            $this->tags = array();
67            return false;
68        }
69        $query = 'SELECT tag
70                    FROM tags
71                   WHERE pid = ?
72                ORDER BY tag ASC';
73        $resid = $this->sqlitehelper->getDB()->query($query, $this->pid);
74        if ($resid === false) {
75            msg('blogtng plugin: failed to load tags!', -1);
76            $this->tags = array();
77            return false;
78        }
79        if ($this->sqlitehelper->getDB()->res2count($resid) == 0) {
80            $this->tags = array();
81            return true;
82        }
83
84        $tags_from_db = $this->sqlitehelper->getDB()->res2arr($resid);
85        $tags = array();
86        foreach ($tags_from_db as $tag_from_db) {
87            array_push($tags, $tag_from_db['tag']);
88        }
89        $this->tags = $tags;
90        return true;
91    }
92
93    /**
94     * Count tags for specified pid
95     *
96     * @param $pid
97     * @return int
98     */
99    public function count($pid) {
100        if(!$this->sqlitehelper->ready()) {
101            msg('BlogTNG plugin: failed to load tags. (sqlite helper plugin not available)', -1);
102            return 0;
103        }
104
105        $pid = trim($pid);
106        $query = 'SELECT COUNT(tag) AS tagcount
107                    FROM tags
108                   WHERE pid = ?';
109
110        $resid = $this->sqlitehelper->getDB()->query($query, $pid);
111        if ($resid === false) {
112            msg('BlogTNG plugin: failed to load tags!', -1);
113            return 0;
114        }
115
116        $tagcount = $this->sqlitehelper->getDB()->res2row($resid, 0);
117        return (int)$tagcount['tagcount'];
118    }
119
120    /**
121     * Load tags for a specified blog
122     *
123     * @param $blogs
124     * @return array|bool
125     */
126    public function load_by_blog($blogs) {
127        if(!$this->sqlitehelper->ready()) return false;
128
129        $query = 'SELECT tags.tag AS tag, tags.pid AS pid
130                    FROM tags, entries
131                   WHERE tags.pid = entries.pid
132                     AND entries.blog IN ("' . implode('","', $blogs) . '")
133                     AND GETACCESSLEVEL(page) >= '.AUTH_READ;
134
135        $resid = $this->sqlitehelper->getDB()->query($query);
136        if($resid) {
137            return $this->sqlitehelper->getDB()->res2arr($resid);
138        }
139        return false;
140    }
141
142    /**
143     * Save tags
144     */
145    public function save() {
146        if (!$this->sqlitehelper->ready()) return;
147
148        $query = 'BEGIN TRANSACTION';
149        if (!$this->sqlitehelper->getDB()->query($query)) {
150            $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION');
151            return;
152        }
153        $query = 'DELETE FROM tags WHERE pid = ?';
154        if (!$this->sqlitehelper->getDB()->query($query, $this->pid)) {
155            $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION');
156            return;
157        }
158        foreach ($this->tags as $tag) {
159            $query = 'INSERT INTO tags (pid, tag) VALUES (?, ?)';
160            if (!$this->sqlitehelper->getDB()->query($query, $this->pid, $tag)) {
161                $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION');
162                return;
163            }
164        }
165        $query = 'END TRANSACTION';
166        if (!$this->sqlitehelper->getDB()->query($query)) {
167            $this->sqlitehelper->getDB()->query('ROLLBACK TRANSACTION');
168            return;
169        }
170    }
171
172    /**
173     * Parses query string to a where clause for use in a query
174     * in the query string:
175     *  - tags are space separated
176     *  - prefix + is AND
177     *  - prefix - is NOT
178     *  - no prefix is OR
179     *
180     * @param string $tagquery query string to be parsed
181     * @return null|string
182     */
183    public function parse_tag_query($tagquery) {
184        if (!$tagquery) {
185            return null;
186        }
187        if(!$this->sqlitehelper->ready()) return null;
188
189        $tags = array_map('trim', explode(' ', $tagquery));
190        $tag_clauses = array(
191            'OR' => array(),
192            'AND' => array(),
193            'NOT' => array(),
194        );
195        foreach ($tags as $tag) {
196            if ($tag{0} == '+') {
197                array_push(
198                    $tag_clauses['AND'],
199                    'tag = '. $this->sqlitehelper->getDB()->quote_string(substr($tag, 1))
200                );
201            } else if ($tag{0} == '-') {
202                array_push(
203                    $tag_clauses['NOT'],
204                    'tag != '.$this->sqlitehelper->getDB()->quote_string(substr($tag, 1))
205                );
206            } else {
207                array_push(
208                    $tag_clauses['OR'],
209                    'tag = '.$this->sqlitehelper->getDB()->quote_string($tag)
210                );
211            }
212        }
213        $tag_clauses = array_map('array_unique', $tag_clauses);
214
215        $where = '';
216        if ($tag_clauses['OR']) {
217            $where .= '('.join(' OR ', $tag_clauses['OR']).')';
218        }
219        if ($tag_clauses['AND']) {
220            $where .= (!empty($where) ? ' AND ' : '').join(' AND ', $tag_clauses['AND']);
221        }
222        if ($tag_clauses['NOT']) {
223            $where .= (!empty($where) ? ' AND ' : '').join(' AND ', $tag_clauses['NOT']);
224        }
225        return $where;
226    }
227
228    /**
229     * Print a list of tags
230     *
231     * @param string $target - tag links will point to this page, tag is passed as parameter
232     */
233    public function tpl_tags($target){
234        $prepared = array();
235        foreach ($this->tags as $tag) {
236            array_push($prepared, DOKU_TAB.'<li><div class="li">'.$this->_format_tag_link($tag, $target).'</div></li>');
237        }
238        $html = '<ul class="blogtng_tags">'.DOKU_LF.join(DOKU_LF, $prepared).'</ul>'.DOKU_LF;
239        echo $html;
240    }
241
242    /**
243     * Print the joined tags as a string
244     *
245     * @param string $target - tag links will point to this page
246     * @param string $separator
247     */
248    public function tpl_tagstring($target, $separator) {
249        echo join($separator, array_map(array($this, '_format_tag_link'), $this->tags, array_fill(0, count($this->tags), $target)));
250    }
251
252    /**
253     * Displays a tag cloud
254     *
255     * @author Michael Klier <chi@chimeric.de>
256     *
257     * @param $conf
258     * @return string
259     */
260    public function xhtml_tagcloud($conf) {
261        $tags = $this->load_by_blog($conf['blog']);
262        if(!$tags) return '';
263        $cloud = array();
264        foreach($tags as $tag) {
265            if(!isset($cloud[$tag['tag']])) {
266                $cloud[$tag['tag']] = 1;
267            } else {
268                $cloud[$tag['tag']]++;
269            }
270            //$cloud[$tag['tag']][] = $tag['pid'];
271        }
272        asort($cloud);
273        $cloud = array_slice(array_reverse($cloud), 0, $conf['limit']);
274        $this->_cloud_weight($cloud, min($cloud), max($cloud), 5);
275        ksort($cloud);
276        $output = "";
277        foreach($cloud as $tag => $weight) {
278            $output .= '<a href="' . wl($conf['target'], array('btng[post][tags]'=>$tag))
279                    . '" class="tag cloud_weight' . $weight
280                    . '" title="' . $tag . '">' . $tag . "</a>\n";
281        }
282        return $output;
283    }
284
285    /**
286     * Happily stolen (and slightly modified) from
287     *
288     * http://www.splitbrain.org/blog/2007-01/03-tagging_splitbrain
289     *
290     * @param $tags
291     * @param $min
292     * @param $max
293     * @param $levels
294     */
295    private function _cloud_weight(&$tags,$min,$max,$levels){
296        // calculate tresholds
297        $tresholds = array();
298        $tresholds[0]= $min; // lowest treshold should always be min
299        for($i=1; $i<=$levels; $i++){
300            $tresholds[$i] = pow($max - $min + 1, $i/$levels) + $min;
301        }
302
303        // assign weights
304        foreach($tags as $tag => $cnt){
305            foreach($tresholds as $tresh => $val){
306                if($cnt <= $val){
307                    $tags[$tag] = $tresh;
308                    break;
309                }
310                $tags[$tag] = $levels;
311            }
312        }
313    }
314
315    /**
316     * Create html of url to target page for given tag
317     *
318     * @param string $tag
319     * @param string $target pageid
320     * @return string html of url
321     */
322    private function _format_tag_link($tag, $target) {
323        return '<a href="'.wl($target,array('btng[post][tags]'=>$tag)).'" class="tag">'.hsc($tag).'</a>';
324    }
325}
326// vim:ts=4:sw=4:et:
327