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