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
10 if (!defined('DOKU_INC')) die();
11 
12 if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13 if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14 if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15 
16 require_once(DOKU_PLUGIN.'syntax.php');
17 
18 class 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