1<?php 2/** 3 * Info Indexmenu: Displays the index of a specified namespace. 4 * 5 * $Id: indexmenu.php 93 2007-05-07 11:56:33Z wingedfox $ 6 * $HeadURL: https://svn.debugger.ru/repos/common/DokuWiki/Indexmenu2/tags/Indexmenu2.v2.1.2/syntax/indexmenu.php $ 7 * 8 * @lastmodified $Date: 2007-05-07 15:56:33 +0400 (Пнд, 07 Май 2007) $ 9 * @license LGPL 2 (http://www.gnu.org/licenses/lgpl.html) 10 * @author Ilya Lebedev <ilya@lebedev.net> 11 * @version $Rev: 93 $ 12 * @copyright (c) 2005-2007, Ilya Lebedev 13 */ 14 15if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 16if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 17if(!defined('INDEXMENU_FS_IMAGES')) define('INDEXMENU_FS_IMAGES',realpath(dirname(__FILE__)."/../templates")."/"); 18require_once(DOKU_PLUGIN.'syntax.php'); 19require_once(DOKU_INC.'inc/search.php'); 20 21/** 22 * All DokuWiki plugins to extend the parser/rendering mechanism 23 * need to inherit from this class 24 */ 25class syntax_plugin_indexmenu_indexmenu extends DokuWiki_Syntax_Plugin { 26 27 /** 28 * sorting target and reverse flag 29 * 30 */ 31 var $s_target = array('fn' => 'target', 'title' => 'title' , 'date' => 'date'); 32 var $s_rev = false; 33 /** 34 * return some info 35 */ 36 function getInfo() { 37 preg_match("#^.+Indexmenu2[/.]([^\\/]+)#"," $HeadURL: https://svn.debugger.ru/repos/common/DokuWiki/Indexmenu2/tags/Indexmenu2.v2.1.2/syntax/indexmenu.php $ ", $v); 38 $v = preg_replace("#.*?((trunk|\.v)[\d.]+)#","\\1",$v[1]); 39 $b = preg_replace("/\\D/","", " $Rev: 93 $ "); 40 41 return array( 'author' => "Ilya Lebedev" 42 ,'email' => 'ilya@lebedev.net' 43 ,'date' => preg_replace("#.*?(\d{4}-\d{2}-\d{2}).*#","\\1",'$Date: 2007-05-07 15:56:33 +0400 (Пнд, 07 Май 2007) $') 44 ,'name' => "Indexmenu 2 {$v}.$b" 45 ,'desc' => "Insert the index of a specified namespace.\nJavascript code: http://cms.debugger.ru by Ilya Lebedev." 46 ,'url' => 'https://www.dokuwiki.org/plugin:indexmenu2' 47 ); 48 } 49 50 /** 51 * What kind of syntax are we? 52 */ 53 function getType(){ 54 return 'substition'; 55 } 56 57 function getPType(){ 58 return 'block'; 59 } 60 /** 61 * Where to sort in? 62 */ 63 function getSort(){ 64 return 138; 65 } 66 /** 67 * Emulates getConf for DW releases prior to current RCs 68 * 69 * @param string $var variable to get 70 * @return string value 71 * @access protected 72 */ 73 function getConf($var) { 74 global $conf; 75 if (method_exists(DokuWiki_Syntax_Plugin,'getConf')) { 76 if (!$this) { 77 $tmp = & new syntax_plugin_indexmenu_indexmenu(); 78 $res = $tmp->getConf($var); 79 unset ($tmp); 80 return $res; 81 } else { 82 return parent::getConf($var); 83 } 84 } else { 85 return @$conf['plugin_indexmenu'][$var]; 86 } 87 } 88 89 /** 90 * Connect pattern to lexer 91 */ 92 function connectTo($mode) { 93 $this->Lexer->addSpecialPattern('{{indexmenu>.+?}}',$mode,'plugin_indexmenu_indexmenu'); 94 $this->Lexer->addSpecialPattern('{{indexmenu>.+?}}',$mode,'plugin_indexmenu_indexmenu'); 95 } 96 97 /** 98 * Handle the match 99 */ 100 function handle($match, $state, $pos, &$handler){ 101 102 return $this->parseOptions(substr($match,12,-2)); 103 104 } 105 106 /** 107 * Render output 108 */ 109 function render($mode, &$renderer, $data) { 110 switch ($mode) { 111 case 'xhtml' : 112 $n = $this->_indexmenu($data); 113 if (!$n && ($n = $this->getConf('empty_msg'))) { 114 $exists = false; 115 resolve_pageid(getNS(getID()),$data[0],$exists); 116 $n = str_replace('{{ns}}'," ".$data[0]." ",$n); 117 } 118 $renderer->doc .= $n ; 119 return true; 120 break; 121 case 'metadata' : 122 /* 123 * used to purge the cache, if path to the current ID is used 124 */ 125 if ("." == $data[0]) $renderer->meta['indexmenu'] = true; 126 return true; 127 break; 128 } 129 return false; 130 } 131 132 /** 133 * Parse plugin options and return array with them 134 * 135 * @author Ilya Lebedev <ilya@lebedev.net> 136 * @param $opts string unparsed options list 137 * @return array options values 138 * @access public 139 */ 140 function parseOptions ($opts) { 141 $theme="DokuWiki"; 142 143 $level = 0; 144 145 $nons = true; 146 /* 147 * split namespace,level,theme 148 * array will have 149 * 0 => namespace name and options 150 * 1 => optional js settings 151 */ 152 $options = explode('|', $opts, 2); 153 154 /* 155 * split namespace 156 * array will have 157 * 0 => namespace name 158 * 1 => options 159 * 2 => sort mode 160 */ 161 $options[0] = explode("#", trim($options[0])); 162 $ns = $options[0][0]; 163 /* 164 * split namespace options 165 * i.e. 1+nons => array(1,'nons') 166 */ 167 @$options[0][1] = explode("+",$options[0][1]); 168 /* 169 * split sort options 170 * i.e. sort+type+rev => array('sort','nons','rev') 171 */ 172 @$options[0][2] = explode("+",$options[0][2]); 173 174 /* 175 * does not matter, if level is not defined 176 */ 177 $level = intval(@$options[0][1][0]); 178 $nons = in_array('nons',$options[0][1]); 179 180 /* 181 * now, parse the JS options 182 */ 183 if (!isset($options[1])) { 184 $js = false; 185 $ajax = false; 186 $theme = ''; 187 } else { 188 $js = true; 189 /* 190 * split js part 191 * array will have 192 * 0 => 'js' 193 * 1 => options 194 */ 195 $options[1] = explode("#", $options[1]); 196 /* 197 * split js options 198 * i.e. IndexMenu+ajax => array('IndexMenu','nons') 199 */ 200 $options[1][1] = explode('+',$options[1][1]); 201 $ajax = in_array('ajax',$options[1][1]); 202 /* 203 * change the default theme name only if it really exists 204 */ 205 if (@file_exists(INDEXMENU_FS_IMAGES.$theme."/".$options[1][1][0]."/design.css")) { 206 $theme .= "/".$options[1][1][0]; 207 } 208 } 209 return array($ns,array( 'level' => $level 210 ,'theme' => $theme 211 ,'nons' => $nons 212 ,'ajax' => $ajax 213 ,'js' => $js 214 ,'sort' => $options[0][2][1] 215 ) 216 ); 217 } 218 /** 219 * Return the index 220 * 221 * @author Ilya Lebedev <ilya@lebedev.net> 222 */ 223 function _indexmenu($myns) { 224 global $conf; 225 global $ID; 226 227 $ns = $myns[0]; 228 $opts = $myns[1]; 229 230 $exists = false; 231 $id = resolve_pageid(getNS(getID()),$ns,$exists); 232 if ($ns == $conf['start']) $ns = ""; 233 /* 234 * if 'nons' is set or NS is root, no need to adjust settings 235 */ 236 if (($opts['js'] || $opts['navigation']) && !$opts['nons'] && $ns) { 237 $opts['root'] = $ns; 238 $ns = (string)getNS($ns); 239 /* 240 * we've moved index root 1 level lower, adjust max level than 241 */ 242 if ($opts['level']) $opts['level']++; 243 } 244 $data = array(); 245 search($data,$conf['datadir'],"indexmenu_search_index",$opts,"/".utf8_encodeFN(str_replace(':','/',$ns))); 246 if ($opts['sort']) { 247 $this->s_rev = substr($opts['sort'],0,1)=='!'; 248 $this->s_target = $this->s_target[preg_replace("#^!#","",$opts['sort'])]; 249 250 if ($this->s_target) { 251 usort($data,array($this,sortCallback)); 252 } 253 } 254 /* 255 * prepare array to convert into nested one 256 */ 257 foreach ($data as $k => $v) { 258 $data[$k]['parent_id'] = (string)getNS($v['id']); 259 } 260 /* 261 * convert array, w/o skipping NSs 262 */ 263 if (!$opts['nons']) 264 $data = array2tree($data,$ns); 265 266 /* 267 * indicate empty tree 268 */ 269 if (empty($data)) return false; 270 /* 271 * if user want to draw complete wiki index, we need to make one more fake level 272 */ 273 if ($opts['js'] && !$opts['nons'] && !isset($opts['root']) && !$ns) { 274 $data = array(array('level' =>0, 275 'child_nodes' => $data, 276 'type' => 'd', 277 'open' => 'true', 278 'id' => $conf['start'], 279 'target' => $conf['start'], 280 'title' => ($conf['useheading'] && ($title=p_get_first_heading($conf['start'])))?$title:"", 281 )); 282 283 } 284 /* 285 * get the list tree 286 */ 287 return syntax_plugin_indexmenu_indexmenu::getHTML ($opts, $opts['navigation']?$this->html_buildlist($data,$opts) 288 :"<ul>".$this->html_buildlist($data,$opts)."</ul>"); 289 } 290 /** 291 * returns complete HTML for the menu 292 * 293 * @param $opts mixed array of the options 294 * @param $html string html layout to be wrapped with javascript code, if needed 295 * @return string 296 * @access public 297 */ 298 299 function getHTML ($opts, $html) { 300 /* 301 * make unique id for current menu 302 */ 303 $idx = 'indexmenu'.str_replace(".","",join(explode(" ",microtime()))); 304 $add = $opts['js']?"id=\"$idx\"":"class=\"idx\""; 305 $html = preg_replace("#<ul[^>]*>#i", "<ul $add>", $html, 1); 306 307 /* 308 * if 'nons' is set, then there's no need in JS 309 */ 310 if ($opts['js'] && !$opts['nons']) { 311 /* 312 * create ajax callback, if needed 313 */ 314 if ($opts['ajax']) { 315 $ajax = <<<EOL 316 ,modifiers : ['ajaxum'] 317 ,ajaxum : { 318 fetcher : function (s, callback) { 319 if ('undefined' == typeof RemoteScript) { 320 callback ({'state' : false, 321 'response' : 'Plugin <a href="http://wiki.splitbrain.org/plugin:remotescript" _target="blank">RemoteScript</a> is not available'}); 322 return; 323 } 324 325 RemoteScript.query( ['indexmenu','getsubmenu'] 326 ,{ 'src' : s 327 ,'sort' : '{$opts['sort']}'} 328 ,function(js, txt) { 329 callback({'state' : !!js, 330 'response' : js||txt}); 331 } 332 ,true); 333 } 334 } 335EOL; 336 } else { 337 $ajax = ''; 338 } 339 $themeRoot = DOKU_BASE.'lib/plugins/indexmenu/templates/'; 340 $html .= <<<EOL 341 <script type="text/javascript"><!--//--><![CDATA[//><!-- 342 var cms = new CompleteMenuSolution() 343 cms.initMenu('$idx',{ 'theme': {'name': '{$opts["theme"]}'} 344 ,'themeRootPath' : '$themeRoot' 345 ,closeSiblings : false 346 $ajax 347 }); 348 //--><!]]></script> 349EOL; 350 } 351 return $html; 352 } 353 /** 354 * Build an unordered list 355 * 356 * Build an unordered list from the given $data array 357 * Each item in the array has to have a 'level' property 358 * the item itself gets printed by the given $func user 359 * function. The second and optional function is used to 360 * print the <li> tag. Both user function need to accept 361 * a single item. 362 * 363 * Both user functions can be given as array to point to 364 * a member of an object. 365 * 366 * @author Andreas Gohr <andi@splitbrain.org> 367 * @author Ilya Lebedev <ilya@lebedev.net> 368 */ 369 function html_buildlist(&$data,&$opts){ 370 $ret = array(); 371 372 foreach ($data as $item) { 373 $ret[] = "<li".(($item['type']=='d')?(" class=\"".($item['open']?'open':'closed')."\" "):'').">"; 374 $ret[] = preg_replace("#^<span[^>]+>(.+)</span>$#i","$1",html_wikilink(":".$item['target'],null)); 375 /* 376 * append child nodes, if exists 377 */ 378 if ($item['type']=='d') { //isset($item['child_nodes'])) { 379 if ($opts['level'] != 0 && ($opts['level'] <= $item['level'])) { 380 /* 381 * for closed nodes add mark for Ajaxum plugin 382 */ 383 if ($opts['ajax']) 384 $ret[] = "<ul ".(!$opts['js']&&!$opts['navigation']?"style=\"display: none\"" 385 :"") 386 ." title=\"{$item['id']}\"><!-- {$item['id']} --></ul>"; 387 } else { 388 /* 389 * open nodes process as usual 390 */ 391 if (isset($item['child_nodes'])) { 392 $ret[] = "<ul>"; 393 /* 394 * static method used to be able to make menu w/o make class object 395 */ 396 $ret[] = syntax_plugin_indexmenu_indexmenu::html_buildlist($item['child_nodes'],$opts); 397 $ret[] = "</ul>"; 398 } 399 } 400 } 401 $ret[] = "</li>"; 402 } 403 return join("\n",$ret); 404 } 405 /** 406 * Sorting callback function 407 * 408 * @param array $a first matching 409 * @param array $b second matching 410 * @return int -1,0,1 sorting result 411 * @access protected 412 */ 413 function sortCallback ($a, $b) { 414 $t1 = $this->s_rev?$b[$this->s_target]:$a[$this->s_target]; 415 $t2 = $this->s_rev?$a[$this->s_target]:$b[$this->s_target]; 416 if ($t1>$t2) return 1; 417 if ($t1<$t2) return -1; 418 return 0; 419 } 420} //Indexmenu class end 421 /** 422 * Build the browsable index of pages 423 * 424 * $opts['ns'] is the current namespace 425 * 426 * @author Andreas Gohr <andi@splitbrain.org> 427 * @author Ilya Lebedev <ilya@lebedev.net> 428 */ 429 function indexmenu_search_index(&$data,$base,$file,$type,$lvl,$opts){ 430 global $conf; 431 $ret = true; 432 433 $item = array(); 434 if($type == 'd'){ 435 if ($opts['level']!=0 && $lvl >= $opts['level']) $ret=false; 436 if ($opts['nons']) return $ret; 437 } elseif($type == 'f' && !preg_match('#\.txt$#',$file)) { 438 //don't add 439 return false; 440 } 441 442 /* 443 * get page id by filename 444 */ 445 $id = pathID($file); 446 447 /* 448 * index only 'root' namespace, if requested 449 */ 450 if ($lvl == 1 && isset($opts['root']) && $id != $opts['root']) return false; 451 452 /* 453 * check for files/folders to skip 454 */ 455 if (syntax_plugin_indexmenu_indexmenu::getConf('skip_index') && preg_match(syntax_plugin_indexmenu_indexmenu::getConf('skip_index'), $file)) 456 return false; 457 458 //check hiddens 459 if($type=='f' && isHiddenPage($id)){ 460 return false; 461 } 462 463 //check ACL (for namespaces too) 464 if(auth_quickaclcheck($id) < AUTH_READ){ 465 return false; 466 } 467 468 //check if it's a headpage (acrobatic check) 469 if(!$opts['nons'] && $type=='f' && $conf['useheading'] && syntax_plugin_indexmenu_indexmenu::getConf('hide_headpage')) { 470 if (noNS(getNS($id))==noNS($id) || // /<ns>/<ns>.txt 471 $id==$conf['start'] || // <ns> == <start_page> 472 $id==getNS($id).":".$conf['start'] || // /<ns>/<start_page>.txt 473 @file_exists(dirname(wikiFN($id.":".noNS($id)))) // /<ns>/ 474 // /<ns>.txt 475 ){ 476 return false; 477 } 478 } 479 /* 480 * bugfix for the 481 * /ns/ 482 * /<ns>.txt 483 * case, need to force the 'directory' type 484 */ 485 if ($type == 'f' && file_exists(dirname(wikiFN($id.":".noNS($id))))) $type = 'd'; 486 487 488 /* 489 * page target id = global id 490 */ 491 $target = $id; 492 if ($type == 'd') { 493 /* 494 * this will check 3 kinds of headpage: 495 * 1. /<ns>/<ns>.txt 496 * 2. /<ns>/ 497 * /<ns>.txt 498 * 3. /<ns>/ 499 * /<ns>/<start_page> 500 */ 501 $nsa = array( $id.":".noNS($id), 502 $id, 503 $id.":".$conf['start'] 504 ); 505 $nspage = false; 506 foreach ($nsa as $nsp) { 507 if (@file_exists(wikiFN($nsp)) && auth_quickaclcheck($nsp) >= AUTH_READ) { 508 $nspage = $nsp; 509 break; 510 } 511 } 512 //headpage exists 513 if ($nspage) { 514 $target = $nspage; 515 } else { 516 /* 517 * open namespace index, if headpage does not exists 518 */ 519 $target = $target.':'; 520 } 521 } 522 523 //Set all pages at first level 524 if ($opts['nons']) { 525 $lvl=1; 526 } 527 528 $data[]=array( 'id' => $id 529 ,'date' => @filectime(wikiFN($target)) 530 ,'type' => $type 531 ,'target' => $target // id to be used in the menu 532 ,'title' => ($conf['useheading'] && ($title=p_get_first_heading($target)))?$title:$id // NS title 533 ,'level' => $lvl 534 ,'open' => $ret ); 535 536 return $ret|$opts['ajax']; 537 538 } 539 540 541 542/** 543 * Converts an associative array into tree 544 * @author Anton Makarenko php[at]ripfolio[dot]com 545 * @copyright GPL 546 * 547 * @param array $source_arr 548 * @param mixed $parent_id 549 * @param string $key_children 550 * @param string $key_id 551 * @param string $key_parent_id 552 * @return array $tree 553 * 554 * @example : 555 * $source = array( 556 * array('id'=>1, 'parent_id'=>0, 'foo'=>'bar'), 557 * array('id'=>2, 'parent_id'=>1, 'foo'=>'barr'), 558 * array('id'=>3, 'parent_id'=>1, 'foo'=>'barrr') 559 * ); 560 * $tree = array2tree($source, 0); 561 */ 562function array2tree($source_arr, $parent_id, $key_children='child_nodes', $key_id='id', $key_parent_id='parent_id') 563{ 564 $tree=array(); 565 if (empty($source_arr)) 566 return $tree; 567 _array2treer($source_arr, $tree, $parent_id, $parent_id, $key_children, $key_id, $key_parent_id); 568 return $tree; 569} 570/** 571 * A private function. Background for array2tree. It is unnecessarily to use this function directly 572 * @author Anton Makarenko php[at]ripfolio[dot]com 573 * @copyright GPL 574 * 575 * @param array $source_arr 576 * @param array &$_this 577 * @param mixed $parent_id 578 * @param mixed $_this_id 579 * @param string $key_children 580 * @param string $key_id 581 * @param string $key_parent_id 582 * @return null 583 */ 584function _array2treer($source_arr, &$_this, $parent_id, $_this_id, $key_children, $key_id, $key_parent_id) 585{ 586 // populate current children 587 foreach ($source_arr as $value) 588 if ($value[$key_parent_id]===$_this_id) 589 $_this[$key_children][$value[$key_id]]=$value; 590 if (isset($_this[$key_children])) 591 { 592 // populate children of the current children 593 foreach ($_this[$key_children] as $value) 594 _array2treer($source_arr, $_this[$key_children][$value[$key_id]], $parent_id, $value[$key_id], $key_children, $key_id, $key_parent_id); 595 // make the tree root look pretty (more convenient to use such tree) 596 if ($_this_id===$parent_id) 597 $_this=$_this[$key_children]; 598 } 599} 600 601//Setup VIM: ex: et ts=4 enc=utf-8 : 602