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" tabindex="-1"
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