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