1<?php
2/**
3 * DokuWiki Plugin Cumulus (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Håkan Sandell <sandell.hakan@gmail.com>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) die();
11
12if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15
16require_once(DOKU_PLUGIN.'syntax.php');
17
18class syntax_plugin_cumulus extends DokuWiki_Syntax_Plugin {
19
20    function getInfo() {
21        return confToHash(dirname(__FILE__).'/plugin.info.txt');
22    }
23
24    function getType() { return 'substition'; }
25    function getPType() { return 'block'; }
26    function getSort() { return 98; }
27
28    function connectTo($mode) {
29        $this->Lexer->addSpecialPattern('~~\w*?CUMULUS.*?~~',$mode,'plugin_cumulus');
30    }
31
32    function handle($match, $state, $pos, &$handler) {
33        $flags = explode('&', substr($match, 2, -2));
34        unset($flags[0]);
35        foreach ($flags as $flag) {
36            list($name, $value) = explode('=', $flag);
37            $data[strtolower(trim($name))] = trim($value);
38        }
39        return $data;
40    }
41
42    function render($mode, &$renderer, $data) {
43        if($mode != 'xhtml') return false;
44
45        // prevent caching to ensure the included pages are always fresh
46        $renderer->info['cache'] = false;
47
48        // flash movie options, input filtering
49        $options['width']   = (int)(is_numeric($data['width']) ? $data['width'] : $this->getConf('width'));
50        $options['height']  = (int)(is_numeric($data['height']) ? $data['height'] : $this->getConf('height'));
51        $options['tcolor']  = hsc(isset($data['tcolor']) ? $data['tcolor'] : $this->getConf('tcolor'));
52        $options['tcolor2'] = hsc(isset($data['tcolor2']) ? $data['tcolor2'] : $this->getConf('tcolor2'));
53        $options['hicolor'] = hsc(isset($data['hicolor']) ? $data['hicolor'] : $this->getConf('hicolor'));
54        $options['bgcolor'] = hsc(isset($data['bgcolor']) ? $data['bgcolor'] : $this->getConf('bgcolor'));
55        $options['speed']   = (int)(is_numeric($data['speed']) ? $data['speed'] : $this->getConf('speed'));
56        $options['distr']   = hsc(isset($data['distr']) ? $data['distr'] : $this->getConf('distr'));
57        $options['max']     = (int)(is_numeric($data['max']) ? $data['max'] : $this->getConf('max'));
58        $options['show']    = $data['show'];
59        if ($options['show'] == 'tag') $options['show'] = 'tags';
60        if ($options['show'] == 'namespace') $options['show'] = 'namespaces';
61
62        // get the tag cloud...
63        $tagcloud = $this->_getTagXml($options);
64
65        // add random seeds to so name and movie url to avoid collisions and force reloading (needed for IE)
66        $movie = 'cumulus/tagcloud.swf';
67        if (!file_exists(DOKU_PLUGIN.$movie)) {
68            $renderer->doc .= $this->getLang('filenotfound');
69            return true;
70        }
71        $movie = DOKU_BASE.'lib/plugins/'.$movie.'?r='.rand(0,9999999);
72
73        // write flash tag
74        $params = array(
75                'allowScriptAccess'  => 'always' ,
76                'bgcolor'  => '#'.$options['bgcolor'] ,
77                );
78
79        if ($this->getConf('trans')) {
80            $params['wmode'] = 'transparent';
81        }
82
83        $flashvars = array(
84                'tcolor'  => '0x'.$options['tcolor'] ,
85                'tcolor2' => '0x'.($options['tcolor2'] == "" ? $options['tcolor'] : $options['tcolor2']) ,
86                'hicolor' => '0x'.($options['hicolor'] == "" ? $options['tcolor'] : $options['hicolor']) ,
87                'tspeed'  => (int)$options['speed'] ,
88                'distr'   => ($options['distr'] ? 'true' : 'false') ,
89                'mode'    => 'tags' ,
90                'tagcloud' => '<tags>'.$tagcloud.'</tags>' ,
91                );
92
93        if ($this->getConf('showtags')) {
94            $alt = '<div id="cloud">';
95        } else {
96            $alt = '<div id="cloud" style="display:none;">';
97        }
98        $alt .= preg_replace('/style=".*?"/', '', urldecode($tagcloud));
99        $alt .= '</div>'.DOKU_LF;
100        $alt .= '<p>Download <a href="http://www.macromedia.com/go/getflashplayer">Flash Player</a> 9 or better for full experience.</p>'.DOKU_LF;
101
102        $renderer->doc .= html_flashobject($movie, $options['width'], $options['height'], $params, $flashvars, null, $alt);
103        return true;
104    }
105
106    /**
107     * Returns <a></a> links with font style attribut representing number of ocurrences
108     * (inspired by DokuWiki Cloud plugin by Gina Häußge, Michael Klier, Esther Brunner)
109     */
110    function _getTagXml($options) {
111        global $conf;
112
113        if ($options['show'] == 'tags') { // we need the tag helper plugin
114            if (plugin_isdisabled('tag') || (!$tag = plugin_load('helper', 'tag'))) {
115                msg('The Tag Plugin must be installed to display tag clouds.', -1);
116                return '';
117            }
118            $cloud = $this->_getTagCloud($options['max'], $min, $max, $tag);
119
120        } elseif ($options['show'] == 'namespaces') {
121            $cloud = $this->_getNamespaceCloud($options['max'], $min, $max);
122
123        } else {
124            $cloud = $this->_getWordCloud($options['max'], $min, $max);
125        }
126        if (!is_array($cloud) || empty($cloud)) return '';
127
128        $delta = ($max-$min)/16;
129        if ($delta == 0) $delta = 1;
130
131        foreach ($cloud as $word => $size) {
132            if ($size < $min+round($delta)) $class = 'cloud1';
133            elseif ($size < $min+round(2*$delta)) $class = 'cloud2';
134            elseif ($size < $min+round(4*$delta)) $class = 'cloud3';
135            elseif ($size < $min+round(8*$delta)) $class = 'cloud4';
136            else $class = 'cloud5';
137
138            $name = $word;
139            if ($options['show'] == 'tags') {
140                $id = $word;
141                resolve_pageID($tag->namespace, $id, $exists);
142                if($exists) {
143                    $link = wl($id, '', true);
144                    if($conf['useheading']) {
145                        $name = p_get_first_heading($id, false);
146                    }
147                } else {
148                    $link = wl($id, array('do'=>'showtag', 'tag'=>noNS($id)), true);
149                }
150                $title = $id;
151                $class .= ($exists ? '_tag1' : '_tag2');
152
153            } elseif ($options['show'] == 'namespaces') {
154                $id ='';
155                resolve_pageID($word, $id, $exists);
156                $link = wl($id, '', true);
157                $title = $id;
158                $size = 108;
159                $class = 'cloud5';
160
161            } else {
162                if($conf['userewrite'] == 2) {
163                    $link = wl($word, array('do'=>'search', 'id'=>$word), true);
164                    $title = $size;
165                } else {
166                    $link = wl($word, 'do=search', true);
167                    $title = $size;
168                }
169            }
170
171            $fsize = 8 + round(($size-$min)/$delta);
172            $xmlCloude .= '<a href="' .$link . '" class="' . $class .'"' .' title="' . $title . '" style="font-size: '. $fsize .'pt;">' . hsc($name) . '</a>' . DOKU_LF;
173        }
174        return $xmlCloude;
175    }
176
177    /**
178     * Returns the sorted namespace cloud array
179     */
180    function _getNamespaceCloud($num, &$min, &$max) {
181        global $conf;
182
183        $cloud = array();
184        $namesp = array();
185        $opts = array();
186        search($namesp,$conf['datadir'],'search_namespaces',$opts);
187
188        foreach ($namesp as $name) {
189            if ($name['ns'] == '') $cloud[$name['id']] = 100;
190        }
191        return $this->_sortCloud($cloud, $num, $min, $max);
192    }
193
194    /**
195     * Returns the sorted word cloud array
196     * (from DokuWiki Cloud plugin by Gina Häußge, Michael Klier, Esther Brunner)
197     */
198    function _getWordCloud($num, &$min, &$max) {
199        global $conf;
200
201        // load stopwords
202        $swfile = DOKU_INC.'inc/lang/'.$conf['lang'].'/stopwords.txt';
203        if (@file_exists($swfile)) $stopwords = file($swfile);
204        else $stopwords = array();
205
206        // load extra local stopwords
207        $swfile = DOKU_CONF.'stopwords.txt';
208        if (@file_exists($swfile)) $stopwords = array_merge($stopwords, file($swfile));
209
210        $cloud = array();
211
212        if (@file_exists($conf['indexdir'].'/page.idx')) { // new word-lenght based index
213            require_once(DOKU_INC.'inc/indexer.php');
214
215            $n = 2; // minimum word length
216            $lengths = idx_indexLengths($n);
217            foreach ($lengths as $len) {
218                $idx      = idx_getIndex('i', $len);
219                $word_idx = idx_getIndex('w', $len);
220
221                $this->_addWordsToCloud($cloud, $idx, $word_idx, $stopwords);
222            }
223
224        } else {                                          // old index
225            $idx      = file($conf['cachedir'].'/index.idx');
226            $word_idx = file($conf['cachedir'].'/word.idx');
227
228            $this->_addWordsToCloud($cloud, $idx, $word_idx, $stopwords);
229        }
230        return $this->_sortCloud($cloud, $num, $min, $max);
231    }
232
233    /**
234     * Adds all words in given index as $word => $freq to $cloud array
235     * (from DokuWiki Cloud plugin by Gina Häußge, Michael Klier, Esther Brunner)
236     */
237    function _addWordsToCloud(&$cloud, $idx, $word_idx, &$stopwords) {
238        $wcount = count($word_idx);
239
240        // collect the frequency of the words
241        for ($i = 0; $i < $wcount; $i++) {
242            $key = trim($word_idx[$i]);
243            if (!is_int(array_search("$key\n", $stopwords))) {
244                $value = explode(':', $idx[$i]);
245                if (!trim($value[0])) continue;
246                $cloud[$key] = count($value);
247            }
248        }
249    }
250
251    /**
252     * Returns the sorted tag cloud array
253     * (from DokuWiki Cloud plugin by Gina Häußge, Michael Klier, Esther Brunner)
254     */
255    function _getTagCloud($num, &$min, &$max, &$tag) {
256        $cloud = $tag->tagOccurrences(NULL, NULL, true);
257        return $this->_sortCloud($cloud, $num, $min, $max);
258    }
259
260    /**
261     * Sorts and slices the cloud
262     * (from DokuWiki Cloud plugin by Gina Häußge, Michael Klier, Esther Brunner)
263     */
264    function _sortCloud($cloud, $num, &$min, &$max) {
265        if(empty($cloud)) return;
266
267        // sort by frequency, then alphabetically
268        arsort($cloud);
269        $cloud = array_chunk($cloud, $num, true);
270        $max = current($cloud[0]);
271        $min = end($cloud[0]);
272        ksort($cloud[0]);
273
274        return $cloud[0];
275    }
276}
277
278// vim:ts=4:sw=4:et:enc=utf-8:
279