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 "[ "; 797 tpl_pagelink($sitenotice_location, hsc($lang["vector_fillplaceholder"]." (".hsc($sitenotice_location).")")); 798 echo " ]<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