1<?php 2/** 3 * DokuWiki template functions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8use dokuwiki\ActionRouter; 9use dokuwiki\Action\Exception\FatalException; 10use dokuwiki\Extension\PluginInterface; 11use dokuwiki\Ui\Admin; 12use dokuwiki\StyleUtils; 13use dokuwiki\Menu\Item\AbstractItem; 14use dokuwiki\Form\Form; 15use dokuwiki\Menu\MobileMenu; 16use dokuwiki\Ui\Subscribe; 17use dokuwiki\Extension\AdminPlugin; 18use dokuwiki\Extension\Event; 19use dokuwiki\File\PageResolver; 20 21/** 22 * Access a template file 23 * 24 * Returns the path to the given file inside the current template, uses 25 * default template if the custom version doesn't exist. 26 * 27 * @author Andreas Gohr <andi@splitbrain.org> 28 * @param string $file 29 * @return string 30 */ 31function template($file) { 32 global $conf; 33 34 if(@is_readable(DOKU_INC.'lib/tpl/'.$conf['template'].'/'.$file)) 35 return DOKU_INC.'lib/tpl/'.$conf['template'].'/'.$file; 36 37 return DOKU_INC.'lib/tpl/dokuwiki/'.$file; 38} 39 40/** 41 * Convenience function to access template dir from local FS 42 * 43 * This replaces the deprecated DOKU_TPLINC constant 44 * 45 * @author Andreas Gohr <andi@splitbrain.org> 46 * @param string $tpl The template to use, default to current one 47 * @return string 48 */ 49function tpl_incdir($tpl='') { 50 global $conf; 51 if(!$tpl) $tpl = $conf['template']; 52 return DOKU_INC.'lib/tpl/'.$tpl.'/'; 53} 54 55/** 56 * Convenience function to access template dir from web 57 * 58 * This replaces the deprecated DOKU_TPL constant 59 * 60 * @author Andreas Gohr <andi@splitbrain.org> 61 * @param string $tpl The template to use, default to current one 62 * @return string 63 */ 64function tpl_basedir($tpl='') { 65 global $conf; 66 if(!$tpl) $tpl = $conf['template']; 67 return DOKU_BASE.'lib/tpl/'.$tpl.'/'; 68} 69 70/** 71 * Print the content 72 * 73 * This function is used for printing all the usual content 74 * (defined by the global $ACT var) by calling the appropriate 75 * outputfunction(s) from html.php 76 * 77 * Everything that doesn't use the main template file isn't 78 * handled by this function. ACL stuff is not done here either. 79 * 80 * @author Andreas Gohr <andi@splitbrain.org> 81 * 82 * @triggers TPL_ACT_RENDER 83 * @triggers TPL_CONTENT_DISPLAY 84 * @param bool $prependTOC should the TOC be displayed here? 85 * @return bool true if any output 86 */ 87function tpl_content($prependTOC = true) { 88 global $ACT; 89 global $INFO; 90 $INFO['prependTOC'] = $prependTOC; 91 92 ob_start(); 93 Event::createAndTrigger('TPL_ACT_RENDER', $ACT, 'tpl_content_core'); 94 $html_output = ob_get_clean(); 95 Event::createAndTrigger('TPL_CONTENT_DISPLAY', $html_output, 'ptln'); 96 97 return !empty($html_output); 98} 99 100/** 101 * Default Action of TPL_ACT_RENDER 102 * 103 * @return bool 104 */ 105function tpl_content_core() { 106 $router = ActionRouter::getInstance(); 107 try { 108 $router->getAction()->tplContent(); 109 } catch(FatalException $e) { 110 // there was no content for the action 111 msg(hsc($e->getMessage()), -1); 112 return false; 113 } 114 return true; 115} 116 117/** 118 * Places the TOC where the function is called 119 * 120 * If you use this you most probably want to call tpl_content with 121 * a false argument 122 * 123 * @author Andreas Gohr <andi@splitbrain.org> 124 * 125 * @param bool $return Should the TOC be returned instead to be printed? 126 * @return string 127 */ 128function tpl_toc($return = false) { 129 global $TOC; 130 global $ACT; 131 global $ID; 132 global $REV; 133 global $INFO; 134 global $conf; 135 global $INPUT; 136 $toc = []; 137 138 if(is_array($TOC)) { 139 // if a TOC was prepared in global scope, always use it 140 $toc = $TOC; 141 } elseif(($ACT == 'show' || substr($ACT, 0, 6) == 'export') && !$REV && $INFO['exists']) { 142 // get TOC from metadata, render if neccessary 143 $meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE); 144 if(isset($meta['internal']['toc'])) { 145 $tocok = $meta['internal']['toc']; 146 } else { 147 $tocok = true; 148 } 149 $toc = $meta['description']['tableofcontents'] ?? null; 150 if(!$tocok || !is_array($toc) || !$conf['tocminheads'] || count($toc) < $conf['tocminheads']) { 151 $toc = []; 152 } 153 } elseif($ACT == 'admin') { 154 // try to load admin plugin TOC 155 /** @var $plugin AdminPlugin */ 156 if ($plugin = plugin_getRequestAdminPlugin()) { 157 $toc = $plugin->getTOC(); 158 $TOC = $toc; // avoid later rebuild 159 } 160 } 161 162 Event::createAndTrigger('TPL_TOC_RENDER', $toc, null, false); 163 $html = html_TOC($toc); 164 if($return) return $html; 165 echo $html; 166 return ''; 167} 168 169/** 170 * Handle the admin page contents 171 * 172 * @author Andreas Gohr <andi@splitbrain.org> 173 * 174 * @return bool 175 */ 176function tpl_admin() { 177 global $INFO; 178 global $TOC; 179 global $INPUT; 180 181 $plugin = null; 182 $class = $INPUT->str('page'); 183 if(!empty($class)) { 184 $pluginlist = plugin_list('admin'); 185 186 if(in_array($class, $pluginlist)) { 187 // attempt to load the plugin 188 /** @var $plugin AdminPlugin */ 189 $plugin = plugin_load('admin', $class); 190 } 191 } 192 193 if($plugin instanceof PluginInterface) { 194 if(!is_array($TOC)) $TOC = $plugin->getTOC(); //if TOC wasn't requested yet 195 if($INFO['prependTOC']) tpl_toc(); 196 $plugin->html(); 197 } else { 198 $admin = new Admin(); 199 $admin->show(); 200 } 201 return true; 202} 203 204/** 205 * Print the correct HTML meta headers 206 * 207 * This has to go into the head section of your template. 208 * 209 * @author Andreas Gohr <andi@splitbrain.org> 210 * 211 * @triggers TPL_METAHEADER_OUTPUT 212 * @param bool $alt Should feeds and alternative format links be added? 213 * @return bool 214 */ 215function tpl_metaheaders($alt = true) { 216 global $ID; 217 global $REV; 218 global $INFO; 219 global $JSINFO; 220 global $ACT; 221 global $QUERY; 222 global $lang; 223 global $conf; 224 global $updateVersion; 225 /** @var Input $INPUT */ 226 global $INPUT; 227 228 // prepare the head array 229 $head = []; 230 231 // prepare seed for js and css 232 $tseed = $updateVersion; 233 $depends = getConfigFiles('main'); 234 $depends[] = DOKU_CONF."tpl/".$conf['template']."/style.ini"; 235 foreach($depends as $f) $tseed .= @filemtime($f); 236 $tseed = md5($tseed); 237 238 // the usual stuff 239 $head['meta'][] = ['name'=> 'generator', 'content'=> 'DokuWiki']; 240 if(actionOK('search')) { 241 $head['link'][] = [ 242 'rel' => 'search', 243 'type'=> 'application/opensearchdescription+xml', 244 'href'=> DOKU_BASE.'lib/exe/opensearch.php', 245 'title'=> $conf['title'] 246 ]; 247 } 248 249 $head['link'][] = ['rel'=> 'start', 'href'=> DOKU_BASE]; 250 if(actionOK('index')) { 251 $head['link'][] = [ 252 'rel' => 'contents', 253 'href'=> wl($ID, 'do=index', false, '&'), 254 'title'=> $lang['btn_index'] 255 ]; 256 } 257 258 if (actionOK('manifest')) { 259 $head['link'][] = [ 260 'rel'=> 'manifest', 261 'href'=> DOKU_BASE.'lib/exe/manifest.php' 262 ]; 263 } 264 265 $styleUtil = new StyleUtils(); 266 $styleIni = $styleUtil->cssStyleini(); 267 $replacements = $styleIni['replacements']; 268 if (!empty($replacements['__theme_color__'])) { 269 $head['meta'][] = [ 270 'name' => 'theme-color', 271 'content' => $replacements['__theme_color__'] 272 ]; 273 } 274 275 if($alt) { 276 if(actionOK('rss')) { 277 $head['link'][] = [ 278 'rel' => 'alternate', 279 'type'=> 'application/rss+xml', 280 'title'=> $lang['btn_recent'], 281 'href'=> DOKU_BASE.'feed.php' 282 ]; 283 $head['link'][] = [ 284 'rel' => 'alternate', 285 'type'=> 'application/rss+xml', 286 'title'=> $lang['currentns'], 287 'href' => DOKU_BASE.'feed.php?mode=list&ns='.(isset($INFO) ? $INFO['namespace'] : '') 288 ]; 289 } 290 if(($ACT == 'show' || $ACT == 'search') && $INFO['writable']) { 291 $head['link'][] = [ 292 'rel' => 'edit', 293 'title'=> $lang['btn_edit'], 294 'href' => wl($ID, 'do=edit', false, '&') 295 ]; 296 } 297 298 if(actionOK('rss') && $ACT == 'search') { 299 $head['link'][] = [ 300 'rel' => 'alternate', 301 'type'=> 'application/rss+xml', 302 'title'=> $lang['searchresult'], 303 'href' => DOKU_BASE.'feed.php?mode=search&q='.$QUERY 304 ]; 305 } 306 307 if(actionOK('export_xhtml')) { 308 $head['link'][] = [ 309 'rel' => 'alternate', 310 'type'=> 'text/html', 311 'title'=> $lang['plainhtml'], 312 'href'=> exportlink($ID, 'xhtml', '', false, '&') 313 ]; 314 } 315 316 if(actionOK('export_raw')) { 317 $head['link'][] = [ 318 'rel' => 'alternate', 319 'type'=> 'text/plain', 320 'title'=> $lang['wikimarkup'], 321 'href'=> exportlink($ID, 'raw', '', false, '&') 322 ]; 323 } 324 } 325 326 // setup robot tags appropriate for different modes 327 if(($ACT == 'show' || $ACT == 'export_xhtml') && !$REV) { 328 if($INFO['exists']) { 329 //delay indexing: 330 if((time() - $INFO['lastmod']) >= $conf['indexdelay'] && !isHiddenPage($ID) ) { 331 $head['meta'][] = ['name'=> 'robots', 'content'=> 'index,follow']; 332 } else { 333 $head['meta'][] = ['name'=> 'robots', 'content'=> 'noindex,nofollow']; 334 } 335 $canonicalUrl = wl($ID, '', true, '&'); 336 if ($ID == $conf['start']) { 337 $canonicalUrl = DOKU_URL; 338 } 339 $head['link'][] = ['rel'=> 'canonical', 'href'=> $canonicalUrl]; 340 } else { 341 $head['meta'][] = ['name'=> 'robots', 'content'=> 'noindex,follow']; 342 } 343 } elseif(defined('DOKU_MEDIADETAIL')) { 344 $head['meta'][] = ['name'=> 'robots', 'content'=> 'index,follow']; 345 } else { 346 $head['meta'][] = ['name'=> 'robots', 'content'=> 'noindex,nofollow']; 347 } 348 349 // set metadata 350 if($ACT == 'show' || $ACT == 'export_xhtml') { 351 // keywords (explicit or implicit) 352 if(!empty($INFO['meta']['subject'])) { 353 $head['meta'][] = ['name'=> 'keywords', 'content'=> implode(',', $INFO['meta']['subject'])]; 354 } else { 355 $head['meta'][] = ['name'=> 'keywords', 'content'=> str_replace(':', ',', $ID)]; 356 } 357 } 358 359 // load stylesheets 360 $head['link'][] = [ 361 'rel' => 'stylesheet', 362 'href'=> DOKU_BASE.'lib/exe/css.php?t='.rawurlencode($conf['template']).'&tseed='.$tseed 363 ]; 364 365 $script = "var NS='".(isset($INFO)?$INFO['namespace']:'')."';"; 366 if($conf['useacl'] && $INPUT->server->str('REMOTE_USER')) { 367 $script .= "var SIG=".toolbar_signature().";"; 368 } 369 jsinfo(); 370 $script .= 'var JSINFO = ' . json_encode($JSINFO, JSON_THROW_ON_ERROR).';'; 371 $head['script'][] = ['_data'=> $script]; 372 373 // load jquery 374 $jquery = getCdnUrls(); 375 foreach($jquery as $src) { 376 $head['script'][] = [ 377 '_data' => '', 378 'src' => $src 379 ] + ($conf['defer_js'] ? [ 'defer' => 'defer'] : []); 380 } 381 382 // load our javascript dispatcher 383 $head['script'][] = [ 384 '_data'=> '', 385 'src' => DOKU_BASE.'lib/exe/js.php'.'?t='.rawurlencode($conf['template']).'&tseed='.$tseed 386 ] + ($conf['defer_js'] ? [ 'defer' => 'defer'] : []); 387 388 // trigger event here 389 Event::createAndTrigger('TPL_METAHEADER_OUTPUT', $head, '_tpl_metaheaders_action', true); 390 return true; 391} 392 393/** 394 * prints the array build by tpl_metaheaders 395 * 396 * $data is an array of different header tags. Each tag can have multiple 397 * instances. Attributes are given as key value pairs. Values will be HTML 398 * encoded automatically so they should be provided as is in the $data array. 399 * 400 * For tags having a body attribute specify the body data in the special 401 * attribute '_data'. This field will NOT BE ESCAPED automatically. 402 * 403 * @author Andreas Gohr <andi@splitbrain.org> 404 * 405 * @param array $data 406 */ 407function _tpl_metaheaders_action($data) { 408 foreach($data as $tag => $inst) { 409 if($tag == 'script') { 410 echo "<!--[if gte IE 9]><!-->\n"; // no scripts for old IE 411 } 412 foreach($inst as $attr) { 413 if ( empty($attr) ) { continue; } 414 echo '<', $tag, ' ', buildAttributes($attr); 415 if(isset($attr['_data']) || $tag == 'script') { 416 if($tag == 'script' && isset($attr['_data'])) 417 $attr['_data'] = "/*<![CDATA[*/". 418 $attr['_data']. 419 "\n/*!]]>*/"; 420 421 echo '>', $attr['_data'] ?? '', '</', $tag, '>'; 422 } else { 423 echo '/>'; 424 } 425 echo "\n"; 426 } 427 if($tag == 'script') { 428 echo "<!--<![endif]-->\n"; 429 } 430 } 431} 432 433/** 434 * Print a link 435 * 436 * Just builds a link. 437 * 438 * @author Andreas Gohr <andi@splitbrain.org> 439 * 440 * @param string $url 441 * @param string $name 442 * @param string $more 443 * @param bool $return if true return the link html, otherwise print 444 * @return bool|string html of the link, or true if printed 445 */ 446function tpl_link($url, $name, $more = '', $return = false) { 447 $out = '<a href="'.$url.'" '; 448 if($more) $out .= ' '.$more; 449 $out .= ">$name</a>"; 450 if($return) return $out; 451 print $out; 452 return true; 453} 454 455/** 456 * Prints a link to a WikiPage 457 * 458 * Wrapper around html_wikilink 459 * 460 * @author Andreas Gohr <andi@splitbrain.org> 461 * 462 * @param string $id page id 463 * @param string|null $name the name of the link 464 * @param bool $return 465 * @return true|string 466 */ 467function tpl_pagelink($id, $name = null, $return = false) { 468 $out = '<bdi>'.html_wikilink($id, $name).'</bdi>'; 469 if($return) return $out; 470 print $out; 471 return true; 472} 473 474/** 475 * get the parent page 476 * 477 * Tries to find out which page is parent. 478 * returns false if none is available 479 * 480 * @author Andreas Gohr <andi@splitbrain.org> 481 * 482 * @param string $id page id 483 * @return false|string 484 */ 485function tpl_getparent($id) { 486 $resolver = new PageResolver('root'); 487 488 $parent = getNS($id).':'; 489 $parent = $resolver->resolveId($parent); 490 if($parent == $id) { 491 $pos = strrpos(getNS($id), ':'); 492 $parent = substr($parent, 0, $pos).':'; 493 $parent = $resolver->resolveId($parent); 494 if($parent == $id) return false; 495 } 496 return $parent; 497} 498 499/** 500 * Print one of the buttons 501 * 502 * @author Adrian Lang <mail@adrianlang.de> 503 * @see tpl_get_action 504 * 505 * @param string $type 506 * @param bool $return 507 * @return bool|string html, or false if no data, true if printed 508 * @deprecated 2017-09-01 see devel:menus 509 */ 510function tpl_button($type, $return = false) { 511 dbg_deprecated('see devel:menus'); 512 $data = tpl_get_action($type); 513 if($data === false) { 514 return false; 515 } elseif(!is_array($data)) { 516 $out = sprintf($data, 'button'); 517 } else { 518 /** 519 * @var string $accesskey 520 * @var string $id 521 * @var string $method 522 * @var array $params 523 */ 524 extract($data); 525 if($id === '#dokuwiki__top') { 526 $out = html_topbtn(); 527 } else { 528 $out = html_btn($type, $id, $accesskey, $params, $method); 529 } 530 } 531 if($return) return $out; 532 echo $out; 533 return true; 534} 535 536/** 537 * Like the action buttons but links 538 * 539 * @author Adrian Lang <mail@adrianlang.de> 540 * @see tpl_get_action 541 * 542 * @param string $type action command 543 * @param string $pre prefix of link 544 * @param string $suf suffix of link 545 * @param string $inner innerHML of link 546 * @param bool $return if true it returns html, otherwise prints 547 * @return bool|string html or false if no data, true if printed 548 * @deprecated 2017-09-01 see devel:menus 549 */ 550function tpl_actionlink($type, $pre = '', $suf = '', $inner = '', $return = false) { 551 dbg_deprecated('see devel:menus'); 552 global $lang; 553 $data = tpl_get_action($type); 554 if($data === false) { 555 return false; 556 } elseif(!is_array($data)) { 557 $out = sprintf($data, 'link'); 558 } else { 559 /** 560 * @var string $accesskey 561 * @var string $id 562 * @var string $method 563 * @var bool $nofollow 564 * @var array $params 565 * @var string $replacement 566 */ 567 extract($data); 568 if(strpos($id, '#') === 0) { 569 $linktarget = $id; 570 } else { 571 $linktarget = wl($id, $params); 572 } 573 $caption = $lang['btn_'.$type]; 574 if(strpos($caption, '%s')){ 575 $caption = sprintf($caption, $replacement); 576 } 577 $akey = ''; 578 $addTitle = ''; 579 if($accesskey) { 580 $akey = 'accesskey="'.$accesskey.'" '; 581 $addTitle = ' ['.strtoupper($accesskey).']'; 582 } 583 $rel = $nofollow ? 'rel="nofollow" ' : ''; 584 $out = tpl_link( 585 $linktarget, $pre.($inner ?: $caption).$suf, 586 'class="action '.$type.'" '. 587 $akey.$rel. 588 'title="'.hsc($caption).$addTitle.'"', true 589 ); 590 } 591 if($return) return $out; 592 echo $out; 593 return true; 594} 595 596/** 597 * Check the actions and get data for buttons and links 598 * 599 * @author Andreas Gohr <andi@splitbrain.org> 600 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> 601 * @author Adrian Lang <mail@adrianlang.de> 602 * 603 * @param string $type 604 * @return array|bool|string 605 * @deprecated 2017-09-01 see devel:menus 606 */ 607function tpl_get_action($type) { 608 dbg_deprecated('see devel:menus'); 609 if($type == 'history') $type = 'revisions'; 610 if($type == 'subscription') $type = 'subscribe'; 611 if($type == 'img_backto') $type = 'imgBackto'; 612 613 $class = '\\dokuwiki\\Menu\\Item\\' . ucfirst($type); 614 if(class_exists($class)) { 615 try { 616 /** @var AbstractItem $item */ 617 $item = new $class; 618 $data = $item->getLegacyData(); 619 $unknown = false; 620 } catch(\RuntimeException $ignored) { 621 return false; 622 } 623 } else { 624 global $ID; 625 $data = [ 626 'accesskey' => null, 627 'type' => $type, 628 'id' => $ID, 629 'method' => 'get', 630 'params' => ['do' => $type], 631 'nofollow' => true, 632 'replacement' => '' 633 ]; 634 $unknown = true; 635 } 636 637 $evt = new Event('TPL_ACTION_GET', $data); 638 if($evt->advise_before()) { 639 //handle unknown types 640 if($unknown) { 641 $data = '[unknown %s type]'; 642 } 643 } 644 $evt->advise_after(); 645 unset($evt); 646 647 return $data; 648} 649 650/** 651 * Wrapper around tpl_button() and tpl_actionlink() 652 * 653 * @author Anika Henke <anika@selfthinker.org> 654 * 655 * @param string $type action command 656 * @param bool $link link or form button? 657 * @param string|bool $wrapper HTML element wrapper 658 * @param bool $return return or print 659 * @param string $pre prefix for links 660 * @param string $suf suffix for links 661 * @param string $inner inner HTML for links 662 * @return bool|string 663 * @deprecated 2017-09-01 see devel:menus 664 */ 665function tpl_action($type, $link = false, $wrapper = false, $return = false, $pre = '', $suf = '', $inner = '') { 666 dbg_deprecated('see devel:menus'); 667 $out = ''; 668 if($link) { 669 $out .= tpl_actionlink($type, $pre, $suf, $inner, true); 670 } else { 671 $out .= tpl_button($type, true); 672 } 673 if($out && $wrapper) $out = "<$wrapper>$out</$wrapper>"; 674 675 if($return) return $out; 676 print $out; 677 return (bool) $out; 678} 679 680/** 681 * Print the search form 682 * 683 * If the first parameter is given a div with the ID 'qsearch_out' will 684 * be added which instructs the ajax pagequicksearch to kick in and place 685 * its output into this div. The second parameter controls the propritary 686 * attribute autocomplete. If set to false this attribute will be set with an 687 * value of "off" to instruct the browser to disable it's own built in 688 * autocompletion feature (MSIE and Firefox) 689 * 690 * @author Andreas Gohr <andi@splitbrain.org> 691 * 692 * @param bool $ajax 693 * @param bool $autocomplete 694 * @return bool 695 */ 696function tpl_searchform($ajax = true, $autocomplete = true) { 697 global $lang; 698 global $ACT; 699 global $QUERY; 700 global $ID; 701 702 // don't print the search form if search action has been disabled 703 if(!actionOK('search')) return false; 704 705 $searchForm = new Form([ 706 'action' => wl(), 707 'method' => 'get', 708 'role' => 'search', 709 'class' => 'search', 710 'id' => 'dw__search', 711 ], true); 712 $searchForm->addTagOpen('div')->addClass('no'); 713 $searchForm->setHiddenField('do', 'search'); 714 $searchForm->setHiddenField('id', $ID); 715 $searchForm->addTextInput('q') 716 ->addClass('edit') 717 ->attrs([ 718 'title' => '[F]', 719 'accesskey' => 'f', 720 'placeholder' => $lang['btn_search'], 721 'autocomplete' => $autocomplete ? 'on' : 'off', 722 ]) 723 ->id('qsearch__in') 724 ->val($ACT === 'search' ? $QUERY : '') 725 ->useInput(false) 726 ; 727 $searchForm->addButton('', $lang['btn_search'])->attrs([ 728 'type' => 'submit', 729 'title' => $lang['btn_search'], 730 ]); 731 if ($ajax) { 732 $searchForm->addTagOpen('div')->id('qsearch__out')->addClass('ajax_qsearch JSpopup'); 733 $searchForm->addTagClose('div'); 734 } 735 $searchForm->addTagClose('div'); 736 737 echo $searchForm->toHTML('QuickSearch'); 738 739 return true; 740} 741 742/** 743 * Print the breadcrumbs trace 744 * 745 * @author Andreas Gohr <andi@splitbrain.org> 746 * 747 * @param string $sep Separator between entries 748 * @param bool $return return or print 749 * @return bool|string 750 */ 751function tpl_breadcrumbs($sep = null, $return = false) { 752 global $lang; 753 global $conf; 754 755 //check if enabled 756 if(!$conf['breadcrumbs']) return false; 757 758 //set default 759 if(is_null($sep)) $sep = '•'; 760 761 $out=''; 762 763 $crumbs = breadcrumbs(); //setup crumb trace 764 765 $crumbs_sep = ' <span class="bcsep">'.$sep.'</span> '; 766 767 //render crumbs, highlight the last one 768 $out .= '<span class="bchead">'.$lang['breadcrumb'].'</span>'; 769 $last = count($crumbs); 770 $i = 0; 771 foreach($crumbs as $id => $name) { 772 $i++; 773 $out .= $crumbs_sep; 774 if($i == $last) $out .= '<span class="curid">'; 775 $out .= '<bdi>' . tpl_link(wl($id), hsc($name), 'class="breadcrumbs" title="'.$id.'"', true) . '</bdi>'; 776 if($i == $last) $out .= '</span>'; 777 } 778 if($return) return $out; 779 print $out; 780 return (bool) $out; 781} 782 783/** 784 * Hierarchical breadcrumbs 785 * 786 * This code was suggested as replacement for the usual breadcrumbs. 787 * It only makes sense with a deep site structure. 788 * 789 * @author Andreas Gohr <andi@splitbrain.org> 790 * @author Nigel McNie <oracle.shinoda@gmail.com> 791 * @author Sean Coates <sean@caedmon.net> 792 * @author <fredrik@averpil.com> 793 * @todo May behave strangely in RTL languages 794 * 795 * @param string $sep Separator between entries 796 * @param bool $return return or print 797 * @return bool|string 798 */ 799function tpl_youarehere($sep = null, $return = false) { 800 global $conf; 801 global $ID; 802 global $lang; 803 804 // check if enabled 805 if(!$conf['youarehere']) return false; 806 807 //set default 808 if(is_null($sep)) $sep = ' » '; 809 810 $out = ''; 811 812 $parts = explode(':', $ID); 813 $count = count($parts); 814 815 $out .= '<span class="bchead">'.$lang['youarehere'].' </span>'; 816 817 // always print the startpage 818 $out .= '<span class="home">' . tpl_pagelink(':'.$conf['start'], null, true) . '</span>'; 819 820 // print intermediate namespace links 821 $part = ''; 822 for($i = 0; $i < $count - 1; $i++) { 823 $part .= $parts[$i].':'; 824 $page = $part; 825 if($page == $conf['start']) continue; // Skip startpage 826 827 // output 828 $out .= $sep . tpl_pagelink($page, null, true); 829 } 830 831 // print current page, skipping start page, skipping for namespace index 832 if (isset($page)) { 833 $page = (new PageResolver('root'))->resolveId($page); 834 if ($page == $part . $parts[$i]) { 835 if ($return) return $out; 836 print $out; 837 return true; 838 } 839 } 840 $page = $part.$parts[$i]; 841 if($page == $conf['start']) { 842 if($return) return $out; 843 print $out; 844 return true; 845 } 846 $out .= $sep; 847 $out .= tpl_pagelink($page, null, true); 848 if($return) return $out; 849 print $out; 850 return (bool) $out; 851} 852 853/** 854 * Print info if the user is logged in 855 * and show full name in that case 856 * 857 * Could be enhanced with a profile link in future? 858 * 859 * @author Andreas Gohr <andi@splitbrain.org> 860 * 861 * @return bool 862 */ 863function tpl_userinfo() { 864 global $lang; 865 /** @var Input $INPUT */ 866 global $INPUT; 867 868 if($INPUT->server->str('REMOTE_USER')) { 869 print $lang['loggedinas'].' '.userlink(); 870 return true; 871 } 872 return false; 873} 874 875/** 876 * Print some info about the current page 877 * 878 * @author Andreas Gohr <andi@splitbrain.org> 879 * 880 * @param bool $ret return content instead of printing it 881 * @return bool|string 882 */ 883function tpl_pageinfo($ret = false) { 884 global $conf; 885 global $lang; 886 global $INFO; 887 global $ID; 888 889 // return if we are not allowed to view the page 890 if(!auth_quickaclcheck($ID)) { 891 return false; 892 } 893 894 // prepare date and path 895 $fn = $INFO['filepath']; 896 if(!$conf['fullpath']) { 897 if($INFO['rev']) { 898 $fn = str_replace($conf['olddir'].'/', '', $fn); 899 } else { 900 $fn = str_replace($conf['datadir'].'/', '', $fn); 901 } 902 } 903 $fn = utf8_decodeFN($fn); 904 $date = dformat($INFO['lastmod']); 905 906 // print it 907 if($INFO['exists']) { 908 $out = ''; 909 $out .= '<bdi>'.$fn.'</bdi>'; 910 $out .= ' · '; 911 $out .= $lang['lastmod']; 912 $out .= ' '; 913 $out .= $date; 914 if($INFO['editor']) { 915 $out .= ' '.$lang['by'].' '; 916 $out .= '<bdi>'.editorinfo($INFO['editor']).'</bdi>'; 917 } else { 918 $out .= ' ('.$lang['external_edit'].')'; 919 } 920 if($INFO['locked']) { 921 $out .= ' · '; 922 $out .= $lang['lockedby']; 923 $out .= ' '; 924 $out .= '<bdi>'.editorinfo($INFO['locked']).'</bdi>'; 925 } 926 if($ret) { 927 return $out; 928 } else { 929 echo $out; 930 return true; 931 } 932 } 933 return false; 934} 935 936/** 937 * Prints or returns the name of the given page (current one if none given). 938 * 939 * If useheading is enabled this will use the first headline else 940 * the given ID is used. 941 * 942 * @author Andreas Gohr <andi@splitbrain.org> 943 * 944 * @param string $id page id 945 * @param bool $ret return content instead of printing 946 * @return bool|string 947 */ 948function tpl_pagetitle($id = null, $ret = false) { 949 global $ACT, $INPUT, $conf, $lang; 950 951 if(is_null($id)) { 952 global $ID; 953 $id = $ID; 954 } 955 956 $name = $id; 957 if(useHeading('navigation')) { 958 $first_heading = p_get_first_heading($id); 959 if($first_heading) $name = $first_heading; 960 } 961 962 // default page title is the page name, modify with the current action 963 switch ($ACT) { 964 // admin functions 965 case 'admin' : 966 $page_title = $lang['btn_admin']; 967 // try to get the plugin name 968 /** @var $plugin AdminPlugin */ 969 if ($plugin = plugin_getRequestAdminPlugin()){ 970 $plugin_title = $plugin->getMenuText($conf['lang']); 971 $page_title = $plugin_title ?: $plugin->getPluginName(); 972 } 973 break; 974 975 // user functions 976 case 'login' : 977 case 'profile' : 978 case 'register' : 979 case 'resendpwd' : 980 $page_title = $lang['btn_'.$ACT]; 981 break; 982 983 // wiki functions 984 case 'search' : 985 case 'index' : 986 $page_title = $lang['btn_'.$ACT]; 987 break; 988 989 // page functions 990 case 'edit' : 991 case 'preview' : 992 $page_title = "✎ ".$name; 993 break; 994 995 case 'revisions' : 996 $page_title = $name . ' - ' . $lang['btn_revs']; 997 break; 998 999 case 'backlink' : 1000 case 'recent' : 1001 case 'subscribe' : 1002 $page_title = $name . ' - ' . $lang['btn_'.$ACT]; 1003 break; 1004 1005 default : // SHOW and anything else not included 1006 $page_title = $name; 1007 } 1008 1009 if($ret) { 1010 return hsc($page_title); 1011 } else { 1012 print hsc($page_title); 1013 return true; 1014 } 1015} 1016 1017/** 1018 * Returns the requested EXIF/IPTC tag from the current image 1019 * 1020 * If $tags is an array all given tags are tried until a 1021 * value is found. If no value is found $alt is returned. 1022 * 1023 * Which texts are known is defined in the functions _exifTagNames 1024 * and _iptcTagNames() in inc/jpeg.php (You need to prepend IPTC 1025 * to the names of the latter one) 1026 * 1027 * Only allowed in: detail.php 1028 * 1029 * @author Andreas Gohr <andi@splitbrain.org> 1030 * 1031 * @param array|string $tags tag or array of tags to try 1032 * @param string $alt alternative output if no data was found 1033 * @param null|string $src the image src, uses global $SRC if not given 1034 * @return string 1035 */ 1036function tpl_img_getTag($tags, $alt = '', $src = null) { 1037 // Init Exif Reader 1038 global $SRC, $imgMeta; 1039 1040 if(is_null($src)) $src = $SRC; 1041 if(is_null($src)) return $alt; 1042 1043 if(!isset($imgMeta) || $imgMeta === null) $imgMeta = new JpegMeta($src); 1044 if($imgMeta === false) return $alt; 1045 $info = cleanText($imgMeta->getField($tags)); 1046 if($info == false) return $alt; 1047 return $info; 1048} 1049 1050 1051/** 1052 * Garbage collects up the open JpegMeta object. 1053 */ 1054function tpl_img_close(){ 1055 global $imgMeta; 1056 $imgMeta = null; 1057} 1058 1059/** 1060 * Returns a description list of the metatags of the current image 1061 * 1062 * @return string html of description list 1063 */ 1064function tpl_img_meta() { 1065 global $lang; 1066 1067 $tags = tpl_get_img_meta(); 1068 1069 echo '<dl>'; 1070 foreach($tags as $tag) { 1071 $label = $lang[$tag['langkey']]; 1072 if(!$label) $label = $tag['langkey'] . ':'; 1073 1074 echo '<dt>'.$label.'</dt><dd>'; 1075 if ($tag['type'] == 'date') { 1076 echo dformat($tag['value']); 1077 } else { 1078 echo hsc($tag['value']); 1079 } 1080 echo '</dd>'; 1081 } 1082 echo '</dl>'; 1083} 1084 1085/** 1086 * Returns metadata as configured in mediameta config file, ready for creating html 1087 * 1088 * @return array with arrays containing the entries: 1089 * - string langkey key to lookup in the $lang var, if not found printed as is 1090 * - string type type of value 1091 * - string value tag value (unescaped) 1092 */ 1093function tpl_get_img_meta() { 1094 1095 $config_files = getConfigFiles('mediameta'); 1096 foreach ($config_files as $config_file) { 1097 if(file_exists($config_file)) { 1098 include($config_file); 1099 } 1100 } 1101 $tags = []; 1102 foreach($fields as $tag){ 1103 $t = []; 1104 if (!empty($tag[0])) { 1105 $t = [$tag[0]]; 1106 } 1107 if(isset($tag[3]) && is_array($tag[3])) { 1108 $t = array_merge($t,$tag[3]); 1109 } 1110 $value = tpl_img_getTag($t); 1111 if ($value) { 1112 $tags[] = ['langkey' => $tag[1], 'type' => $tag[2], 'value' => $value]; 1113 } 1114 } 1115 return $tags; 1116} 1117 1118/** 1119 * Prints the image with a link to the full sized version 1120 * 1121 * Only allowed in: detail.php 1122 * 1123 * @triggers TPL_IMG_DISPLAY 1124 * @param $maxwidth int - maximal width of the image 1125 * @param $maxheight int - maximal height of the image 1126 * @param $link bool - link to the orginal size? 1127 * @param $params array - additional image attributes 1128 * @return bool Result of TPL_IMG_DISPLAY 1129 */ 1130function tpl_img($maxwidth = 0, $maxheight = 0, $link = true, $params = null) { 1131 global $IMG; 1132 /** @var Input $INPUT */ 1133 global $INPUT; 1134 global $REV; 1135 $w = (int) tpl_img_getTag('File.Width'); 1136 $h = (int) tpl_img_getTag('File.Height'); 1137 1138 //resize to given max values 1139 $ratio = 1; 1140 if ($w >= $h) { 1141 if($maxwidth && $w >= $maxwidth) { 1142 $ratio = $maxwidth / $w; 1143 } elseif($maxheight && $h > $maxheight) { 1144 $ratio = $maxheight / $h; 1145 } 1146 } elseif ($maxheight && $h >= $maxheight) { 1147 $ratio = $maxheight / $h; 1148 } elseif($maxwidth && $w > $maxwidth) { 1149 $ratio = $maxwidth / $w; 1150 } 1151 if($ratio) { 1152 $w = floor($ratio * $w); 1153 $h = floor($ratio * $h); 1154 } 1155 1156 //prepare URLs 1157 $url = ml($IMG, ['cache'=> $INPUT->str('cache'), 'rev'=>$REV], true, '&'); 1158 $src = ml($IMG, ['cache'=> $INPUT->str('cache'), 'rev'=>$REV, 'w'=> $w, 'h'=> $h], true, '&'); 1159 1160 //prepare attributes 1161 $alt = tpl_img_getTag('Simple.Title'); 1162 if(is_null($params)) { 1163 $p = []; 1164 } else { 1165 $p = $params; 1166 } 1167 if($w) $p['width'] = $w; 1168 if($h) $p['height'] = $h; 1169 $p['class'] = 'img_detail'; 1170 if($alt) { 1171 $p['alt'] = $alt; 1172 $p['title'] = $alt; 1173 } else { 1174 $p['alt'] = ''; 1175 } 1176 $p['src'] = $src; 1177 1178 $data = ['url'=> ($link ? $url : null), 'params'=> $p]; 1179 return Event::createAndTrigger('TPL_IMG_DISPLAY', $data, '_tpl_img_action', true); 1180} 1181 1182/** 1183 * Default action for TPL_IMG_DISPLAY 1184 * 1185 * @param array $data 1186 * @return bool 1187 */ 1188function _tpl_img_action($data) { 1189 global $lang; 1190 $p = buildAttributes($data['params']); 1191 1192 if($data['url']) print '<a href="'.hsc($data['url']).'" title="'.$lang['mediaview'].'">'; 1193 print '<img '.$p.'/>'; 1194 if($data['url']) print '</a>'; 1195 return true; 1196} 1197 1198/** 1199 * This function inserts a small gif which in reality is the indexer function. 1200 * 1201 * Should be called somewhere at the very end of the main.php 1202 * template 1203 * 1204 * @return bool 1205 */ 1206function tpl_indexerWebBug() { 1207 global $ID; 1208 1209 $p = []; 1210 $p['src'] = DOKU_BASE.'lib/exe/taskrunner.php?id='.rawurlencode($ID). 1211 '&'.time(); 1212 $p['width'] = 2; //no more 1x1 px image because we live in times of ad blockers... 1213 $p['height'] = 1; 1214 $p['alt'] = ''; 1215 $att = buildAttributes($p); 1216 print "<img $att />"; 1217 return true; 1218} 1219 1220/** 1221 * tpl_getConf($id) 1222 * 1223 * use this function to access template configuration variables 1224 * 1225 * @param string $id name of the value to access 1226 * @param mixed $notset what to return if the setting is not available 1227 * @return mixed 1228 */ 1229function tpl_getConf($id, $notset=false) { 1230 global $conf; 1231 static $tpl_configloaded = false; 1232 1233 $tpl = $conf['template']; 1234 1235 if(!$tpl_configloaded) { 1236 $tconf = tpl_loadConfig(); 1237 if($tconf !== false) { 1238 foreach($tconf as $key => $value) { 1239 if(isset($conf['tpl'][$tpl][$key])) continue; 1240 $conf['tpl'][$tpl][$key] = $value; 1241 } 1242 $tpl_configloaded = true; 1243 } 1244 } 1245 1246 return $conf['tpl'][$tpl][$id] ?? $notset; 1247} 1248 1249/** 1250 * tpl_loadConfig() 1251 * 1252 * reads all template configuration variables 1253 * this function is automatically called by tpl_getConf() 1254 * 1255 * @return array 1256 */ 1257function tpl_loadConfig() { 1258 1259 $file = tpl_incdir().'/conf/default.php'; 1260 $conf = []; 1261 1262 if(!file_exists($file)) return false; 1263 1264 // load default config file 1265 include($file); 1266 1267 return $conf; 1268} 1269 1270// language methods 1271/** 1272 * tpl_getLang($id) 1273 * 1274 * use this function to access template language variables 1275 * 1276 * @param string $id key of language string 1277 * @return string 1278 */ 1279function tpl_getLang($id) { 1280 static $lang = []; 1281 1282 if(count($lang) === 0) { 1283 global $conf, $config_cascade; // definitely don't invoke "global $lang" 1284 1285 $path = tpl_incdir() . 'lang/'; 1286 1287 $lang = []; 1288 1289 // don't include once 1290 @include($path . 'en/lang.php'); 1291 foreach($config_cascade['lang']['template'] as $config_file) { 1292 if(file_exists($config_file . $conf['template'] . '/en/lang.php')) { 1293 include($config_file . $conf['template'] . '/en/lang.php'); 1294 } 1295 } 1296 1297 if($conf['lang'] != 'en') { 1298 @include($path . $conf['lang'] . '/lang.php'); 1299 foreach($config_cascade['lang']['template'] as $config_file) { 1300 if(file_exists($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php')) { 1301 include($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php'); 1302 } 1303 } 1304 } 1305 } 1306 return $lang[$id] ?? ''; 1307} 1308 1309/** 1310 * Retrieve a language dependent file and pass to xhtml renderer for display 1311 * template equivalent of p_locale_xhtml() 1312 * 1313 * @param string $id id of language dependent wiki page 1314 * @return string parsed contents of the wiki page in xhtml format 1315 */ 1316function tpl_locale_xhtml($id) { 1317 return p_cached_output(tpl_localeFN($id)); 1318} 1319 1320/** 1321 * Prepends appropriate path for a language dependent filename 1322 * 1323 * @param string $id id of localized text 1324 * @return string wiki text 1325 */ 1326function tpl_localeFN($id) { 1327 $path = tpl_incdir().'lang/'; 1328 global $conf; 1329 $file = DOKU_CONF.'template_lang/'.$conf['template'].'/'.$conf['lang'].'/'.$id.'.txt'; 1330 if (!file_exists($file)){ 1331 $file = $path.$conf['lang'].'/'.$id.'.txt'; 1332 if(!file_exists($file)){ 1333 //fall back to english 1334 $file = $path.'en/'.$id.'.txt'; 1335 } 1336 } 1337 return $file; 1338} 1339 1340/** 1341 * prints the "main content" in the mediamanager popup 1342 * 1343 * Depending on the user's actions this may be a list of 1344 * files in a namespace, the meta editing dialog or 1345 * a message of referencing pages 1346 * 1347 * Only allowed in mediamanager.php 1348 * 1349 * @triggers MEDIAMANAGER_CONTENT_OUTPUT 1350 * @param bool $fromajax - set true when calling this function via ajax 1351 * @param string $sort 1352 * 1353 * @author Andreas Gohr <andi@splitbrain.org> 1354 */ 1355function tpl_mediaContent($fromajax = false, $sort='natural') { 1356 global $IMG; 1357 global $AUTH; 1358 global $INUSE; 1359 global $NS; 1360 global $JUMPTO; 1361 /** @var Input $INPUT */ 1362 global $INPUT; 1363 1364 $do = $INPUT->extract('do')->str('do'); 1365 if(in_array($do, ['save', 'cancel'])) $do = ''; 1366 1367 if(!$do) { 1368 if($INPUT->bool('edit')) { 1369 $do = 'metaform'; 1370 } elseif(is_array($INUSE)) { 1371 $do = 'filesinuse'; 1372 } else { 1373 $do = 'filelist'; 1374 } 1375 } 1376 1377 // output the content pane, wrapped in an event. 1378 if(!$fromajax) ptln('<div id="media__content">'); 1379 $data = ['do' => $do]; 1380 $evt = new Event('MEDIAMANAGER_CONTENT_OUTPUT', $data); 1381 if($evt->advise_before()) { 1382 $do = $data['do']; 1383 if($do == 'filesinuse') { 1384 media_filesinuse($INUSE, $IMG); 1385 } elseif($do == 'filelist') { 1386 media_filelist($NS, $AUTH, $JUMPTO,false,$sort); 1387 } elseif($do == 'searchlist') { 1388 media_searchlist($INPUT->str('q'), $NS, $AUTH); 1389 } else { 1390 msg('Unknown action '.hsc($do), -1); 1391 } 1392 } 1393 $evt->advise_after(); 1394 unset($evt); 1395 if(!$fromajax) ptln('</div>'); 1396 1397} 1398 1399/** 1400 * Prints the central column in full-screen media manager 1401 * Depending on the opened tab this may be a list of 1402 * files in a namespace, upload form or search form 1403 * 1404 * @author Kate Arzamastseva <pshns@ukr.net> 1405 */ 1406function tpl_mediaFileList() { 1407 global $AUTH; 1408 global $NS; 1409 global $JUMPTO; 1410 global $lang; 1411 /** @var Input $INPUT */ 1412 global $INPUT; 1413 1414 $opened_tab = $INPUT->str('tab_files'); 1415 if(!$opened_tab || !in_array($opened_tab, ['files', 'upload', 'search'])) $opened_tab = 'files'; 1416 if($INPUT->str('mediado') == 'update') $opened_tab = 'upload'; 1417 1418 echo '<h2 class="a11y">'.$lang['mediaselect'].'</h2>'.NL; 1419 1420 media_tabs_files($opened_tab); 1421 1422 echo '<div class="panelHeader">'.NL; 1423 echo '<h3>'; 1424 $tabTitle = $NS ?: '['.$lang['mediaroot'].']'; 1425 printf($lang['media_'.$opened_tab], '<strong>'.hsc($tabTitle).'</strong>'); 1426 echo '</h3>'.NL; 1427 if($opened_tab === 'search' || $opened_tab === 'files') { 1428 media_tab_files_options(); 1429 } 1430 echo '</div>'.NL; 1431 1432 echo '<div class="panelContent">'.NL; 1433 if($opened_tab == 'files') { 1434 media_tab_files($NS, $AUTH, $JUMPTO); 1435 } elseif($opened_tab == 'upload') { 1436 media_tab_upload($NS, $AUTH, $JUMPTO); 1437 } elseif($opened_tab == 'search') { 1438 media_tab_search($NS, $AUTH); 1439 } 1440 echo '</div>'.NL; 1441} 1442 1443/** 1444 * Prints the third column in full-screen media manager 1445 * Depending on the opened tab this may be details of the 1446 * selected file, the meta editing dialog or 1447 * list of file revisions 1448 * 1449 * @author Kate Arzamastseva <pshns@ukr.net> 1450 * 1451 * @param string $image 1452 * @param boolean $rev 1453 */ 1454function tpl_mediaFileDetails($image, $rev) { 1455 global $conf, $DEL, $lang; 1456 /** @var Input $INPUT */ 1457 global $INPUT; 1458 1459 $removed = ( 1460 !file_exists(mediaFN($image)) && 1461 file_exists(mediaMetaFN($image, '.changes')) && 1462 $conf['mediarevisions'] 1463 ); 1464 if(!$image || (!file_exists(mediaFN($image)) && !$removed) || $DEL) return; 1465 if($rev && !file_exists(mediaFN($image, $rev))) $rev = false; 1466 $ns = getNS($image); 1467 $do = $INPUT->str('mediado'); 1468 1469 $opened_tab = $INPUT->str('tab_details'); 1470 1471 $tab_array = ['view']; 1472 [, $mime] = mimetype($image); 1473 if($mime == 'image/jpeg') { 1474 $tab_array[] = 'edit'; 1475 } 1476 if($conf['mediarevisions']) { 1477 $tab_array[] = 'history'; 1478 } 1479 1480 if(!$opened_tab || !in_array($opened_tab, $tab_array)) $opened_tab = 'view'; 1481 if($INPUT->bool('edit')) $opened_tab = 'edit'; 1482 if($do == 'restore') $opened_tab = 'view'; 1483 1484 media_tabs_details($image, $opened_tab); 1485 1486 echo '<div class="panelHeader"><h3>'; 1487 [$ext] = mimetype($image, false); 1488 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 1489 $class = 'select mediafile mf_'.$class; 1490 1491 $attributes = $rev ? ['rev' => $rev] : []; 1492 $tabTitle = '<strong><a href="'.ml($image, $attributes).'" class="'.$class.'" title="'.$lang['mediaview'].'">'. 1493 $image.'</a>'.'</strong>'; 1494 if($opened_tab === 'view' && $rev) { 1495 printf($lang['media_viewold'], $tabTitle, dformat($rev)); 1496 } else { 1497 printf($lang['media_'.$opened_tab], $tabTitle); 1498 } 1499 1500 echo '</h3></div>'.NL; 1501 1502 echo '<div class="panelContent">'.NL; 1503 1504 if($opened_tab == 'view') { 1505 media_tab_view($image, $ns, null, $rev); 1506 1507 } elseif($opened_tab == 'edit' && !$removed) { 1508 media_tab_edit($image, $ns); 1509 1510 } elseif($opened_tab == 'history' && $conf['mediarevisions']) { 1511 media_tab_history($image, $ns); 1512 } 1513 1514 echo '</div>'.NL; 1515} 1516 1517/** 1518 * prints the namespace tree in the mediamanager popup 1519 * 1520 * Only allowed in mediamanager.php 1521 * 1522 * @author Andreas Gohr <andi@splitbrain.org> 1523 */ 1524function tpl_mediaTree() { 1525 global $NS; 1526 ptln('<div id="media__tree">'); 1527 media_nstree($NS); 1528 ptln('</div>'); 1529} 1530 1531/** 1532 * Print a dropdown menu with all DokuWiki actions 1533 * 1534 * Note: this will not use any pretty URLs 1535 * 1536 * @author Andreas Gohr <andi@splitbrain.org> 1537 * 1538 * @param string $empty empty option label 1539 * @param string $button submit button label 1540 * @deprecated 2017-09-01 see devel:menus 1541 */ 1542function tpl_actiondropdown($empty = '', $button = '>') { 1543 dbg_deprecated('see devel:menus'); 1544 $menu = new MobileMenu(); 1545 echo $menu->getDropdown($empty, $button); 1546} 1547 1548/** 1549 * Print a informational line about the used license 1550 * 1551 * @author Andreas Gohr <andi@splitbrain.org> 1552 * @param string $img print image? (|button|badge) 1553 * @param bool $imgonly skip the textual description? 1554 * @param bool $return when true don't print, but return HTML 1555 * @param bool $wrap wrap in div with class="license"? 1556 * @return string 1557 */ 1558function tpl_license($img = 'badge', $imgonly = false, $return = false, $wrap = true) { 1559 global $license; 1560 global $conf; 1561 global $lang; 1562 if(!$conf['license']) return ''; 1563 if(!is_array($license[$conf['license']])) return ''; 1564 $lic = $license[$conf['license']]; 1565 $target = ($conf['target']['extern']) ? ' target="'.$conf['target']['extern'].'"' : ''; 1566 1567 $out = ''; 1568 if($wrap) $out .= '<div class="license">'; 1569 if($img) { 1570 $src = license_img($img); 1571 if($src) { 1572 $out .= '<a href="'.$lic['url'].'" rel="license"'.$target; 1573 $out .= '><img src="'.DOKU_BASE.$src.'" alt="'.$lic['name'].'" /></a>'; 1574 if(!$imgonly) $out .= ' '; 1575 } 1576 } 1577 if(!$imgonly) { 1578 $out .= $lang['license'].' '; 1579 $out .= '<bdi><a href="'.$lic['url'].'" rel="license" class="urlextern"'.$target; 1580 $out .= '>'.$lic['name'].'</a></bdi>'; 1581 } 1582 if($wrap) $out .= '</div>'; 1583 1584 if($return) return $out; 1585 echo $out; 1586 return ''; 1587} 1588 1589/** 1590 * Includes the rendered HTML of a given page 1591 * 1592 * This function is useful to populate sidebars or similar features in a 1593 * template 1594 * 1595 * @param string $pageid The page name you want to include 1596 * @param bool $print Should the content be printed or returned only 1597 * @param bool $propagate Search higher namespaces, too? 1598 * @param bool $useacl Include the page only if the ACLs check out? 1599 * @return bool|null|string 1600 */ 1601function tpl_include_page($pageid, $print = true, $propagate = false, $useacl = true) { 1602 if($propagate) { 1603 $pageid = page_findnearest($pageid, $useacl); 1604 } elseif($useacl && auth_quickaclcheck($pageid) == AUTH_NONE) { 1605 return false; 1606 } 1607 if(!$pageid) return false; 1608 1609 global $TOC; 1610 $oldtoc = $TOC; 1611 $html = p_wiki_xhtml($pageid, '', false); 1612 $TOC = $oldtoc; 1613 1614 if($print) echo $html; 1615 return $html; 1616} 1617 1618/** 1619 * Display the subscribe form 1620 * 1621 * @author Adrian Lang <lang@cosmocode.de> 1622 * @deprecated 2020-07-23 1623 */ 1624function tpl_subscribe() { 1625 dbg_deprecated(Subscribe::class .'::show()'); 1626 (new Subscribe)->show(); 1627} 1628 1629/** 1630 * Tries to send already created content right to the browser 1631 * 1632 * Wraps around ob_flush() and flush() 1633 * 1634 * @author Andreas Gohr <andi@splitbrain.org> 1635 */ 1636function tpl_flush() { 1637 if( ob_get_level() > 0 ) ob_flush(); 1638 flush(); 1639} 1640 1641/** 1642 * Tries to find a ressource file in the given locations. 1643 * 1644 * If a given location starts with a colon it is assumed to be a media 1645 * file, otherwise it is assumed to be relative to the current template 1646 * 1647 * @param string[] $search locations to look at 1648 * @param bool $abs if to use absolute URL 1649 * @param array &$imginfo filled with getimagesize() 1650 * @param bool $fallback use fallback image if target isn't found or return 'false' if potential 1651 * false result is required 1652 * @return string 1653 * 1654 * @author Andreas Gohr <andi@splitbrain.org> 1655 */ 1656function tpl_getMediaFile($search, $abs = false, &$imginfo = null, $fallback = true) { 1657 $img = ''; 1658 $file = ''; 1659 $ismedia = false; 1660 // loop through candidates until a match was found: 1661 foreach($search as $img) { 1662 if(substr($img, 0, 1) == ':') { 1663 $file = mediaFN($img); 1664 $ismedia = true; 1665 } else { 1666 $file = tpl_incdir().$img; 1667 $ismedia = false; 1668 } 1669 1670 if(file_exists($file)) break; 1671 } 1672 1673 // manage non existing target 1674 if (!file_exists($file)) { 1675 // give result for fallback image 1676 if ($fallback) { 1677 $file = DOKU_INC . 'lib/images/blank.gif'; 1678 // stop process if false result is required (if $fallback is false) 1679 } else { 1680 return false; 1681 } 1682 } 1683 1684 // fetch image data if requested 1685 if(!is_null($imginfo)) { 1686 $imginfo = getimagesize($file); 1687 } 1688 1689 // build URL 1690 if($ismedia) { 1691 $url = ml($img, '', true, '', $abs); 1692 } else { 1693 $url = tpl_basedir().$img; 1694 if($abs) $url = DOKU_URL.substr($url, strlen(DOKU_REL)); 1695 } 1696 1697 return $url; 1698} 1699 1700/** 1701 * PHP include a file 1702 * 1703 * either from the conf directory if it exists, otherwise use 1704 * file in the template's root directory. 1705 * 1706 * The function honours config cascade settings and looks for the given 1707 * file next to the ´main´ config files, in the order protected, local, 1708 * default. 1709 * 1710 * Note: no escaping or sanity checking is done here. Never pass user input 1711 * to this function! 1712 * 1713 * @author Anika Henke <anika@selfthinker.org> 1714 * @author Andreas Gohr <andi@splitbrain.org> 1715 * 1716 * @param string $file 1717 */ 1718function tpl_includeFile($file) { 1719 global $config_cascade; 1720 foreach(['protected', 'local', 'default'] as $config_group) { 1721 if(empty($config_cascade['main'][$config_group])) continue; 1722 foreach($config_cascade['main'][$config_group] as $conf_file) { 1723 $dir = dirname($conf_file); 1724 if(file_exists("$dir/$file")) { 1725 include("$dir/$file"); 1726 return; 1727 } 1728 } 1729 } 1730 1731 // still here? try the template dir 1732 $file = tpl_incdir().$file; 1733 if(file_exists($file)) { 1734 include($file); 1735 } 1736} 1737 1738/** 1739 * Returns <link> tag for various icon types (favicon|mobile|generic) 1740 * 1741 * @author Anika Henke <anika@selfthinker.org> 1742 * 1743 * @param array $types - list of icon types to display (favicon|mobile|generic) 1744 * @return string 1745 */ 1746function tpl_favicon($types = ['favicon']) { 1747 1748 $return = ''; 1749 1750 foreach($types as $type) { 1751 switch($type) { 1752 case 'favicon': 1753 $look = [':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico']; 1754 $return .= '<link rel="shortcut icon" href="'.tpl_getMediaFile($look).'" />'.NL; 1755 break; 1756 case 'mobile': 1757 $look = [':wiki:apple-touch-icon.png', ':apple-touch-icon.png', 'images/apple-touch-icon.png']; 1758 $return .= '<link rel="apple-touch-icon" href="'.tpl_getMediaFile($look).'" />'.NL; 1759 break; 1760 case 'generic': 1761 // ideal world solution, which doesn't work in any browser yet 1762 $look = [':wiki:favicon.svg', ':favicon.svg', 'images/favicon.svg']; 1763 $return .= '<link rel="icon" href="'.tpl_getMediaFile($look).'" type="image/svg+xml" />'.NL; 1764 break; 1765 } 1766 } 1767 1768 return $return; 1769} 1770 1771/** 1772 * Prints full-screen media manager 1773 * 1774 * @author Kate Arzamastseva <pshns@ukr.net> 1775 */ 1776function tpl_media() { 1777 global $NS, $IMG, $JUMPTO, $REV, $lang, $fullscreen, $INPUT; 1778 $fullscreen = true; 1779 require_once DOKU_INC.'lib/exe/mediamanager.php'; 1780 1781 $rev = ''; 1782 $image = cleanID($INPUT->str('image')); 1783 if(isset($IMG)) $image = $IMG; 1784 if(isset($JUMPTO)) $image = $JUMPTO; 1785 if(isset($REV) && !$JUMPTO) $rev = $REV; 1786 1787 echo '<div id="mediamanager__page">'.NL; 1788 echo '<h1>'.$lang['btn_media'].'</h1>'.NL; 1789 html_msgarea(); 1790 1791 echo '<div class="panel namespaces">'.NL; 1792 echo '<h2>'.$lang['namespaces'].'</h2>'.NL; 1793 echo '<div class="panelHeader">'; 1794 echo $lang['media_namespaces']; 1795 echo '</div>'.NL; 1796 1797 echo '<div class="panelContent" id="media__tree">'.NL; 1798 media_nstree($NS); 1799 echo '</div>'.NL; 1800 echo '</div>'.NL; 1801 1802 echo '<div class="panel filelist">'.NL; 1803 tpl_mediaFileList(); 1804 echo '</div>'.NL; 1805 1806 echo '<div class="panel file">'.NL; 1807 echo '<h2 class="a11y">'.$lang['media_file'].'</h2>'.NL; 1808 tpl_mediaFileDetails($image, $rev); 1809 echo '</div>'.NL; 1810 1811 echo '</div>'.NL; 1812} 1813 1814/** 1815 * Return useful layout classes 1816 * 1817 * @author Anika Henke <anika@selfthinker.org> 1818 * 1819 * @return string 1820 */ 1821function tpl_classes() { 1822 global $ACT, $conf, $ID, $INFO; 1823 /** @var Input $INPUT */ 1824 global $INPUT; 1825 1826 $classes = [ 1827 'dokuwiki', 1828 'mode_'.$ACT, 1829 'tpl_'.$conf['template'], 1830 $INPUT->server->bool('REMOTE_USER') ? 'loggedIn' : '', 1831 (isset($INFO['exists']) && $INFO['exists']) ? '' : 'notFound', 1832 ($ID == $conf['start']) ? 'home' : '' 1833 ]; 1834 return implode(' ', $classes); 1835} 1836 1837/** 1838 * Create event for tools menues 1839 * 1840 * @author Anika Henke <anika@selfthinker.org> 1841 * @param string $toolsname name of menu 1842 * @param array $items 1843 * @param string $view e.g. 'main', 'detail', ... 1844 * @deprecated 2017-09-01 see devel:menus 1845 */ 1846function tpl_toolsevent($toolsname, $items, $view = 'main') { 1847 dbg_deprecated('see devel:menus'); 1848 $data = ['view' => $view, 'items' => $items]; 1849 1850 $hook = 'TEMPLATE_' . strtoupper($toolsname) . '_DISPLAY'; 1851 $evt = new Event($hook, $data); 1852 if($evt->advise_before()) { 1853 foreach($evt->data['items'] as $html) echo $html; 1854 } 1855 $evt->advise_after(); 1856} 1857 1858//Setup VIM: ex: et ts=4 : 1859 1860