Инструменты пользователя

Инструменты сайта


wiki:xref:dokuwiki:inc:template.php

Это старая версия документа!


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