1<?php 2/** 3 * Template Functions 4 * 5 * This file provides template specific custom functions that are 6 * not provided by the DokuWiki core. 7 * It is common practice to start each function with an underscore 8 * to make sure it won't interfere with future core functions. 9 */ 10 11// must be run from within DokuWiki 12if (!defined('DOKU_INC')) die(); 13 14/** 15 * Create link/button to register page 16 * @deprecated DW versions > 2011-02-20 can use the core function tpl_action('register') 17 * 18 * @author Anika Henke <anika@selfthinker.org> 19 */ 20function _tpl_register($link=0, $wrapper=0) { 21 global $conf; 22 global $lang; 23 global $ID; 24 $lang_register = !empty($lang['btn_register']) ? $lang['btn_register'] : $lang['register']; 25 26 if ($_SERVER['REMOTE_USER'] || !$conf['useacl'] || !actionOK('register')) return; 27 28 if ($wrapper) echo "<$wrapper>"; 29 30 if ($link) 31 tpl_link(wl($ID, 'do=register'), $lang_register, 'class="action register" rel="nofollow"'); 32 else 33 echo html_btn('register', $ID, '', array('do'=>'register'), 'get', 0, $lang_register); 34 35 if ($wrapper) echo "</$wrapper>"; 36} 37 38/** 39 * Wrapper around custom template actions 40 * 41 * @author Anika Henke <anika@selfthinker.org> 42 */ 43function _tpl_action($type, $link=0, $wrapper=0) { 44 switch ($type) { 45 case 'register': // deprecated 46 _tpl_register($link, $wrapper); 47 break; 48 } 49} 50 51 52 53/* fallbacks for things missing in older DokuWiki versions 54********************************************************************/ 55 56 57/* if newer settings exist in the core, use them, otherwise fall back to template settings */ 58 59if (!isset($conf['tagline'])) { 60 $conf['tagline'] = tpl_getConf('tagline'); 61} 62 63if (!isset($conf['sidebar'])) { 64 $conf['sidebar'] = tpl_getConf('sidebarID'); 65} 66 67/* these $lang strings are now in the core */ 68 69if (!isset($lang['user_tools'])) { 70 $lang['user_tools'] = tpl_getLang('user_tools'); 71} 72if (!isset($lang['site_tools'])) { 73 $lang['site_tools'] = tpl_getLang('site_tools'); 74} 75if (!isset($lang['page_tools'])) { 76 $lang['page_tools'] = tpl_getLang('page_tools'); 77} 78if (!isset($lang['skip_to_content'])) { 79 $lang['skip_to_content'] = tpl_getLang('skip_to_content'); 80} 81 82 83/** 84 * copied from core (available since Adora Belle) 85 */ 86if (!function_exists('tpl_getMediaFile')) { 87 function tpl_getMediaFile($search, $abs = false, &$imginfo = null) { 88 $img = ''; 89 $file = ''; 90 $ismedia = false; 91 // loop through candidates until a match was found: 92 foreach($search as $img) { 93 if(substr($img, 0, 1) == ':') { 94 $file = mediaFN($img); 95 $ismedia = true; 96 } else { 97 $file = tpl_incdir().$img; 98 $ismedia = false; 99 } 100 101 if(file_exists($file)) break; 102 } 103 104 // fetch image data if requested 105 if(!is_null($imginfo)) { 106 $imginfo = getimagesize($file); 107 } 108 109 // build URL 110 if($ismedia) { 111 $url = ml($img, '', true, '', $abs); 112 } else { 113 $url = tpl_basedir().$img; 114 if($abs) $url = DOKU_URL.substr($url, strlen(DOKU_REL)); 115 } 116 117 return $url; 118 } 119} 120 121/** 122 * copied from core (available since Angua) 123 */ 124if (!function_exists('tpl_favicon')) { 125 function tpl_favicon($types = array('favicon')) { 126 127 $return = ''; 128 129 foreach($types as $type) { 130 switch($type) { 131 case 'favicon': 132 $look = array(':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'); 133 $return .= '<link rel="shortcut icon" href="'.tpl_getMediaFile($look).'" />'.NL; 134 break; 135 case 'mobile': 136 $look = array(':wiki:apple-touch-icon.png', ':apple-touch-icon.png', 'images/apple-touch-icon.png'); 137 $return .= '<link rel="apple-touch-icon" href="'.tpl_getMediaFile($look).'" />'.NL; 138 break; 139 case 'generic': 140 // ideal world solution, which doesn't work in any browser yet 141 $look = array(':wiki:favicon.svg', ':favicon.svg', 'images/favicon.svg'); 142 $return .= '<link rel="icon" href="'.tpl_getMediaFile($look).'" type="image/svg+xml" />'.NL; 143 break; 144 } 145 } 146 147 return $return; 148 } 149} 150 151/** 152 * copied from core (available since Adora Belle) 153 */ 154if (!function_exists('tpl_includeFile')) { 155 function tpl_includeFile($file) { 156 global $config_cascade; 157 foreach(array('protected', 'local', 'default') as $config_group) { 158 if(empty($config_cascade['main'][$config_group])) continue; 159 foreach($config_cascade['main'][$config_group] as $conf_file) { 160 $dir = dirname($conf_file); 161 if(file_exists("$dir/$file")) { 162 include("$dir/$file"); 163 return; 164 } 165 } 166 } 167 168 // still here? try the template dir 169 $file = tpl_incdir().$file; 170 if(file_exists($file)) { 171 include($file); 172 } 173 } 174} 175 176/** 177 * copied from core (available since Adora Belle) 178 */ 179if (!function_exists('tpl_incdir')) { 180 function tpl_incdir() { 181 global $conf; 182 return DOKU_INC.'lib/tpl/'.$conf['template'].'/'; 183 } 184} 185 186 187/** 188 * Print the search form 189 * 190 * If the first parameter is given a div with the ID 'qsearch_out' will 191 * be added which instructs the ajax pagequicksearch to kick in and place 192 * its output into this div. The second parameter controls the propritary 193 * attribute autocomplete. If set to false this attribute will be set with an 194 * value of "off" to instruct the browser to disable it's own built in 195 * autocompletion feature (MSIE and Firefox) 196 * 197 * @author Andreas Gohr <andi@splitbrain.org> 198 * @param bool $ajax 199 * @param bool $autocomplete 200 * @return bool 201 */ 202function _tpl_searchform($ajax = true, $autocomplete = true) { 203 global $lang; 204 global $ACT; 205 global $QUERY; 206 207 // don't print the search form if search action has been disabled 208 if(!actionOK('search')) return false; 209 210 print '<form action="'.wl().'" accept-charset="utf-8" class="navbar-form navbar-right" id="dw__search" method="get" role="search">'; 211 print '<input type="hidden" name="do" value="search" />'; 212 print '<div class="form-group">'; 213 print '<input type="text" '; 214 if($ACT == 'search') print 'value="'.htmlspecialchars($QUERY).'" '; 215 print ' autocomplete="off" '; 216 print 'id="qsearch__in" accesskey="f" name="id" class="form-control col-lg-3" title="[F]" placeholder="' . $lang['btn_search'] . '" /> '; 217 print '</div>'; 218 if($ajax) print '<div id="qsearch__out" class="ajax_qsearch"></div>'; 219 print '</form>'; 220 return true; 221} 222 223/* table of contents */ 224function _tpl_toc($return = false) { 225 global $TOC; 226 global $ACT; 227 global $ID; 228 global $REV; 229 global $INFO; 230 global $conf; 231 global $INPUT; 232 $toc = array(); 233 234 //if(is_array($TOC)) 235 // NOTE: 236 // This will happen if sidebar has headings in it, so we don't want to use 237 // a TOC from the global scope. I suspect this could break some plugins. 238 if(($ACT == 'show' || substr($ACT, 0, 6) == 'export') && !$REV && $INFO['exists']) { 239 // get TOC from metadata, render if neccessary 240 $meta = p_get_metadata($ID, false, METADATA_RENDER_USING_CACHE); 241 if(isset($meta['internal']['toc'])) { 242 $tocok = $meta['internal']['toc']; 243 } else { 244 $tocok = true; 245 } 246 $toc = $meta['description']['tableofcontents']; 247 if(!$tocok || !is_array($toc) || !$conf['tocminheads'] || count($toc) < $conf['tocminheads']) { 248 $toc = array(); 249 } 250 } elseif($ACT == 'admin') { 251 // try to load admin plugin TOC FIXME: duplicates code from tpl_admin 252 $plugin = null; 253 $class = $INPUT->str('page'); 254 if(!empty($class)) { 255 $pluginlist = plugin_list('admin'); 256 if(in_array($class, $pluginlist)) { 257 // attempt to load the plugin 258 /** @var $plugin DokuWiki_Admin_Plugin */ 259 $plugin =& plugin_load('admin', $class); 260 } 261 } 262 if( ($plugin !== null) && (!$plugin->forAdminOnly() || $INFO['isadmin']) ) { 263 $toc = $plugin->getTOC(); 264 $TOC = $toc; // avoid later rebuild 265 } 266 } 267 268 trigger_event('TPL_TOC_RENDER', $toc, null, false); 269 $html = bootstrap_html_TOC($toc); 270 if($return) return $html; 271 echo $html; 272 return ''; 273} 274/** 275 * Return the TOC rendered to XHTML 276 * 277 * @author Andreas Gohr <andi@splitbrain.org> 278 */ 279function bootstrap_html_TOC($toc){ 280 if(!count($toc)) return ''; 281 global $lang; 282 $out = '<!-- TOC START -->'.DOKU_LF; 283 $out .= '<div id="dw_toc" class="hidden-print panel panel-default pull-right col-sm-4 col-md-3 col-xs-12">'.DOKU_LF; 284 $out .= '<div class="panel-heading hidden-print"><h3 class="panel-title" data-toggle="collapse" data-target="#toc_contents">'; 285 $out .= $lang['toc']; 286 $out .= ' <b class="caret"></b></h3></div>'.DOKU_LF; 287 $out .= '<div id="toc_contents" class="hidden-print panel-collapse collapse in"><div class="panel-body">'; 288 $out .= bootstrap_toc_html_buildlist($toc,'','html_list_toc'); 289 $out .= '</div></div>'; 290 $out .= '</div>'.DOKU_LF; 291 $out .= '<!-- TOC END -->'.DOKU_LF; 292 return $out; 293} 294function bootstrap_toc_html_buildlist($data,$class,$func,$lifunc='html_li_default',$forcewrapper=false){ 295 if (count($data) === 0) { 296 return ''; 297 } 298 299 $start_level = $data[0]['level']; 300 $level = $start_level; 301 $ret = ''; 302 $open = 0; 303 304 foreach ($data as $item){ 305 if( $item['level'] > $level ){ 306 //open new list 307 for($i=0; $i<($item['level'] - $level); $i++){ 308 if ($i) $ret .= '<li class="">'; 309 $ret .= "\n<ul class=\"$class\">\n"; 310 $open++; 311 } 312 $level = $item['level']; 313 314 }elseif( $item['level'] < $level ){ 315 //close last item 316 $ret .= "</li>\n"; 317 while( $level > $item['level'] && $open > 0 ){ 318 //close higher lists 319 $ret .= "</ul>\n</li>\n"; 320 $level--; 321 $open--; 322 } 323 } elseif ($ret !== '') { 324 //close previous item 325 $ret .= "</li>\n"; 326 } 327 328 //print item 329 $ret .= call_user_func($lifunc,$item); 330 331 $ret .= call_user_func($func,$item); 332 } 333 334 //close remaining items and lists 335 $ret .= "</li>\n"; 336 while($open-- > 0) { 337 $ret .= "</ul></li>\n"; 338 } 339 340 if ($forcewrapper || $start_level < 2) { 341 // Trigger building a wrapper ul if the first level is 342 // 0 (we have a root object) or 1 (just the root content) 343 $ret = "\n<ul class=\"$class\">\n".$ret."</ul>\n"; 344 } 345 346 return $ret; 347} 348 349function _tpl_breadcrumbs() { 350 global $lang; 351 global $conf; 352 353 // check if enabled 354 if (!$conf['breadcrumbs']) return false; 355 356 $crumbs = breadcrumbs(); 357 358 $last = count($crumbs); 359 if ($last > 1) { 360 print '<!-- BREADCRUMBS --><div id="breadcrumbs" class="row hidden-print"><div class="col-lg-12"><ul class="breadcrumb">'.$lang['breadcrumb'].': '; 361 $i = 0; 362 foreach ($crumbs as $id => $name) { 363 $i++; 364 if ($i == $last - 1) { 365 print '<li>'; 366 tpl_pagelink(':'.$id, hsc($name), 'title="' . $id . '"'); 367 } else if ($i != $last) { 368 print '<li>'; 369 tpl_pagelink(':'.$id, hsc($name), 'title="' . $id . '"'); 370 } 371 print '</li> '; 372 } 373 print '</ul></div></div>'; 374 } 375 return true; 376} 377 378function bootstrap_tpl_youarehere() { 379 global $lang; 380 global $ID; 381 global $conf; 382 383 // check if enabled 384 if (!$conf['youarehere']) return false; 385 386 $parts = explode(':', $ID); 387 $count = count($parts); 388 389 print '<ul class="breadcrumb hidden-print">' . $lang['youarehere'] . ': '; 390 391 // always print the start page 392 echo '<li class="home">'; 393 tpl_pagelink(':'.$conf['start']); 394 echo '</li> '; 395 396 // print intermediate namespace links 397 $part = ''; 398 for ($i = 0; $i < $count - 1; $i++) { 399 $part .= $parts[$i].':'; 400 $page = $part; 401 if ($page == $conf['start']) continue; // skip startpage 402 echo '<li>'; 403 tpl_pagelink($page); 404 echo '</li> '; 405 } 406 407 // print current page, skipping start page, skipping for namespace index 408 resolve_pageid('', $page, $exists); 409 if (isset($page) && $page == $part.$parts[$i]) return true; 410 $page = $part.$parts[$i]; 411 if ($page == $conf['start']) return true; 412 echo '<li>'; 413 tpl_pagelink($page); 414 echo '</li> '; 415 print '</ul>'; 416 return true; 417} 418 419function bootstrap_tpl_userinfo() { 420 global $lang; 421 global $INFO; 422 if(isset($_SERVER['REMOTE_USER'])) { 423 print 'you are:'.hsc($INFO['userinfo']['name']); 424 return true; 425 } 426 return false; 427} 428 429 430/** 431 * prints the namespace tree in the mediamanger popup 432 * 433 * Only allowed in mediamanager.php 434 * 435 * @author Andreas Gohr <andi@splitbrain.org> 436 */ 437function bootstrap_tpl_mediaTree() { 438 global $NS; 439 ptln('<div id="media__tree" class="well well-sm">'); 440 bootstrap_media_nstree($NS); 441 ptln('</div>'); 442} 443/** 444 * Build a tree outline of available media namespaces 445 * 446 * @author Andreas Gohr <andi@splitbrain.org> 447 */ 448function bootstrap_media_nstree($ns){ 449 global $conf; 450 global $lang; 451 452 // currently selected namespace 453 $ns = cleanID($ns); 454 if(empty($ns)){ 455 global $ID; 456 $ns = (string)getNS($ID); 457 } 458 459 $ns_dir = utf8_encodeFN(str_replace(':','/',$ns)); 460 461 $data = array(); 462 search($data,$conf['mediadir'],'search_index',array('ns' => $ns_dir, 'nofiles' => true)); 463 464 // wrap a list with the root level around the other namespaces 465 array_unshift($data, array('level' => 0, 'id' => '', 'open' =>'true', 'label' => '['.$lang['mediaroot'].']')); 466 467 // insert the current ns into the hierarchy if it isn't already part of it 468 $ns_parts = explode(':', $ns); 469 $tmp_ns = ''; 470 $pos = 0; 471 foreach ($ns_parts as $level => $part) { 472 if ($tmp_ns) $tmp_ns .= ':'.$part; 473 else $tmp_ns = $part; 474 475 // find the namespace parts or insert them 476 while ($data[$pos]['id'] != $tmp_ns) { 477 if ($pos >= count($data) || ($data[$pos]['level'] <= $level+1 && strnatcmp(utf8_encodeFN($data[$pos]['id']), utf8_encodeFN($tmp_ns)) > 0)) { 478 array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true'))); 479 break; 480 } 481 ++$pos; 482 } 483 } 484 485 echo bootstrap_toc_html_buildlist($data,'','bootstrap_media_nstree_item','bootstrap_media_nstree_li'); 486} 487/** 488 * Userfunction for html_buildlist 489 * 490 * Prints a media namespace tree item 491 * 492 * @author Andreas Gohr <andi@splitbrain.org> 493 */ 494function bootstrap_media_nstree_item($item){ 495 global $INPUT; 496 $pos = strrpos($item['id'], ':'); 497 $label = substr($item['id'], $pos > 0 ? $pos + 1 : 0); 498 if(!$item['label']) $item['label'] = $label; 499 500 501 $class = 'level'.$item['level']; 502 // TODO: only deliver an image if it actually has a subtree... 503 if($item['open']){ 504 $class .= ' open'; 505 $icon = '<i class="glyphicon glyphicon-minus"></i> '; 506 $alt = '−'; 507 } else { 508 $class .= ' closed'; 509 $icon = '<i class="glyphicon glyphicon-plus"></i> '; 510 $alt = '+'; 511 } 512 $ret = '<li class="'.$class.'">'; 513 514 if (!($INPUT->str('do') == 'media')) 515 $ret .= '<a href="'.DOKU_BASE.'lib/exe/mediamanager.php?ns='.idfilter($item['id']).'" class="idx_dir">'; 516 else $ret .= '<a href="'.media_managerURL(array('ns' => idfilter($item['id'], false), 'tab_files' => 'files')).'>'; 517 $ret .= $icon; 518 $ret .= $item['label']; 519 $ret .= '</a>'; 520 return $ret; 521} 522function bootstrap_media_nstree_li($item){ 523} 524 525/** 526 * Get the sidebar html. Get cached sidebar if $cache param is true. 527 * 528 * @author Cameron Little <cameron@camlittle.com> 529 */ 530function bootstrap_tpl_get_sidebar($pageid, $cache) { 531 global $TOC; 532 $oldtoc = $TOC; 533 $html = ''; 534 $rev = ''; 535 $file = wikiFN($pageid, $rev); 536 537 if ($cache && !$rev) { 538 if(@file_exists($file)) { 539 $html = p_cached_output($file,'xhtml',$pageid); 540 } 541 } else { 542 if(@file_exists($file)) { 543 $html = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$pageid,$rev)),$info); //no caching on old revisions 544 } 545 } 546 547 return $html; 548} 549