1<?php 2/** 3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15use Doku_Event; 16use dokuwiki\Extension\Event; 17use dokuwiki\Menu\PageMenu; 18use dokuwiki\Menu\SiteMenu; 19use dokuwiki\Menu\UserMenu; 20use dokuwiki\plugin\config\core\Configuration; 21use dokuwiki\plugin\config\core\Writer; 22use Exception; 23 24 25/** 26 * Class TplUtility 27 * @package ComboStrap 28 * Utility class 29 */ 30class TplUtility 31{ 32 33 /** 34 * Constant for the function {@link msg()} 35 * -1 = error, 0 = info, 1 = success, 2 = notify 36 */ 37 const LVL_MSG_ERROR = -1; 38 const LVL_MSG_INFO = 0; 39 const LVL_MSG_SUCCESS = 1; 40 const LVL_MSG_WARNING = 2; 41 const LVL_MSG_DEBUG = 3; 42 const TEMPLATE_NAME = 'strap'; 43 44 45 const CONF_HEADER_SLOT_PAGE_NAME = "headerSlotPageName"; 46 47 const CONF_FOOTER_SLOT_PAGE_NAME = "footerSlotPageName"; 48 49 50 /** 51 * @deprecated for {@link TplUtility::CONF_BOOTSTRAP_VERSION_STYLESHEET} 52 */ 53 const CONF_BOOTSTRAP_VERSION = "bootstrapVersion"; 54 /** 55 * @deprecated for {@link TplUtility::CONF_BOOTSTRAP_VERSION_STYLESHEET} 56 */ 57 const CONF_BOOTSTRAP_STYLESHEET = "bootstrapStylesheet"; 58 59 /** 60 * Stylesheet and Boostrap should have the same version 61 * This conf is a mix between the version and the stylesheet 62 * 63 * majorVersion.0.0 - stylesheetname 64 */ 65 const CONF_BOOTSTRAP_VERSION_STYLESHEET = "bootstrapVersionStylesheet"; 66 /** 67 * The separator in {@link TplUtility::CONF_BOOTSTRAP_VERSION_STYLESHEET} 68 */ 69 const BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR = " - "; 70 const DEFAULT_BOOTSTRAP_VERSION_STYLESHEET = "5.0.1" . self::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR . "bootstrap"; 71 72 /** 73 * Jquery UI 74 */ 75 const CONF_JQUERY_DOKU = 'jQueryDoku'; 76 const CONF_REM_SIZE = "remSize"; 77 const CONF_GRID_COLUMNS = "gridColumns"; 78 const CONF_USE_CDN = "useCDN"; 79 80 const CONF_PRELOAD_CSS = "preloadCss"; // preload all css ? 81 const BS_4_BOOTSTRAP_VERSION_STYLESHEET = "4.5.0 - bootstrap"; 82 83 const CONF_SIDEKICK_OLD = "sidekickbar"; 84 const CONF_SIDEKICK_SLOT_PAGE_NAME = "sidekickSlotPageName"; 85 const CONF_SLOT_HEADER_PAGE_NAME_VALUE = "slot_header"; 86 87 /** 88 * @deprecated see {@link TplUtility::CONF_HEADER_SLOT_PAGE_NAME} 89 */ 90 const CONF_HEADER_OLD = "headerbar"; 91 /** 92 * @deprecated 93 */ 94 const CONF_HEADER_OLD_VALUE = TplUtility::CONF_HEADER_OLD; 95 /** 96 * @deprecated see {@link TplUtility::CONF_FOOTER_SLOT_PAGE_NAME} 97 */ 98 const CONF_FOOTER_OLD = "footerbar"; 99 100 /** 101 * Disable the javascript of Dokuwiki 102 * if public 103 * https://combostrap.com/frontend/optimization 104 */ 105 const CONF_DISABLE_BACKEND_JAVASCRIPT = "disableBackendJavascript"; 106 107 /** 108 * A parameter switch to allows the update 109 * of conf in test 110 */ 111 const COMBO_TEST_UPDATE = "combo_update_conf"; 112 113 /** 114 * Do we show the rail bar for anonymous user 115 */ 116 const CONF_PRIVATE_RAIL_BAR = "privateRailbar"; 117 118 /** 119 * When do we toggle from offcanvas to fixed railbar 120 */ 121 const CONF_BREAKPOINT_RAIL_BAR = "breakpointRailbar"; 122 123 /** 124 * Breakpoint naming 125 */ 126 const BREAKPOINT_EXTRA_SMALL_NAME = "extra-small"; 127 const BREAKPOINT_SMALL_NAME = "small"; 128 const BREAKPOINT_MEDIUM_NAME = "medium"; 129 const BREAKPOINT_LARGE_NAME = "large"; 130 const BREAKPOINT_EXTRA_LARGE_NAME = "extra-large"; 131 const BREAKPOINT_EXTRA_EXTRA_LARGE_NAME = "extra-extra-large"; 132 const BREAKPOINT_NEVER_NAME = "never"; 133 /** 134 * Name of the main footer slot 135 */ 136 public const SLOT_MAIN_FOOTER_NAME = "slot_main_footer"; 137 /** 138 * Name of the main header slot 139 */ 140 public const SLOT_MAIN_HEADER_NAME = "slot_main_header"; 141 142 /** 143 * @var array|null 144 */ 145 private static $TEMPLATE_INFO = null; 146 147 148 /** 149 * Print the breadcrumbs trace with Bootstrap class 150 * 151 * @param string $sep Separator between entries 152 * @return bool 153 * @author Nicolas GERARD 154 * 155 * 156 */ 157 static function renderTrailBreadcrumb($sep = '�') 158 { 159 160 global $conf; 161 global $lang; 162 163 //check if enabled 164 if (!$conf['breadcrumbs']) return false; 165 166 $crumbs = breadcrumbs(); //setup crumb trace 167 168 echo '<nav id="breadcrumb" aria-label="breadcrumb" class="my-3 d-print-none">' . PHP_EOL; 169 170 $i = 0; 171 // Try to get the template custom breadcrumb 172 // $breadCrumb = tpl_getLang('breadcrumb'); 173 // if ($breadCrumb == '') { 174 // // If not present for the language, get the default one 175 // $breadCrumb = $lang['breadcrumb']; 176 // } 177 178 // echo '<span id="breadCrumbTitle" ">' . $breadCrumb . ': </span>' . PHP_EOL; 179 echo '<ol class="breadcrumb py-1 px-2" style="background-color:unset">' . PHP_EOL; 180 print '<li class="pr-2" style="display:flex;font-weight: 200">' . $lang['breadcrumb'] . '</li>'; 181 182 foreach ($crumbs as $id => $name) { 183 $i++; 184 185 if ($i == 0) { 186 print '<li class="breadcrumb-item active">'; 187 } else { 188 print '<li class="breadcrumb-item">'; 189 } 190 if ($name == "start") { 191 $name = "Home"; 192 } 193 tpl_link(wl($id), hsc($name), 'title="' . $name . '"'); 194 195 print '</li>' . PHP_EOL; 196 197 } 198 echo '</ol>' . PHP_EOL; 199 echo '</nav>' . PHP_EOL; 200 return true; 201 } 202 203 /** 204 * @param string $text add a comment into the HTML page 205 */ 206 private static function addAsHtmlComment($text) 207 { 208 print_r('<!-- TplUtility Comment: ' . hsc($text) . '-->'); 209 } 210 211 private static function getApexDomainUrl() 212 { 213 return self::getTemplateInfo()["url"]; 214 } 215 216 private static function getTemplateInfo() 217 { 218 if (self::$TEMPLATE_INFO == null) { 219 self::$TEMPLATE_INFO = confToHash(__DIR__ . '/../template.info.txt'); 220 } 221 return self::$TEMPLATE_INFO; 222 } 223 224 public static function getFullQualifyVersion() 225 { 226 return "v" . self::getTemplateInfo()['version'] . " (" . self::getTemplateInfo()['date'] . ")"; 227 } 228 229 230 private static function getStrapUrl() 231 { 232 return self::getTemplateInfo()["strap"]; 233 } 234 235 236 public static function registerHeaderHandler() 237 { 238 /** 239 * In test, we may test for 4 and for 5 240 * on the same test, making two request 241 * This two requests will register the event two times 242 * To avoid that we use a global variable 243 */ 244 global $COMBO_STRAP_METAHEADER_HOOK_ALREADY_REGISTERED; 245 if ($COMBO_STRAP_METAHEADER_HOOK_ALREADY_REGISTERED !== true) { 246 global $EVENT_HANDLER; 247 $method = array('\Combostrap\TplUtility', 'handleBootstrapMetaHeaders'); 248 /** 249 * A call to a method is via an array and the hook declare a string 250 * @noinspection PhpParamsInspection 251 */ 252 $EVENT_HANDLER->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', null, $method); 253 $COMBO_STRAP_METAHEADER_HOOK_ALREADY_REGISTERED = true; 254 } 255 256 } 257 258 /** 259 * Add the preloaded CSS resources 260 * at the end 261 */ 262 public static function addPreloadedResources() 263 { 264 // For the preload if any 265 global $preloadedCss; 266 // 267 // Note: Adding this css in an animationFrame 268 // such as https://github.com/jakearchibald/svgomg/blob/master/src/index.html#L183 269 // would be difficult to test 270 if (isset($preloadedCss)) { 271 foreach ($preloadedCss as $link) { 272 $htmlLink = '<link rel="stylesheet" href="' . $link['href'] . '" '; 273 if ($link['crossorigin'] != "") { 274 $htmlLink .= ' crossorigin="' . $link['crossorigin'] . '" '; 275 } 276 if (!empty($link['class'])) { 277 $htmlLink .= ' class="' . $link['class'] . '" '; 278 } 279 // No integrity here 280 $htmlLink .= '>'; 281 ptln($htmlLink); 282 } 283 /** 284 * Reset 285 * Needed in test when we start two requests 286 */ 287 $preloadedCss = []; 288 } 289 290 } 291 292 /** 293 * @param $linkData - an array of link style sheet data 294 * @return array - the array with the preload attributes 295 */ 296 private static function captureStylePreloadingAndTransformToPreloadCssTag($linkData): array 297 { 298 /** 299 * Save the stylesheet to load it at the end 300 */ 301 global $preloadedCss; 302 $preloadedCss[] = $linkData; 303 304 /** 305 * Modify the actual tag data 306 * Change the loading mechanism to preload 307 */ 308 $linkData['rel'] = 'preload'; 309 $linkData['as'] = 'style'; 310 return $linkData; 311 } 312 313 public static function getBootStrapVersion() 314 { 315 $bootstrapStyleSheetVersion = tpl_getConf(TplUtility::CONF_BOOTSTRAP_VERSION_STYLESHEET, TplUtility::DEFAULT_BOOTSTRAP_VERSION_STYLESHEET); 316 $bootstrapStyleSheetArray = explode(self::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR, $bootstrapStyleSheetVersion); 317 return $bootstrapStyleSheetArray[0]; 318 } 319 320 public static function getStyleSheetConf() 321 { 322 $bootstrapStyleSheetVersion = tpl_getConf(TplUtility::CONF_BOOTSTRAP_VERSION_STYLESHEET, TplUtility::DEFAULT_BOOTSTRAP_VERSION_STYLESHEET); 323 $bootstrapStyleSheetArray = explode(self::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR, $bootstrapStyleSheetVersion); 324 return $bootstrapStyleSheetArray[1]; 325 } 326 327 /** 328 * Return the XHMTL for the bar or null if not found 329 * 330 * An adaptation from {@link tpl_include_page()} 331 * to make the cache namespace 332 * 333 * @param $slotId 334 * @return string|null 335 * 336 * 337 * Note: Even if there is no sidebar 338 * the rendering may output 339 * debug information in the form of 340 * an HTML comment 341 */ 342 public static function renderSlot($slotId): ?string 343 { 344 345 if (class_exists("ComboStrap\Page")) { 346 try { 347 $page = Page::createPageFromId($slotId); 348 $html = $page->toXhtml(); 349 if (class_exists("ComboStrap\PageEdit") && $html !== null) { 350 $html = PageEdit::replaceAll($html); 351 } 352 } catch (Exception $e) { 353 $html = "Rendering the slot, returns an error. {$e->getMessage()}"; 354 } 355 return $html; 356 } else { 357 TplUtility::msg("The combo plugin is not installed, sidebars automatic bursting will not work", self::LVL_MSG_INFO, "sidebars"); 358 return tpl_include_page($slotId, 0, 1); 359 } 360 361 } 362 363 private static function getBootStrapMajorVersion() 364 { 365 return self::getBootStrapVersion()[0]; 366 } 367 368 369 public static function getSideKickSlotPageName() 370 { 371 372 return TplUtility::migrateSlotConfAndGetValue( 373 TplUtility::CONF_SIDEKICK_SLOT_PAGE_NAME, 374 "slot_sidekick", 375 TplUtility::CONF_SIDEKICK_OLD, 376 "sidekickbar", 377 "sidekick_slot" 378 ); 379 } 380 381 public static function getHeaderSlotPageName() 382 { 383 384 return TplUtility::migrateSlotConfAndGetValue( 385 TplUtility::CONF_HEADER_SLOT_PAGE_NAME, 386 TplUtility::CONF_SLOT_HEADER_PAGE_NAME_VALUE, 387 TplUtility::CONF_HEADER_OLD, 388 TplUtility::CONF_HEADER_OLD_VALUE, 389 "header_slot" 390 ); 391 392 } 393 394 public static function getFooterSlotPageName() 395 { 396 return self::migrateSlotConfAndGetValue( 397 TplUtility::CONF_FOOTER_SLOT_PAGE_NAME, 398 "slot_footer", 399 TplUtility::CONF_FOOTER_OLD, 400 "footerbar", 401 "footer_slot" 402 ); 403 } 404 405 /** 406 * @param string $key the key configuration 407 * @param string $value the value 408 * @return bool 409 */ 410 public static function updateConfiguration($key, $value) 411 { 412 413 /** 414 * Hack to avoid updating during {@link \TestRequest} 415 * when not asked 416 * Because the test request environment is wiped out only on the class level, 417 * the class / test function needs to specifically say that it's open 418 * to the modification of the configuration 419 */ 420 global $_REQUEST; 421 if (defined('DOKU_UNITTEST') && !isset($_REQUEST[self::COMBO_TEST_UPDATE])) { 422 423 /** 424 * This hack resolves two problems 425 * 426 * First one 427 * this is a test request 428 * the local.php file has a the `DOKU_TMP_DATA` 429 * constant in the file and updating the file 430 * with this method will then update the value of savedir to DOKU_TMP_DATA 431 * we get then the error 432 * The datadir ('pages') at DOKU_TMP_DATA/pages is not found 433 * 434 * 435 * Second one 436 * if in a php test unit, we send a php request two times 437 * the headers have been already send and the 438 * {@link msg()} function will send them 439 * causing the {@link TplUtility::outputBuffer() output buffer check} to fail 440 */ 441 global $MSG_shown; 442 if (isset($MSG_shown) || headers_sent()) { 443 return false; 444 } else { 445 return true; 446 } 447 448 } 449 450 451 $configuration = new Configuration(); 452 $settings = $configuration->getSettings(); 453 454 $key = "tpl____strap____" . $key; 455 if (isset($settings[$key])) { 456 $setting = &$settings[$key]; 457 $setting->update($value); 458 /** 459 * We cannot update the setting 460 * via the configuration object 461 * We are taking another pass 462 */ 463 464 $writer = new Writer(); 465 if (!$writer->isLocked()) { 466 try { 467 $writer->save($settings); 468 return true; 469 } catch (Exception $e) { 470 TplUtility::msg("An error occurred while trying to save automatically the configuration ($key) to the value ($value). Error: " . $e->getMessage()); 471 return false; 472 } 473 } else { 474 TplUtility::msg("The configuration file was locked. The upgrade configuration ($key) value could not be not changed to ($value)"); 475 return false; 476 } 477 478 } else { 479 480 /** 481 * When we run test, 482 * strap is not always the active template 483 * and therefore the configurations are not loaded 484 */ 485 global $conf; 486 if ($conf['template'] == TplUtility::TEMPLATE_NAME) { 487 TplUtility::msg("The configuration ($key) is unknown and was therefore not change to ($value)"); 488 } 489 } 490 491 return false; 492 493 494 } 495 496 /** 497 * Helper to migrate from bar to slot 498 * @return mixed|string 499 */ 500 public static function migrateSlotConfAndGetValue($newConf, $newDefaultValue, $oldConf, $oldDefaultValue, $canonical) 501 { 502 503 $name = tpl_getConf($newConf, null); 504 if ($name == null) { 505 $name = tpl_getConf($oldConf, null); 506 } 507 if ($name == null) { 508 509 $foundOldName = false; 510 if (page_exists($oldConf)) { 511 $foundOldName = true; 512 } 513 514 if (!$foundOldName) { 515 global $conf; 516 $startPageName = $conf["start"]; 517 $startPagePath = wikiFN($startPageName); 518 $directory = dirname($startPagePath); 519 520 521 $childrenDirectories = glob($directory . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); 522 foreach ($childrenDirectories as $childrenDirectory) { 523 $directoryName = pathinfo($childrenDirectory)['filename']; 524 $dokuFilePath = $directoryName . ":" . $oldDefaultValue; 525 if (page_exists($dokuFilePath)) { 526 $foundOldName = true; 527 break; 528 } 529 } 530 } 531 532 if ($foundOldName) { 533 $name = $oldDefaultValue; 534 } else { 535 $name = $newDefaultValue; 536 } 537 $updated = TplUtility::updateConfiguration($newConf, $name); 538 if ($updated) { 539 TplUtility::msg("The <a href=\"https://combostrap.com/$canonical\">$newConf</a> configuration was set with the value <mark>$name</mark>", self::LVL_MSG_INFO, $canonical); 540 } 541 } 542 return $name; 543 } 544 545 /** 546 * Output buffer checks 547 * 548 * It should be null before printing otherwise 549 * you may get a text before the HTML header 550 * and it mess up the whole page 551 */ 552 public static function outputBuffer() 553 { 554 $length = ob_get_length(); 555 $ob = ""; 556 if ($length > 0) { 557 $ob = ob_get_contents(); 558 ob_clean(); 559 global $ACT; 560 if ($ACT === "show") { 561 /** 562 * If you got this problem check that this is not a character before a `<?php` declaration 563 */ 564 TplUtility::msg("A plugin has send text before the creation of the page. Because it will mess the rendering, we have deleted it. The content was: (" . $ob . ")", TplUtility::LVL_MSG_ERROR, "strap"); 565 } 566 } 567 return $ob; 568 569 } 570 571 572 /** 573 * @return string 574 * Railbar items can add snippet in the head 575 * And should then be could before the HTML output 576 * 577 * In Google Material Design, they call it a 578 * navigational drawer 579 * https://material.io/components/navigation-drawer 580 */ 581 public static function getRailBar() 582 { 583 584 if (tpl_getConf(TplUtility::CONF_PRIVATE_RAIL_BAR) === 1 && empty($_SERVER['REMOTE_USER'])) { 585 return ""; 586 } 587 $breakpoint = tpl_getConf(TplUtility::CONF_BREAKPOINT_RAIL_BAR, TplUtility::BREAKPOINT_LARGE_NAME); 588 589 $bootstrapBreakpoint = ""; 590 switch ($breakpoint) { 591 case TplUtility::BREAKPOINT_EXTRA_SMALL_NAME: 592 $bootstrapBreakpoint = "xs"; 593 break; 594 case TplUtility::BREAKPOINT_SMALL_NAME: 595 $bootstrapBreakpoint = "sm"; 596 break; 597 case TplUtility::BREAKPOINT_MEDIUM_NAME: 598 $bootstrapBreakpoint = "md"; 599 break; 600 case TplUtility::BREAKPOINT_LARGE_NAME: 601 $bootstrapBreakpoint = "lg"; 602 break; 603 case TplUtility::BREAKPOINT_EXTRA_LARGE_NAME: 604 $bootstrapBreakpoint = "xl"; 605 break; 606 case TplUtility::BREAKPOINT_EXTRA_EXTRA_LARGE_NAME: 607 $bootstrapBreakpoint = "xxl"; 608 break; 609 } 610 611 $classOffCanvas = ""; 612 $classFixed = ""; 613 if (!empty($bootstrapBreakpoint)) { 614 $classOffCanvas = "class=\"d-$bootstrapBreakpoint-none\""; 615 $classFixed = "class=\"d-none d-$bootstrapBreakpoint-flex\""; 616 } 617 618 $railBarListItems = TplUtility::getRailBarListItems(); 619 $railBarOffCanvas = <<<EOF 620<div id="railbar-offcanvas-wrapper" $classOffCanvas> 621 <button id="railbar-offcanvas-open" class="btn" type="button" data-bs-toggle="offcanvas" 622 data-bs-target="#railbar-offcanvas" aria-controls="railbar-offcanvas"> 623 </button> 624 625 <div id="railbar-offcanvas" class="offcanvas offcanvas-end" 626 aria-labelledby="offcanvas-label" 627 style="visibility: hidden;" aria-hidden="true"> 628 <h5 class="d-none" id="offcanvas-label">Railbar</h5> 629 <!-- Pseudo relative element https://stackoverflow.com/questions/6040005/relatively-position-an-element-without-it-taking-up-space-in-document-flow --> 630 <div style="position: relative; width: 0; height: 0"> 631 <button id="railbar-offcanvas-close" class="btn" type="button" data-bs-dismiss="offcanvas" 632 aria-label="Close"> 633 </button> 634 </div> 635 <div id="railbar-offcanvas-body" class="offcanvas-body" style="align-items: center;display: flex;"> 636 $railBarListItems 637 </div> 638 </div> 639</div> 640EOF; 641 642 if ($breakpoint != TplUtility::BREAKPOINT_NEVER_NAME) { 643 $zIndexRailbar = 1000; // A navigation bar (below the drop down because we use it in the search box for auto-completion) 644 $railBarFixed = <<<EOF 645<div id="railbar-fixed" style="z-index: $zIndexRailbar;" $classFixed> 646 <div class="tools"> 647 $railBarListItems 648 </div> 649</div> 650EOF; 651 return <<<EOF 652$railBarOffCanvas 653$railBarFixed 654EOF; 655 } else { 656 657 return $railBarOffCanvas; 658 659 } 660 661 662 } 663 664 /** 665 * 666 * https://material.io/components/navigation-rail|Navigation rail 667 * @return string - the ul part of the railbar 668 */ 669 public 670 static function getRailBarListItems(): string 671 { 672 $liUserTools = (new UserMenu())->getListItems('action'); 673 $liPageTools = (new PageMenu())->getListItems(); 674 $liSiteTools = (new SiteMenu())->getListItems('action'); 675 // FYI: The below code outputs all menu in mobile (in another HTML layout) 676 // echo (new \dokuwiki\Menu\MobileMenu())->getDropdown($lang['tools']); 677 return <<<EOF 678<ul class="railbar"> 679 <li><a href="#" style="height: 19px;line-height: 17px;text-align: left;font-weight:bold"><span>User</span><svg style="height:19px"></svg></a></li> 680 $liUserTools 681 <li><a href="#" style="height: 19px;line-height: 17px;text-align: left;font-weight:bold"><span>Page</span><svg style="height:19px"></svg></a></li> 682 $liPageTools 683 <li><a href="#" style="height: 19px;line-height: 17px;text-align: left;font-weight:bold"><span>Website</span><svg style="height:19px"></svg></a></li> 684 $liSiteTools 685</ul> 686EOF; 687 688 } 689 690 public static function getMainHeaderSlotName(): string 691 { 692 return self::SLOT_MAIN_HEADER_NAME; 693 } 694 695 public static function getMainFooterSlotName(): string 696 { 697 return self::SLOT_MAIN_FOOTER_NAME; 698 } 699 700 public static function isNotSlot(): bool 701 { 702 global $ID; 703 return strpos($ID, TplUtility::getSideSlotPageName()) === false 704 && strpos($ID, TplUtility::getSideKickSlotPageName()) === false 705 && strpos($ID, TplUtility::SLOT_MAIN_HEADER_NAME) === false 706 && strpos($ID, TplUtility::SLOT_MAIN_FOOTER_NAME) === false 707 && strpos($ID, TplUtility::getHeaderSlotPageName()) === false 708 && strpos($ID, TplUtility::getFooterSlotPageName()) === false; 709 } 710 711 public static function getSideSlotPageName() 712 { 713 global $conf; 714 return $conf['sidebar']; 715 } 716 717 public static function isNotRootHome(): bool 718 { 719 global $ID; 720 global $conf; 721 $startName = $conf['start']; 722 return $ID !== $startName; 723 } 724 725 public static function getRem() 726 { 727 return tpl_getConf(TplUtility::CONF_REM_SIZE, null); 728 } 729 730 /** 731 * Hierarchical breadcrumbs 732 * 733 * This will return the Hierarchical breadcrumbs. 734 * 735 * Config: 736 * - $conf['youarehere'] must be true 737 * - add $lang['youarehere'] if $printPrefix is true 738 * 739 * @param bool $printPrefix print or not the $lang['youarehere'] 740 * @return string 741 */ 742 function renderHierarchicalBreadcrumb($printPrefix = false) 743 { 744 745 global $conf; 746 global $lang; 747 748 // check if enabled 749 if (!$conf['youarehere']) return ""; 750 751 // print intermediate namespace links 752 $htmlOutput = '<ol class="breadcrumb">' . PHP_EOL; 753 754 // Print the home page 755 $htmlOutput .= '<li>' . PHP_EOL; 756 if ($printPrefix) { 757 $htmlOutput .= $lang['youarehere'] . ' '; 758 } 759 $page = $conf['start']; 760 $htmlOutput .= tpl_link(wl($page), '<span class="glyphicon glyphicon-home" aria-hidden="true"></span>', 'title="' . tpl_pagetitle($page, true) . '"', $return = true); 761 $htmlOutput .= '</li>' . PHP_EOL; 762 763 // Print the parts if there is more than one 764 global $ID; 765 $idParts = explode(':', $ID); 766 if (count($idParts) > 1) { 767 768 // Print the parts without the last one ($count -1) 769 $page = ""; 770 for ($i = 0; $i < count($idParts) - 1; $i++) { 771 772 $page .= $idParts[$i] . ':'; 773 774 // Skip home page of the namespace 775 // if ($page == $conf['start']) continue; 776 777 // The last part is the active one 778// if ($i == $count) { 779// $htmlOutput .= '<li class="active">'; 780// } else { 781// $htmlOutput .= '<li>'; 782// } 783 784 $htmlOutput .= '<li>'; 785 // html_wikilink because the page has the form pagename: and not pagename:pagename 786 $htmlOutput .= html_wikilink($page); 787 $htmlOutput .= '</li>' . PHP_EOL; 788 789 } 790 } 791 792 // Skipping Wiki Global Root Home Page 793// resolve_pageid('', $page, $exists); 794// if(isset($page) && $page == $idPart.$idParts[$i]) { 795// echo '</ol>'.PHP_EOL; 796// return true; 797// } 798// // skipping for namespace index 799// $page = $idPart.$idParts[$i]; 800// if($page == $conf['start']) { 801// echo '</ol>'.PHP_EOL; 802// return true; 803// } 804 805 // print current page 806// print '<li>'; 807// tpl_link(wl($page), tpl_pagetitle($page,true), 'title="' . $page . '"'); 808 $htmlOutput .= '</li>' . PHP_EOL; 809 // close the breadcrumb 810 $htmlOutput .= '</ol>' . PHP_EOL; 811 return $htmlOutput; 812 813 } 814 815 816 /* 817 * Function return the page name from an id 818 * @author Nicolas GERARD 819 * 820 * @param string $sep Separator between entries 821 * @return bool 822 */ 823 824 function getPageTitle($id) 825 { 826 827 // page names 828 $name = noNSorNS($id); 829 if (useHeading('navigation')) { 830 // get page title 831 $title = p_get_first_heading($id, METADATA_RENDER_USING_SIMPLE_CACHE); 832 if ($title) { 833 $name = $title; 834 } 835 } 836 return $name; 837 838 } 839 840 841 /** 842 * This is a fork of tpl_actionlink where I have added the class parameters 843 * 844 * Like the action buttons but links 845 * 846 * @param string $type action command 847 * @param string $pre prefix of link 848 * @param string $suf suffix of link 849 * @param string $inner innerHML of link 850 * @param bool $return if true it returns html, otherwise prints 851 * @param string $class the class to be added 852 * @return bool|string html or false if no data, true if printed 853 * @see tpl_get_action 854 * 855 * @author Adrian Lang <mail@adrianlang.de> 856 */ 857 function renderActionLink($type, $class = '', $pre = '', $suf = '', $inner = '', $return = false) 858 { 859 global $lang; 860 $data = tpl_get_action($type); 861 if ($data === false) { 862 return false; 863 } elseif (!is_array($data)) { 864 $out = sprintf($data, 'link'); 865 } else { 866 /** 867 * @var string $accesskey 868 * @var string $id 869 * @var string $method 870 * @var bool $nofollow 871 * @var array $params 872 * @var string $replacement 873 */ 874 extract($data); 875 if (strpos($id, '#') === 0) { 876 $linktarget = $id; 877 } else { 878 $linktarget = wl($id, $params); 879 } 880 $caption = $lang['btn_' . $type]; 881 if (strpos($caption, '%s')) { 882 $caption = sprintf($caption, $replacement); 883 } 884 $akey = $addTitle = ''; 885 if ($accesskey) { 886 $akey = 'accesskey="' . $accesskey . '" '; 887 $addTitle = ' [' . strtoupper($accesskey) . ']'; 888 } 889 $rel = $nofollow ? 'rel="nofollow" ' : ''; 890 $out = $pre . tpl_link( 891 $linktarget, (($inner) ? $inner : $caption), 892 'class="nav-link action ' . $type . ' ' . $class . '" ' . 893 $akey . $rel . 894 'title="' . hsc($caption) . $addTitle . '"', true 895 ) . $suf; 896 } 897 if ($return) return $out; 898 echo $out; 899 return true; 900 } 901 902 903 /** 904 * @return array 905 * Return the headers needed by this template 906 * 907 * @throws Exception 908 */ 909 static function getBootstrapMetaHeaders() 910 { 911 912 // The version 913 $bootstrapVersion = TplUtility::getBootStrapVersion(); 914 if ($bootstrapVersion === false) { 915 /** 916 * Strap may be called for test 917 * by combo 918 * In this case, the conf may not be reloaded 919 */ 920 self::reloadConf(); 921 $bootstrapVersion = TplUtility::getBootStrapVersion(); 922 if ($bootstrapVersion === false) { 923 throw new Exception("Bootstrap version should not be false"); 924 } 925 } 926 $scriptsMeta = self::buildBootstrapMetas($bootstrapVersion); 927 928 // if cdn 929 $useCdn = tpl_getConf(self::CONF_USE_CDN); 930 931 932 // Build the returned Js script array 933 $jsScripts = array(); 934 foreach ($scriptsMeta as $key => $script) { 935 $path_parts = pathinfo($script["file"]); 936 $extension = $path_parts['extension']; 937 if ($extension === "js") { 938 $src = DOKU_BASE . "lib/tpl/strap/bootstrap/$bootstrapVersion/" . $script["file"]; 939 if ($useCdn) { 940 if (isset($script["url"])) { 941 $src = $script["url"]; 942 } 943 } 944 $jsScripts[$key] = 945 array( 946 'src' => $src, 947 'defer' => null 948 ); 949 if (isset($script['integrity'])) { 950 $jsScripts[$key]['integrity'] = $script['integrity']; 951 $jsScripts[$key]['crossorigin'] = 'anonymous'; 952 } 953 } 954 } 955 956 $css = array(); 957 $cssScript = $scriptsMeta['css']; 958 $href = DOKU_BASE . "lib/tpl/strap/bootstrap/$bootstrapVersion/" . $cssScript["file"]; 959 if ($useCdn) { 960 if (isset($script["url"])) { 961 $href = $script["url"]; 962 } 963 } 964 $css['css'] = 965 array( 966 'href' => $href, 967 'rel' => "stylesheet" 968 ); 969 if (isset($script['integrity'])) { 970 $css['css']['integrity'] = $script['integrity']; 971 $css['css']['crossorigin'] = 'anonymous'; 972 } 973 974 975 return array( 976 'script' => $jsScripts, 977 'link' => $css 978 ); 979 980 981 } 982 983 /** 984 * @return array - A list of all available stylesheets 985 * This function is used to build the configuration as a list of files 986 */ 987 static function getStylesheetsForMetadataConfiguration() 988 { 989 $cssVersionsMetas = self::getStyleSheetsFromJsonFileAsArray(); 990 $listVersionStylesheetMeta = array(); 991 foreach ($cssVersionsMetas as $bootstrapVersion => $cssVersionMeta) { 992 foreach ($cssVersionMeta as $fileName => $values) { 993 $listVersionStylesheetMeta[] = $bootstrapVersion . TplUtility::BOOTSTRAP_VERSION_STYLESHEET_SEPARATOR . $fileName; 994 } 995 } 996 return $listVersionStylesheetMeta; 997 } 998 999 /** 1000 * 1001 * @param $version - return only the selected version if set 1002 * @return array - an array of the meta JSON custom files 1003 */ 1004 static function getStyleSheetsFromJsonFileAsArray($version = null) 1005 { 1006 1007 $jsonAsArray = true; 1008 $stylesheetsFile = __DIR__ . '/../bootstrap/bootstrapStylesheet.json'; 1009 $styleSheets = json_decode(file_get_contents($stylesheetsFile), $jsonAsArray); 1010 if ($styleSheets == null) { 1011 self::msg("Unable to read the file {$stylesheetsFile} as json"); 1012 } 1013 1014 1015 $localStyleSheetsFile = __DIR__ . '/../bootstrap/bootstrapLocal.json'; 1016 if (file_exists($localStyleSheetsFile)) { 1017 $localStyleSheets = json_decode(file_get_contents($localStyleSheetsFile), $jsonAsArray); 1018 if ($localStyleSheets == null) { 1019 self::msg("Unable to read the file {$localStyleSheets} as json"); 1020 } 1021 foreach ($styleSheets as $bootstrapVersion => &$stylesheetsFiles) { 1022 if (isset($localStyleSheets[$bootstrapVersion])) { 1023 $stylesheetsFiles = array_merge($stylesheetsFiles, $localStyleSheets[$bootstrapVersion]); 1024 } 1025 } 1026 } 1027 1028 if (isset($version)) { 1029 if (!isset($styleSheets[$version])) { 1030 self::msg("The bootstrap version ($version) could not be found in the custom CSS file ($stylesheetsFile, or $localStyleSheetsFile)"); 1031 } else { 1032 $styleSheets = $styleSheets[$version]; 1033 } 1034 } 1035 1036 /** 1037 * Select Rtl or Ltr 1038 * Stylesheet name may have another level 1039 * with direction property of the language 1040 * 1041 * Bootstrap needs another stylesheet 1042 * See https://getbootstrap.com/docs/5.0/getting-started/rtl/ 1043 */ 1044 global $lang; 1045 $direction = $lang["direction"]; 1046 if (empty($direction)) { 1047 $direction = "ltr"; 1048 } 1049 $directedStyleSheets = []; 1050 foreach ($styleSheets as $name => $styleSheetDefinition) { 1051 if (isset($styleSheetDefinition[$direction])) { 1052 $directedStyleSheets[$name] = $styleSheetDefinition[$direction]; 1053 } else { 1054 $directedStyleSheets[$name] = $styleSheetDefinition; 1055 } 1056 } 1057 1058 return $directedStyleSheets; 1059 } 1060 1061 /** 1062 * 1063 * Build from all Bootstrap JSON meta files only one array 1064 * @param $version 1065 * @return array 1066 * 1067 */ 1068 static function buildBootstrapMetas($version) 1069 { 1070 1071 $jsonAsArray = true; 1072 $bootstrapJsonFile = __DIR__ . '/../bootstrap/bootstrapJavascript.json'; 1073 $bootstrapMetas = json_decode(file_get_contents($bootstrapJsonFile), $jsonAsArray); 1074 // Decodage problem 1075 if ($bootstrapMetas == null) { 1076 self::msg("Unable to read the file {$bootstrapJsonFile} as json"); 1077 return array(); 1078 } 1079 if (!isset($bootstrapMetas[$version])) { 1080 self::msg("The bootstrap version ($version) could not be found in the file $bootstrapJsonFile"); 1081 return array(); 1082 } 1083 $bootstrapMetas = $bootstrapMetas[$version]; 1084 1085 1086 // Css 1087 $bootstrapCssFile = TplUtility::getStyleSheetConf(); 1088 $bootstrapCustomMetas = self::getStyleSheetsFromJsonFileAsArray($version); 1089 1090 if (!isset($bootstrapCustomMetas[$bootstrapCssFile])) { 1091 self::msg("The bootstrap custom file ($bootstrapCssFile) could not be found in the custom CSS files for the version ($version)"); 1092 } else { 1093 $bootstrapMetas['css'] = $bootstrapCustomMetas[$bootstrapCssFile]; 1094 } 1095 1096 1097 return $bootstrapMetas; 1098 } 1099 1100 /** 1101 * @param Doku_Event $event 1102 * @param $param 1103 * Function that handle the META HEADER event 1104 * * It will add the Bootstrap Js and CSS 1105 * * Make all script and resources defer 1106 * @throws Exception 1107 */ 1108 static function handleBootstrapMetaHeaders(Doku_Event &$event, $param) 1109 { 1110 1111 $debug = tpl_getConf('debug'); 1112 if ($debug) { 1113 self::addAsHtmlComment('Request: ' . json_encode($_REQUEST)); 1114 } 1115 1116 1117 $newHeaderTypes = array(); 1118 $bootstrapHeaders = self::getBootstrapMetaHeaders(); 1119 $eventHeaderTypes = $event->data; 1120 foreach ($eventHeaderTypes as $headerType => $headerData) { 1121 switch ($headerType) { 1122 1123 case "link": 1124 // index, rss, manifest, search, alternate, stylesheet 1125 // delete edit 1126 $bootstrapCss = $bootstrapHeaders[$headerType]['css']; 1127 $headerData[] = $bootstrapCss; 1128 1129 // preload all CSS is an heresy as it creates a FOUC (Flash of non-styled element) 1130 // but we know it only now and this is it 1131 $cssPreloadConf = tpl_getConf(self::CONF_PRELOAD_CSS); 1132 $newLinkData = array(); 1133 foreach ($headerData as $linkData) { 1134 switch ($linkData['rel']) { 1135 case 'edit': 1136 break; 1137 case 'preload': 1138 /** 1139 * Preload can be set at the array level with the critical attribute 1140 * If the preload attribute is present 1141 * We get that for instance for css animation style sheet 1142 * that are not needed for rendering 1143 */ 1144 if (isset($linkData["as"])) { 1145 if ($linkData["as"] === "style") { 1146 $newLinkData[] = TplUtility::captureStylePreloadingAndTransformToPreloadCssTag($linkData); 1147 continue 2; 1148 } 1149 } 1150 $newLinkData[] = $linkData; 1151 break; 1152 case 'stylesheet': 1153 if ($cssPreloadConf) { 1154 $newLinkData[] = TplUtility::captureStylePreloadingAndTransformToPreloadCssTag($linkData); 1155 continue 2; 1156 } 1157 $newLinkData[] = $linkData; 1158 break; 1159 default: 1160 $newLinkData[] = $linkData; 1161 break; 1162 } 1163 } 1164 1165 $newHeaderTypes[$headerType] = $newLinkData; 1166 break; 1167 1168 case "script": 1169 1170 /** 1171 * Do we delete the dokuwiki javascript ? 1172 */ 1173 $scriptToDeletes = []; 1174 if (empty($_SERVER['REMOTE_USER']) && tpl_getConf(TplUtility::CONF_DISABLE_BACKEND_JAVASCRIPT, 0)) { 1175 $scriptToDeletes = [ 1176 //'JSINFO', Don't delete Jsinfo !! It contains metadata information (that is used to get context) 1177 'js.php' 1178 ]; 1179 if (TplUtility::getBootStrapMajorVersion() == "5") { 1180 // bs 5 does not depends on jquery 1181 $scriptToDeletes[] = "jquery.php"; 1182 } 1183 } 1184 1185 /** 1186 * The new script array 1187 */ 1188 $newScriptData = array(); 1189 // A variable to hold the Jquery scripts 1190 // jquery-migrate, jquery, jquery-ui ou jquery.php 1191 // see https://www.dokuwiki.org/config:jquerycdn 1192 $jqueryDokuScripts = array(); 1193 foreach ($headerData as $scriptData) { 1194 1195 foreach ($scriptToDeletes as $scriptToDelete) { 1196 if (isset($scriptData["_data"]) && !empty($scriptData["_data"])) { 1197 $haystack = $scriptData["_data"]; 1198 } else { 1199 $haystack = $scriptData["src"]; 1200 } 1201 if (preg_match("/$scriptToDelete/i", $haystack)) { 1202 continue 2; 1203 } 1204 } 1205 1206 $critical = false; 1207 if (isset($scriptData["critical"])) { 1208 $critical = $scriptData["critical"]; 1209 unset($scriptData["critical"]); 1210 } 1211 1212 // defer is only for external resource 1213 // if this is not, this is illegal 1214 if (isset($scriptData["src"])) { 1215 if (!$critical) { 1216 $scriptData['defer'] = null; 1217 } 1218 } 1219 1220 if (isset($scriptData["type"])) { 1221 $type = strtolower($scriptData["type"]); 1222 if ($type == "text/javascript") { 1223 unset($scriptData["type"]); 1224 } 1225 } 1226 1227 // The charset attribute on the script element is obsolete. 1228 if (isset($scriptData["charset"])) { 1229 unset($scriptData["charset"]); 1230 } 1231 1232 // Jquery ? 1233 $jqueryFound = false; 1234 // script may also be just an online script without the src attribute 1235 if (array_key_exists('src', $scriptData)) { 1236 $jqueryFound = strpos($scriptData['src'], 'jquery'); 1237 } 1238 if ($jqueryFound === false) { 1239 $newScriptData[] = $scriptData; 1240 } else { 1241 $jqueryDokuScripts[] = $scriptData; 1242 } 1243 1244 } 1245 1246 // Add Jquery at the beginning 1247 $boostrapMajorVersion = TplUtility::getBootStrapMajorVersion(); 1248 if ($boostrapMajorVersion == "4") { 1249 if ( 1250 empty($_SERVER['REMOTE_USER']) 1251 && tpl_getConf(self::CONF_JQUERY_DOKU) == 0 1252 ) { 1253 // We take the Jquery of Bootstrap 1254 $newScriptData = array_merge($bootstrapHeaders[$headerType], $newScriptData); 1255 } else { 1256 // Logged in 1257 // We take the Jqueries of doku and we add Bootstrap 1258 $newScriptData = array_merge($jqueryDokuScripts, $newScriptData); // js 1259 // We had popper of Bootstrap 1260 $newScriptData[] = $bootstrapHeaders[$headerType]['popper']; 1261 // We had the js of Bootstrap 1262 $newScriptData[] = $bootstrapHeaders[$headerType]['js']; 1263 } 1264 } else { 1265 1266 // There is no JQuery in 5 1267 // We had the js of Bootstrap and popper 1268 // Add Jquery before the js.php 1269 $newScriptData = array_merge($jqueryDokuScripts, $newScriptData); // js 1270 // Then add at the top of the top (first of the first) bootstrap 1271 // Why ? Because Jquery should be last to be able to see the missing icon 1272 // https://stackoverflow.com/questions/17367736/jquery-ui-dialog-missing-close-icon 1273 $bootstrap[] = $bootstrapHeaders[$headerType]['popper']; 1274 $bootstrap[] = $bootstrapHeaders[$headerType]['js']; 1275 $newScriptData = array_merge($bootstrap, $newScriptData); 1276 1277 } 1278 1279 1280 $newHeaderTypes[$headerType] = $newScriptData; 1281 break; 1282 case "meta": 1283 $newHeaderData = array(); 1284 foreach ($headerData as $metaData) { 1285 // Content should never be null 1286 // Name may change 1287 // https://www.w3.org/TR/html4/struct/global.html#edef-META 1288 if (!key_exists("content", $metaData)) { 1289 $message = "Strap - The head meta (" . print_r($metaData, true) . ") does not have a content property"; 1290 msg($message, -1, "", "", MSG_ADMINS_ONLY); 1291 if (defined('DOKU_UNITTEST') 1292 ) { 1293 throw new \RuntimeException($message); 1294 } 1295 } else { 1296 $content = $metaData["content"]; 1297 if (empty($content)) { 1298 $messageEmpty = "Strap - the below head meta has an empty content property (" . print_r($metaData, true) . ")"; 1299 msg($messageEmpty, -1, "", "", MSG_ADMINS_ONLY); 1300 if (defined('DOKU_UNITTEST') 1301 ) { 1302 throw new \RuntimeException($messageEmpty); 1303 } 1304 } else { 1305 $newHeaderData[] = $metaData; 1306 } 1307 } 1308 } 1309 $newHeaderTypes[$headerType] = $newHeaderData; 1310 break; 1311 case "noscript": // https://github.com/ComboStrap/dokuwiki-plugin-gtm/blob/master/action.php#L32 1312 case "style": 1313 $newHeaderTypes[$headerType] = $headerData; 1314 break; 1315 default: 1316 $message = "Strap - The header type ($headerType) is unknown and was not controlled."; 1317 $newHeaderTypes[$headerType] = $headerData; 1318 msg($message, -1, "", "", MSG_ADMINS_ONLY); 1319 if (defined('DOKU_UNITTEST') 1320 ) { 1321 throw new \RuntimeException($message); 1322 } 1323 } 1324 } 1325 1326 if ($debug) { 1327 self::addAsHtmlComment('Script Header : ' . json_encode($newHeaderTypes['script'])); 1328 } 1329 $event->data = $newHeaderTypes; 1330 1331 1332 } 1333 1334 /** 1335 * Returns the icon link as created by https://realfavicongenerator.net/ 1336 * 1337 * 1338 * 1339 * @return string 1340 */ 1341 static function renderFaviconMetaLinks() 1342 { 1343 1344 $return = ''; 1345 1346 // FavIcon.ico 1347 $possibleLocation = array(':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'); 1348 $return .= '<link rel="shortcut icon" href="' . tpl_getMediaFile($possibleLocation, true) . '" />' . NL; 1349 1350 // Icon Png 1351 $possibleLocation = array(':wiki:favicon-32x32.png', ':favicon-32x32.png', 'images/favicon-32x32.png'); 1352 $return .= '<link rel="icon" type="image/png" sizes="32x32" href="' . tpl_getMediaFile($possibleLocation, true) . '"/>'; 1353 1354 $possibleLocation = array(':wiki:favicon-16x16.png', ':favicon-16x16.png', 'images/favicon-16x16.png'); 1355 $return .= '<link rel="icon" type="image/png" sizes="16x16" href="' . tpl_getMediaFile($possibleLocation, true) . '"/>'; 1356 1357 // Apple touch icon 1358 $possibleLocation = array(':wiki:apple-touch-icon.png', ':apple-touch-icon.png', 'images/apple-touch-icon.png'); 1359 $return .= '<link rel="apple-touch-icon" href="' . tpl_getMediaFile($possibleLocation, true) . '" />' . NL; 1360 1361 return $return; 1362 1363 } 1364 1365 static function renderPageTitle() 1366 { 1367 1368 global $conf; 1369 global $ID; 1370 $title = tpl_pagetitle($ID, true) . ' |' . $conf["title"]; 1371 // trigger event here 1372 Event::createAndTrigger('TPL_TITLE_OUTPUT', $title, '\ComboStrap\TplUtility::callBackPageTitle', true); 1373 return true; 1374 1375 } 1376 1377 /** 1378 * Print the title that we get back from the event TPL_TITLE_OUTPUT 1379 * triggered by the function {@link tpl_strap_title()} 1380 * @param $title 1381 */ 1382 static function callBackPageTitle($title) 1383 { 1384 echo $title; 1385 } 1386 1387 /** 1388 * 1389 * Set a template conf value 1390 * 1391 * To set a template configuration, you need to first load them 1392 * and there is no set function in template.php 1393 * 1394 * @param $confName - the configuration name 1395 * @param $confValue - the configuration value 1396 */ 1397 static function setConf($confName, $confValue) 1398 { 1399 1400 /** 1401 * Env variable 1402 */ 1403 global $conf; 1404 $template = $conf['template']; 1405 1406 if ($template != "strap") { 1407 throw new \RuntimeException("This is not the strap template, in test, active it in setup"); 1408 } 1409 1410 /** 1411 * Make sure to load the configuration first by calling getConf 1412 */ 1413 $actualValue = tpl_getConf($confName); 1414 if ($actualValue === false) { 1415 1416 self::reloadConf(); 1417 1418 // Check that the conf was loaded 1419 if (tpl_getConf($confName) === false) { 1420 throw new \RuntimeException("The configuration (" . $confName . ") returns no value or has no default"); 1421 } 1422 } 1423 1424 $conf['tpl'][$template][$confName] = $confValue; 1425 1426 } 1427 1428 /** 1429 * When running multiple test, the function {@link tpl_getConf()} 1430 * does not reload the configuration twice 1431 */ 1432 static function reloadConf() 1433 { 1434 /** 1435 * Env variable 1436 */ 1437 global $conf; 1438 $template = $conf['template']; 1439 1440 $tconf = tpl_loadConfig(); 1441 if ($tconf !== false) { 1442 foreach ($tconf as $key => $value) { 1443 if (isset($conf['tpl'][$template][$key])) continue; 1444 $conf['tpl'][$template][$key] = $value; 1445 } 1446 } 1447 } 1448 1449 1450 /** 1451 * Send a message to a manager and log it 1452 * Fail if in test 1453 * @param string $message 1454 * @param int $level - the level see LVL constant 1455 * @param string $canonical - the canonical 1456 */ 1457 static function msg($message, $level = self::LVL_MSG_ERROR, $canonical = "strap") 1458 { 1459 $strapUrl = self::getStrapUrl(); 1460 $prefix = "<a href=\"$strapUrl\">Strap</a>"; 1461 $prefix = '<a href="https://combostrap.com/' . $canonical . '">' . ucfirst($canonical) . '</a>'; 1462 1463 $htmlMsg = $prefix . " - " . $message; 1464 if ($level != self::LVL_MSG_DEBUG) { 1465 msg($htmlMsg, $level, '', '', MSG_MANAGERS_ONLY); 1466 } 1467 /** 1468 * Print to a log file 1469 * Note: {@link dbg()} dbg print to the web page 1470 */ 1471 $prefix = 'strap'; 1472 if ($canonical != null) { 1473 $prefix .= ' - ' . $canonical; 1474 } 1475 $msg = $prefix . ' - ' . $message; 1476 dbglog($msg); 1477 if (defined('DOKU_UNITTEST') && ($level == self::LVL_MSG_WARNING || $level == self::LVL_MSG_ERROR)) { 1478 throw new \RuntimeException($msg); 1479 } 1480 } 1481 1482 /** 1483 * @param bool $prependTOC 1484 * @return false|string - Adapted from {@link tpl_content()} to return the HTML 1485 * Call {@link html_show()} from {@link Show::tplContent()} 1486 */ 1487 static function tpl_content(bool $prependTOC = true) 1488 { 1489 global $ACT; 1490 global $REV; 1491 global $DATE_AT; 1492 1493 global $INFO; 1494 $INFO['prependTOC'] = $prependTOC; 1495 1496 try { 1497 ob_start(); 1498 if ( 1499 class_exists("ComboStrap\Page") 1500 && $ACT === "show" // show only 1501 && ($REV === 0 && $DATE_AT === "") // ro revisions 1502 ) { 1503 /** 1504 * The code below replace the other block 1505 * to take the snippet management into account 1506 * (ie we write them when the {@link HtmlDocument::storeContent() document is stored into cache) 1507 */ 1508 global $ID; 1509 /** 1510 * The action null does nothing. 1511 * See {@link Event::trigger()} 1512 */ 1513 Event::createAndTrigger('TPL_ACT_RENDER', $ACT, null); 1514 1515 /** 1516 * The code below replace {@link html_show()} 1517 */ 1518 $html_output = Page::createPageFromId($ID) 1519 ->toXhtml(); 1520 // section editing show only if not a revision and $ACT=show 1521 // which is the case in this block 1522 $showEdit = true; 1523 $html_output = html_secedit($html_output, $showEdit); 1524 1525 /** 1526 * Add the buffer (eventually) 1527 * 1528 * Not needed with our code, may be with other plugins, it should not as the 1529 * syntax plugin should use the {@link \Doku_Renderer::$doc) 1530 * 1531 */ 1532 $html_output .= ob_get_clean(); 1533 } else { 1534 Event::createAndTrigger('TPL_ACT_RENDER', $ACT, 'tpl_content_core'); 1535 $html_output = ob_get_clean(); 1536 } 1537 1538 1539 /** 1540 * The action null does nothing. 1541 * See {@link Event::trigger()} 1542 */ 1543 Event::createAndTrigger('TPL_CONTENT_DISPLAY', $html_output, null); 1544 1545 return $html_output; 1546 } catch (Exception $e) { 1547 $message = "Unfortunately, an error has occurred during the rendering of the main content. The error was logged."; 1548 LogUtility::log2file($message . " Error: " . $e->getTraceAsString()); 1549 return $message; 1550 } 1551 } 1552 1553 static function getPageHeader(): string 1554 { 1555 1556 $navBarPageName = TplUtility::getHeaderSlotPageName(); 1557 if ($id = page_findnearest($navBarPageName)) { 1558 1559 $header = TplUtility::renderSlot($id); 1560 1561 } else { 1562 1563 $domain = self::getApexDomainUrl(); 1564 $header = '<div class="container p-3" style="text-align: center;position:relative;z-index:100">Welcome to the <a href="' . $domain . '/">Strap template</a>.<br/> 1565 If you don\'t known the <a href="https://combostrap.com/">ComboStrap</a>, it\'s recommended to follow the <a href="' . $domain . '/getting_started">Getting Started Guide</a>.<br/> 1566 Otherwise, to create a menu bar in the header, create a page with the id (' . html_wikilink(':' . $navBarPageName) . ') and the <a href="' . $domain . '/menubar">menubar component</a>. 1567 </div>'; 1568 1569 } 1570 // No header on print 1571 return "<div class=\"d-print-none\">$header</div>"; 1572 1573 } 1574 1575 static function getFooter(): string 1576 { 1577 $domain = self::getApexDomainUrl(); 1578 1579 $footerPageName = TplUtility::getFooterSlotPageName(); 1580 if ($nearestFooterPageId = page_findnearest($footerPageName)) { 1581 $footer = TplUtility::renderSlot($nearestFooterPageId); 1582 } else { 1583 $footer = '<div class="container p-3" style="text-align: center">Welcome to the <a href="' . $domain . '/strap">Strap template</a>. To get started, create a page with the id ' . html_wikilink(':' . $footerPageName) . ' to create a footer.</div>'; 1584 } 1585 1586 // No footer on print 1587 return "<div class=\"d-print-none\">$footer</div>"; 1588 } 1589 1590 static function getPoweredBy(): string 1591 { 1592 1593 $domain = self::getApexDomainUrl(); 1594 $version = self::getFullQualifyVersion(); 1595 $poweredBy = "<div class=\"mx-auto\" style=\"width: 300px;text-align: center;margin-bottom: 1rem\">"; 1596 $poweredBy .= " <small><i>Powered by <a href=\"$domain\" title=\"ComboStrap " . $version . "\" style=\"color:#495057\">ComboStrap</a></i></small>"; 1597 $poweredBy .= '</div>'; 1598 return $poweredBy; 1599 } 1600 1601 1602} 1603 1604 1605