1<?php
2
3/**
4 * Main file of the "vector" template for DokuWiki
5 *
6 *
7 * LICENSE: This file is open source software (OSS) and may be copied under
8 *          certain conditions. See COPYING file for details or try to contact
9 *          the author(s) of this file in doubt.
10 *
11 * @license GPLv2 (http://www.gnu.org/licenses/gpl2.html)
12 * @author ARSAVA <dokuwiki@dev.arsava.com>
13 * @link https://www.dokuwiki.org/template:vector
14 * @link https://www.dokuwiki.org/devel:templates
15 * @link https://www.dokuwiki.org/devel:coding_style
16 * @link https://www.dokuwiki.org/devel:environment
17 * @link https://www.dokuwiki.org/devel:action_modes
18 */
19
20
21//check if we are running within the DokuWiki environment
22if (!defined("DOKU_INC")){
23    die();
24}
25
26
27/**
28 * Stores the template wide action
29 *
30 * Different DokuWiki actions requiring some template logic. Therefore the
31 * template has to know, what we are doing right now - and that is what this
32 * var is for.
33 *
34 * Please have a look at the "detail.php" file in the same folder, it is also
35 * influencing the var's value.
36 *
37 * @var string
38 * @author ARSAVA <dokuwiki@dev.arsava.com>
39 */
40$vector_action = "article";
41//note: I used $_REQUEST before (cause DokuWiki controls and fills it. Normally,
42//      using $_REQUEST is a possible security threat. For details, see
43//      <http://www.suspekt.org/2008/10/01/php-53-and-delayed-cross-site-request-forgerieshijacking/>
44//      and <https://forum.dokuwiki.org/post/16524>), but it did not work as
45//      expected by me (maybe it is a reference and setting $vector_action
46//      also changed the contents of $_REQUEST?!). That is why I switched back,
47//      checking $_GET and $_POST like I did it before.
48if (!empty($_GET["vecdo"])){
49    $vector_action = (string)$_GET["vecdo"];
50}elseif (!empty($_POST["vecdo"])){
51    $vector_action = (string)$_POST["vecdo"];
52}
53if (!empty($vector_action) &&
54    $vector_action !== "article" &&
55    $vector_action !== "print" &&
56    $vector_action !== "detail" &&
57    $vector_action !== "cite"){
58    //ignore unknown values
59    $vector_action = "article";
60}
61
62
63/**
64 * Stores the template wide context
65 *
66 * This template offers discussion pages via common articles, which should be
67 * marked as "special". DokuWiki does not know any "special" articles, therefore
68 * we have to take care about detecting if the current page is a discussion
69 * page or not.
70 *
71 * @var string
72 * @author ARSAVA <dokuwiki@dev.arsava.com>
73 */
74$vector_context = "article";
75if (preg_match("/^".tpl_getConf("vector_discuss_ns")."?$|^".tpl_getConf("vector_discuss_ns").".*?$/i", ":".getNS(getID()))){
76    $vector_context = "discuss";
77}
78
79
80/**
81 * Stores the name the current client used to login
82 *
83 * @var string
84 * @author ARSAVA <dokuwiki@dev.arsava.com>
85 */
86$loginname = "";
87if (!empty($conf["useacl"])){
88    if (isset($_SERVER["REMOTE_USER"]) && //no empty() but isset(): "0" may be a valid username...
89        $_SERVER["REMOTE_USER"] !== ""){
90        $loginname = $_SERVER["REMOTE_USER"]; //$INFO["client"] would not work here (-> e.g. if
91                                              //current IP differs from the one used to login)
92    }
93}
94
95
96//get needed language array
97include DOKU_TPLINC."lang/en/lang.php";
98//overwrite English language values with available translations
99if (!empty($conf["lang"]) &&
100    $conf["lang"] !== "en" &&
101    file_exists(DOKU_TPLINC."/lang/".$conf["lang"]."/lang.php")){
102    //get language file (partially translated language files are no problem
103    //cause non translated stuff is still existing as English array value)
104    include DOKU_TPLINC."/lang/".$conf["lang"]."/lang.php";
105}
106
107
108//detect revision
109$rev = (int)$INFO["rev"]; //$INFO comes from the DokuWiki core
110if ($rev < 1){
111    $rev = (int)$INFO["lastmod"];
112}
113
114
115//get tab config
116include DOKU_TPLINC."/conf/tabs.php";  //default
117if (file_exists(DOKU_TPLINC."/user/tabs.php")){
118    include DOKU_TPLINC."/user/tabs.php"; //add user defined
119}
120
121
122//get boxes config
123include DOKU_TPLINC."/conf/boxes.php"; //default
124if (file_exists(DOKU_TPLINC."/user/boxes.php")){
125    include DOKU_TPLINC."/user/boxes.php"; //add user defined
126}
127
128
129//get button config
130include DOKU_TPLINC."/conf/buttons.php"; //default
131if (file_exists(DOKU_TPLINC."/user/buttons.php")){
132    include DOKU_TPLINC."/user/buttons.php"; //add user defined
133}
134
135
136/**
137 * Helper to render the tabs (like a dynamic XHTML snippet)
138 *
139 * @param array The tab data to render within the snippet. Each element is
140 *        represented by a subarray:
141 *        $array = array("tab1" => array("text"     => "hello world!",
142 *                                       "href"     => "http://www.example.com"
143 *                                       "nofollow" => true),
144 *                       "tab2" => array("text"  => "I did it again",
145 *                                       "href"  => DOKU_BASE."doku.php?id=foobar",
146 *                                       "class" => "foobar-css"),
147 *                       "tab3" => array("text"  => "I did it again and again",
148 *                                       "href"  => wl("start", false, false, "&"),
149 *                                       "class" => "foobar-css"),
150 *                       "tab4" => array("text"      => "Home",
151 *                                       "wiki"      => ":start"
152 *                                       "accesskey" => "H"));
153 *        Available keys within the subarrays:
154 *        - "text" (mandatory)
155 *          The text/label of the element.
156 *        - "href" (optional)
157 *          URL the element should point to (as link). Please submit raw,
158 *          unencoded URLs, the encoding will be done by this function for
159 *          security reasons. If the URL is not relative
160 *          (= starts with http(s)://), the URL will be treated as external
161 *          (=a special style will be used if "class" is not set).
162 *        - "wiki" (optional)
163 *          ID of a WikiPage to link (like ":start" or ":wiki:foobar").
164 *        - "class" (optional)
165 *          Name of an additional CSS class to use for the element content.
166 *          Works only in combination with "text" or "href", NOT with "wiki"
167 *          (will be ignored in this case).
168 *        - "nofollow" (optional)
169 *          If set to TRUE, rel="nofollow" will be added to the link if "href"
170 *          is set (otherwise this flag will do nothing).
171 *        - "accesskey" (optional)
172 *          accesskey="<value>" will be added to the link if "href" is set
173 *          (otherwise this option will do nothing).
174 * @author ARSAVA <dokuwiki@dev.arsava.com>
175 * @return bool
176 * @see _vector_renderButtons()
177 * @see _vector_renderBoxes()
178 * @link http://www.wikipedia.org/wiki/Nofollow
179 * @link http://de.selfhtml.org/html/verweise/tastatur.htm#kuerzel
180 * @link https://www.dokuwiki.org/devel:environment
181 * @link https://www.dokuwiki.org/devel:coding_style
182 */
183function _vector_renderTabs($arr)
184{
185    //is there something useful?
186    if (empty($arr) ||
187        !is_array($arr)){
188        return false; //nope, break operation
189    }
190
191    //array to store the created tabs into
192    $elements = array();
193
194    //handle the tab data
195    foreach($arr as $li_id => $element){
196        //basic check
197        if (empty($element) ||
198            !is_array($element) ||
199            !isset($element["text"]) ||
200            (empty($element["href"]) &&
201             empty($element["wiki"]))){
202            continue; //ignore invalid stuff and go on
203        }
204        $li_created = true; //flag to control if we created any list element
205        $interim = "";
206        //do we have an external link?
207        if (!empty($element["href"])){
208            //add URL
209            $interim = "<a href=\"".hsc($element["href"])."\""; //@TODO: real URL encoding
210            //add rel="nofollow" attribute to the link?
211            if (!empty($element["nofollow"])){
212                $interim .= " rel=\"nofollow\"";
213            }
214            //mark external link?
215            if (substr($element["href"], 0, 4) === "http" ||
216                substr($element["href"], 0, 3) === "ftp"){
217                $interim .= " class=\"urlextern\"";
218            }
219            //add access key?
220            if (!empty($element["accesskey"])){
221                $interim .= " accesskey=\"".hsc($element["accesskey"])."\" title=\"[ALT+".hsc(strtoupper($element["accesskey"]))."]\"";
222            }
223            $interim .= "><span>".hsc($element["text"])."</span></a>";
224        //internal wiki link
225        }else if (!empty($element["wiki"])){
226            $interim = "<a href=\"".hsc(wl(cleanID($element["wiki"])))."\"><span>".hsc($element["text"])."</span></a>";
227        }
228        //store it
229        $elements[] = "\n        <li id=\"".hsc($li_id)."\"".(!empty($element["class"])
230                                                             ? " class=\"".hsc($element["class"])."\""
231                                                             : "").">".$interim."</li>";
232    }
233
234    //show everything created
235    if (!empty($elements)){
236        foreach ($elements as $element){
237            echo $element;
238        }
239    }
240    return true;
241}
242
243
244/**
245 * Helper to render the boxes (like a dynamic XHTML snippet)
246 *
247 * @param array The box data to render within the snippet. Each box is
248 *        represented by a subarray:
249 *        $array = array("box-id1" => array("headline" => "hello world!",
250 *                                          "xhtml"    => "I am <i>here</i>."));
251 *        Available keys within the subarrays:
252 *        - "xhtml" (mandatory)
253 *          The content of the Box you want to show as XHTML. Attention: YOU
254 *          HAVE TO TAKE CARE ABOUT FILTER EVENTUALLY USED INPUT/SECURITY. Be
255 *          aware of XSS and stuff.
256 *        - "headline" (optional)
257 *          Headline to show above the box. Leave empty/do not set for none.
258 * @author ARSAVA <dokuwiki@dev.arsava.com>
259 * @return bool
260 * @see _vector_renderButtons()
261 * @see _vector_renderTabs()
262 * @link http://www.wikipedia.org/wiki/Nofollow
263 * @link http://www.wikipedia.org/wiki/Cross-site_scripting
264 * @link https://www.dokuwiki.org/devel:coding_style
265 */
266function _vector_renderBoxes($arr)
267{
268    //is there something useful?
269    if (empty($arr) ||
270        !is_array($arr)){
271        return false; //nope, break operation
272    }
273
274    //array to store the created boxes into
275    $boxes = array();
276
277    //handle the box data
278    foreach($arr as $div_id => $contents){
279        //basic check
280        if (empty($contents) ||
281            !is_array($contents) ||
282            !isset($contents["xhtml"])){
283            continue; //ignore invalid stuff and go on
284        }
285        $interim  = "  <div id=\"".hsc($div_id)."\" class=\"portal\">\n";
286        if (isset($contents["headline"])
287            && $contents["headline"] !== ""){
288            $interim .= "    <h5>".hsc($contents["headline"])."</h5>\n";
289        }
290        $interim .= "    <div class=\"body\">\n"
291                   ."      <div class=\"dokuwiki\">\n" //dokuwiki CSS class needed cause we might have to show rendered page content
292                   .$contents["xhtml"]."\n"
293                   ."      </div>\n"
294                   ."    </div>\n"
295                   ."  </div>\n";
296        //store it
297        $boxes[] = $interim;
298    }
299    //show everything created
300    if (!empty($boxes)){
301        echo  "\n";
302        foreach ($boxes as $box){
303            echo $box;
304        }
305        echo  "\n";
306    }
307
308    return true;
309}
310
311
312/**
313 * Helper to render the footer buttons (like a dynamic XHTML snippet)
314 *
315 * @param array The button data to render within the snippet. Each element is
316 *        represented by a subarray:
317 *        $array = array("btn1" => array("img"      => DOKU_TPL."static/img/button-vector.png",
318 *                                       "href"     => "https://andreashaerter.com/",
319 *                                       "width"    => 80,
320 *                                       "height"   => 15,
321 *                                       "title"    => "Andreas Haerter's website",
322 *                                       "nofollow" => true),
323 *                       "btn2" => array("img"   => DOKU_TPL."user/mybutton1.png",
324 *                                       "href"  => wl("start", false, false, "&")),
325 *                       "btn3" => array("img"   => DOKU_TPL."user/mybutton2.png",
326 *                                       "href"  => "http://www.example.com");
327 *        Available keys within the subarrays:
328 *        - "img" (mandatory)
329 *          The relative or full path of an image/button to show. Users may
330 *          place own images within the /user/ dir of this template.
331 *        - "href" (mandatory)
332 *          URL the element should point to (as link). Please submit raw,
333 *          unencoded URLs, the encoding will be done by this function for
334 *          security reasons.
335 *        - "width" (optional)
336 *          width="<value>" will be added to the image tag if both "width" and
337 *          "height" are set (otherwise, this will be ignored).
338 *        - "height" (optional)
339 *          height="<value>" will be added to the image tag if both "height" and
340 *          "width" are set (otherwise, this will be ignored).
341 *        - "nofollow" (optional)
342 *          If set to TRUE, rel="nofollow" will be added to the link.
343 *        - "title" (optional)
344 *          title="<value>"  will be added to the link and image if "title"
345 *          is set + alt="<value>".
346 * @author ARSAVA <dokuwiki@dev.arsava.com>
347 * @return bool
348 * @see _vector_renderButtons()
349 * @see _vector_renderBoxes()
350 * @link http://www.wikipedia.org/wiki/Nofollow
351 * @link https://www.dokuwiki.org/devel:coding_style
352 */
353function _vector_renderButtons($arr)
354{
355    //array to store the created buttons into
356    $elements = array();
357
358    //handle the button data
359    foreach($arr as $li_id => $element){
360        //basic check
361        if (empty($element) ||
362            !is_array($element) ||
363            !isset($element["img"]) ||
364            !isset($element["href"])){
365            continue; //ignore invalid stuff and go on
366        }
367        $interim = "";
368
369        //add URL
370        $interim = "<a href=\"".hsc($element["href"])."\""; //@TODO: real URL encoding
371        //add rel="nofollow" attribute to the link?
372        if (!empty($element["nofollow"])){
373            $interim .= " rel=\"nofollow\"";
374        }
375        //add title attribute to the link?
376        if (!empty($element["title"])){
377            $interim .= " title=\"".hsc($element["title"])."\"";
378        }
379        $interim .= " target=\"_blank\"><img src=\"".hsc($element["img"])."\"";
380        //add width and height attribute to the image?
381        if (!empty($element["width"]) &&
382            !empty($element["height"])){
383            $interim .= " width=\"".(int)$element["width"]."\" height=\"".(int)$element["height"]."\"";
384        }
385        //add title and alt attribute to the image?
386        if (!empty($element["title"])){
387            $interim .= " title=\"".hsc($element["title"])."\" alt=\"".hsc($element["title"])."\"";
388        } else {
389            $interim .= " alt=\"\""; //alt is a mandatory attribute for images
390        }
391        $interim .= " border=\"0\" /></a>";
392
393        //store it
394        $elements[] = "      ".$interim."\n";
395    }
396
397    //show everything created
398    if (!empty($elements)){
399        echo  "\n";
400        foreach ($elements as $element){
401            echo $element;
402        }
403    }
404    return true;
405}
406
407//workaround for the "jumping textarea" IE bug. CSS only fix not possible cause
408//some DokuWiki JavaScript is triggering this bug, too. See the following for
409//info:
410//- <http://blog.andreas-haerter.com/2010/05/28/fix-msie-8-auto-scroll-textarea-css-width-percentage-bug>
411//- <http://msdn.microsoft.com/library/cc817574.aspx>
412if ($ACT === "edit" &&
413    !headers_sent()){
414    header("X-UA-Compatible: IE=EmulateIE7");
415}
416
417?><!DOCTYPE html>
418<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo hsc($conf["lang"]); ?>" lang="<?php echo hsc($conf["lang"]); ?>" dir="<?php echo hsc($lang["direction"]); ?>">
419<head>
420<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
421<meta name="viewport" content="width=device-width, initial-scale=1">
422<title><?php tpl_pagetitle(); echo " - ".hsc($conf["title"]); ?></title>
423
424<!-- Bootstrap CSS -->
425<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
426
427<!-- Bootstrap Icons CSS -->
428<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css">
429
430<?php
431//show meta-tags
432tpl_metaheaders();
433echo "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />";
434
435//include default or userdefined favicon
436//
437//note: since 2011-04-22 "Rincewind RC1", there is a core function named
438//      "tpl_getFavicon()". But its functionality is not really fitting the
439//      behaviour of this template, therefore I don't use it here.
440if (file_exists(DOKU_TPLINC."user/favicon.ico")){
441    //user defined - you might find http://tools.dynamicdrive.com/favicon/
442    //useful to generate one
443    echo "\n<link rel=\"shortcut icon\" href=\"".DOKU_TPL."user/favicon.ico\" />\n";
444}elseif (file_exists(DOKU_TPLINC."user/favicon.png")){
445    //note: I do NOT recommend PNG for favicons (cause it is not supported by
446    //all browsers), but some users requested this feature.
447    echo "\n<link rel=\"shortcut icon\" href=\"".DOKU_TPL."user/favicon.png);\" />\n";
448}else{
449    //default
450    echo "\n<link rel=\"shortcut icon\" href=\"".DOKU_TPL."static/3rd/dokuwiki/favicon.ico\" />\n";
451}
452
453//include default or userdefined Apple Touch Icon (see <http://j.mp/sx3NMT> for
454//details)
455if (file_exists(DOKU_TPLINC."user/apple-touch-icon.png")){
456    echo "<link rel=\"apple-touch-icon\" href=\"".DOKU_TPL."user/apple-touch-icon.png\" />\n";
457}else{
458    //default
459    echo "<link rel=\"apple-touch-icon\" href=\"".DOKU_TPL."static/3rd/dokuwiki/apple-touch-icon.png\" />\n";
460}
461
462//load userdefined js?
463if (tpl_getConf("vector_loaduserjs") && file_exists(DOKU_TPLINC."user/user.js")){
464    echo "<script type=\"text/javascript\" charset=\"utf-8\" src=\"".DOKU_TPL."user/user.js\"></script>\n";
465}
466
467//show printable version?
468if ($vector_action === "print"){
469  //note: this is just a workaround for people searching for a print version.
470  //      don't forget to update the styles.ini, this is the really important
471  //      thing! BTW: good text about this: http://is.gd/5MyG5
472  echo  "<link rel=\"stylesheet\" media=\"all\" type=\"text/css\" href=\"".DOKU_TPL."static/3rd/dokuwiki/print.css\" />\n"
473       ."<link rel=\"stylesheet\" media=\"all\" type=\"text/css\" href=\"".DOKU_TPL."static/css/print.css\" />\n";
474  if (file_exists(DOKU_TPL."user/print.css")){
475      echo "<link rel=\"stylesheet\" media=\"all\" type=\"text/css\" href=\"".DOKU_TPL."user/print.css\" />\n";
476  }
477}
478
479//load language specific css hacks?
480if (file_exists(DOKU_TPLINC."lang/".$conf["lang"]."/style.css")){
481  $interim = trim(file_get_contents(DOKU_TPLINC."lang/".$conf["lang"]."/style.css"));
482  if (!empty($interim)){
483      echo "<style type=\"text/css\" media=\"all\">\n".hsc($interim)."\n</style>\n";
484  }
485}
486?>
487<!--[if lte IE 8]><link rel="stylesheet" media="all" type="text/css" href="<?php echo DOKU_TPL; ?>static/css/screen_iehacks.css" /><![endif]-->
488<!--[if lt IE 7]><style type="text/css">body{behavior:url("<?php echo DOKU_TPL; ?>static/3rd/vector/csshover.htc")}</style><![endif]-->
489</head>
490<body class="<?php
491             //different styles/backgrounds for different page types
492             switch (true){
493                  //special: tech
494                  case ($vector_action === "detail"):
495                  case ($vector_action === "cite"):
496                  case ($ACT === "media"): //var comes from DokuWiki
497                  case ($ACT === "search"): //var comes from DokuWiki
498                    echo "mediawiki ltr ns-1 ns-special ";
499                    break;
500                  //special: wiki
501                  case (preg_match("/^wiki$|^wiki:.*?$/i", getNS(getID()))):
502                    case "mediawiki ltr capitalize-all-nouns ns-4 ns-subject ";
503                    break;
504                  //discussion
505                  case ($vector_context === "discuss"):
506                    echo "mediawiki ltr capitalize-all-nouns ns-1 ns-talk ";
507                    break;
508                  //"normal" content
509                  case ($ACT === "edit"): //var comes from DokuWiki
510                  case ($ACT === "draft"): //var comes from DokuWiki
511                  case ($ACT === "revisions"): //var comes from DokuWiki
512                  case ($vector_action === "print"):
513                  default:
514                    echo "mediawiki ltr capitalize-all-nouns ns-0 ns-subject ";
515                    break;
516              } ?>skin-vector bg-light min-vh-100">
517
518  <div class="main-wrapper"> <!-- Replace the row class with our custom wrapper -->
519    <!-- Sidebar -->
520    <div class="sidebar collapse" id="sidebar"> <!-- Removed 'collapse-horizontal' -->
521      <!-- Header for mobile view -->
522      <div class="d-md-none border-bottom p-2 position-relative">
523        <!-- Close button positioned absolutely -->
524        <button type="button" class="btn-close" aria-label="Close" id="sidebarCloseButton"></button>
525
526        <!-- Top row with search -->
527        <?php if (actionOK("search")){ ?>
528        <form class="d-flex me-2" action="<?php echo wl(); ?>" accept-charset="utf-8" id="dw__search_mobile">
529          <input type="hidden" name="do" value="search" />
530          <div class="input-group input-group-sm" style="width: 160px;">
531            <input class="form-control form-control-sm" type="search" id="qsearch__in_mobile"
532                   accesskey="f" name="id" placeholder="<?php echo hsc($lang['vector_search']); ?>..." />
533            <button class="btn btn-outline-primary btn-sm" type="submit">
534              <?php echo hsc($lang['vector_btn_search']); ?>
535            </button>
536          </div>
537        </form>
538        <?php } ?>
539
540        <!-- Language selector below -->
541        <?php if(file_exists(DOKU_PLUGIN.'translation/syntax.php')): ?>
542        <div class="mt-2">
543          <div class="dropdown">
544            <button class="btn btn-outline-secondary btn-sm dropdown-toggle w-100" type="button" data-bs-toggle="dropdown">
545              <?php echo hsc($conf['lang']); ?>
546            </button>
547            <ul class="dropdown-menu">
548              <?php
549              /** @var helper_plugin_translation $translation */
550              $translation = plugin_load('helper', 'translation');
551              if ($translation) {
552                  $translations = $translation->getAvailableTranslations();
553                  foreach ($translations as $lang => $name) {
554                      echo '<li><a class="dropdown-item" href="'.wl($ID, array('lang' => $lang)).'">'.$name.'</a></li>';
555                  }
556              }
557              ?>
558            </ul>
559          </div>
560        </div>
561        <?php endif; ?>
562      </div>
563
564      <!-- Logo (hidden on mobile) -->
565      <div class="logo-wrapper d-none d-md-block">
566        <?php
567        //include default or userdefined logo
568        echo "<a href=\"".wl()."\" ";
569        if (file_exists(DOKU_TPLINC."user/logo.png")){
570            echo "style=\"background-image:url(".DOKU_TPL."user/logo.png);\"";
571        }elseif (file_exists(DOKU_TPLINC."user/logo.gif")){
572            echo "style=\"background-image:url(".DOKU_TPL."user/logo.gif);\"";
573        }elseif (file_exists(DOKU_TPLINC."user/logo.jpg")){
574            echo "style=\"background-image:url(".DOKU_TPL."user/logo.jpg);\"";
575        }else{
576            echo "style=\"background-image:url(".DOKU_TPL."static/3rd/dokuwiki/logo.png);\"";
577        }
578        echo " accesskey=\"h\" title=\"[ALT+H]\"></a>\n";
579        ?>
580      </div>
581
582      <!-- Navigation boxes -->
583      <div class="sidebar-nav">
584        <?php
585        if (!empty($_vector_boxes) && is_array($_vector_boxes)) {
586          foreach ($_vector_boxes as $box_id => $box) {
587            echo '<div id="'.hsc($box_id).'" class="portal mb-3">';
588
589            // Box header
590            if (!empty($box['headline'])) {
591              echo '<h5 class="mb-2">'.hsc($box['headline']).'</h5>';
592            }
593
594            // Box content with Bootstrap classes
595            echo '<div class="body">';
596            echo '<div class="dokuwiki">';
597
598            // Special handling for admin menu
599            if ($box_id === 'p-admin') {
600              $admin_content = $box['xhtml'];
601              // Convert admin list to Bootstrap list
602              $admin_content = str_replace(
603                array('<ul class="admin_tasks">', '<li>', '</li>'),
604                array('<ul class="list-group admin_tasks">', '<li class="list-group-item">', '</li>'),
605                $admin_content
606              );
607              echo '<div class="admin-list-wrapper">';
608              echo $admin_content;
609              echo '</div>';
610            }
611            // Special handling for QR code
612            else if ($box_id === 't-qrcode') {
613              echo '<div class="text-center w-100">';
614              echo $box['xhtml'];
615              echo '</div>';
616            }
617            // Default handling for other boxes
618            else {
619              echo $box['xhtml'];
620            }
621
622            echo '</div>';
623            echo '</div>';
624            echo '</div>';
625          }
626        }
627        ?>
628      </div>
629    </div>
630
631    <!-- Main content -->
632    <main class="main-content">
633      <!-- Header -->
634      <nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
635        <div class="container-fluid">
636          <div class="d-flex align-items-center">
637            <button class="navbar-toggler me-3" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar">
638              <span class="navbar-toggler-icon"></span>
639            </button>
640
641            <!-- Desktop language selector -->
642            <?php if(file_exists(DOKU_PLUGIN.'translation/syntax.php')): ?>
643            <div class="dropdown d-none d-md-block me-3">
644              <button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
645                <?php echo hsc($conf['lang']); ?>
646              </button>
647              <ul class="dropdown-menu">
648                <?php
649                if ($translation) {
650                    foreach ($translations as $lang => $name) {
651                        echo '<li><a class="dropdown-item" href="'.wl($ID, array('lang' => $lang)).'">'.$name.'</a></li>';
652                    }
653                }
654                ?>
655              </ul>
656            </div>
657            <?php endif; ?>
658
659            <!-- Page actions -->
660            <div class="btn-group me-3">
661              <?php
662              if ($ACT == 'show') {
663                if (actionOK('edit')): ?>
664                  <a href="<?php echo wl($ID, array('do' => 'edit')); ?>" class="btn btn-outline-primary btn-sm">
665                    <span class="d-none d-md-inline">Create/Edit</span>
666                    <span class="d-md-none"><i class="bi bi-pencil-square"></i></span>
667                  </a>
668                <?php endif;
669
670                // Add discussion button if enabled
671                if (tpl_getConf("vector_discuss")): ?>
672                  <a href="<?php echo wl($ID, array('do' => 'discussion')); ?>" class="btn btn-outline-info btn-sm">
673                    <span class="d-none d-md-inline">Discussion</span>
674                    <span class="d-md-none"><i class="bi bi-chat-dots"></i></span>
675                  </a>
676                <?php endif;
677              } // Added missing closing bracket here
678              ?>
679              <?php if ($ACT == 'show'): ?>
680                <a href="<?php echo wl($ID, array('do' => 'revisions')); ?>" class="btn btn-outline-secondary btn-sm">
681                  <span class="d-none d-md-inline">Old revisions</span>
682                  <span class="d-md-none"><i class="bi bi-clock-history"></i></span>
683                </a>
684              <?php endif; ?>
685            </div>
686
687            <!-- Theme toggle button -->
688            <div class="navbar-nav ms-2">
689              <div class="nav-item">
690                <button class="btn btn-link nav-link" id="darkModeToggle">
691                  <i class="bi bi-sun-fill" id="lightIcon"></i>
692                  <i class="bi bi-moon-fill" id="darkIcon" style="display: none;"></i>
693                </button>
694              </div>
695            </div>
696
697          </div>
698
699          <!-- Search -->
700          <?php if (actionOK("search")){ ?>
701          <form class="d-flex d-none d-md-inline" action="<?php echo wl(); ?>" accept-charset="utf-8" id="dw__search">
702            <input type="hidden" name="do" value="search" />
703            <div class="input-group" style="max-width: 300px;">
704              <input class="form-control" type="search" id="qsearch__in" accesskey="f" name="id"
705                     placeholder="<?php echo hsc($lang['vector_search']); ?>..." />
706              <button class="btn btn-outline-primary" type="submit">
707                <?php echo hsc($lang['vector_btn_search']); ?>
708              </button>
709            </div>
710          </form>
711          <?php } ?>
712
713          <!-- User menu -->
714          <?php if (!empty($conf["useacl"])){ ?>
715          <div class="navbar-nav ms-auto">
716            <div class="nav-item dropdown">
717              <a class="nav-link dropdown-toggle" href="#" id="userMenu" role="button" data-bs-toggle="dropdown">
718                <?php echo hsc($loginname ? $loginname : $lang["btn_login"]); ?>
719              </a>
720              <ul class="dropdown-menu dropdown-menu-end">
721                <?php
722                if (!empty($conf["useacl"])){ //...makes only sense if there are users
723                  //login?
724                  if ($loginname === "") {
725                    echo '<li><a class="dropdown-item" href="' . wl(cleanID(getId()), ['do' => 'login']) . '" rel="nofollow">' . hsc($lang["btn_login"]) . '</a></li>';
726                  } else {
727                    global $INFO;
728                    //username and userpage
729                    $profilePage = 'user:' . $loginname;
730                    echo '<li><a class="dropdown-item" href="' . wl($profilePage) . '" rel="nofollow">' . hsc($loginname) . '</a></li>';
731
732                    //admin
733                    if (!empty($INFO["isadmin"]) || !empty($INFO["ismanager"])){
734                      echo '<li><a class="dropdown-item" href="' . wl('', ['do' => 'admin']) . '" rel="nofollow">Admin</a></li>';
735                    }
736
737                    //profile
738                    if (actionOK("profile")){ //check if action is disabled
739                      echo '<li><a class="dropdown-item" href="' . wl('', ['do' => 'profile']) . '" rel="nofollow">Update Profile</a></li>';
740                    }
741
742                    //logout
743                    echo '<li><hr class="dropdown-divider"></li>';
744                    echo '<li><a class="dropdown-item" href="' . wl('', ['do' => 'logout']) . '" rel="nofollow">Log Out</a></li>';
745                  }
746                }
747                ?>
748              </ul>
749            </div>
750          </div>
751          <?php } ?>
752        </div>
753      </nav>
754
755      <!-- Content wrapper with TOC sidebar -->
756      <div class="d-flex">
757        <!-- Main content area -->
758        <div class="flex-grow-1 py-4">
759          <?php
760          //show messages (if there are any)
761          html_msgarea();
762          //show site notice
763          if (tpl_getConf("vector_sitenotice")){
764              //detect wiki page to load as content
765              if (!empty($transplugin) && //var comes from conf/boxes.php
766                  is_object($transplugin) &&
767                  tpl_getConf("vector_sitenotice_translate")){
768                  //translated site notice?
769                  $transplugin_langcur = $transplugin->hlp->getLangPart(cleanID(getId())); //current language part
770                  $transplugin_langs   = explode(" ", trim($transplugin->getConf("translations"))); //available languages
771                  if (empty($transplugin_langs) ||
772                      empty($transplugin_langcur) ||
773                      !is_array($transplugin_langs) ||
774                      !in_array($transplugin_langcur, $transplugin_langs)) {
775                      //current page is no translation or something is wrong, load default site notice
776                      $sitenotice_location = tpl_getConf("vector_sitenotice_location");
777                  } else {
778                      //load language specific site notice
779                      $sitenotice_location = tpl_getConf("vector_sitenotice_location")."_".$transplugin_langcur;
780                  }
781              }else{
782                  //default site notice, no translation
783                  $sitenotice_location = tpl_getConf("vector_sitenotice_location");
784              }
785
786              //we have to show a custom site notice
787              if (empty($conf["useacl"]) ||
788                  auth_quickaclcheck(cleanID($sitenotice_location)) >= AUTH_READ){ //current user got access?
789                  echo "\n  <div id=\"siteNotice\" class=\"noprint\">\n";
790                  //get the rendered content of the defined wiki article to use as
791                  //custom site notice.
792                  $interim = tpl_include_page($sitenotice_location, false);
793                  if ($interim === "" ||
794                      $interim === false){
795                      //show creation/edit link if the defined page got no content
796                      echo "[&#160;";
797                      tpl_pagelink($sitenotice_location, hsc($lang["vector_fillplaceholder"]." (".hsc($sitenotice_location).")"));
798                      echo "&#160;]<br />";
799                  }else{
800                      //show the rendered page content
801                      echo  "    <div class=\"dokuwiki\">\n" //dokuwiki CSS class needed cause we are showing rendered page content
802                           .$interim."\n    "
803                           ."</div>";
804                  }
805                  echo "\n  </div>\n";
806              }
807          }
808          //show breadcrumps if enabled and position = top
809          if ($conf["breadcrumbs"] == true &&
810              $ACT !== "media" && //var comes from DokuWiki
811              (empty($conf["useacl"]) || //are there any users?
812               $loginname !== "" || //user is logged in?
813               !tpl_getConf("vector_closedwiki")) &&
814              tpl_getConf("vector_breadcrumbs_position") === "top"){
815              echo "\n  <div class=\"catlinks noprint\"><p>\n    ";
816              tpl_breadcrumbs();
817              echo "\n  </p></div>\n";
818          }
819          //show hierarchical breadcrumps if enabled and position = top
820          if ($conf["youarehere"] == true &&
821              $ACT !== "media" && //var comes from DokuWiki
822              (empty($conf["useacl"]) || //are there any users?
823               $loginname !== "" || //user is logged in?
824               !tpl_getConf("vector_closedwiki")) &&
825              tpl_getConf("vector_youarehere_position") === "top"){
826              echo "\n  <div class=\"catlinks noprint\"><p>\n    ";
827              tpl_youarehere();
828              echo "\n  </p></div>\n";
829          }
830          ?>
831
832          <!-- start div id bodyContent -->
833          <div id="bodyContent" class="dokuwiki">
834            <!-- start rendered wiki content -->
835            <?php
836            //flush the buffer for faster page rendering, heaviest content follows
837            if (function_exists("tpl_flush")) {
838                tpl_flush(); //exists since 2010-11-07 "Anteater"...
839            } else {
840                flush(); //...but I won't loose compatibility to 2009-12-25 "Lemming" right now.
841            }
842            //decide which type of pagecontent we have to show
843            switch ($vector_action){
844                //"image details"
845                case "detail":
846                    include DOKU_TPLINC."inc_detail.php";
847                    break;
848                //"cite this article"
849                case "cite":
850                    include DOKU_TPLINC."inc_cite.php";
851                    break;
852                //show "normal" content
853                default:
854                    tpl_content(((tpl_getConf("vector_toc_position") === "article") ? true : false));
855                    break;
856            }
857            ?>
858            <!-- end rendered wiki content -->
859            <div class="clearer"></div>
860          </div>
861          <!-- end div id bodyContent -->
862
863          <?php
864          //show breadcrumps if enabled and position = bottom
865          if ($conf["breadcrumbs"] == true &&
866              $ACT !== "media" && //var comes from DokuWiki
867              (empty($conf["useacl"]) || //are there any users?
868               $loginname !== "" || //user is logged in?
869               !tpl_getConf("vector_closedwiki")) &&
870              tpl_getConf("vector_breadcrumbs_position") === "bottom"){
871              echo "\n  <div class=\"catlinks noprint\"><p>\n    ";
872              tpl_breadcrumbs();
873              echo "\n  </p></div>\n";
874          }
875          //show hierarchical breadcrumps if enabled and position = bottom
876          if ($conf["youarehere"] == true &&
877              $ACT !== "media" && //var comes from DokuWiki
878              (empty($conf["useacl"]) || //are there any users?
879               $loginname !== "" || //user is logged in?
880               !tpl_getConf("vector_closedwiki")) &&
881              tpl_getConf("vector_youarehere_position") === "bottom"){
882              echo "\n  <div class=\"catlinks noprint\"><p>\n    ";
883              tpl_youarehere();
884              echo "\n  </p></div>\n";
885          }
886          ?>
887        </div>
888
889        <!-- TOC Sidebar -->
890        <?php if ($toc = tpl_toc(true)): ?>
891        <div class="d-none d-lg-block ms-4" style="width: 280px;">
892          <div class="sidebar-toc">
893            <?php echo $toc; ?>
894          </div>
895        </div>
896        <?php endif; ?>
897      </div>
898    </main>
899  </div>
900
901<!-- Bootstrap Bundle with Popper -->
902<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
903
904<script>
905  // Ensure the sidebar toggles correctly on mobile
906  document.querySelector('.navbar-toggler').addEventListener('click', function() {
907    document.getElementById('sidebar').classList.add('show');
908  });
909
910  // Close sidebar on mobile
911  document.getElementById('sidebarCloseButton').addEventListener('click', function() {
912    document.getElementById('sidebar').classList.remove('show');
913  });
914
915  // Theme switcher
916  document.addEventListener('DOMContentLoaded', function() {
917    const darkModeToggle = document.getElementById('darkModeToggle');
918    const lightIcon = document.getElementById('lightIcon');
919    const darkIcon = document.getElementById('darkIcon');
920
921    // Function to set theme
922    function setTheme(theme) {
923      document.documentElement.setAttribute('data-bs-theme', theme);
924      localStorage.setItem('theme', theme);
925      updateIcon(theme);
926    }
927
928    // Function to update icon
929    function updateIcon(theme) {
930      if (theme === 'dark') {
931        lightIcon.style.display = 'none';
932        darkIcon.style.display = 'inline';
933      } else {
934        lightIcon.style.display = 'inline';
935        darkIcon.style.display = 'none';
936      }
937    }
938
939    // Check for saved theme preference
940    const savedTheme = localStorage.getItem('theme');
941
942    // Check if user has system-level preference
943    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
944
945    if (savedTheme) {
946      // Use saved preference if it exists
947      setTheme(savedTheme);
948    } else {
949      // Otherwise use system preference
950      setTheme(prefersDark.matches ? 'dark' : 'light');
951    }
952
953    // Listen for system theme changes
954    prefersDark.addEventListener('change', (e) => {
955      if (!localStorage.getItem('theme')) {
956        // Only update based on system changes if user hasn't set a preference
957        setTheme(e.matches ? 'dark' : 'light');
958      }
959    });
960
961    // Handle manual toggle
962    darkModeToggle.addEventListener('click', function() {
963      const currentTheme = document.documentElement.getAttribute('data-bs-theme');
964      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
965      setTheme(newTheme);
966    });
967  });
968</script>
969
970<?php
971//provide DokuWiki housekeeping, required in all templates
972tpl_indexerWebBug();
973
974//include web analytics software
975if (file_exists(DOKU_TPLINC."/user/tracker.php")){
976    include DOKU_TPLINC."/user/tracker.php";
977}
978?>
979
980</body>
981</html>
982