* @license GPLv2 (http://www.gnu.org/licenses/gpl-2.0.html) */ namespace dokuwiki\template\mikio; use Doku_Event; use dokuwiki\Menu\PageMenu; use dokuwiki\Menu\SiteMenu; use dokuwiki\Menu\UserMenu; use ParensParser; use simple_html_dom; if (defined('DOKU_INC') === false) { die(); } require_once('icons/icons.php'); require_once('inc/simple_html_dom.php'); require_once('inc/parens-parser.php'); class Template { /** * @var string Template directory path from local FS. */ public $tplDir = ''; /** * @var string Template directory path from web. */ public $baseDir = ''; /** * @var array Array of Javascript files to include in footer. */ public $footerScript = []; /** * @var string Notifications from included pages. */ private $includedPageNotifications = ''; /** * Class constructor */ public function __construct() { $this->tplDir = tpl_incdir(); $this->baseDir = tpl_basedir(); $this->registerHooks(); } /** * Returns the instance of the class * * @return Template class instance */ public static function getInstance(): Template { static $instance = null; if (empty($instance) === true) { $instance = new Template(); } return $instance; } /** * Register the themes hooks into Dokuwiki * * @return void */ private function registerHooks() { global $EVENT_HANDLER; $events_dispatcher = [ 'TPL_METAHEADER_OUTPUT' => 'metaheadersHandler' ]; foreach ($events_dispatcher as $event => $method) { $EVENT_HANDLER->register_hook($event, 'BEFORE', $this, $method); } } /** * Meta handler hook for DokuWiki * * @param Doku_Event $event DokuWiki Event. * @return void * @noinspection PhpUnused */ public function metaHeadersHandler(Doku_Event $event) { global $MIKIO_ICONS; global $conf; global $MIKIO_TEMPLATE; $MIKIO_TEMPLATE = '123'; $this->includePage('theme', false); $stylesheets = []; $scripts = []; if (empty($this->getConf('customTheme')) === false) { if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/style.less') === true) { $stylesheets[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/style.less'; } else { if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/style.css') === true) { $stylesheets[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/style.css'; } } if (file_exists($this->tplDir . 'themes/' . $this->getConf('customTheme') . '/script.js') === true) { $scripts[] = $this->baseDir . 'themes/' . $this->getConf('customTheme') . '/script.js'; } } if (is_array($MIKIO_ICONS) === true && empty($this->getConf('iconTag', 'icon')) === false) { $icons = []; foreach ($MIKIO_ICONS as $icon) { if (isset($icon['name']) === true && isset($icon['css']) === true && isset($icon['insert']) === true) { $icons[] = $icon; if (empty($icon['css']) === false) { if (strpos($icon['css'], '//') === false) { $stylesheets[] = $this->baseDir . 'icons/' . $icon['css']; } else { $stylesheets[] = $icon['css']; } } } } $MIKIO_ICONS = $icons; } else { $MIKIO_ICONS = []; } $scripts[] = $this->baseDir . 'assets/mikio-typeahead.js'; $scripts[] = $this->baseDir . 'assets/mikio.js'; if ($this->getConf('useLESS') === true) { $stylesheets[] = $this->baseDir . 'assets/mikio.less'; } else { $stylesheets[] = $this->baseDir . 'assets/mikio.css'; } /* MikioPlugin Support */ if (plugin_load('action', 'mikioplugin') !== null) { if ($this->getConf('useLESS') === true) { $stylesheets[] = $this->baseDir . 'assets/mikioplugin.less'; } else { $stylesheets[] = $this->baseDir . 'assets/mikioplugin.css'; } } $set = []; foreach ($stylesheets as $style) { if (in_array($style, $set) === false) { if (strcasecmp(substr($style, -5), '.less') === 0 && $this->getConf('useLESS') === true) { $style = $this->baseDir . 'css.php?css=' . str_replace($this->baseDir, '', $style); } array_unshift($event->data['link'], [ 'type' => 'text/css', 'rel' => 'stylesheet', 'href' => $style ]); } $set[] = $style; } $set = []; foreach ($scripts as $script) { if (in_array($script, $set) === false) { $script_params = [ 'type' => 'text/javascript', '_data' => '', 'src' => $script ]; // equal to or greator than hogfather if ($this->dwVersionNumber() >= 20200729 || $this->dwVersionNumber() == 0) { // greator than hogfather - defer always on if ($this->dwVersionNumber() >= 20200729 || $this->dwVersionNumber() == 0) { $script_params += ['defer' => 'defer']; } else { // hogfather - defer always on unless $conf['defer_js'] is false if (array_key_exists('defer_js', $conf) === false || $conf['defer_js'] === true) { $script_params += ['defer' => 'defer']; } } } $event->data['script'][] = $script_params; }//end if $set[] = $script; }//end foreach } /** * Print or return the footer metadata * * @param boolean $print Print the data to buffer. * @return string HTML footer meta data */ public function includeFooterMeta(bool $print = true): string { $html = ''; if (count($this->footerScript) > 0) { $html .= ''; } if ($print === true) { echo $html; } return $html; } // phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing /** * Retreive and parse theme configuration options * * @param string $key The configuration key to retreive. * @param mixed $default If key doesn't exist, return this value. * @return mixed parsed value of configuration */ public function getConf(string $key, $default = false) { $value = tpl_getConf($key, $default); $data = [ ['keys' => ['navbarDWMenuType'], 'type' => 'choice', 'values' => ['both', 'icons', 'text'] ], ['keys' => ['navbarDWMenuCombine'], 'type' => 'choice', 'values' => ['combine', 'seperate', 'dropdown'] ], ['keys' => ['navbarPosLeft', 'navbarPosMiddle', 'navbarPosRight'], 'type' => 'choice', 'values' => ['none', 'custom', 'search', 'dokuwiki'], 'default' => [ 'navbarPosLeft' => 'none', 'navbarPosMiddle' => 'search', 'navbarPosRight' => 'dokuwiki' ] ], ['keys' => ['navbarItemShowCreate', 'navbarItemShowShow', 'navbarItemShowRevs', 'navbarItemShowBacklink', 'navbarItemShowRecent', 'navbarItemShowMedia', 'navbarItemShowIndex', 'navbarItemShowProfile', 'navbarItemShowAdmin' ], 'type' => 'choice', 'values' => ['always', 'logged in', 'logged out', 'never'] ], ['keys' => ['navbarItemShowLogin', 'navbarItemShowLogout'], 'type' => 'choice', 'values' => ['always', 'never'] ], ['keys' => ['searchButton'], 'type' => 'choice', 'values' => ['icon', 'text'] ], ['keys' => ['breadcrumbPosition', 'youareherePosition'], 'type' => 'choice', 'values' => ['top', 'hero', 'page', 'none'] ], ['keys' => ['youarehereHome'], 'type' => 'choice', 'values' => ['page title', 'home', 'icon', 'none'] ], ['keys' => ['sidebarLeftRow1', 'sidebarLeftRow2', 'sidebarLeftRow3', 'sidebarLeftRow4'], 'type' => 'choice', 'values' => ['none', 'logged in user', 'search', 'content', 'tags'], 'default' => [ 'sidebarLeftRow1' => 'logged in user', 'sidebarLeftRow2' => 'search', 'sidebarLeftRow3' => 'content' ] ], ['keys' => ['pageToolsFloating', 'pageToolsFooter'], 'type' => 'choice', 'values' => ['always', 'none', 'page editors'] ], ['keys' => ['pageToolsShowCreate', 'pageToolsShowEdit', 'pageToolsShowRevs', 'pageToolsShowBacklink', 'pageToolsShowTop' ], 'type' => 'choice', 'values' => ['always', 'logged in', 'logged out', 'never'] ], ['keys' => ['showNotifications'], 'type' => 'choice', 'values' => ['admin', 'always', 'none', '', 'never'] ], ['keys' => ['licenseType'], 'type' => 'choice', 'values' => ['badge', 'button', 'none'] ], ['keys' => ['navbarUseTitleIcon'], 'type' => 'bool'], ['keys' => ['navbarUseTitleText'], 'type' => 'bool'], ['keys' => ['navbarUseTaglineText'], 'type' => 'bool'], ['keys' => ['navbarShowSub'], 'type' => 'bool'], ['keys' => ['heroTitle'], 'type' => 'bool'], ['keys' => ['heroImagePropagation'], 'type' => 'bool'], ['keys' => ['breadcrumbPrefix'], 'type' => 'bool'], ['keys' => ['breadcrumbSep'], 'type' => 'bool'], ['keys' => ['youareherePrefix'], 'type' => 'bool'], ['keys' => ['youarehereSep'], 'type' => 'bool'], ['keys' => ['sidebarShowLeft'], 'type' => 'bool'], ['keys' => ['sidebarShowRight'], 'type' => 'bool'], ['keys' => ['tocFull'], 'type' => 'bool'], ['keys' => ['footerSearch'], 'type' => 'bool'], ['keys' => ['licenseImageOnly'], 'type' => 'bool'], ['keys' => ['includePageUseACL'], 'type' => 'bool'], ['keys' => ['includePagePropagate'], 'type' => 'bool'], ['keys' => ['youarehereHideHome'], 'type' => 'bool'], ['keys' => ['tagsConsolidate'], 'type' => 'bool'], ['keys' => ['tagsShowHero'], 'type' => 'bool'], ['keys' => ['footerInPage'], 'type' => 'bool'], ['keys' => ['sidebarMobileDefaultCollapse'], 'type' => 'bool'], ['keys' => ['sidebarAlwaysShowLeft'], 'type' => 'bool'], ['keys' => ['sidebarAlwaysShowRight'], 'type' => 'bool'], ['keys' => ['searchUseTypeahead'], 'type' => 'bool'], ['keys' => ['showLightDark'], 'type' => 'bool'], ['keys' => ['autoLightDark'], 'type' => 'bool'], ['keys' => ['youarehereShowLast'], 'type' => 'int'], ['keys' => ['iconTag'], 'type' => 'string'], ['keys' => ['customTheme'], 'type' => 'string'], ['keys' => ['navbarCustomMenuText'], 'type' => 'string'], ['keys' => ['breadcrumbPrefixText'], 'type' => 'string'], ['keys' => ['breadcrumbSepText'], 'type' => 'string'], ['keys' => ['youareherePrefixText'], 'type' => 'string'], ['keys' => ['youarehereSepText'], 'type' => 'string'], ['keys' => ['footerPageInfoText'], 'type' => 'string'], ['keys' => ['footerCustomMenuText'], 'type' => 'string'], ['keys' => ['brandURLGuest'], 'type' => 'string'], ['keys' => ['brandURLUser'], 'type' => 'string'], ['keys' => ['useLESS'], 'type' => 'bool'], ['keys' => ['stickyTopHeader'], 'type' => 'bool'], ['keys' => ['stickyNavbar'], 'type' => 'bool'], ['keys' => ['stickyHeader'], 'type' => 'bool'], ['keys' => ['stickyLeftSidebar'], 'type' => 'bool'], ]; foreach ($data as $row) { // does not check case.... if (in_array($key, $row['keys']) === true) { if (array_key_exists('type', $row) === true) { switch ($row['type']) { case 'bool': return (bool) $value; case 'int': return (int) $value; case 'string': return $value; }//end switch }//end if if (in_array($value, $row['values']) === true) { return $value; } if (array_key_exists('default', $row) === true) { if (is_array($row['default']) === true) { if (array_key_exists($key, $row['default']) === true) { return $row['default'][$key]; } } else { return $row['default']; } } return reset($row['values']); }//end if }//end foreach return $value; } // phpcs:enable /** * Check if a page exist in directory or namespace * * @param string $page Page/namespace to search. * @return boolean if page exists */ public function pageExists(string $page): bool { ob_start(); tpl_includeFile($page . '.html'); $html = ob_get_contents(); ob_end_clean(); if (empty($html) === false) { return true; } $useACL = $this->getConf('includePageUseACL'); $propagate = $this->getConf('includePagePropagate'); if ($propagate === true) { if (page_findnearest($page, $useACL) !== false) { return true; } } elseif ($useACL === true && auth_quickaclcheck($page) !== AUTH_NONE) { return true; } return false; } /** * Print or return page from directory or namespace * * @param string $page Page/namespace to include. * @param boolean $print Print content. * @param boolean $parse Parse content before printing/returning. * @param string $classWrapper Wrap page in a div with class. * @return string contents of page found */ public function includePage(string $page, bool $print = true, bool $parse = true, string $classWrapper = ''): string { ob_start(); tpl_includeFile($page . '.html'); $html = ob_get_contents(); ob_end_clean(); if (empty($html) === true) { $useACL = $this->getConf('includePageUseACL'); $propagate = $this->getConf('includePagePropagate'); ob_start(); $html = tpl_include_page($page, false, $propagate, $useACL); $this->includedPageNotifications .= ob_get_contents(); ob_end_clean(); } if (empty($html) === false && $parse === true) { $html = $this->parseContent($html); } if (empty($classWrapper) === false && empty($html) === false) { $html = '
(.*)[^<]*/'; } $content = preg_replace_callback($page_regex, function ($icons) { $iconTag = $this->getConf('iconTag', 'icon'); return preg_replace_callback( '/<' . $iconTag . ' ([\w\- #]*)>(?=[^>]*(<|$))/', function ($matches) { global $MIKIO_ICONS; $s = $matches[0]; if (count($MIKIO_ICONS) > 0) { $icon = $MIKIO_ICONS[0]; if (count($matches) > 1) { $e = explode(' ', $matches[1]); if (count($e) > 1) { foreach ($MIKIO_ICONS as $iconItem) { if (strcasecmp($iconItem['name'], $e[0]) === 0) { $icon = $iconItem; $s = $icon['insert']; for ($i = 1; $i < 9; $i++) { if (count($e) < $i || empty($e[$i]) === true) { if (isset($icon['$' . $i]) === true) { $s = str_replace('$' . $i, $icon['$' . $i], $s); } } else { $s = str_replace('$' . $i, $e[$i], $s); } } $dir = ''; if (isset($icon['dir']) === true) { $dir = $this->baseDir . 'icons/' . $icon['dir'] . '/'; } $s = str_replace('$0', $dir, $s); break; }//end if }//end foreach } else { $s = str_replace('$1', $matches[1], $icon['insert']); }//end if }//end if }//end if $s = preg_replace('/(class=")(.*)"/', '$1mikio-icon $2"', $s, -1, $count); if ($count === 0) { $s = preg_replace('/(<\w* )/', '$1class="mikio-icon" ', $s); } return $s; }, $icons[0] ); }, $content); if (strcasecmp($ACT, 'preview') === 0) { if (is_array($preview) === true && count($preview) > 0) { $preview[0]->innertext = $content; } $str = $html->save(); $html->clear(); unset($html); } else { $str = $content; } }//end if return $str; } /** * Parse HTML for theme * * @param string $content HTML content to parse. * @return string Parsed content */ public function parseContent(string $content) { global $INPUT, $ACT; // Add Mikio Section titles if (strcasecmp($INPUT->str('page'), 'config') === 0) { $admin_sections = [ // Section Insert Before Icon 'navbar' => ['navbarUseTitleIcon', ''], 'search' => ['searchButton', ''], 'hero' => ['heroTitle', ''], 'tags' => ['tagsConsolidate', ''], 'breadcrumb' => ['breadcrumbHideHome', ''], 'youarehere' => ['youarehereHideHome', ''], 'sidebar' => ['sidebarShowLeft', ''], 'toc' => ['tocFull', ''], 'pagetools' => ['pageToolsFloating', ''], 'footer' => ['footerPageInfoText', ''], 'license' => ['licenseType', ''], 'acl' => ['includePageUseACL', ''], 'sticky' => ['stickyTopHeader', ''], ]; foreach ($admin_sections as $section => $items) { $search = $items[0]; $icon = $items[1]; $content = preg_replace( '/\s* \s*(tpl»mikio»' . $search . ')<\/span>/', ' ' . $this->mikioInlineIcon($icon) . tpl_getLang('config_' . $section) . ' tpl»mikio»' . $search . '', $content ); } } elseif (strcasecmp($INPUT->str('page'), 'styling') === 0) { $mikioPluginMissing = true; /* Hide plugin fields if not installed */ if (plugin_load('action', 'mikioplugin') !== null) { $mikioPluginMissing = false; } $style_headers = [ ['title' => 'Base', 'starts_with' => '__text_'], ['title' => 'Code', 'starts_with' => '__code_'], ['title' => 'Controls', 'starts_with' => '__control_'], ['title' => 'Header', 'starts_with' => '__topheader_'], ['title' => 'Navbar', 'starts_with' => '__navbar_'], ['title' => 'Sub Navbar', 'starts_with' => '__subnavbar_'], ['title' => 'Tags', 'starts_with' => '__tag_background_color_'], ['title' => 'Breadcrumbs', 'starts_with' => '__breadcrumb_'], ['title' => 'Hero', 'starts_with' => '__hero_'], ['title' => 'Sidebar', 'starts_with' => '__sidebar_'], ['title' => 'Content', 'starts_with' => '__content_'], ['title' => 'TOC', 'starts_with' => '__toc_'], ['title' => 'Page Tools', 'starts_with' => '__pagetools_'], ['title' => 'Footer', 'starts_with' => '__footer_'], ['title' => 'Table', 'starts_with' => '__table_'], ['title' => 'Dropdown', 'starts_with' => '__dropdown_'], ['title' => 'Section Edit', 'starts_with' => '__section_edit_'], ['title' => 'Tree', 'starts_with' => '__tree_'], ['title' => 'Tabs', 'starts_with' => '__tab_'], ['title' => 'Mikio Plugin', 'starts_with' => '__plugin_', 'heading' => 'h2', 'hidden' => $mikioPluginMissing ], ['title' => 'Primary Colours', 'starts_with' => '__plugin_primary_', 'hidden' => $mikioPluginMissing], ['title' => 'Secondary Colours', 'starts_with' => '__plugin_secondary_', 'hidden' => $mikioPluginMissing ], ['title' => 'Success Colours', 'starts_with' => '__plugin_success_', 'hidden' => $mikioPluginMissing], ['title' => 'Danger Colours', 'starts_with' => '__plugin_danger_', 'hidden' => $mikioPluginMissing], ['title' => 'Warning Colours', 'starts_with' => '__plugin_warning_', 'hidden' => $mikioPluginMissing], ['title' => 'Info Colours', 'starts_with' => '__plugin_info_', 'hidden' => $mikioPluginMissing], ['title' => 'Light Colours', 'starts_with' => '__plugin_light_', 'hidden' => $mikioPluginMissing], ['title' => 'Dark Colours', 'starts_with' => '__plugin_dark_', 'hidden' => $mikioPluginMissing], ['title' => 'Link Colours', 'starts_with' => '__plugin_link_', 'hidden' => $mikioPluginMissing], ['title' => 'Carousel', 'starts_with' => '__plugin_carousel_', 'hidden' => $mikioPluginMissing], ['title' => 'Steps', 'starts_with' => '__plugin_steps_', 'hidden' => $mikioPluginMissing], ['title' => 'Tabgroup', 'starts_with' => '__plugin_tabgroup_', 'hidden' => $mikioPluginMissing], ['title' => 'Tooltip', 'starts_with' => '__plugin_tooltip_', 'hidden' => $mikioPluginMissing], ['title' => 'Dark Mode', 'starts_with' => '__darkmode_', 'heading' => 'h2'], ['title' => 'Base', 'starts_with' => '__darkmode_text_'], ['title' => 'Code', 'starts_with' => '__darkmode_code_'], ['title' => 'Controls', 'starts_with' => '__darkmode_control_'], ['title' => 'Header', 'starts_with' => '__darkmode_topheader_'], ['title' => 'Navbar', 'starts_with' => '__darkmode_navbar_'], ['title' => 'Sub Navbar', 'starts_with' => '__darkmode_subnavbar_'], ['title' => 'Tags', 'starts_with' => '__darkmode_tag_background_color_'], ['title' => 'Breadcrumbs', 'starts_with' => '__darkmode_breadcrumb_'], ['title' => 'Hero', 'starts_with' => '__darkmode_hero_'], ['title' => 'Sidebar', 'starts_with' => '__darkmode_sidebar_'], ['title' => 'Content', 'starts_with' => '__darkmode_content_'], ['title' => 'TOC', 'starts_with' => '__darkmode_toc_'], ['title' => 'Page Tools', 'starts_with' => '__darkmode_pagetools_'], ['title' => 'Footer', 'starts_with' => '__darkmode_footer_'], ['title' => 'Table', 'starts_with' => '__darkmode_table_'], ['title' => 'Dropdown', 'starts_with' => '__darkmode_dropdown_'], ['title' => 'Section Edit', 'starts_with' => '__darkmode_section_edit_'], ['title' => 'Tree', 'starts_with' => '__darkmode_tree_'], ['title' => 'Tabs', 'starts_with' => '__darkmode_tab_'], ['title' => 'Mikio Plugin (Dark mode)', 'starts_with' => '__plugin_darkmode_', 'heading' => 'h2', 'hidden' => $mikioPluginMissing ], ['title' => 'Primary Colours', 'starts_with' => '__plugin_darkmode_primary_', 'hidden' => $mikioPluginMissing ], ['title' => 'Secondary Colours', 'starts_with' => '__plugin_darkmode_secondary_', 'hidden' => $mikioPluginMissing ], ['title' => 'Success Colours', 'starts_with' => '__plugin_darkmode_success_', 'hidden' => $mikioPluginMissing ], ['title' => 'Danger Colours', 'starts_with' => '__plugin_darkmode_danger_', 'hidden' => $mikioPluginMissing ], ['title' => 'Warning Colours', 'starts_with' => '__plugin_darkmode_warning_', 'hidden' => $mikioPluginMissing ], ['title' => 'Info Colours', 'starts_with' => '__plugin_darkmode_info_', 'hidden' => $mikioPluginMissing ], ['title' => 'Light Colours', 'starts_with' => '__plugin_darkmode_light_', 'hidden' => $mikioPluginMissing ], ['title' => 'Dark Colours', 'starts_with' => '__plugin_darkmode_dark_', 'hidden' => $mikioPluginMissing ], ['title' => 'Link Colours', 'starts_with' => '__plugin_darkmode_link_', 'hidden' => $mikioPluginMissing ], ['title' => 'Carousel', 'starts_with' => '__plugin_darkmode_carousel_', 'hidden' => $mikioPluginMissing ], ['title' => 'Steps', 'starts_with' => '__plugin_darkmode_steps_', 'hidden' => $mikioPluginMissing], ['title' => 'Tabgroup', 'starts_with' => '__plugin_darkmode_tabgroup_', 'hidden' => $mikioPluginMissing ], ['title' => 'Tooltip', 'starts_with' => '__plugin_darkmode_tooltip_', 'hidden' => $mikioPluginMissing], ]; foreach ($style_headers as $header) { if (array_key_exists('heading', $header) === false) { $header['heading'] = 'h3'; } if (array_key_exists('hidden', $header) === false) { $header['hidden'] = false; } $content = preg_replace( '/( \s* \s*