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