xref: /plugin/menu/syntax.php (revision dad308ecb6408df8c8806b502020b99f58c28253)
1<?php
2/**
3 * Plugin: Displays a link list in a nice way
4 *
5 * Syntax: <menu col=2,align=center,caption="headline">
6 *           <item>name|description|link|image</item>
7 *         </menu>
8 *
9 * Options have to be separated by comma.
10 * col (opt)     The number of columns of the menu. Allowed are 1-4, default is 1
11 * align (opt)   Alignment of the menu. Allowed are "left", "center" or "right",
12 *               default is "left"
13 * caption (opt) Headline of the menu, default is none
14 *
15 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
16 * @author     Matthias Grimm <matthiasgrimm@users.sourceforge.net>
17 * @author     Frank Schiebel <frank@linuxmuster.net>
18 */
19
20if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
21if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
22require_once(DOKU_PLUGIN.'syntax.php');
23
24/**
25 * All DokuWiki plugins to extend the parser/rendering mechanism
26 * need to inherit from this class
27 */
28class syntax_plugin_menu extends DokuWiki_Syntax_Plugin {
29
30    var $rcmd = array();  /**< Command array for the renderer */
31
32    function __construct() {
33        global $ID;
34        global $conf;
35    }
36
37
38   /**
39    * Get an associative array with plugin info.
40    *
41    * <p>
42    * The returned array holds the following fields:
43    * <dl>
44    * <dt>author</dt><dd>Author of the plugin</dd>
45    * <dt>email</dt><dd>Email address to contact the author</dd>
46    * <dt>date</dt><dd>Last modified date of the plugin in
47    * <tt>YYYY-MM-DD</tt> format</dd>
48    * <dt>name</dt><dd>Name of the plugin</dd>
49    * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd>
50    * <dt>url</dt><dd>Website with more information on the plugin
51    * (eg. syntax description)</dd>
52    * </dl>
53    * @param none
54    * @return Array Information about this plugin class.
55    * @public
56    * @static
57    */
58    function getInfo(){
59        return array(
60            'author' => 'Matthias Grimm',
61            'email'  => 'matthiasgrimm@users.sourceforge.net',
62            'date'   => '2009-07-25',
63            'name'   => 'Menu Plugin',
64            'desc'   => 'Shows a list of links as a nice menu card',
65            'url'    => 'http://www.dokuwiki.org/wiki:plugins',
66        );
67    }
68
69   /**
70    * Get the type of syntax this plugin defines.
71    *
72    * The type of this plugin is "protected". It has a start and an end
73    * token and no other wiki commands shall be parsed between them.
74    *
75    * @param none
76    * @return String <tt>'protected'</tt>.
77    * @public
78    * @static
79    */
80    function getType(){
81        return 'protected';
82    }
83
84   /**
85    * Define how this plugin is handled regarding paragraphs.
86    *
87    * <p>
88    * This method is important for correct XHTML nesting. It returns
89    * one of the following values:
90    * </p>
91    * <dl>
92    * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
93    * <dt>block</dt><dd>Open paragraphs need to be closed before
94    * plugin output.</dd>
95    * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
96    * </dl>
97    * @param none
98    * @return String <tt>'block'</tt>.
99    * @public
100    * @static
101    */
102    function getPType(){
103        return 'block';
104    }
105
106   /**
107    * Where to sort in?
108    *
109    * Sort the plugin in just behind the formating tokens
110    *
111    * @param none
112    * @return Integer <tt>135</tt>.
113    * @public
114    * @static
115    */
116    function getSort(){
117        return 135;
118    }
119
120   /**
121    * Connect lookup pattern to lexer.
122    *
123    * @param $aMode String The desired rendermode.
124    * @return none
125    * @public
126    * @see render()
127    */
128    function connectTo($mode) {
129       $this->Lexer->addEntryPattern('<menu>(?=.*?</menu.*?>)',$mode,'plugin_menu');
130       $this->Lexer->addEntryPattern('<menu\s[^\r\n\|]*?>(?=.*?</menu.*?>)',$mode,'plugin_menu');
131    }
132
133    function postConnect() {
134      $this->Lexer->addPattern('<item>.+?</item>','plugin_menu');
135      $this->Lexer->addExitPattern('</menu>','plugin_menu');
136    }
137
138   /**
139    * Handler to prepare matched data for the rendering process.
140    *
141    * <p>
142    * The <tt>$aState</tt> parameter gives the type of pattern
143    * which triggered the call to this method:
144    * </p>
145    * <dl>
146    * <dt>DOKU_LEXER_ENTER</dt>
147    * <dd>a pattern set by <tt>addEntryPattern()</tt></dd>
148    * <dt>DOKU_LEXER_MATCHED</dt>
149    * <dd>a pattern set by <tt>addPattern()</tt></dd>
150    * <dt>DOKU_LEXER_EXIT</dt>
151    * <dd> a pattern set by <tt>addExitPattern()</tt></dd>
152    * <dt>DOKU_LEXER_SPECIAL</dt>
153    * <dd>a pattern set by <tt>addSpecialPattern()</tt></dd>
154    * <dt>DOKU_LEXER_UNMATCHED</dt>
155    * <dd>ordinary text encountered within the plugin's syntax mode
156    * which doesn't match any pattern.</dd>
157    * </dl>
158    * @param $aMatch String The text matched by the patterns.
159    * @param $aState Integer The lexer state for the match.
160    * @param $aPos Integer The character position of the matched text.
161    * @param $aHandler Object Reference to the Doku_Handler object.
162    * @return Integer The current lexer state for the match.
163    * @public
164    * @see render()
165    * @static
166    */
167    function handle($match, $state, $pos, Doku_Handler $handler)
168    {
169        switch ($state) {
170            case DOKU_LEXER_ENTER:
171                $this->_reset();        // reset object;
172
173                $opts = $this->_parseOptions(trim(substr($match,5,-1)));
174                $col = $opts['col'];
175                if (!empty($col) && is_numeric($col) && $col > 0 && $col < 5)
176                    $this->rcmd['columns'] = $col;
177                if ($opts['align'] == "left")   $this->rcmd['float'] = "left";
178                if ($opts['align'] == "center") $this->rcmd['float'] = "center";
179                if ($opts['align'] == "right")  $this->rcmd['float'] = "right";
180                if (!empty($opts['caption']))
181                    $this->rcmd['caption'] = hsc($opts['caption']);
182                if (!empty($opts['type']))
183                    $this->rcmd['type'] = hsc($opts['type']);
184            break;
185          case DOKU_LEXER_MATCHED:
186
187                $menuitem = preg_split('/\|/', trim(substr($match,6,-7)));
188
189                $title = hsc($menuitem[0]);
190                if (substr($menuitem[2],0,2) == "{{")
191                    $link = $this->_itemimage($menuitem[2], $title);
192                else
193                    $link = $this->_itemLink($menuitem[2], $title);
194                $image = $this->_itemimage($menuitem[3], $title);
195
196                $this->rcmd['items'][] = array("image" => $image,
197                                               "link"  => $link,
198                                               "descr" => hsc($menuitem[1]));
199
200                // find out how much space the biggest menu item needs
201                $titlelen = mb_strlen($menuitem[0], "UTF-8");
202                if ($titlelen > $this->rcmd['width'])
203                    $this->rcmd['width'] = $titlelen;
204            break;
205          case DOKU_LEXER_EXIT:
206              // give the menu a convinient width. IE6 needs more space here than Firefox
207              $this->rcmd['width'] += 5;
208              return $this->rcmd;
209          default:
210            break;
211        }
212        return array();
213    }
214
215    function _reset()
216    {
217        $this->rcmd = array();
218        $this->rcmd['columns'] = 1;
219        $this->rcmd['float']   = "left";
220    }
221
222    function _itemlink($match, $title) {
223        // Strip the opening and closing markup
224        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
225
226        // Split title from URL
227        $link = explode('|',$link,2);
228        $ref  = trim($link[0]);
229
230        //decide which kind of link it is
231        if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$ref) ) {
232            // Interwiki
233            $interwiki = explode('>',$ref,2);
234            return array('interwikilink',
235                         array($ref,$title,strtolower($interwiki[0]),$interwiki[1]));
236        } elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$ref) ) {
237            // Windows Share
238            return array('windowssharelink', array($ref,$title));
239        } elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$ref) ) {
240            // external link (accepts all protocols)
241            return array('externallink', array($ref,$title));
242        } elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$ref) ) {
243            // E-Mail (pattern above is defined in inc/mail.php)
244            return array('emaillink', array($ref,$title));
245        } elseif ( preg_match('!^#.+!',$ref) ) {
246            // local link
247            return array('locallink', array(substr($ref,1),$title));
248        } else {
249            // internal link
250            return array('internallink', array($ref,$title));
251        }
252    }
253
254    function _itemimage($match, $title) {
255        $p = Doku_Handler_Parse_Media($match);
256
257        return array($p['type'],
258                     array($p['src'], $title, $p['align'], $p['width'],
259                     $p['height'], $p['cache'], $p['linking']));
260    }
261
262   /**
263    * Handle the actual output creation.
264    *
265    * <p>
266    * The method checks for the given <tt>$aFormat</tt> and returns
267    * <tt>FALSE</tt> when a format isn't supported. <tt>$aRenderer</tt>
268    * contains a reference to the renderer object which is currently
269    * handling the rendering. The contents of <tt>$aData</tt> is the
270    * return value of the <tt>handle()</tt> method.
271    * </p>
272    * @param $aFormat String The output format to generate.
273    * @param $aRenderer Object A reference to the renderer object.
274    * @param $aData Array The data created by the <tt>handle()</tt>
275    * method.
276    * @return Boolean <tt>TRUE</tt> if rendered successfully, or
277    * <tt>FALSE</tt> otherwise.
278    * @public
279    * @see handle()
280    */
281    function render($mode, Doku_Renderer $renderer, $data) {
282
283        if (empty($data)) return false;
284
285        if($mode == 'xhtml'){
286            if ($data['type'] != "menubar"){
287                    // for IE6 2x10em does not fit into 20em, it needs 21em
288                    $renderer->doc .= '<div class="menu menu'.$data['float'].'"';
289                    $renderer->doc .= ' style="width:'.($data['columns'] * $data['width'] + 2).'em;">'."\n";
290                    if (isset($data['caption']))
291                        $renderer->doc .= '<p class="caption">'.$data['caption'].'</p>'."\n";
292
293                    foreach($data['items'] as $item) {
294                        $renderer->doc .= '<div class="menuitem" style="width:'.$data['width'].'em;">'."\n";
295
296                        // create <img .. /> tag
297                        list($type, $args) = $item['image'];
298                        list($ext,$mime,$dl) = mimetype($args[0]);
299                        $class = ($ext == 'png') ? ' png' : NULL;
300                        $img = $renderer->_media($args[0],$args[1],$class,$args[3],$args[4],$args[5]);
301
302                        // create <a href= .. /> tag
303                        list($type, $args) = $item['link'];
304                        $link = $this->_getLink($type, $args, $renderer);
305                        $link['title'] = $args[1];
306
307                        $link['name']  = $img;
308                        $renderer->doc .= $renderer->_formatLink($link);
309
310                        $link['name']  = '<span class="menutext">'.$args[1].'</span>';
311                        $renderer->doc .= $renderer->_formatLink($link);
312                        $renderer->doc .= '<p class="menudesc">'.$item['descr'].'</p>';
313                        $renderer->doc .= '</div>'."\n";
314                    }
315
316                    $renderer->doc .= '</div>'."\n";
317
318                    if ($data['float'] == "right") /* center: clear text floating */
319                        $renderer->doc .= '<p style="clear:both;" />';
320                    if ($data['float'] == "left") /* center: clear text floating */
321                        $renderer->doc .= '<p style="clear:both;" />';
322                    if ($data['float'] == "center") /* center: clear text floating */
323                        $renderer->doc .= '<p style="clear:both;" />';
324
325                    return true;
326            }
327            // menubar mode: 1 row with small captions
328            if ($data['type'] == "menubar"){
329                    // for IE6 2x10em does not fit into 20em, it needs 21em
330                    $renderer->doc .= '<div id="menu"><ul class="menubar">'."\n";
331                  //  if (isset($data['caption']))
332                  //      $renderer->doc .= '<p class="caption">'.$data['caption'].'</p>'."\n";
333
334                    foreach($data['items'] as $item) {
335                        $renderer->doc .= '<li>'."\n";
336
337                        // create <img .. /> tag
338                        list($type, $args) = $item['image'];
339                        list($ext,$mime,$dl) = mimetype($args[0]);
340                        $class = ($ext == 'png') ? ' png' : NULL;
341                        $img = $renderer->_media($args[0],$item['descr'],$class,$args[3],$args[4],$args[5]);
342
343                        // create <a href= .. /> tag
344                        list($type, $args) = $item['link'];
345                        $link = $this->_getLink($type, $args, $renderer);
346                        $link['title'] = $args[1];
347
348                        $link['name']  = $img;
349                        $renderer->doc .= $renderer->_formatLink($link);
350
351                        $link['name']  = '<p class="menutext">'.$args[1].'</p>';
352                        $renderer->doc .= $renderer->_formatLink($link);
353                        //$renderer->doc .= '<p class="menudesc">'.$item['descr'].'</p>';
354                        $renderer->doc .= '</li>';
355                    }
356
357                    $renderer->doc .= '</ul></div>'."\n";
358                    if ($data['float'] == "center") /* center: clear text floating */
359                        $renderer->doc .= '<p style="clear:both;" />';
360
361                    return true;
362            }
363
364        }
365        return false;
366    }
367
368    function _createLink($url, $target=NULL)
369    {
370        global $conf;
371
372        $link = array();
373        $link['class']  = '';
374        $link['style']  = '';
375        $link['pre']    = '';
376        $link['suf']    = '';
377        $link['more']   = '';
378        $link['title']  = '';
379        $link['name']   = '';
380        $link['url']    = $url;
381
382        $link['target'] = $target == NULL ? '' : $conf['target'][$target];
383        if ($target == 'interwiki' && strpos($url,DOKU_URL) === 0) {
384            //we stay at the same server, so use local target
385            $link['target'] = $conf['target']['wiki'];
386        }
387
388        return $link;
389    }
390
391    function _getLink($type, $args, &$renderer)
392    {
393        global $ID;
394
395        $check = false;
396        $exists = false;
397
398        switch ($type) {
399        case 'interwikilink':
400            $url  = $renderer->_resolveInterWiki($args[2],$args[3]);
401            $link = $this->_createLink($url, 'interwiki');
402            break;
403        case 'windowssharelink':
404            $url  = str_replace('\\','/',$args[0]);
405            $url  = 'file:///'.$url;
406            $link = $this->_createLink($url, 'windows');
407            break;
408        case 'externallink':
409            $link = $this->_createLink($args[0], 'extern');
410            break;
411        case 'emaillink':
412            $address = $renderer->_xmlEntities($args[0]);
413            $address = obfuscate($address);
414            if ($conf['mailguard'] == 'visible')
415                 $address = rawurlencode($address);
416
417            $link = $this->_createLink('mailto:'.$address);
418            $link['class'] = 'JSnocheck';
419            break;
420        case 'locallink':
421            $hash = sectionID($args[0], $check);
422            $link = $this->_createLink('#'.$hash);
423            $link['class'] = "wikilink1";
424            break;
425        case 'internallink':
426            resolve_pageid(getNS($ID),$args[0],$exists);
427            $url  = wl($args[0]);
428            list($id,$hash) = explode('#',$args[0],2);
429            if (!empty($hash)) $hash = sectionID($hash, $check);
430            if ($hash) $url .= '#'.$hash;    //keep hash anchor
431
432            $link = $this->_createLink($url, 'wiki');
433            $link['class'] = $exists ? 'wikilink1' : 'wikilink2';
434            break;
435        case 'internalmedia':
436            resolve_mediaid(getNS($ID),$args[0], $exists);
437            $url  = ml($args[0],array('id'=>$ID,'cache'=>$args[5]),true);
438            $link = $this->_createLink($url);
439            if (!$exists) $link['class'] = 'wikilink2';
440            break;
441        case 'externalmedia':
442            $url  = ml($args[0],array('cache'=>$args[5]));
443            $link = $this->_createLink($url);
444            break;
445        }
446        return $link;
447    }
448
449   /**
450    * Parse menu options
451    *
452    *
453    * @param $string String Option string from <menu> tag.
454    * @return array of options (name >= option). the array will be empty
455    *         if no options are found
456    * @private
457    */
458    function _parseOptions($string) {
459		$data = array();
460
461		$dq    = false;
462		$iskey = true;
463		$key   = '';
464		$val   = '';
465
466		$len = strlen($string);
467		for ($i=0; $i<=$len; $i++) {
468			// done for this one?
469			if ($string[$i] == ',' || $i == $len) {
470				$key = trim($key);
471				$val = trim($val);
472				if($key && $val) $data[strtolower($key)] = $val;
473				$iskey = true;
474				$key = '';
475				$val = '';
476				continue;
477			}
478
479			// double quotes
480			if ($string[$i] == '"') {
481				$dq = $dq ? false : true;
482				continue;
483			}
484
485			// key value separator
486			if ($string[$i] == '=' && !$dq && $iskey) {
487				$iskey = false;
488				continue;
489			}
490
491			// default
492			if ($iskey)
493				$key .= $string[$i];
494			else
495				$val .= $string[$i];
496		}
497		return $data;
498    }
499
500}
501
502//Setup VIM: ex: et ts=4 enc=utf-8 :
503?>
504