1<?php /** @noinspection DuplicatedCode */
2
3/**
4 * DokuWiki Mikio Template
5 *
6 * @link    http://dokuwiki.org/template:mikio
7 * @author  James Collins <james.collins@outlook.com.au>
8 * @license GPLv2 (http://www.gnu.org/licenses/gpl-2.0.html)
9 */
10
11namespace dokuwiki\template\mikio;
12
13use Doku_Event;
14use dokuwiki\Menu\PageMenu;
15use dokuwiki\Menu\SiteMenu;
16use dokuwiki\Menu\UserMenu;
17use ParensParser;
18use simple_html_dom;
19
20if (defined('DOKU_INC') === false) {
21    die();
22}
23
24require_once('icons/icons.php');
25require_once('inc/simple_html_dom.php');
26require_once('inc/parens-parser.php');
27
28class Template
29{
30    /**
31     * @var string Template directory path from local FS.
32     */
33    public $tplDir  = '';
34
35    /**
36     * @var string Template directory path from web.
37     */
38    public $baseDir = '';
39
40    /**
41     * @var array Array of Javascript files to include in footer.
42     */
43    public $footerScript = [];
44
45    /**
46     * @var string Notifications from included pages.
47     */
48    private $includedPageNotifications = '';
49
50
51    /**
52     * Class constructor
53     */
54    public function __construct()
55    {
56        $this->tplDir  = tpl_incdir();
57        $this->baseDir = tpl_basedir();
58
59        $this->registerHooks();
60    }
61
62    /**
63     * Returns the instance of the class
64     *
65     * @return  Template        class instance
66     */
67    public static function getInstance(): Template
68    {
69        static $instance = null;
70
71        if (empty($instance) === true) {
72            $instance = new Template();
73        }
74
75        return $instance;
76    }
77
78
79    /**
80     * Register the themes hooks into Dokuwiki
81     *
82     * @return void
83     */
84    private function registerHooks()
85    {
86        global $EVENT_HANDLER;
87
88        $events_dispatcher = [
89            'TPL_METAHEADER_OUTPUT'     => 'metaheadersHandler'
90        ];
91
92        foreach ($events_dispatcher as $event => $method) {
93            $EVENT_HANDLER->register_hook($event, 'BEFORE', $this, $method);
94        }
95    }
96
97
98    /**
99     * Meta handler hook for DokuWiki
100     *
101     * @param   Doku_Event $event DokuWiki Event.
102     * @return  void
103     * @noinspection PhpUnused
104     */
105    public function metaHeadersHandler(Doku_Event $event)
106    {
107        global $MIKIO_ICONS;
108        global $conf;
109
110        global $MIKIO_TEMPLATE;
111        $MIKIO_TEMPLATE = '123';
112
113        $this->includePage('theme', false);
114
115        $stylesheets    = [];
116        $scripts        = [];
117
118        if (empty($this->getConf('customTheme')) === false) {
119            if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/style.less') === true) {
120                $stylesheets[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/style.less';
121            } else {
122                if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/style.css') === true) {
123                    $stylesheets[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/style.css';
124                }
125            }
126            if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/script.js') === true) {
127                $scripts[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/script.js';
128            }
129        }
130
131        if (is_array($MIKIO_ICONS) === true && empty($this->getConf('iconTag', 'icon')) === false) {
132            $icons = [];
133            foreach ($MIKIO_ICONS as $icon) {
134                if (isset($icon['name']) === true && isset($icon['css']) === true && isset($icon['insert']) === true) {
135                    $icons[] = $icon;
136
137                    if (empty($icon['css']) === false) {
138                        if (strpos($icon['css'], '//') === false) {
139                            $stylesheets[] = $this->baseDir . 'icons/' . $icon['css'];
140                        } else {
141                            $stylesheets[] = $icon['css'];
142                        }
143                    }
144                }
145            }
146            $MIKIO_ICONS = $icons;
147        } else {
148            $MIKIO_ICONS = [];
149        }
150
151        $scripts[] = $this->baseDir . 'assets/mikio-typeahead.js';
152        $scripts[] = $this->baseDir . 'assets/mikio.js';
153
154        if ($this->getConf('useLESS') === true) {
155            $stylesheets[] = $this->baseDir . 'assets/mikio.less';
156        } else {
157            $stylesheets[] = $this->baseDir . 'assets/mikio.css';
158        }
159
160        /* MikioPlugin Support */
161        if (plugin_load('action', 'mikioplugin') !== null) {
162            if ($this->getConf('useLESS') === true) {
163                $stylesheets[] = $this->baseDir . 'assets/mikioplugin.less';
164            } else {
165                $stylesheets[] = $this->baseDir . 'assets/mikioplugin.css';
166            }
167        }
168
169        $set = [];
170        foreach ($stylesheets as $style) {
171            if (in_array($style, $set) === false) {
172                if (strcasecmp(substr($style, -5), '.less') === 0 && $this->getConf('useLESS') === true) {
173                    $style = $this->baseDir . 'css.php?css=' . str_replace($this->baseDir, '', $style);
174                }
175
176                array_unshift($event->data['link'], [
177                    'type' => 'text/css',
178                    'rel'  => 'stylesheet',
179                    'href' => $style
180                ]);
181            }
182            $set[] = $style;
183        }
184
185        $set = [];
186        foreach ($scripts as $script) {
187            if (in_array($script, $set) === false) {
188                $script_params = [
189                    'type'  => 'text/javascript',
190                    '_data' => '',
191                    'src'   => $script
192                ];
193
194                // equal to or greator than hogfather
195                if ($this->dwVersionNumber() >= 20200729 || $this->dwVersionNumber() == 0) {
196                    // greator than hogfather - defer always on
197                    if ($this->dwVersionNumber() >= 20200729 || $this->dwVersionNumber() == 0) {
198                        $script_params += ['defer' => 'defer'];
199                    } else {
200                        // hogfather - defer always on unless $conf['defer_js'] is false
201                        if (array_key_exists('defer_js', $conf) === false || $conf['defer_js'] === true) {
202                            $script_params += ['defer' => 'defer'];
203                        }
204                    }
205                }
206
207                $event->data['script'][] = $script_params;
208            }//end if
209            $set[] = $script;
210        }//end foreach
211    }
212
213
214    /**
215     * Print or return the footer metadata
216     *
217     * @param   boolean $print Print the data to buffer.
218     * @return  string         HTML footer meta data
219     */
220    public function includeFooterMeta(bool $print = true): string
221    {
222        $html = '';
223
224        if (count($this->footerScript) > 0) {
225            $html .= '<script type="text/javascript">function mikioFooterRun() {';
226            foreach ($this->footerScript as $script) {
227                $html .= $script . ';';
228            }
229            $html .= '}</script>';
230        }
231
232
233        if ($print === true) {
234            echo $html;
235        }
236        return $html;
237    }
238
239
240    // phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
241
242
243    /**
244     * Retreive and parse theme configuration options
245     *
246     * @param   string $key     The configuration key to retreive.
247     * @param   mixed  $default If key doesn't exist, return this value.
248     * @return  mixed           parsed value of configuration
249     */
250    public function getConf(string $key, $default = false)
251    {
252        $value = tpl_getConf($key, $default);
253
254        $data = [
255            ['keys' => ['navbarDWMenuType'],                'type' => 'choice',
256                'values' => ['both', 'icons', 'text']
257            ],
258            ['keys' => ['navbarDWMenuCombine'],             'type' => 'choice',
259                'values' => ['combine', 'seperate', 'dropdown']
260            ],
261            ['keys' => ['navbarPosLeft', 'navbarPosMiddle', 'navbarPosRight'],
262                'type' => 'choice',
263                'values' => ['none', 'custom', 'search', 'dokuwiki'],
264                'default' => [
265                    'navbarPosLeft' => 'none',
266                    'navbarPosMiddle' => 'search',
267                    'navbarPosRight' => 'dokuwiki'
268                ]
269            ],
270            ['keys' => ['navbarItemShowCreate', 'navbarItemShowShow', 'navbarItemShowRevs', 'navbarItemShowBacklink',
271                'navbarItemShowRecent', 'navbarItemShowMedia', 'navbarItemShowIndex', 'navbarItemShowProfile',
272                'navbarItemShowAdmin'
273            ],
274                'type' => 'choice',
275                'values' => ['always', 'logged in', 'logged out', 'never']
276            ],
277            ['keys' => ['navbarItemShowLogin', 'navbarItemShowLogout'],
278                'type' => 'choice',
279                'values' => ['always', 'never']
280            ],
281            ['keys' => ['searchButton'],                    'type' => 'choice',
282                'values' => ['icon', 'text']
283            ],
284            ['keys' => ['breadcrumbPosition', 'youareherePosition'],
285                'type' => 'choice',
286                'values' => ['top', 'hero', 'page', 'none']
287            ],
288            ['keys' => ['youarehereHome'],                  'type' => 'choice',
289                'values' => ['page title', 'home', 'icon', 'none']
290            ],
291            ['keys' => ['sidebarLeftRow1', 'sidebarLeftRow2', 'sidebarLeftRow3', 'sidebarLeftRow4'],
292                'type' => 'choice',
293                'values' => ['none', 'logged in user', 'search', 'content', 'tags'],
294                'default' => [
295                    'sidebarLeftRow1' => 'logged in user',
296                    'sidebarLeftRow2' => 'search',
297                    'sidebarLeftRow3' => 'content'
298                ]
299            ],
300            ['keys' => ['pageToolsFloating', 'pageToolsFooter'],
301                'type' => 'choice',
302                'values' => ['always', 'none', 'page editors']
303            ],
304            ['keys' => ['pageToolsShowCreate', 'pageToolsShowEdit', 'pageToolsShowRevs', 'pageToolsShowBacklink',
305                'pageToolsShowTop'
306            ],
307                'type' => 'choice',
308                'values' => ['always', 'logged in', 'logged out', 'never']
309            ],
310            ['keys' => ['showNotifications'],               'type' => 'choice',
311                'values' => ['admin', 'always', 'none', '', 'never']
312            ],
313            ['keys' => ['licenseType'],                     'type' => 'choice',
314                'values' => ['badge', 'button', 'none']
315            ],
316            ['keys' => ['navbarUseTitleIcon'],              'type' => 'bool'],
317            ['keys' => ['navbarUseTitleText'],              'type' => 'bool'],
318            ['keys' => ['navbarUseTaglineText'],            'type' => 'bool'],
319            ['keys' => ['navbarShowSub'],                   'type' => 'bool'],
320            ['keys' => ['heroTitle'],                       'type' => 'bool'],
321            ['keys' => ['heroImagePropagation'],            'type' => 'bool'],
322            ['keys' => ['breadcrumbPrefix'],                'type' => 'bool'],
323            ['keys' => ['breadcrumbSep'],                   'type' => 'bool'],
324            ['keys' => ['youareherePrefix'],                'type' => 'bool'],
325            ['keys' => ['youarehereSep'],                   'type' => 'bool'],
326            ['keys' => ['sidebarShowLeft'],                 'type' => 'bool'],
327            ['keys' => ['sidebarShowRight'],                'type' => 'bool'],
328            ['keys' => ['tocFull'],                         'type' => 'bool'],
329            ['keys' => ['footerSearch'],                    'type' => 'bool'],
330            ['keys' => ['licenseImageOnly'],                'type' => 'bool'],
331            ['keys' => ['includePageUseACL'],               'type' => 'bool'],
332            ['keys' => ['includePagePropagate'],            'type' => 'bool'],
333            ['keys' => ['youarehereHideHome'],              'type' => 'bool'],
334            ['keys' => ['tagsConsolidate'],                 'type' => 'bool'],
335            ['keys' => ['footerInPage'],                    'type' => 'bool'],
336            ['keys' => ['sidebarMobileDefaultCollapse'],    'type' => 'bool'],
337            ['keys' => ['sidebarAlwaysShowLeft'],           'type' => 'bool'],
338            ['keys' => ['sidebarAlwaysShowRight'],          'type' => 'bool'],
339            ['keys' => ['searchUseTypeahead'],              'type' => 'bool'],
340            ['keys' => ['showLightDark'],                   'type' => 'bool'],
341            ['keys' => ['autoLightDark'],                   'type' => 'bool'],
342            ['keys' => ['youarehereShowLast'],              'type' => 'int'],
343
344            ['keys' => ['iconTag'],                         'type' => 'string'],
345            ['keys' => ['customTheme'],                     'type' => 'string'],
346            ['keys' => ['navbarCustomMenuText'],            'type' => 'string'],
347            ['keys' => ['breadcrumbPrefixText'],            'type' => 'string'],
348            ['keys' => ['breadcrumbSepText'],               'type' => 'string'],
349            ['keys' => ['youareherePrefixText'],            'type' => 'string'],
350            ['keys' => ['youarehereSepText'],               'type' => 'string'],
351            ['keys' => ['footerPageInfoText'],              'type' => 'string'],
352            ['keys' => ['footerCustomMenuText'],            'type' => 'string'],
353            ['keys' => ['brandURLGuest'],                   'type' => 'string'],
354            ['keys' => ['brandURLUser'],                    'type' => 'string'],
355
356            ['keys' => ['useLESS'],                         'type' => 'bool'],
357        ];
358
359        foreach ($data as $row) {
360            // does not check case....
361            if (in_array($key, $row['keys']) === true) {
362                if (array_key_exists('type', $row) === true) {
363                    switch ($row['type']) {
364                        case 'bool':
365                            return (bool) $value;
366                        case 'int':
367                            return (int) $value;
368                        case 'string':
369                            return $value;
370                    }//end switch
371                }//end if
372
373                if (in_array($value, $row['values']) === true) {
374                    return $value;
375                }
376
377                if (array_key_exists('default', $row) === true) {
378                    if (is_array($row['default']) === true) {
379                        if (array_key_exists($key, $row['default']) === true) {
380                            return $row['default'][$key];
381                        }
382                    } else {
383                        return $row['default'];
384                    }
385                }
386
387                return reset($row['values']);
388            }//end if
389        }//end foreach
390
391        return $value;
392    }
393
394
395    // phpcs:enable
396
397
398    /**
399     * Check if a page exist in directory or namespace
400     *
401     * @param   string $page Page/namespace to search.
402     * @return  boolean      if page exists
403     */
404    public function pageExists(string $page): bool
405    {
406        ob_start();
407        tpl_includeFile($page . '.html');
408        $html = ob_get_contents();
409        ob_end_clean();
410
411        if (empty($html) === false) {
412            return true;
413        }
414
415        $useACL = $this->getConf('includePageUseACL');
416        $propagate = $this->getConf('includePagePropagate');
417
418        if ($propagate === true) {
419            if (page_findnearest($page, $useACL) !== false) {
420                return true;
421            }
422        } elseif ($useACL === true && auth_quickaclcheck($page) !== AUTH_NONE) {
423            return true;
424        }
425
426        return false;
427    }
428
429
430    /**
431     * Print or return page from directory or namespace
432     *
433     * @param   string  $page         Page/namespace to include.
434     * @param   boolean $print        Print content.
435     * @param   boolean $parse        Parse content before printing/returning.
436     * @param   string  $classWrapper Wrap page in a div with class.
437     * @return  string                contents of page found
438     */
439    public function includePage(string $page, bool $print = true, bool $parse = true, string $classWrapper = ''): string
440    {
441        ob_start();
442        tpl_includeFile($page . '.html');
443        $html = ob_get_contents();
444        ob_end_clean();
445
446        if (empty($html) === true) {
447            $useACL = $this->getConf('includePageUseACL');
448            $propagate = $this->getConf('includePagePropagate');
449
450            ob_start();
451            $html = tpl_include_page($page, false, $propagate, $useACL);
452            $this->includedPageNotifications .= ob_get_contents();
453            ob_end_clean();
454        }
455
456        if (empty($html) === false && $parse === true) {
457            $html = $this->parseContent($html);
458        }
459
460        if (empty($classWrapper) === false && empty($html) === false) {
461            $html = '<div class="' . $classWrapper . '">' . $html . '</div>';
462        }
463
464        if ($print === true) {
465            echo $html;
466        }
467        return $html;
468    }
469
470
471    /**
472     * Print or return logged-in user information
473     *
474     * @param   boolean $print Print content.
475     * @return  string         user information
476     */
477    public function includeLoggedIn(bool $print = true): string
478    {
479        $html = '';
480
481        if (empty($_SERVER['REMOTE_USER']) === false) {
482            $html .= '<div class="mikio-user-info">';
483            ob_start();
484            tpl_userinfo();
485            $html .= ob_get_contents();
486            ob_end_clean();
487            $html .= '</div>';
488        }
489
490        if ($print === true) {
491            echo $html;
492        }
493        return $html;
494    }
495
496
497    /**
498     * Print or return DokuWiki Menu
499     *
500     * @param   boolean $print Print content.
501     * @return  string         contents of the menu
502     */
503    public function includeDWMenu(bool $print = true): string
504    {
505        global $lang;
506        global $USERINFO;
507
508        $loggedIn = (is_array($USERINFO) === true && count($USERINFO) > 0);
509        $html = '<ul class="mikio-nav">';
510
511        $pageToolsMenu = [];
512        $siteToolsMenu = [];
513        $userToolsMenu = [];
514
515        $showIcons  = ($this->getConf('navbarDWMenuType') != 'text');
516        $showText   = ($this->getConf('navbarDWMenuType') != 'icons');
517        $isDropDown = ($this->getConf('navbarDWMenuCombine') != 'seperate');
518
519        $items = (new PageMenu())->getItems();
520        foreach ($items as $item) {
521            if ($item->getType() !== 'top') {
522                $itemHtml = '';
523
524                $showItem = $this->getConf('navbarItemShow' . ucfirst($item->getType()));
525                if (
526                    $showItem !== false && (strcasecmp($showItem, 'always') === 0 ||
527                    (strcasecmp($showItem, 'logged in') === 0 && $loggedIn === true) ||
528                    (strcasecmp($showItem, 'logged out') === 0 && $loggedIn === false))
529                ) {
530                    $title = isset($attr['title']) && $attr['title'] !== 0 ? $attr['title'] : $item->getTitle();
531
532                    $itemHtml .= '<a class="mikio-nav-link ' . ($isDropDown === true ? 'mikio-dropdown-item' : '') .
533                        ' ' . $item->getType() . '" href="' . $item->getLink() . '" title="' . $title . '"' . (isset($attr['accesskey']) && $attr['accesskey'] !== '' ? ' accesskey="' . $attr['accesskey'] . '"' : '') . '>';
534                    if ($showIcons === true) {
535                        $itemHtml .= '<span class="mikio-icon">' . inlineSVG($item->getSvg()) . '</span>';
536                    }
537                    if ($showText === true || $isDropDown === true) {
538                        $itemHtml .= '<span>' . $item->getLabel() . '</span>';
539                    }
540                    $itemHtml .= '</a>';
541
542                    $pageToolsMenu[] = $itemHtml;
543                }
544            }//end if
545        }//end foreach
546
547        $items = (new SiteMenu())->getItems('action');
548        foreach ($items as $item) {
549            $itemHtml = '';
550
551            $showItem = $this->getConf('navbarItemShow' . ucfirst($item->getType()));
552            if (
553                $showItem !== false && (strcasecmp($showItem, 'always') === 0 ||
554                (strcasecmp($showItem, 'logged in') === 0 && $loggedIn === true) ||
555                (strcasecmp($showItem, 'logged out') === 0 && $loggedIn === false))
556            ) {
557                $itemHtml .= '<a class="mikio-nav-link ' . ($isDropDown === true ? 'mikio-dropdown-item' : '') . ' ' .
558                    $item->getType() . '" href="' . $item->getLink() . '" title="' . $item->getTitle() . '">';
559                if ($showIcons === true) {
560                    $itemHtml .= '<span class="mikio-icon">' . inlineSVG($item->getSvg()) . '</span>';
561                }
562                if ($showText === true || $isDropDown === true) {
563                    $itemHtml .= '<span>' . $item->getLabel() . '</span>';
564                }
565                $itemHtml .= '</a>';
566
567                $siteToolsMenu[] = $itemHtml;
568            }
569        }//end foreach
570
571        $items = (new UserMenu())->getItems('action');
572        foreach ($items as $item) {
573            $itemHtml = '';
574
575            $showItem = $this->getConf('navbarItemShow' . ucfirst($item->getType()));
576            if (
577                $showItem !== false && (strcasecmp($showItem, 'always') === 0 ||
578                (strcasecmp($showItem, 'logged in') === 0 && $loggedIn === true) ||
579                (strcasecmp($showItem, 'logged out') === 0 && $loggedIn === false))
580            ) {
581                $itemHtml .= '<a class="mikio-nav-link' . ($isDropDown === true ? ' mikio-dropdown-item' : '') . ' ' .
582                $item->getType() . '" href="' . $item->getLink() . '" title="' . $item->getTitle() . '">';
583                if ($showIcons === true) {
584                    $itemHtml .= '<span class="mikio-icon">' . inlineSVG($item->getSvg()) . '</span>';
585                }
586                if ($showText === true || $isDropDown === true) {
587                    $itemHtml .= '<span>' . $item->getLabel() . '</span>';
588                }
589                $itemHtml .= '</a>';
590
591                $userToolsMenu[] = $itemHtml;
592            }
593        }//end foreach
594
595
596        switch ($this->getConf('navbarDWMenuCombine')) {
597            case 'dropdown':
598                $html .= '<li id="dokuwiki__pagetools" class="mikio-nav-dropdown">';
599                $html .= '<a id="mikio_dropdown_pagetools" class="nav-link dropdown-toggle" href="#" role="button"
600data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' .
601                ($showIcons === true ? $this->mikioInlineIcon('file') : '') .
602                ($showText === true ? $lang['page_tools'] : '<span class="mikio-small-only">' . $lang['page_tools'] .
603                '</span>') . '</a>';
604
605                $html .= '<div class="mikio-dropdown closed">' . implode('', $pageToolsMenu);
606
607                $html .= '</div>';
608                $html .= '</li>';
609
610                $html .= '<li id="dokuwiki__sitetools" class="mikio-nav-dropdown">';
611                $html .= '<a id="mikio_dropdown_sitetools" class="nav-link dropdown-toggle" href="#" role="button"
612data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' .
613                    ($showIcons === true ? $this->mikioInlineIcon('gear') : '') .
614                    ($showText === true ? $lang['site_tools'] : '<span class="mikio-small-only">' .
615                    $lang['site_tools'] . '</span>') . '</a>';
616
617                $html .= '<div class="mikio-dropdown closed">' . implode('', $siteToolsMenu);
618
619                $html .= '</div>';
620                $html .= '</li>';
621
622                $html .= '<li id="dokuwiki__usertools" class="mikio-nav-dropdown">';
623                $html .= '<a id="mikio_dropdown_usertools" class="nav-link dropdown-toggle" href="#" role="button"
624data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' .
625                    ($showIcons === true ? $this->mikioInlineIcon('user') : '') .
626                    ($showText === true ? $lang['user_tools'] : '<span class="mikio-small-only">' .
627                    $lang['user_tools'] . '</span>') . '</a>';
628
629                $html .= '<div class="mikio-dropdown closed">' . implode('', $userToolsMenu);
630
631                $html .= '</div>';
632                $html .= '</li>';
633
634                break;
635
636            case 'combine':
637                $html .= '<li class="mikio-nav-dropdown">';
638                $html .= '<a class="mikio-nav-link" href="#">' .
639                    ($showIcons === true ? $this->mikioInlineIcon('wrench') : '') .
640                    ($showText === true ? tpl_getLang('tools-menu') : '<span class="mikio-small-only">' .
641                    tpl_getLang('tools-menu') . '</span>') . '</a>';
642                $html .= '<div class="mikio-dropdown closed">';
643
644                $html .= '<h6 class="mikio-dropdown-header">' . $lang['page_tools'] . '</h6>';
645                foreach ($pageToolsMenu as $item) {
646                    $html .= $item;
647                }
648
649                $html .= '<div class="mikio-dropdown-divider"></div>';
650                $html .= '<h6 class="mikio-dropdown-header">' . $lang['site_tools'] . '</h6>';
651                foreach ($siteToolsMenu as $item) {
652                    $html .= $item;
653                }
654
655                $html .= '<div class="mikio-dropdown-divider"></div>';
656                $html .= '<h6 class="mikio-dropdown-header">' . $lang['user_tools'] . '</h6>';
657                foreach ($userToolsMenu as $item) {
658                    $html .= $item;
659                }
660
661                $html .= '</div>';
662                $html .= '</li>';
663                break;
664
665            default:    // seperate
666                foreach ($siteToolsMenu as $item) {
667                    $html .= '<li class="mikio-nav-item">' . $item . '</li>';
668                }
669
670                foreach ($pageToolsMenu as $item) {
671                    $html .= '<li class="mikio-nav-item">' . $item . '</li>';
672                }
673
674                foreach ($userToolsMenu as $item) {
675                    $html .= '<li class="mikio-nav-item">' . $item . '</li>';
676                }
677
678                break;
679        }//end switch
680
681        $translation = plugin_load('helper', 'translation');
682        if ($translation !== null && method_exists($translation, 'showTranslations')) {
683            $html .= '<li id="mikio__translate" class="mikio-nav-dropdown">';
684            $html .= '<a id="mikio_dropdown_translate" class="nav-link dropdown-toggle" href="#" role="button"
685data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' .
686                $this->mikioInlineIcon('language') .
687                 '</a>';
688            $html .= '<div class="mikio-dropdown closed">';
689
690                $html .= $translation->showTranslations();
691
692            $html .= '</div>';
693            $html .= '</li>';
694        }
695
696        if ($this->getConf('showLightDark') === true) {
697            $autoLightDark = $this->getConf('autoLightDark');
698            $html .= '<li class="mikio-darklight">
699<a href="#" class="mikio-control mikio-button mikio-darklight-button">' .
700            ($autoLightDark === true ? $this->mikioInlineIcon('sunmoon', 'mikio-darklight-auto') : '') .
701            $this->mikioInlineIcon('sun', 'mikio-darklight-light') .
702            $this->mikioInlineIcon('moon', 'mikio-darklight-dark') .
703            '</a></li>';
704        }
705
706        $html .= '</ul>';
707
708        if ($print === true) {
709            echo $html;
710        }
711        return $html;
712    }
713
714
715    /**
716     * Create a nav element from a string. <uri>|<title>;
717     *
718     * @param string $str String to generate nav.
719     * @return string     nav elements generated
720     */
721    public function stringToNav(string $str)
722    {
723        $html = '';
724
725        if (empty($str) === false) {
726            $items = explode(';', $str);
727            if (count($items) > 0) {
728                $html .= '<ul class="mikio-nav">';
729                foreach ($items as $item) {
730                    $parts = explode('|', $item);
731                    if ($parts > 1) {
732                        $html .= '<li class="mikio-nav-item"><a class="mikio-nav-link" href="' .
733                            strip_tags($this->getLink(trim($parts[0]))) . '">' . strip_tags(trim($parts[1])) .
734                            '</a></li>';
735                    }
736                }
737                $html .= '</ul>';
738            }
739        }
740
741        return $html;
742    }
743
744    /**
745     * print or return the main navbar
746     *
747     * @param boolean $print   Print the navbar.
748     * @param boolean $showSub Include the sub navbar.
749     * @return string          generated content
750     */
751    public function includeNavbar(bool $print = true, bool $showSub = false)
752    {
753        global $conf, $USERINFO;
754
755        $homeUrl = wl();
756
757        if (plugin_isdisabled('showpageafterlogin') === false) {
758            $p = plugin_load('action', 'showpageafterlogin');
759            if (empty($p) === false) {
760                if (is_array($USERINFO) === true && count($USERINFO) > 0) {
761                    $homeUrl = wl($p->getConf('page_after_login'));
762                }
763            }
764        } else {
765            if (is_array($USERINFO) === true && count($USERINFO) > 0) {
766                $url = $this->getConf('brandURLUser');
767                if (strlen($url) > 0) {
768                    $homeUrl = $url;
769                }
770            } else {
771                $url = $this->getConf('brandURLGuest');
772                if (strlen($url) > 0) {
773                    $homeUrl = $url;
774                }
775            }
776        }
777
778        $html = '';
779
780        $html .= '<nav class="mikio-navbar'  . (($this->getConf('stickyNavbar') === true) ? ' mikio-sticky' : '') .
781            '">';
782        $html .= '<div class="mikio-container">';
783        $html .= '<a class="mikio-navbar-brand" href="' . $homeUrl . '" accesskey="h" title="Home [h]">';
784        if ($this->getConf('navbarUseTitleIcon') === true || $this->getConf('navbarUseTitleText') === true) {
785            // Brand image
786            if ($this->getConf('navbarUseTitleIcon') === true) {
787                $logo = $this->getMediaFile('logo', false);
788                if (empty($logo) === false) {
789                    $width = $this->getConf('navbarTitleIconWidth');
790                    $height = $this->getConf('navbarTitleIconHeight');
791                    $styles = '';
792
793                    if (strlen($width) > 0 || strlen($height) > 0) {
794                        if (ctype_digit($width) === true) {
795                            $styles .= 'max-width:' . intval($width) . 'px;';
796                        } elseif (preg_match('/^\d+(px|rem|em|%)$/', $width) === 1) {
797                            $styles .= 'max-width:' . $width . ';';
798                        } elseif (strcasecmp($width, 'none') === 0) {
799                            $styles .= 'max-width:none;';
800                        }
801
802                        if (ctype_digit($height) === true) {
803                            $styles .= 'max-height:' . intval($height) . 'px;';
804                        } elseif (preg_match('/^\d+(px|rem|em|%)$/', $height) === 1) {
805                            $styles .= 'max-height:' . $height . ';';
806                        } elseif (strcasecmp($height, 'none') === 0) {
807                            $styles .= 'max-height:none;';
808                        }
809
810                        if (strlen($styles) > 0) {
811                            $styles = ' style="' . $styles . '"';
812                        }
813                    }//end if
814
815                    $html .= '<img src="' . $logo . '" class="mikio-navbar-brand-image"' . $styles . '>';
816                }//end if
817            }//end if
818
819            // Brand title
820            if ($this->getConf('navbarUseTitleText') === true) {
821                $html .= '<div class="mikio-navbar-brand-title">';
822                $html .= '<h1 class="mikio-navbar-brand-title-text">' . $conf['title'] . '</h1>';
823                if ($this->getConf('navbarUseTaglineText') === true) {
824                    $html .= '<p class="claim mikio-navbar-brand-title-tagline">' . $conf['tagline'] . '</p>';
825                }
826                $html .= '</div>';
827            }
828        }//end if
829        $html .= '</a>';
830        $html .= '<div class="mikio-navbar-toggle"><span class="icon"></span></div>';
831
832        // Menus
833        $html .= '<div class="mikio-navbar-collapse">';
834
835        $menus = [$this->getConf('navbarPosLeft', 'none'), $this->getConf('navbarPosMiddle', 'none'),
836            $this->getConf('navbarPosRight', 'none')
837        ];
838        foreach ($menus as $menuType) {
839            switch ($menuType) {
840                case 'custom':
841                    $html .= $this->stringToNav($this->getConf('navbarCustomMenuText', ''));
842                    break;
843                case 'search':
844                    $html .= '<div class="mikio-nav-item">';
845                    $html .= $this->includeSearch(false);
846                    $html .= '</div>';
847                    break;
848                case 'dokuwiki':
849                    $html .= $this->includeDWMenu(false);
850                    break;
851            }
852        }
853
854        $html .= '</div>';
855        $html .= '</div>';
856        $html .= '</nav>';
857
858        // Sub Navbar
859        if ($showSub === true) {
860            $sub = $this->includePage('submenu', false);
861            if (empty($sub) === false) {
862                $html .= '<nav class="mikio-navbar mikio-sub-navbar">' . $sub . '</nav>';
863            }
864        }
865
866        if ($print === true) {
867            echo $html;
868        }
869        return $html;
870    }
871
872
873    /**
874     * Is there a sidebar
875     *
876     * @param   string $prefix Sidebar prefix to use when searching.
877     * @return  boolean        if sidebar exists
878     */
879    public function sidebarExists(string $prefix = '')
880    {
881        global $conf;
882
883        if (strcasecmp($prefix, 'left') === 0) {
884            $prefix = '';
885        }
886
887        return $this->pageExists($conf['sidebar' . $prefix]);
888    }
889
890
891    /**
892     * Print or return the sidebar content
893     *
894     * @param   string  $prefix Sidebar prefix to use when searching.
895     * @param   boolean $print  Print the generated content to the output buffer.
896     * @param   boolean $parse  Parse the content.
897     * @return  string          generated content
898     */
899    public function includeSidebar(string $prefix = '', bool $print = true, bool $parse = true)
900    {
901        global $conf, $ID;
902
903        $html = '';
904        $confPrefix = preg_replace('/[^a-zA-Z0-9]/', '', ucwords($prefix));
905        $prefix = preg_replace('/[^a-zA-Z0-9]/', '', strtolower($prefix));
906
907        if (empty($confPrefix) === true) {
908            $confPrefix = 'Left';
909        }
910        if (strcasecmp($prefix, 'left') === 0) {
911            $prefix = '';
912        }
913
914        $sidebarPage = empty($conf[$prefix . 'sidebar']) === true ? $prefix . 'sidebar' : $conf[$prefix . 'sidebar'];
915
916        if (
917            $this->getConf('sidebarShow' . $confPrefix) === true && page_findnearest($sidebarPage) !== false &&
918            p_get_metadata($ID, 'nosidebar', false) === null
919        ) {
920            $content = $this->includePage($sidebarPage . 'header', false);
921            if (empty($content) === false) {
922                $html .= '<div class="mikio-sidebar-header">' . $content . '</div>';
923            }
924
925            if (empty($prefix) === true) {
926                $rows = [$this->getConf('sidebarLeftRow1'), $this->getConf('sidebarLeftRow2'),
927                    $this->getConf('sidebarLeftRow3'), $this->getConf('sidebarLeftRow4')
928                ];
929
930                foreach ($rows as $row) {
931                    switch ($row) {
932                        case 'search':
933                            $html .= $this->includeSearch(false);
934                            break;
935                        case 'logged in user':
936                            $html .= $this->includeLoggedIn(false);
937                            break;
938                        case 'content':
939                            $content = $this->includePage($sidebarPage, false);
940                            if (empty($content) === false) {
941                                $html .= '<div class="mikio-sidebar-content">' . $content . '</div>';
942                            }
943                            break;
944                        case 'tags':
945                            $html .= '<div class="mikio-tags"></div>';
946                    }
947                }
948            } else {
949                $content = $this->includePage($sidebarPage, false);
950                if (empty($content) === false) {
951                    $html .= '<div class="mikio-sidebar-content">' . $content . '</div>';
952                }
953            }//end if
954
955            $content = $this->includePage($sidebarPage . 'footer', false);
956            if (empty($content) === false) {
957                $html .= '<div class="mikio-sidebar-footer">' . $content . '</div>';
958            }
959        }//end if
960
961        if (empty($html) === true) {
962            if (empty($prefix) === true && $this->getConf('sidebarAlwaysShowLeft') === true) {
963                $html = '&nbsp;';
964            }
965            if ($this->getConf('sidebarAlwaysShow' . ucfirst($prefix)) === true) {
966                $html = '&nbsp;';
967            }
968        }
969
970        if (empty($html) === false) {
971            $html = '<aside class="mikio-sidebar mikio-sidebar-' . (empty($prefix) === true ? 'left' : $prefix) .
972                '"><a class="mikio-sidebar-toggle' .
973                ($this->getConf('sidebarMobileDefaultCollapse') === true ? ' closed' : '') . '" href="#">' .
974                tpl_getLang('sidebar-title') . ' <span class="icon"></span></a><div class="mikio-sidebar-collapse">' .
975                $html . '</div></aside>';
976        }
977
978        if ($parse === true) {
979            $html = $this->includeIcons($html);
980        }
981        if ($print === true) {
982            echo $html;
983        }
984
985        return $html;
986    }
987
988
989    /**
990     * Print or return the page tools content
991     *
992     * @param   boolean $print     Print the generated content to the output buffer.
993     * @param   boolean $includeId Include the dw__pagetools id in the element.
994     * @return  string             generated content
995     */
996    public function includePageTools(bool $print = true, bool $includeId = false)
997    {
998        global $USERINFO;
999
1000        $loggedIn = (is_array($USERINFO) === true && count($USERINFO) > 0);
1001        $html = '';
1002
1003        $html .= '<nav' . ($includeId === true ? ' id="dw__pagetools"' : '') . ' class="hidden-print dw__pagetools">';
1004        $html .= '<ul class="tools">';
1005
1006        $items = (new PageMenu())->getItems();
1007        foreach ($items as $item) {
1008            $classes = [];
1009            $classes[] = $item->getType();
1010            $attr = $item->getLinkAttributes();
1011
1012            if (empty($attr['class']) === false) {
1013                $classes = array_merge($classes, explode(' ', $attr['class']));
1014            }
1015
1016            $classes = array_unique($classes);
1017            $title = isset($attr['title']) && $attr['title'] !== 0 ? $attr['title'] : $item->getTitle();
1018
1019            $showItem = $this->getConf('pageToolsShow' . ucfirst($item->getType()), 'always');
1020            if (
1021                $showItem !== false && (strcasecmp($showItem, 'always') === 0 ||
1022                (strcasecmp($showItem, 'logged in') === 0 && $loggedIn === true) ||
1023                (strcasecmp($showItem, 'logged out') === 0 && $loggedIn === true))
1024            ) {
1025                $html .= '<li class="' . implode(' ', $classes) . '">';
1026                $html .= '<a href="' . $item->getLink() . '" class="' . $item->getType() . '" title="' .
1027                    $title . '"' . (isset($attr['accesskey']) && $attr['accesskey'] !== '' ? ' accesskey="' . $attr['accesskey'] . '"' : '') . '><div class="icon">' . inlineSVG($item->getSvg()) .
1028                    '</div><span class="a11y">' . $item->getLabel() . '</span></a>';
1029                $html .= '</li>';
1030            }
1031        }//end foreach
1032
1033        $html .= '</ul>';
1034        $html .= '</nav>';
1035
1036        if ($print === true) {
1037            echo $html;
1038        }
1039        return $html;
1040    }
1041
1042
1043    /**
1044     * Print or return the search bar
1045     *
1046     * @param   boolean $print Print content.
1047     * @return  string         contents of the search bar
1048     */
1049    public function includeSearch(bool $print = true)
1050    {
1051        global $lang, $ID, $ACT, $QUERY;
1052        $html = '';
1053
1054        $html .= '<form class="mikio-search search" action="' . wl() .
1055            '" accept-charset="utf-8" method="get" role="search">';
1056        $html .= '<input type="hidden" name="do" value="search">';
1057        $html .= '<input type="hidden" name="id" value="' . $ID . '">';
1058        $html .= '<input name="q" ';
1059        if ($this->getConf('searchUseTypeahead') === true) {
1060            $html .= 'class="search_typeahead" ';
1061        }
1062        $html .= 'autocomplete="off" type="search" placeholder="' . $lang['btn_search'] . '" value="' .
1063            ((strcasecmp($ACT, 'search') === 0) ? htmlspecialchars($QUERY) : '') . '" accesskey="f" title="[F]" />';
1064        $html .= '<button type="submit" title="' .  $lang['btn_search'] . '">';
1065        if (strcasecmp($this->getConf('searchButton'), 'icon') === 0) {
1066            $html .= $this->mikioInlineIcon('search');
1067        } else {
1068            $html .= $lang['btn_search'];
1069        }
1070        $html .= '</button>';
1071        $html .= '</form>';
1072
1073        if ($print === true) {
1074            echo $html;
1075        }
1076        return $html;
1077    }
1078
1079
1080    /**
1081     * Print or return content
1082     *
1083     * @param   boolean $print Print content.
1084     * @return  string         contents
1085     */
1086    public function includeContent(bool $print = true)
1087    {
1088        ob_start();
1089        tpl_content(false);
1090        $html = ob_get_contents();
1091        ob_end_clean();
1092
1093        $html = $this->includeIcons($html);
1094        $html = $this->parseContent($html);
1095
1096        $html .= '<div style="clear:both"></div>';
1097
1098        if ($this->getConf('heroTitle') === false) {
1099            $html = '<div class="mikio-tags"></div>' . $html;
1100        }
1101
1102        $html = '<div class="mikio-article-content">' . $html . '</div>';
1103
1104        if ($print === true) {
1105            echo $html;
1106        }
1107        return $html;
1108    }
1109
1110    private function custom_tpl_pageinfo($ret = false)
1111    {
1112        global $conf;
1113        global $lang;
1114        global $INFO;
1115        global $ID;
1116
1117        // return if we are not allowed to view the page
1118        if (!auth_quickaclcheck($ID)) {
1119            return false;
1120        }
1121
1122        if ($INFO['exists']) {
1123            $file = $INFO['filepath'];
1124            if (!$conf['fullpath']) {
1125                if ($INFO['rev']) {
1126                    $file = str_replace($conf['olddir'] . '/', '', $file);
1127                } else {
1128                    $file = str_replace($conf['datadir'] . '/', '', $file);
1129                }
1130            }
1131            $file = utf8_decodeFN($file);
1132            $date = dformat($INFO['lastmod']);
1133
1134            $string = $this->getConf('footerPageInfoText', '');
1135
1136            // replace lang items
1137            $string = preg_replace_callback('/%([^%]+)%/', function ($matches) use ($lang) {
1138                return isset($lang[$matches[1]]) ? $lang[$matches[1]] : '';
1139            }, $string);
1140
1141            $options = [
1142                'file' => '<bdi>' . $file . '</bdi>',
1143                'date' => $date,
1144                'user' => $INFO['editor'] ? '<bdi>' . editorinfo($INFO['editor']) . '</bdi>' : $lang['external_edit']
1145            ];
1146
1147            if (!empty($_SERVER['REMOTE_USER'])) {
1148                $options['loggedin'] = true;
1149            }
1150
1151            if ($INFO['locked']) {
1152                $options['locked'] = '<bdi>' . editorinfo($INFO['locked']) . '</bdi>';
1153            }
1154
1155            $parser = new ParensParser();
1156            $result = $parser->parse($string);
1157
1158            $parserIterate = function ($arr, $func) use ($options) {
1159                $str = '';
1160
1161                foreach ($arr as $value) {
1162                    if (is_array($value)) {
1163                        $str .= $func($value, $func);
1164                    } else {
1165                        if (preg_match('/^([a-zA-Z]+)=(.*)/', $value, $matches)) {
1166                            $key = strtolower($matches[1]); // Extract the key (a-zA-Z part)
1167
1168                            if (isset($options[$key])) {
1169                                $str .= $matches[2];
1170                            } else {
1171                                return $str;
1172                            }
1173                        } else {
1174                            $str .= $value;
1175                        }
1176                    }
1177                }//end foreach
1178
1179                return $str;
1180            };
1181
1182            $string = $parserIterate($result, $parserIterate);
1183
1184            $string = preg_replace_callback('/{([^}]+)}/', function ($matches) use ($options) {
1185                $key = strtolower($matches[1]);
1186                return isset($options[$key]) ? $options[$key] : '';
1187            }, $string);
1188
1189            if ($ret) {
1190                return $string;
1191            } else {
1192                echo $string;
1193                return true;
1194            }
1195        }//end if
1196
1197        return false;
1198    }
1199
1200    /**
1201     * Print or return footer
1202     *
1203     * @param   boolean $print Print footer.
1204     * @return  string         HTML string containing footer
1205     */
1206    public function includeFooter(bool $print = true)
1207    {
1208        global $ACT;
1209
1210        $html = '';
1211
1212        $html .= '<footer class="mikio-footer">';
1213        $html .= '<div class="doc">' . $this->custom_tpl_pageinfo(true) . '</div>';
1214        $html .= $this->includePage('footer', false);
1215
1216        $html .= $this->stringToNav($this->getConf('footerCustomMenuText'));
1217
1218        if ($this->getConf('footerSearch') === true) {
1219            $html .= '<div class="mikio-footer-search">';
1220            $html .= $this->includeSearch(false);
1221            $html .= '</div>';
1222        }
1223
1224        $showPageTools = $this->getConf('pageToolsFooter');
1225        if (
1226            strcasecmp($ACT, 'show') === 0 && (strcasecmp($showPageTools, 'always') === 0 ||
1227            $this->userCanEdit() === true && strcasecmp($showPageTools, 'page editors') === 0)
1228        ) {
1229            $html .= $this->includePageTools(false);
1230        }
1231
1232        $meta['licenseType']            = ['multichoice', '_choices' => ['none', 'badge', 'button']];
1233        $meta['licenseImageOnly']       = ['onoff'];
1234
1235        $licenseType = $this->getConf('licenseType');
1236        if ($licenseType !== 'none') {
1237            $html .= tpl_license($licenseType, $this->getConf('licenseImageOnly'), true, true);
1238        }
1239
1240        $html .= '</footer>';
1241
1242        if ($print === true) {
1243            echo $html;
1244        }
1245        return $html;
1246    }
1247
1248
1249    /**
1250     * Print or return breadcrumb trail
1251     *
1252     * @param   boolean $print Print out trail.
1253     * @param   boolean $parse Parse trail before printing.
1254     * @return  string         HTML string containing breadcrumbs
1255     */
1256    public function includeBreadcrumbs(bool $print = true, bool $parse = true)
1257    {
1258        global $conf, $ID, $lang, $ACT;
1259
1260        if (
1261            $this->getConf('breadcrumbHideHome') === true && strcasecmp($ID, 'start') === 0 &&
1262            strcasecmp($ACT, 'show') === 0 || strcasecmp($ACT, 'showtag') === 0 || $conf['breadcrumbs'] === 0
1263        ) {
1264            return '';
1265        }
1266
1267        $html = '<div class="mikio-breadcrumbs">';
1268        $html .= '<div class="mikio-container">';
1269        if (strcasecmp($ACT, 'show') === 0) {
1270            if ($conf['breadcrumbs'] !== 0) {
1271                if ($this->getConf('breadcrumbPrefix') === false && $this->getConf('breadcrumbSep') === false) {
1272                    ob_start();
1273                    tpl_breadcrumbs();
1274                    $html .= ob_get_contents();
1275                    ob_end_clean();
1276                } else {
1277                    $sep = '•';
1278                    $prefix = $lang['breadcrumb'];
1279
1280                    if ($this->getConf('breadcrumbSep') === true) {
1281                        $sep = $this->getConf('breadcrumbSepText');
1282                        $img = $this->getMediaFile('breadcrumb-sep', false);
1283
1284                        if ($img !== false) {
1285                            $sep = '<img src="' . $img . '">';
1286                        }
1287                    }
1288
1289                    if ($this->getConf('breadcrumbPrefix') === true) {
1290                        $prefix = $this->getConf('breadcrumbPrefixText');
1291                        $img = $this->getMediaFile('breadcrumb-prefix', false);
1292
1293                        if ($img !== false) {
1294                            $prefix = '<img src="' . $img . '">';
1295                        }
1296                    }
1297
1298                    $crumbs = breadcrumbs();
1299
1300                    $html .= '<ul>';
1301                    if (empty($prefix) === false) {
1302                        $html .= '<li class="prefix">' . $prefix . '</li>';
1303                    }
1304
1305                    $last = count($crumbs);
1306                    $i    = 0;
1307                    foreach ($crumbs as $id => $name) {
1308                        $i++;
1309                        if ($i !== 1) {
1310                            $html .= '<li class="sep">' . $sep . '</li>';
1311                        }
1312                        $html .= '<li' . ($i === $last ? ' class="curid"' : '') . '>';
1313                        $html .= tpl_pagelink($id, null, true);
1314                        $html .= '</li>';
1315                    }
1316
1317                    $html .= '</ul>';
1318                }//end if
1319            }//end if
1320        }//end if
1321
1322        $html .= '</div>';
1323        $html .= '</div>';
1324
1325        if ($parse === true) {
1326            $html = $this->includeIcons($html);
1327        }
1328        if ($print === true) {
1329            echo $html;
1330        }
1331        return $html;
1332    }
1333
1334    /**
1335     * Print or return you are here trail
1336     *
1337     * @param   boolean $print Print out trail.
1338     * @param   boolean $parse Parse trail before printing.
1339     * @return  string         HTML string containing breadcrumbs
1340     */
1341    public function includeYouAreHere(bool $print = true, bool $parse = true)
1342    {
1343        global $conf, $ID, $lang, $ACT;
1344
1345        if (
1346            $this->getConf('youarehereHideHome') === true && strcasecmp($ID, 'start') === 0 &&
1347            strcasecmp($ACT, 'show') === 0 || strcasecmp($ACT, 'showtag') === 0 || $conf['youarehere'] === 0
1348        ) {
1349            return '';
1350        }
1351
1352        $html = '<div class="mikio-youarehere">';
1353        $html .= '<div class="mikio-container">';
1354        if (strcasecmp($ACT, 'show') === 0) {
1355            if ($conf['youarehere'] !== 0) {
1356                if ($this->getConf('youareherePrefix') === false && $this->getConf('youarehereSep') === false) {
1357                    $html .= '<div class="mikio-bcdw">';
1358                    ob_start();
1359                    tpl_youarehere();
1360                    $html .= ob_get_contents();
1361                    ob_end_clean();
1362                    $html .= '</div>';
1363                } else {
1364                    $sep = ' » ';
1365                    $prefix = $lang['youarehere'];
1366
1367                    if ($this->getConf('youarehereSep') === true) {
1368                        $sep = $this->getConf('youarehereSepText');
1369                        $img = $this->getMediaFile('youarehere-sep', false);
1370
1371                        if ($img !== false) {
1372                            $sep = '<img src="' . $img . '">';
1373                        }
1374                    }
1375
1376                    if ($this->getConf('youareherePrefix') === true) {
1377                        $prefix = $this->getConf('youareherePrefixText');
1378                        $img = $this->getMediaFile('youarehere-prefix', false);
1379
1380                        if ($img !== false) {
1381                            $prefix = '<img src="' . $img . '">';
1382                        }
1383                    }
1384
1385                    $html .= '<ul>';
1386                    if (empty($prefix) === false) {
1387                        $html .= '<li class="prefix">' . $prefix . '</li>';
1388                    }
1389                    $html .= '<li>' . tpl_pagelink(':' . $conf['start'], null, true) . '</li>';
1390
1391                    $parts = explode(':', $ID);
1392                    $count = count($parts);
1393
1394                    $part = '';
1395                    for ($i = 0; $i < ($count - 1); $i++) {
1396                        $part .= $parts[$i] . ':';
1397                        $page = $part;
1398                        if ($page === $conf['start']) {
1399                            continue;
1400                        }
1401
1402                        $html .= '<li class="sep">' . $sep . '</li>';
1403                        $html .= '<li>' . tpl_pagelink($page, null, true) . '</li>';
1404                    }
1405
1406                    $exists = false;
1407                    resolve_pageid('', $page, $exists);
1408                    if ((isset($page) === true && $page === $part . $parts[$i]) === false) {
1409                        $page = $part . $parts[$i];
1410                        if ($page !== $conf['start']) {
1411                            $html .= '<li class="sep">' . $sep . '</li>';
1412                            $html .= '<li>' . tpl_pagelink($page, null, true) . '</li>';
1413                        }
1414                    }
1415
1416                    $html .= '</ul>';
1417                }//end if
1418            }//end if
1419
1420            $showLast = $this->getConf('youarehereShowLast');
1421            if ($showLast !== 0) {
1422                preg_match_all('/(<li[^>]*>.+?<\/li>)/', $html, $matches);
1423                if (count($matches) > 0 && count($matches[0]) > (($showLast * 2) + 2)) {
1424                    $count = count($matches[0]);
1425                    $list = '';
1426
1427                    // Show Home
1428                    $list .= $matches[0][0] . $matches[0][1];
1429
1430                    $list .= '<li>...</li>';
1431                    for ($i = ($count - ($showLast * 2)); $i <= $count; $i++) {
1432                        $list .= $matches[0][$i];
1433                    }
1434
1435                    $html = preg_replace('/<ul>.*<\/ul>/', '<ul>' . $list . '</ul>', $html);
1436                }
1437            }
1438
1439            switch ($this->getConf('youarehereHome')) {
1440                case 'none':
1441                    $html = preg_replace('/<li[^>]*>.+?<\/li>/', '', $html, 2);
1442                    break;
1443                case 'home':
1444                    $html = preg_replace('/(<a[^>]*>)(.+?)(<\/a>)/', '$1' . tpl_getlang('home') . '$3', $html, 1);
1445                    break;
1446                case 'icon':
1447                    $html = preg_replace('/(<a[^>]*>)(.+?)(<\/a>)/', '$1' .
1448                        $this->mikioInlineIcon('home') . '$3', $html, 1);
1449                    break;
1450            }
1451        } else {
1452            $html .= '&#8810; ';
1453            if (isset($_GET['page']) === true) {
1454                $html .= '<a href="' . wl($ID, ['do' => $ACT]) . '">Back</a>&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;';
1455            }
1456            $html .= '<a href="' . wl($ID) . '">View Page</a>';
1457        }//end if
1458
1459        $html .= '</div>';
1460        $html .= '</div>';
1461
1462        if ($parse === true) {
1463            $html = $this->includeIcons($html);
1464        }
1465        if ($print === true) {
1466            echo $html;
1467        }
1468        return $html;
1469    }
1470
1471    /**
1472     * Get Page Title
1473     *
1474     * @return string page title
1475     */
1476    public function parsePageTitle()
1477    {
1478        global $ID;
1479
1480        $title = p_get_first_heading($ID);
1481        if (strlen($title) <= 0) {
1482            $title = tpl_pagetitle(null, true);
1483        }
1484        $title = $this->includeIcons($title);
1485
1486        return $title;
1487    }
1488
1489
1490    /**
1491     * Print or return hero block
1492     *
1493     * @param   boolean $print Print content.
1494     * @return  string         contents of hero
1495     */
1496    public function includeHero(bool $print = true)
1497    {
1498        $html = '';
1499
1500        if ($this->getConf('heroTitle') === true) {
1501            $html .= '<div class="mikio-hero">';
1502            $html .= '<div class="mikio-container">';
1503            $html .= '<div class="mikio-hero-text">';
1504            if (strcasecmp($this->getConf('youareherePosition'), 'hero') === 0) {
1505                $html .= $this->includeYouAreHere(false);
1506            }
1507            if (strcasecmp($this->getConf('breadcrumbPosition'), 'hero') === 0) {
1508                $html .= $this->includeBreadcrumbs(false);
1509            }
1510
1511            $html .= '<h1 class="mikio-hero-title">';
1512            $html .= $this->parsePageTitle();    // No idea why this requires a blank space afterward to work?
1513            $html .= '</h1>';
1514            $html .= '<h2 class="mikio-hero-subtitle"></h2>';
1515            $html .= '</div>';
1516
1517            $hero_image = $this->getMediaFile('hero', true, $this->getConf('heroImagePropagation', true));
1518            $hero_image_resize_class = '';
1519            if (empty($hero_image) === false) {
1520                $hero_image = ' style="background-image:url(\'' . $hero_image . '\');"';
1521                $hero_image_resize_class = ' mikio-hero-image-resize';
1522            }
1523
1524            $html .= '<div class="mikio-hero-image' . $hero_image_resize_class . '"' . $hero_image .
1525                '><div class="mikio-tags"></div></div>';
1526
1527            $html .= '</div>';
1528            $html .= '</div>';
1529        }//end if
1530
1531        if ($print === true) {
1532            echo $html;
1533        }
1534
1535        return $html;
1536    }
1537
1538
1539    /**
1540     * Print or return out TOC
1541     *
1542     * @param   boolean $print Print TOC.
1543     * @param   boolean $parse Parse icons.
1544     * @return  string         contents of TOC
1545     */
1546    public function includeTOC(bool $print = true, bool $parse = true)
1547    {
1548        $html = '';
1549
1550        $tocHtml = tpl_toc(true);
1551
1552        if (empty($tocHtml) === false) {
1553            $tocHtml = preg_replace(
1554                '/(<h3.+?toggle.+?>)(.+?)<\/h3>/',
1555                '$1' .
1556                $this->mikioInlineIcon('hamburger', 'hamburger') . '$2' .
1557                $this->mikioInlineIcon('down-arrow', 'down-arrow') . '</h3>',
1558                $tocHtml
1559            );
1560            $tocHtml = preg_replace('/<li.*><div.*><a.*><\/a><\/div><\/li>\s*/', '', $tocHtml);
1561            $tocHtml = preg_replace('/<ul.*>\s*<\/ul>\s*/', '', $tocHtml);
1562
1563            $html .= '<div class="mikio-toc">';
1564            $html .= $tocHtml;
1565            $html .= '</div>';
1566        }
1567
1568        if ($parse === true) {
1569            $html = $this->includeIcons($html);
1570        }
1571
1572        if ($print === true) {
1573            echo $html;
1574        }
1575
1576        return $html;
1577    }
1578
1579
1580    /**
1581     * Parse the string and replace icon elements with included icon libraries
1582     *
1583     * @param   string $str Content to parse.
1584     * @return  string      parsed string
1585     */
1586    public function includeIcons(string $str)
1587    {
1588        global $ACT, $MIKIO_ICONS;
1589
1590        $iconTag = $this->getConf('iconTag', 'icon');
1591        if (empty($iconTag) === true) {
1592            return $str;
1593        }
1594
1595        if (
1596            in_array($ACT, ['show', 'showtag', 'revisions', 'index', 'preview']) === true ||
1597            strcasecmp($ACT, 'admin') === 0 && count($MIKIO_ICONS) > 0
1598        ) {
1599            $content = $str;
1600            $preview = null;
1601
1602            if (strcasecmp($ACT, 'preview') === 0) {
1603                $html = new simple_html_dom();
1604                $html->stripRNAttrValues = false;
1605                $html->load($str, true, false);
1606
1607                $preview = $html->find('div.preview');
1608                if (is_array($preview) === true && count($preview) > 0) {
1609                    $content = $preview[0]->innertext;
1610                }
1611            }
1612
1613            $page_regex = '/(.*)/';
1614            if (stripos($str, '<pre') !== false) {
1615                $page_regex = '/<(?!pre|\/).*?>(.*)[^<]*/';
1616            }
1617
1618            $content = preg_replace_callback($page_regex, function ($icons) {
1619                $iconTag = $this->getConf('iconTag', 'icon');
1620
1621                return preg_replace_callback(
1622                    '/&lt;' . $iconTag . ' ([\w\- #]*)&gt;(?=[^>]*(<|$))/',
1623                    function ($matches) {
1624                        global $MIKIO_ICONS;
1625
1626                        $s = $matches[0];
1627
1628                        if (count($MIKIO_ICONS) > 0) {
1629                            $icon = $MIKIO_ICONS[0];
1630
1631                            if (count($matches) > 1) {
1632                                $e = explode(' ', $matches[1]);
1633
1634                                if (count($e) > 1) {
1635                                    foreach ($MIKIO_ICONS as $iconItem) {
1636                                        if (strcasecmp($iconItem['name'], $e[0]) === 0) {
1637                                            $icon = $iconItem;
1638
1639                                            $s = $icon['insert'];
1640                                            for ($i = 1; $i < 9; $i++) {
1641                                                if (count($e) < $i || empty($e[$i]) === true) {
1642                                                    if (isset($icon['$' . $i]) === true) {
1643                                                        $s = str_replace('$' . $i, $icon['$' . $i], $s);
1644                                                    }
1645                                                } else {
1646                                                    $s = str_replace('$' . $i, $e[$i], $s);
1647                                                }
1648                                            }
1649
1650                                            $dir = '';
1651                                            if (isset($icon['dir']) === true) {
1652                                                $dir = $this->baseDir . 'icons/' . $icon['dir'] . '/';
1653                                            }
1654
1655                                            $s = str_replace('$0', $dir, $s);
1656
1657                                            break;
1658                                        }//end if
1659                                    }//end foreach
1660                                } else {
1661                                    $s = str_replace('$1', $matches[1], $icon['insert']);
1662                                }//end if
1663                            }//end if
1664                        }//end if
1665
1666                        $s = preg_replace('/(class=")(.*)"/', '$1mikio-icon $2"', $s, -1, $count);
1667                        if ($count === 0) {
1668                            $s = preg_replace('/(<\w* )/', '$1class="mikio-icon" ', $s);
1669                        }
1670
1671                        return $s;
1672                    },
1673                    $icons[0]
1674                );
1675            }, $content);
1676
1677            if (strcasecmp($ACT, 'preview') === 0) {
1678                if (is_array($preview) === true && count($preview) > 0) {
1679                    $preview[0]->innertext = $content;
1680                }
1681
1682                $str = $html->save();
1683                $html->clear();
1684                unset($html);
1685            } else {
1686                $str = $content;
1687            }
1688        }//end if
1689
1690        return $str;
1691    }
1692
1693    /**
1694     * Parse HTML for theme
1695     *
1696     * @param   string $content HTML content to parse.
1697     * @return  string          Parsed content
1698     */
1699    public function parseContent(string $content)
1700    {
1701        global $INPUT, $ACT;
1702
1703        // Add Mikio Section titles
1704        if (strcasecmp($INPUT->str('page'), 'config') === 0) {
1705            $admin_sections = [
1706                // Section      Insert Before                 Icon
1707                'navbar'        => ['navbarUseTitleIcon',      ''],
1708                'search'        => ['searchButton',            ''],
1709                'hero'          => ['heroTitle',               ''],
1710                'tags'          => ['tagsConsolidate',         ''],
1711                'breadcrumb'    => ['breadcrumbHideHome',      ''],
1712                'youarehere'    => ['youarehereHideHome',      ''],
1713                'sidebar'       => ['sidebarShowLeft',         ''],
1714                'toc'           => ['tocFull',                 ''],
1715                'pagetools'     => ['pageToolsFloating',       ''],
1716                'footer'        => ['footerPageInfoText',      ''],
1717                'license'       => ['licenseType',             ''],
1718                'acl'           => ['includePageUseACL',       ''],
1719                'sticky'        => ['stickyTopHeader',         ''],
1720            ];
1721
1722            foreach ($admin_sections as $section => $items) {
1723                $search = $items[0];
1724                $icon   = $items[1];
1725
1726                $content = preg_replace(
1727                    '/<tr(.*)>\s*<td class="label">\s*<span class="outkey">(tpl»mikio»' . $search . ')<\/span>/',
1728                    '<tr$1><td class="mikio-config-table-header" colspan="2">' . $this->mikioInlineIcon($icon) .
1729                        tpl_getLang('config_' . $section) .
1730                        '</td></tr><tr class="default"><td class="label"><span class="outkey">tpl»mikio»' .
1731                        $search . '</span>',
1732                    $content
1733                );
1734            }
1735        } elseif (strcasecmp($INPUT->str('page'), 'styling') === 0) {
1736            $mikioPluginMissing = true;
1737            /* Hide plugin fields if not installed */
1738            if (plugin_load('action', 'mikioplugin') !== null) {
1739                $mikioPluginMissing = false;
1740            }
1741
1742            $style_headers = [
1743                ['title' => 'Base', 'starts_with' => '__text_'],
1744                ['title' => 'Code', 'starts_with' => '__code_'],
1745                ['title' => 'Controls', 'starts_with' => '__control_'],
1746                ['title' => 'Header', 'starts_with' => '__topheader_'],
1747                ['title' => 'Navbar', 'starts_with' => '__navbar_'],
1748                ['title' => 'Sub Navbar', 'starts_with' => '__subnavbar_'],
1749                ['title' => 'Tags', 'starts_with' => '__tag_background_color_'],
1750                ['title' => 'Breadcrumbs', 'starts_with' => '__breadcrumb_'],
1751                ['title' => 'Hero', 'starts_with' => '__hero_'],
1752                ['title' => 'Sidebar', 'starts_with' => '__sidebar_'],
1753                ['title' => 'Content', 'starts_with' => '__content_'],
1754                ['title' => 'TOC', 'starts_with' => '__toc_'],
1755                ['title' => 'Page Tools', 'starts_with' => '__pagetools_'],
1756                ['title' => 'Footer', 'starts_with' => '__footer_'],
1757                ['title' => 'Table', 'starts_with' => '__table_'],
1758                ['title' => 'Dropdown', 'starts_with' => '__dropdown_'],
1759                ['title' => 'Section Edit', 'starts_with' => '__section_edit_'],
1760                ['title' => 'Tree', 'starts_with' => '__tree_'],
1761                ['title' => 'Tabs', 'starts_with' => '__tab_'],
1762                ['title' => 'Mikio Plugin', 'starts_with' => '__plugin_', 'heading' => 'h2',
1763                    'hidden' => $mikioPluginMissing
1764                ],
1765                ['title' => 'Primary Colours', 'starts_with' => '__plugin_primary_', 'hidden' => $mikioPluginMissing],
1766                ['title' => 'Secondary Colours', 'starts_with' => '__plugin_secondary_',
1767                    'hidden' => $mikioPluginMissing
1768                ],
1769                ['title' => 'Success Colours', 'starts_with' => '__plugin_success_', 'hidden' => $mikioPluginMissing],
1770                ['title' => 'Danger Colours', 'starts_with' => '__plugin_danger_', 'hidden' => $mikioPluginMissing],
1771                ['title' => 'Warning Colours', 'starts_with' => '__plugin_warning_', 'hidden' => $mikioPluginMissing],
1772                ['title' => 'Info Colours', 'starts_with' => '__plugin_info_', 'hidden' => $mikioPluginMissing],
1773                ['title' => 'Light Colours', 'starts_with' => '__plugin_light_', 'hidden' => $mikioPluginMissing],
1774                ['title' => 'Dark Colours', 'starts_with' => '__plugin_dark_', 'hidden' => $mikioPluginMissing],
1775                ['title' => 'Link Colours', 'starts_with' => '__plugin_link_', 'hidden' => $mikioPluginMissing],
1776                ['title' => 'Carousel', 'starts_with' => '__plugin_carousel_', 'hidden' => $mikioPluginMissing],
1777                ['title' => 'Steps', 'starts_with' => '__plugin_steps_', 'hidden' => $mikioPluginMissing],
1778                ['title' => 'Tabgroup', 'starts_with' => '__plugin_tabgroup_', 'hidden' => $mikioPluginMissing],
1779                ['title' => 'Tooltip', 'starts_with' => '__plugin_tooltip_', 'hidden' => $mikioPluginMissing],
1780                ['title' => 'Dark Mode', 'starts_with' => '__darkmode_', 'heading' => 'h2'],
1781                ['title' => 'Base', 'starts_with' => '__darkmode_text_'],
1782                ['title' => 'Code', 'starts_with' => '__darkmode_code_'],
1783                ['title' => 'Controls', 'starts_with' => '__darkmode_control_'],
1784                ['title' => 'Header', 'starts_with' => '__darkmode_topheader_'],
1785                ['title' => 'Navbar', 'starts_with' => '__darkmode_navbar_'],
1786                ['title' => 'Sub Navbar', 'starts_with' => '__darkmode_subnavbar_'],
1787                ['title' => 'Tags', 'starts_with' => '__darkmode_tag_background_color_'],
1788                ['title' => 'Breadcrumbs', 'starts_with' => '__darkmode_breadcrumb_'],
1789                ['title' => 'Hero', 'starts_with' => '__darkmode_hero_'],
1790                ['title' => 'Sidebar', 'starts_with' => '__darkmode_sidebar_'],
1791                ['title' => 'Content', 'starts_with' => '__darkmode_content_'],
1792                ['title' => 'TOC', 'starts_with' => '__darkmode_toc_'],
1793                ['title' => 'Page Tools', 'starts_with' => '__darkmode_pagetools_'],
1794                ['title' => 'Footer', 'starts_with' => '__darkmode_footer_'],
1795                ['title' => 'Table', 'starts_with' => '__darkmode_table_'],
1796                ['title' => 'Dropdown', 'starts_with' => '__darkmode_dropdown_'],
1797                ['title' => 'Section Edit', 'starts_with' => '__darkmode_section_edit_'],
1798                ['title' => 'Tree', 'starts_with' => '__darkmode_tree_'],
1799                ['title' => 'Tabs', 'starts_with' => '__darkmode_tab_'],
1800                ['title' => 'Mikio Plugin (Dark mode)', 'starts_with' => '__plugin_darkmode_', 'heading' => 'h2',
1801                    'hidden' => $mikioPluginMissing
1802                ],
1803                ['title' => 'Primary Colours', 'starts_with' => '__plugin_darkmode_primary_',
1804                    'hidden' => $mikioPluginMissing
1805                ],
1806                ['title' => 'Secondary Colours', 'starts_with' => '__plugin_darkmode_secondary_',
1807                    'hidden' => $mikioPluginMissing
1808                ],
1809                ['title' => 'Success Colours', 'starts_with' => '__plugin_darkmode_success_',
1810                    'hidden' => $mikioPluginMissing
1811                ],
1812                ['title' => 'Danger Colours', 'starts_with' => '__plugin_darkmode_danger_',
1813                    'hidden' => $mikioPluginMissing
1814                ],
1815                ['title' => 'Warning Colours', 'starts_with' => '__plugin_darkmode_warning_',
1816                    'hidden' => $mikioPluginMissing
1817                ],
1818                ['title' => 'Info Colours', 'starts_with' => '__plugin_darkmode_info_',
1819                    'hidden' => $mikioPluginMissing
1820                ],
1821                ['title' => 'Light Colours', 'starts_with' => '__plugin_darkmode_light_',
1822                    'hidden' => $mikioPluginMissing
1823                ],
1824                ['title' => 'Dark Colours', 'starts_with' => '__plugin_darkmode_dark_',
1825                    'hidden' => $mikioPluginMissing
1826                ],
1827                ['title' => 'Link Colours', 'starts_with' => '__plugin_darkmode_link_',
1828                    'hidden' => $mikioPluginMissing
1829                ],
1830                ['title' => 'Carousel', 'starts_with' => '__plugin_darkmode_carousel_',
1831                    'hidden' => $mikioPluginMissing
1832                ],
1833                ['title' => 'Steps', 'starts_with' => '__plugin_darkmode_steps_', 'hidden' => $mikioPluginMissing],
1834                ['title' => 'Tabgroup', 'starts_with' => '__plugin_darkmode_tabgroup_',
1835                    'hidden' => $mikioPluginMissing
1836                ],
1837                ['title' => 'Tooltip', 'starts_with' => '__plugin_darkmode_tooltip_', 'hidden' => $mikioPluginMissing],
1838            ];
1839
1840            foreach ($style_headers as $header) {
1841                if (array_key_exists('heading', $header) === false) {
1842                    $header['heading'] = 'h3';
1843                }
1844
1845                if (array_key_exists('hidden', $header) === false) {
1846                    $header['hidden'] = false;
1847                }
1848
1849                $content = preg_replace(
1850                    '/(<tr>\s*<td>\s*<label for="tpl__' . $header['starts_with'] . '.+?<\/tr>)/s',
1851                    '</tbody></table><' . $header['heading'] . ' style="display:' .
1852                    ($header['hidden'] === true ? 'none' : 'block') . '">' .
1853                    $header['title'] . '</' . $header['heading'] . '>
1854                    <table style="display:' . ($header['hidden'] === true ? 'none' : 'table') . '"><tbody>$1',
1855                    $content,
1856                    1
1857                );
1858            }
1859
1860            $content = preg_replace_callback('/<input type="color"[^>]*>/', function ($match) {
1861                // Get the ID of the <input type="color"> element
1862                preg_match('/id="([^"]*)"/', $match[0], $matches);
1863
1864                // Replace type with text and remove the id attribute
1865                $replacement = preg_replace(
1866                    ['/type="color"/', '/id="([^"]*)"/'],
1867                    ['type="text" class="mikio-color-text-input"', 'for="$1"'],
1868                    $match[0]
1869                );
1870
1871                return '<div class="mikio-color-picker">' . $replacement . $match[0] . '</div>';
1872            }, $content);
1873        }//end if
1874
1875        if (strcasecmp($ACT, 'admin') === 0 && isset($_GET['page']) === false) {
1876            $content = preg_replace('/(<ul.*?>.*?)<\/ul>.*?<ul.*?>(.*?<\/ul>)/s', '$1$2', $content);
1877        }
1878
1879        // Page Revisions - Table Fix
1880        if (strpos($content, 'id="page__revisions"') !== false) {
1881            $content = preg_replace(
1882                '/(<span class="sum">\s.*<\/span>\s.*<span class="user">\s.*<\/span>)/',
1883                '<span>$1</span>',
1884                $content
1885            );
1886        }
1887
1888        $html = new simple_html_dom();
1889        $html->stripRNAttrValues = false;
1890        $html->load($content, true, false);
1891
1892        if ($html === false) {
1893            return $content;
1894        }
1895
1896        /* Buttons */
1897        foreach ($html->find('#config__manager button') as $node) {
1898            $c = explode(' ', $node->class);
1899            if (in_array('mikio-button', $c) === false) {
1900                $c[] = 'mikio-button';
1901            }
1902            $node->class = implode(' ', $c);
1903        }
1904
1905
1906        /* Buttons - Primary */
1907        foreach ($html->find('#config__manager [type=submit]') as $node) {
1908            $c = explode(' ', $node->class);
1909            if (in_array('mikio-primary', $c) === false) {
1910                $c[] = 'mikio-primary';
1911            }
1912            $node->class = implode(' ', $c);
1913        }
1914
1915        /* Hide page title if hero is enabled */
1916        if ($this->getConf('heroTitle') === true && $ACT !== 'preview') {
1917            $pageTitle = $this->parsePageTitle();
1918
1919            foreach ($html->find('h1,h2,h3,h4') as $elm) {
1920                if ($elm->innertext === $pageTitle) {
1921                    // $elm->innertext = '';
1922                    $elm->setAttribute('style', 'display:none');
1923
1924                    break;
1925                }
1926            }
1927        }
1928
1929        /* Hero subtitle */
1930        foreach ($html->find('p') as $elm) {
1931            if (preg_match('/[~-]~hero-subtitle (.+?)~[~-]/ui', $elm->innertext, $matches) === 1) {
1932                $subtitle = $matches[1];
1933                $this->footerScript['hero-subtitle'] = 'mikio.setHeroSubTitle(\'' . $subtitle . '\')';
1934
1935                $elm->innertext = preg_replace('/[~-]~hero-subtitle (.+?)~[~-]/ui', '', $elm->innertext);
1936                break;
1937            }
1938        }
1939
1940        /* Hero image */
1941        foreach ($html->find('p') as $elm) {
1942            $image = '';
1943            preg_match('/[~-]~hero-image (.+?)~[~-](?!.?")/ui', $elm->innertext, $matches);
1944            if (count($matches) > 0) {
1945                preg_match('/<img.*src="(.+?)"/ui', $matches[1], $imageTagMatches);
1946                if (count($imageTagMatches) > 0) {
1947                    $image = $imageTagMatches[1];
1948                } else {
1949                    preg_match('/<a.+?>(.+?)[~<]/ui', $matches[1], $imageTagMatches);
1950                    if (count($imageTagMatches) > 0) {
1951                        $image = $imageTagMatches[1];
1952                    } else {
1953                        $image = strip_tags($matches[1]);
1954                        if (stripos($image, ':') === false) {
1955                            $image = str_replace(['{', '}'], '', $image);
1956                            $i = stripos($image, '?');
1957                            if ($i !== false) {
1958                                $image = substr($image, 0, $i);
1959                            }
1960
1961                            $image = ml($image, '', true, '', false);
1962                        }
1963                    }
1964                }
1965
1966                $this->footerScript['hero-image'] = 'mikio.setHeroImage(\'' . $image . '\')';
1967
1968                $elm->innertext = preg_replace('/[~-]~hero-image (.+?)~[~-].*/ui', '', $elm->innertext);
1969            }//end if
1970        }//end foreach
1971
1972        /* Hero colors - ~~hero-colors [background-color] [hero-title-color] [hero-subtitle-color]
1973        [breadcrumb-text-color] [breadcrumb-hover-color] (use 'initial' for original color) */
1974        foreach ($html->find('p') as $elm) {
1975            if (preg_match('/[~-]~hero-colors (.+?)~[~-]/ui', $elm->innertext, $matches) === 1) {
1976                $subtitle = $matches[1];
1977                $this->footerScript['hero-colors'] = 'mikio.setHeroColor(\'' . $subtitle . '\')';
1978
1979                $elm->innertext = preg_replace('/[~-]~hero-colors (.+?)~[~-]/ui', '', $elm->innertext);
1980                break;
1981            }
1982        }
1983
1984        /* Hide parts - ~~hide-parts [parts]~~  */
1985        foreach ($html->find('p') as $elm) {
1986            if (preg_match('/[~-]~hide-parts (.+?)~[~-]/ui', $elm->innertext, $matches) === 1) {
1987                $parts = explode(' ', $matches[1]);
1988                $script = '';
1989
1990                foreach ($parts as $part) {
1991                    if (strlen($part) > 0) {
1992                        $script .= 'mikio.hidePart(\'' . $part . '\');';
1993                    }
1994                }
1995
1996                if (strlen($script) > 0) {
1997                    $this->footerScript['hide-parts'] = $script;
1998                }
1999
2000                $elm->innertext = preg_replace('/[~-]~hide-parts (.+?)~[~-]/ui', '', $elm->innertext);
2001                break;
2002            }
2003        }//end foreach
2004
2005
2006        /* Page Tags (tag plugin) */
2007        if ($this->getConf('tagsConsolidate') === true) {
2008            $tags = '';
2009            foreach ($html->find('div.tags a') as $elm) {
2010                $tags .= $elm->outertext;
2011            }
2012
2013            foreach ($html->find('div.tags') as $elm) {
2014                $elm->innertext = '';
2015                $elm->setAttribute('style', 'display:none');
2016            }
2017
2018            if (empty($tags) === false) {
2019                $this->footerScript['tags'] = 'mikio.setTags(\'' . $tags . '\')';
2020            }
2021        }
2022
2023        // Configuration Manager
2024        if (strcasecmp($INPUT->str('page'), 'config') === 0) {
2025            // Additional save buttons
2026            foreach ($html->find('#config__manager') as $cm) {
2027                $saveButtons = '';
2028
2029                foreach ($cm->find('p') as $elm) {
2030                    $saveButtons = $elm->outertext;
2031                    $saveButtons = str_replace('<p>', '<p style="text-align:right">', $saveButtons);
2032                    $elm->outertext = '';
2033                }
2034
2035                foreach ($cm->find('fieldset') as $elm) {
2036                    $elm->innertext .= $saveButtons;
2037                }
2038            }
2039        }
2040
2041        $content = $html->save();
2042        $html->clear();
2043        unset($html);
2044
2045        return $content;
2046    }
2047
2048
2049    /**
2050     * Get DokuWiki namespace/page/URI as link
2051     *
2052     * @param   string $str String to parse.
2053     * @return  string      parsed URI
2054     */
2055    public function getLink(string $str)
2056    {
2057        $i = strpos($str, '://');
2058        if ($i !== false) {
2059            return $str;
2060        }
2061
2062        return wl($str);
2063    }
2064
2065
2066    /**
2067     * Check if the user can edit current namespace/page
2068     *
2069     * @return  boolean  user can edit
2070     */
2071    public function userCanEdit()
2072    {
2073        global $INFO;
2074        global $ID;
2075
2076        $wiki_file = wikiFN($ID);
2077        if (@file_exists($wiki_file) === false) {
2078            return true;
2079        }
2080        if ($INFO['isadmin'] === true || $INFO['ismanager'] === true) {
2081            return true;
2082        }
2083        // $meta_file = metaFN($ID, '.meta');
2084        if ($INFO['meta']['user'] === false) {
2085            return true;
2086        }
2087        if ($INFO['client'] === $INFO['meta']['user']) {
2088            return true;
2089        }
2090
2091        return false;
2092    }
2093
2094
2095    /**
2096     * Search for and return the uri of a media file
2097     *
2098     * @param string  $image           Image name to search for (without extension).
2099     * @param boolean $searchCurrentNS Search the current namespace.
2100     * @param boolean $propagate       Propagate search through the namespace.
2101     * @return string                  URI of the found media file
2102     */
2103    public function getMediaFile(string $image, bool $searchCurrentNS = true, bool $propagate = true)
2104    {
2105        global $INFO;
2106
2107        $ext = ['png', 'jpg', 'gif', 'svg'];
2108
2109        if ($searchCurrentNS === true) {
2110            $prefix[] = ':' . $INFO['namespace'] . ':';
2111        }
2112        if ($propagate === true) {
2113            $prefix[] = ':';
2114            $prefix[] = ':wiki:';
2115        }
2116        $theme = $this->getConf('customTheme');
2117        if (empty($theme) === false) {
2118            $prefix[] = 'themes/' . $theme . '/images/';
2119        }
2120        $prefix[] = 'images/';
2121
2122        $search = [];
2123        foreach ($prefix as $pitem) {
2124            foreach ($ext as $eitem) {
2125                $search[] = $pitem . $image . '.' . $eitem;
2126            }
2127        }
2128
2129        $img = '';
2130        $url = '';
2131        $ismedia = false;
2132        $found = false;
2133
2134        foreach ($search as $img) {
2135            if (strcasecmp(substr($img, 0, 1), ':') === 0) {
2136                $file    = mediaFN($img);
2137                $ismedia = true;
2138            } else {
2139                $file    = tpl_incdir() . $img;
2140                $ismedia = false;
2141            }
2142
2143            if (file_exists($file) === true) {
2144                $found = true;
2145                break;
2146            }
2147        }
2148
2149        if ($found === false) {
2150            return false;
2151        }
2152
2153        if ($ismedia === true) {
2154            $url = ml($img, '', true, '', false);
2155        } else {
2156            $url = tpl_basedir() . $img;
2157        }
2158
2159        return $url;
2160    }
2161
2162
2163    /**
2164     * Print or return the page title
2165     *
2166     * @param string $page Page id or empty string for current page.
2167     * @return string      generated content
2168     */
2169    public function getPageTitle(string $page = ''): string
2170    {
2171        global $ID, $conf;
2172
2173        $html = '';
2174
2175        if (empty($page) === true) {
2176            $page = $ID;
2177        }
2178
2179        $html = p_get_first_heading($page);
2180        $html = strip_tags($html);
2181        $html = preg_replace('/\s+/', ' ', $html);
2182        $html .= ' [' . strip_tags($conf['title']) . ']';
2183        return trim($html);
2184    }
2185
2186
2187    /**
2188     * Return inline theme icon
2189     *
2190     * @param   string $type  Icon to retreive.
2191     * @param   string $class Classname to insert.
2192     * @return  string        HTML icon content
2193     */
2194    public function mikioInlineIcon(string $type, string $class = ""): string
2195    {
2196        if (is_array($class) === true) {
2197            $class = implode(' ', $class);
2198        }
2199
2200        if (strlen($class) > 0) {
2201            $class = ' ' . $class;
2202        }
2203
2204        switch ($type) {
2205            case 'wrench':
2206                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 -256 1792
22071792" style="fill:currentColor"><g transform="matrix(1,0,0,-1,53.152542,1217.0847)"><path d="m 384,64 q 0,26 -19,45 -19,
220819 -45,19 -26,0 -45,-19 -19,-19 -19,-45 0,-26 19,-45 19,-19 45,-19 26,0 45,19 19,19 19,45 z m 644,420 -682,-682 q -37,
2209-37 -90,-37 -52,0 -91,37 L 59,-90 Q 21,-54 21,0 21,53 59,91 L 740,772 Q 779,674 854.5,598.5 930,523 1028,484 z m 634,
2210435 q 0,-39 -23,-106 Q 1592,679 1474.5,595.5 1357,512 1216,512 1031,512 899.5,643.5 768,775 768,960 q 0,185 131.5,316.5
2211131.5,131.5 316.5,131.5 58,0 121.5,-16.5 63.5,-16.5 107.5,-46.5 16,-11 16,-28 0,-17 -16,-28 L 1152,1120 V 896 l 193,
2212-107 q 5,3 79,48.5 74,45.5 135.5,81 61.5,35.5 70.5,35.5 15,0 23.5,-10 8.5,-10 8.5,-25 z"/></g></svg>';
2213            case 'file':
2214                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2215viewBox="0 -256 1792 1792" style="fill:currentColor"><g transform="matrix(1,0,0,-1,235.38983,1277.8305)" id="g2991">
2216<path d="M 128,0 H 1152 V 768 H 736 q -40,0 -68,28 -28,28 -28,68 v 416 H 128 V 0 z m 640,896 h 299 L 768,1195 V 896 z M
22171280,768 V -32 q 0,-40 -28,-68 -28,-28 -68,-28 H 96 q -40,0 -68,28 -28,28 -28,68 v 1344 q 0,40 28,68 28,28 68,28 h 544
2218q 40,0 88,-20 48,-20 76,-48 l 408,-408 q 28,-28 48,-76 20,-48 20,-88 z" id="path2993" /></g></svg>';
2219            case 'gear':
2220                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2221viewBox="0 -256 1792 1792" style="fill:currentColor"><g transform="matrix(1,0,0,-1,121.49153,1285.4237)" id="g3027">
2222<path d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0
2223181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12
222410,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28
2225-36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132
2226147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222
2227q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,
222871.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184
2229q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5
2230-54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z" id="path3029" />
2231</g></svg>';
2232            case 'user':
2233                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2234viewBox="0 -256 1792 1792" style="fill:currentColor"><g transform="matrix(1,0,0,-1,197.42373,1300.6102)"><path d="M
22351408,131 Q 1408,11 1335,-58.5 1262,-128 1141,-128 H 267 Q 146,-128 73,-58.5 0,11 0,131 0,184 3.5,234.5 7,285 17.5,343.5
223628,402 44,452 q 16,50 43,97.5 27,47.5 62,81 35,33.5 85.5,53.5 50.5,20 111.5,20 9,0 42,-21.5 33,-21.5 74.5,-48 41.5,
2237-26.5 108,-48 Q 637,565 704,565 q 67,0 133.5,21.5 66.5,21.5 108,48 41.5,26.5 74.5,48 33,21.5 42,21.5 61,0 111.5,-20
223850.5,-20 85.5,-53.5 35,-33.5 62,-81 27,-47.5 43,-97.5 16,-50 26.5,-108.5 10.5,-58.5 14,-109 Q 1408,184 1408,131 z m
2239-320,893 Q 1088,865 975.5,752.5 863,640 704,640 545,640 432.5,752.5 320,865 320,1024 320,1183 432.5,1295.5 545,1408 704,
22401408 863,1408 975.5,1295.5 1088,1183 1088,1024 z"/></g></svg>';
2241            case 'search':
2242                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"
2243aria-hidden="true" style="fill:currentColor"><path d="M27 24.57l-5.647-5.648a8.895 8.895 0 0 0 1.522-4.984C22.875 9.01
224418.867 5 13.938 5 9.01 5 5 9.01 5 13.938c0 4.929 4.01 8.938 8.938 8.938a8.887 8.887 0 0 0 4.984-1.522L24.568 27 27
224524.57zm-13.062-4.445a6.194 6.194 0 0 1-6.188-6.188 6.195 6.195 0 0 1 6.188-6.188 6.195 6.195 0 0 1 6.188 6.188 6.195
22466.195 0 0 1-6.188 6.188z"/></svg>';
2247            case 'home':
2248                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2249viewBox="0 -256 1792 1792" aria-hidden="true" style="fill:currentColor"><g
2250transform="matrix(1,0,0,-1,68.338983,1285.4237)" id="g3015"><path d="M 1408,544 V 64 Q 1408,38 1389,19 1370,0 1344,0 H
2251960 V 384 H 704 V 0 H 320 q -26,0 -45,19 -19,19 -19,45 v 480 q 0,1 0.5,3 0.5,2 0.5,3 l 575,474 575,-474 q 1,-2 1,-6 z
2252m 223,69 -62,-74 q -8,-9 -21,-11 h -3 q -13,0 -21,7 L 832,1112 140,535 q -12,-8 -24,-7 -13,2 -21,11 l -62,74 q -8,10
2253-7,23.5 1,13.5 11,21.5 l 719,599 q 32,26 76,26 44,0 76,-26 l 244,-204 v 195 q 0,14 9,23 9,9 23,9 h 192 q 14,0 23,-9 9,
2254-9 9,-23 V 840 l 219,-182 q 10,-8 11,-21.5 1,-13.5 -7,-23.5 z" id="path3017" /></g></svg>';
2255            case 'sun':
2256                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2257style="fill:currentColor" viewBox="0 0 16 16"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0
22580 8zm.5-9.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm0 11a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm5-5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-11
22590a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm9.743-4.036a.5.5 0 1 1-.707-.707.5.5 0 0 1 .707.707zm-7.779 7.779a.5.5 0 1
22601-.707-.707.5.5 0 0 1 .707.707zm7.072 0a.5.5 0 1 1 .707-.707.5.5 0 0 1-.707.707zM3.757 4.464a.5.5 0 1 1 .707-.707.5.5
22610 0 1-.707.707z" /></svg>';
2262            case 'moon':
2263                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2264style="fill:currentColor" viewBox="0 0 16 16"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0
22654.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0
22661 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0
22671.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61
22680-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z" /></svg>';
2269            case 'sunmoon':
2270                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg"
2271style="fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"
2272viewBox="0 0 32 32"><line x1="16" y1="3" x2="16" y2="29"/><path d="M16,23c-3.87,0-7-3.13-7-7s3.13-7,7-7"/><line
2273x1="6.81" y1="6.81" x2="8.93" y2="8.93"/><line x1="3" y1="16" x2="6" y2="16"/><line x1="6.81" y1="25.19" x2="8.93"
2274y2="23.07"/><path d="M16,12.55C17.2,10.43,19.48,9,22.09,9c0.16,0,0.31,0.01,0.47,0.02c-1.67,0.88-2.8,2.63-2.8,4.64c0,2.9,
22752.35,5.25,5.25,5.25c1.6,0,3.03-0.72,3.99-1.85C28.48,20.43,25.59,23,22.09,23c-2.61,0-4.89-1.43-6.09-3.55"/></svg>';
2276            case 'hamburger':
2277                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"
2278style="fill:currentColor"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0
227976v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16
228016v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16
228116v40c0 8.837 7.163 16 16 16z"/></svg>';
2282            case 'down-arrow':
2283                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"
2284aria-hidden="true" style="fill:currentColor"><path d="M16.003 18.626l7.081-7.081L25 13.46l-8.997 8.998-9.003-9
22851.917-1.916z"/></svg>';
2286            case 'language':
2287                return '<svg class="mikio-iicon' . $class . '" xmlns="http://www.w3.org/2000/svg" width="16"
2288height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M4.545 6.714 4.11
22898H3l1.862-5h1.284L8 8H6.833l-.435-1.286H4.545zm1.634-.736L5.5 3.956h-.049l-.679 2.022H6.18z"/><path d="M0 2a2 2 0 0 1
22902-2h7a2 2 0 0 1 2 2v3h3a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3H2a2 2 0 0 1-2-2V2zm2-1a1 1 0 0 0-1 1v7a1 1 0 0
22910 1 1h7a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H2zm7.138 9.995c.193.301.402.583.63.846-.748.575-1.673 1.001-2.768
22921.292.178.217.451.635.555.867 1.125-.359 2.08-.844 2.886-1.494.777.665 1.739 1.165 2.93
22931.472.133-.254.414-.673.629-.89-1.125-.253-2.057-.694-2.82-1.284.681-.747 1.222-1.651
22941.621-2.757H14V8h-3v1.047h.765c-.318.844-.74 1.546-1.272 2.13a6.066 6.066 0 0 1-.415-.492 1.988 1.988 0 0 1-.94.31z"/>
2295</svg>';
2296        }//end switch
2297
2298        return '';
2299    }
2300
2301    /**
2302     * Finalize theme
2303     *
2304     * @return void
2305     */
2306    public function finalize()
2307    {
2308    }
2309
2310    /**
2311     * Show Messages
2312     *
2313     * @return void
2314     */
2315    public function showMessages()
2316    {
2317        global $ACT;
2318
2319        $show = $this->getConf('showNotifications');
2320        if (
2321            strlen($show) === 0 ||
2322            strcasecmp($show, 'always') === 0 ||
2323            (strcasecmp($show, 'admin') === 0 && strcasecmp($ACT, 'admin') === 0)
2324        ) {
2325            html_msgarea();
2326
2327            // global $MSG, $MSG_shown;
2328
2329            // if (isset($MSG) !== false) {
2330            //     if (isset($MSG_shown) === false) {
2331            //         $MSG_shown = [];
2332            //     }
2333
2334            //     foreach ($MSG as $msg) {
2335            //         $hash = md5($msg['msg']);
2336            //         if (isset($MSG_shown[$hash]) === true) {
2337            //             continue;
2338            //         }
2339            //         // skip double messages
2340
2341            //         if (info_msg_allowed($msg) === true) {
2342            //             echo '<div class="me ' . $msg['lvl'] . '">';
2343            //             echo $msg['msg'];
2344            //             echo '</div>';
2345            //         }
2346
2347            //         $MSG_shown[$hash] = true;
2348            //     }
2349
2350            //     unset($GLOBALS['MSG']);
2351            // }//end if
2352
2353            if (strlen($this->includedPageNotifications) > 0) {
2354                echo $this->includedPageNotifications;
2355            }
2356        }//end if
2357    }
2358
2359    /**
2360     * Dokuwiki version number
2361     *
2362     * @return  string        the dw version date converted to integer
2363     */
2364    public function dwVersionNumber()
2365    {
2366        if (function_exists('getVersionData') === true) {
2367            $version_data = getVersionData();
2368            if (is_array($version_data) === true && array_key_exists('date', $version_data) === true) {
2369                $version_items = explode(' ', $version_data['date']);
2370                if (count($version_items) >= 1) {
2371                    return intval(preg_replace('/[^0-9]+/', '', strtolower($version_items[0])));
2372                }
2373            }
2374        }
2375
2376        return 0;
2377    }
2378}
2379
2380global $TEMPLATE;
2381$TEMPLATE = Template::getInstance();
2382