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