[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> template.php (source)

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