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