Files
mr-legend_wiki/lib/tpl/bootstrap3/Template.php
T
2024-07-08 03:32:47 +09:00

2425 lines
76 KiB
PHP

<?php
namespace dokuwiki\template\bootstrap3;
/**
* DokuWiki Bootstrap3 Template: Template Class
*
* @link http://dokuwiki.org/template:bootstrap3
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
*/
class Template
{
private $plugins = [];
private $confMetadata = [];
private $toolsMenu = [];
private $handlers;
public $tplDir = '';
public $baseDir = '';
public function __construct()
{
global $JSINFO;
global $INPUT;
global $ACT;
global $INFO;
$this->tplDir = tpl_incdir();
$this->baseDir = tpl_basedir();
$this->initPlugins();
$this->initToolsMenu();
$this->loadConfMetadata();
// Get the template info (useful for debug)
if (isset($INFO['isadmin']) && $INPUT->str('do') && $INPUT->str('do') == 'check') {
msg('Template version ' . $this->getVersion(), 1, '', '', MSG_ADMINS_ONLY);
}
// Populate JSINFO object
$JSINFO['bootstrap3'] = [
'mode' => $ACT,
'toc' => [],
'config' => [
'collapsibleSections' => (int) $this->getConf('collapsibleSections'),
'fixedTopNavbar' => (int) $this->getConf('fixedTopNavbar'),
'showSemanticPopup' => (int) $this->getConf('showSemanticPopup'),
'sidebarOnNavbar' => (int) $this->getConf('sidebarOnNavbar'),
'tagsOnTop' => (int) $this->getConf('tagsOnTop'),
'tocAffix' => (int) $this->getConf('tocAffix'),
'tocCollapseOnScroll' => (int) $this->getConf('tocCollapseOnScroll'),
'tocCollapsed' => (int) $this->getConf('tocCollapsed'),
'tocLayout' => $this->getConf('tocLayout'),
'useAnchorJS' => (int) $this->getConf('useAnchorJS'),
'useAlternativeToolbarIcons' => (int) $this->getConf('useAlternativeToolbarIcons'),
'disableSearchSuggest' => (int) $this->getConf('disableSearchSuggest'),
],
];
if ($ACT == 'admin') {
$JSINFO['bootstrap3']['admin'] = hsc($INPUT->str('page'));
}
if (!defined('MAX_FILE_SIZE') && $pagesize = $this->getConf('domParserMaxPageSize')) {
define('MAX_FILE_SIZE', $pagesize);
}
# Start Event Handlers
$this->handlers = new EventHandlers($this);
}
public function getVersion()
{
$template_info = confToHash($this->tplDir . 'template.info.txt');
$template_version = 'v' . $template_info['date'];
if (isset($template_info['build'])) {
$template_version .= ' (' . $template_info['build'] . ')';
}
return $template_version;
}
private function initPlugins()
{
$plugins = ['tplinc', 'tag', 'userhomepage', 'translation', 'pagelist'];
foreach ($plugins as $plugin) {
$this->plugins[$plugin] = plugin_load('helper', $plugin);
}
}
public function getPlugin($plugin)
{
if (plugin_isdisabled($plugin)) {
return false;
}
if (!isset($this->plugins[$plugin])) {
return false;
}
return $this->plugins[$plugin];
}
/**
* Get the singleton instance
*
* @return Template
*/
public static function getInstance()
{
static $instance = null;
if ($instance === null) {
$instance = new self;
}
return $instance;
}
/**
* Get the content to include from the tplinc plugin
*
* prefix and postfix are only added when there actually is any content
*
* @param string $location
* @return string
*/
public function includePage($location, $return = false)
{
$content = '';
if ($plugin = $this->getPlugin('tplinc')) {
$content = $plugin->renderIncludes($location);
}
if ($content === '') {
$content = tpl_include_page($location, 0, 1, $this->getConf('useACL'));
}
if ($content === '') {
return '';
}
$content = $this->normalizeContent($content);
if ($return) {
return $content;
}
echo $content;
return '';
}
/**
* Get the template configuration metadata
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $key
* @return array|string
*/
public function getConfMetadata($key = null)
{
if ($key && isset($this->confMetadata[$key])) {
return $this->confMetadata[$key];
}
return null;
}
private function loadConfMetadata()
{
$meta = [];
$file = $this->tplDir . 'conf/metadata.php';
include $file;
$this->confMetadata = $meta;
}
/**
* Simple wrapper for tpl_getConf
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $key
* @param mixed $default value
* @return mixed
*/
public function getConf($key, $default = false)
{
global $ACT, $INFO, $ID, $conf;
$value = tpl_getConf($key, $default);
switch ($key) {
case 'useAvatar':
if ($value == 'off') {
return false;
}
return $value;
case 'bootstrapTheme':
@list($theme, $bootswatch) = $this->getThemeForNamespace();
if ($theme) {
return $theme;
}
return $value;
case 'bootswatchTheme':
@list($theme, $bootswatch) = $this->getThemeForNamespace();
if ($bootswatch) {
return $bootswatch;
}
return $value;
case 'showTools':
case 'showSearchForm':
case 'showPageTools':
case 'showEditBtn':
case 'showAddNewPage':
return $value !== 'never' && ($value == 'always' || !empty($_SERVER['REMOTE_USER']));
case 'showAdminMenu':
return $value && ($INFO['isadmin'] || $INFO['ismanager']);
case 'hideLoginLink':
case 'showLoginOnFooter':
return ($value && !isset($_SERVER['REMOTE_USER']));
case 'showCookieLawBanner':
return $value && page_findnearest(tpl_getConf('cookieLawBannerPage'), $this->getConf('useACL')) && ($ACT == 'show');
case 'showSidebar':
if ($ACT !== 'show') {
return false;
}
if ($this->getConf('showLandingPage')) {
return false;
}
return page_findnearest($conf['sidebar'], $this->getConf('useACL'));
case 'showRightSidebar':
if ($ACT !== 'show') {
return false;
}
if ($this->getConf('sidebarPosition') == 'right') {
return false;
}
return page_findnearest(tpl_getConf('rightSidebar'), $this->getConf('useACL'));
case 'showLandingPage':
return ($value && (bool) preg_match_all($this->getConf('landingPages'), $ID));
case 'pageOnPanel':
if ($this->getConf('showLandingPage')) {
return false;
}
return $value;
case 'showThemeSwitcher':
return $value && ($this->getConf('bootstrapTheme') == 'bootswatch');
case 'tocCollapseSubSections':
if (!$this->getConf('tocAffix')) {
return false;
}
return $value;
case 'schemaOrgType':
if ($semantic = plugin_load('helper', 'semantic')) {
if (method_exists($semantic, 'getSchemaOrgType')) {
return $semantic->getSchemaOrgType();
}
}
return $value;
case 'tocCollapseOnScroll':
if ($this->getConf('tocLayout') !== 'default') {
return false;
}
return $value;
}
$metadata = $this->getConfMetadata($key);
if (isset($metadata[0])) {
switch ($metadata[0]) {
case 'regex':
return '/' . $value . '/';
case 'multicheckbox':
return explode(',', $value);
}
}
return $value;
}
/**
* Return the Bootswatch.com theme lists defined in metadata.php
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return array
*/
public function getBootswatchThemeList()
{
$bootswatch_themes = $this->getConfMetadata('bootswatchTheme');
return $bootswatch_themes['_choices'];
}
/**
* Get a Gravatar, Libravatar, Office365/EWS URL or local ":user" DokuWiki namespace
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $username User ID
* @param string $email The email address
* @param string $size Size in pixels, defaults to 80px [ 1 - 2048 ]
* @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
* @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
*
* @return string
*/
public function getAvatar($username, $email, $size = 80, $d = 'mm', $r = 'g')
{
global $INFO;
$avatar_url = '';
$avatar_provider = $this->getConf('useAvatar');
if (!$avatar_provider) {
return false;
}
if ($avatar_provider == 'local') {
$interwiki = getInterwiki();
$user_url = str_replace('{NAME}', $username, $interwiki['user']);
$logo_size = [];
$logo = tpl_getMediaFile(["$user_url.png", "$user_url.jpg", 'images/avatar.png'], false, $logo_size);
return $logo;
}
if ($avatar_provider == 'activedirectory') {
$logo = "data:image/jpeg;base64," . base64_encode($INFO['userinfo']['thumbnailphoto']);
return $logo;
}
$email = strtolower(trim($email));
if ($avatar_provider == 'office365') {
$office365_url = rtrim($this->getConf('office365URL'), '/');
$avatar_url = $office365_url . '/owa/service.svc/s/GetPersonaPhoto?email=' . $email . '&size=HR' . $size . 'x' . $size;
}
if ($avatar_provider == 'gravatar' || $avatar_provider == 'libavatar') {
$gravatar_url = rtrim($this->getConf('gravatarURL'), '/') . '/';
$libavatar_url = rtrim($this->getConf('libavatarURL'), '/') . '/';
switch ($avatar_provider) {
case 'gravatar':
$avatar_url = $gravatar_url;
break;
case 'libavatar':
$avatar_url = $libavatar_url;
break;
}
$avatar_url .= md5($email);
$avatar_url .= "?s=$size&d=$d&r=$r";
}
if ($avatar_url) {
$media_link = ml("$avatar_url&.jpg", ['cache' => 'recache', 'w' => $size, 'h' => $size]);
return $media_link;
}
return false;
}
/**
* Return template classes
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @see tpl_classes();
*
* @return string
**/
public function getClasses()
{
global $ACT;
$page_on_panel = $this->getConf('pageOnPanel');
$bootstrap_theme = $this->getConf('bootstrapTheme');
$bootswatch_theme = $this->getBootswatchTheme();
$classes = [];
$classes[] = (($bootstrap_theme == 'bootswatch') ? $bootswatch_theme : $bootstrap_theme);
$classes[] = trim(tpl_classes());
if ($page_on_panel) {
$classes[] = 'dw-page-on-panel';
}
if (!$this->getConf('tableFullWidth')) {
$classes[] = 'dw-table-width';
}
if ($this->isFluidNavbar()) {
$classes[] = 'dw-fluid-container';
}
return implode(' ', $classes);
}
/**
* Return the current Bootswatch theme
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getBootswatchTheme()
{
global $INPUT;
$bootswatch_theme = $this->getConf('bootswatchTheme');
if ($this->getConf('showThemeSwitcher')) {
if (get_doku_pref('bootswatchTheme', null) !== null && get_doku_pref('bootswatchTheme', null) !== '') {
$bootswatch_theme = get_doku_pref('bootswatchTheme', null);
}
}
return $bootswatch_theme;
}
/**
* Return only the available Bootswatch.com themes
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return array
*/
public function getAvailableBootswatchThemes()
{
return array_diff($this->getBootswatchThemeList(), $this->getConf('hideInThemeSwitcher'));
}
/**
* Return the active theme
*
* @return string
*/
public function getTheme()
{
$bootstrap_theme = $this->getConf('bootstrapTheme');
$bootswatch_theme = $this->getBootswatchTheme();
$theme = (($bootstrap_theme == 'bootswatch') ? $bootswatch_theme : $bootstrap_theme);
return $theme;
}
/**
* Return the active theme
*
* @return string
*/
public function getThemeFeatures()
{
$features = [];
if ($this->isFluidNavbar()) {
$features[] = 'fluid-container';
}
if ($this->getConf('fixedTopNavbar')) {
$features[] = 'fixed-top-navbar';
}
if ($this->getConf('tocCollapseSubSections')) {
$features[] = 'toc-cullapse-sub-sections';
}
return implode(' ', $features);
}
/**
* Print some info about the current page
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param bool $ret return content instead of printing it
* @return bool|string
*/
public function getPageInfo($ret = false)
{
global $conf;
global $lang;
global $INFO;
global $ID;
// return if we are not allowed to view the page
if (!auth_quickaclcheck($ID)) {
return false;
}
// prepare date and path
$fn = $INFO['filepath'];
if (!$conf['fullpath']) {
if ($INFO['rev']) {
$fn = str_replace(fullpath($conf['olddir']) . '/', '', $fn);
} else {
$fn = str_replace(fullpath($conf['datadir']) . '/', '', $fn);
}
}
$date_format = $this->getConf('pageInfoDateFormat');
$page_info = $this->getConf('pageInfo');
$fn = utf8_decodeFN($fn);
$date = (($date_format == 'dformat')
? dformat($INFO['lastmod'])
: datetime_h($INFO['lastmod']));
// print it
if ($INFO['exists']) {
$fn_full = $fn;
if (!in_array('extension', $page_info)) {
$fn = str_replace(['.txt.gz', '.txt'], '', $fn);
}
$out = '<ul class="list-inline">';
if (in_array('filename', $page_info)) {
$out .= '<li>' . iconify('mdi:file-document-outline', ['class' => 'text-muted']) . ' <span title="' . $fn_full . '">' . $fn . '</span></li>';
}
if (in_array('date', $page_info)) {
$out .= '<li>' . iconify('mdi:calendar', ['class' => 'text-muted']) . ' ' . $lang['lastmod'] . ' <span title="' . dformat($INFO['lastmod']) . '">' . $date . '</span></li>';
}
if (in_array('editor', $page_info)) {
if (isset($INFO['editor'])) {
$user = editorinfo($INFO['editor']);
if ($this->getConf('useAvatar')) {
global $auth;
$user_data = $auth->getUserData($INFO['editor']);
$avatar_img = $this->getAvatar($INFO['editor'], $user_data['mail'], 16);
$user_img = '<img src="' . $avatar_img . '" alt="" width="16" height="16" class="img-rounded" /> ';
$user = str_replace(['iw_user', 'interwiki'], '', $user);
$user = $user_img . "<bdi>$user<bdi>";
}
$out .= '<li class="text-muted">' . $lang['by'] . ' <bdi>' . $user . '</bdi></li>';
} else {
$out .= '<li>(' . $lang['external_edit'] . ')</li>';
}
}
if ($INFO['locked'] && in_array('locked', $page_info)) {
$out .= '<li>' . iconify('mdi:lock', ['class' => 'text-muted']) . ' ' . $lang['lockedby'] . ' ' . editorinfo($INFO['locked']) . '</li>';
}
$out .= '</ul>';
if ($ret) {
return $out;
} else {
echo $out;
return true;
}
}
return false;
}
/**
* Prints the global message array in Bootstrap style
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @see html_msgarea()
*/
public function getMessageArea()
{
global $MSG, $MSG_shown;
/** @var array $MSG */
// store if the global $MSG has already been shown and thus HTML output has been started
$MSG_shown = true;
// Check if translation is outdate
if ($this->getConf('showTranslation') && $translation = $this->getPlugin('translation')) {
global $ID;
if ($translation->istranslatable($ID)) {
$translation->checkage();
}
}
if (!isset($MSG)) {
return;
}
$shown = [];
foreach ($MSG as $msg) {
$hash = md5($msg['msg']);
if (isset($shown[$hash])) {
continue;
}
// skip double messages
if (info_msg_allowed($msg)) {
switch ($msg['lvl']) {
case 'info':
$level = 'info';
$icon = 'mdi:information';
break;
case 'error':
$level = 'danger';
$icon = 'mdi:alert-octagon';
break;
case 'notify':
$level = 'warning';
$icon = 'mdi:alert';
break;
case 'success':
$level = 'success';
$icon = 'mdi:check-circle';
break;
}
print '<div class="alert alert-' . $level . '">';
print iconify($icon, ['class' => 'mr-2']);
print $msg['msg'];
print '</div>';
}
$shown[$hash] = 1;
}
unset($GLOBALS['MSG']);
}
/**
* Get the license (link or image)
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $type ("link" or "image")
* @param integer $size of image
* @param bool $return or print
* @return string
*/
public function getLicense($type = 'link', $size = 24, $return = false)
{
global $conf, $license, $lang;
$target = $conf['target']['extern'];
$lic = $license[$conf['license']];
$output = '';
if (!$lic) {
return '';
}
if ($type == 'link') {
$output .= $lang['license'] . '<br/>';
}
$license_url = $lic['url'];
$license_name = $lic['name'];
$output .= '<a href="' . $license_url . '" title="' . $license_name . '" target="' . $target . '" itemscope itemtype="http://schema.org/CreativeWork" itemprop="license" rel="license" class="license">';
if ($type == 'image') {
foreach (explode('-', $conf['license']) as $license_img) {
if ($license_img == 'publicdomain') {
$license_img = 'pd';
}
$output .= '<img src="' . tpl_basedir() . "images/license/$license_img.png" . '" width="' . $size . '" height="' . $size . '" alt="' . $license_img . '" /> ';
}
} else {
$output .= $lic['name'];
}
$output .= '</a>';
if ($return) {
return $output;
}
echo $output;
return '';
}
/**
* Add Google Analytics
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getGoogleAnalitycs()
{
global $INFO;
global $ID;
if (!$this->getConf('useGoogleAnalytics')) {
return false;
}
if (!$google_analitycs_id = $this->getConf('googleAnalyticsTrackID')) {
return false;
}
if ($this->getConf('googleAnalyticsNoTrackAdmin') && $INFO['isadmin']) {
return false;
}
if ($this->getConf('googleAnalyticsNoTrackUsers') && isset($_SERVER['REMOTE_USER'])) {
return false;
}
if (tpl_getConf('googleAnalyticsNoTrackPages')) {
if (preg_match_all($this->getConf('googleAnalyticsNoTrackPages'), $ID)) {
return false;
}
}
$out = DOKU_LF;
$out .= '// Google Analytics' . DOKU_LF;
$out .= "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');" . DOKU_LF;
$out .= 'ga("create", "' . $google_analitycs_id . '", "auto");' . DOKU_LF;
$out .= 'ga("send", "pageview");' . DOKU_LF;
if ($this->getConf('googleAnalyticsAnonymizeIP')) {
$out .= 'ga("set", "anonymizeIp", true);' . DOKU_LF;
}
if ($this->getConf('googleAnalyticsTrackActions')) {
$out .= 'ga("send", "event", "DokuWiki", JSINFO.bootstrap3.mode);' . DOKU_LF;
}
$out .= '// End Google Analytics' . DOKU_LF;
return $out;
}
/**
* Return the user home-page link
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getUserHomePageLink()
{
return wl($this->getUserHomePageID());
}
/**
* Return the user home-page ID
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getUserHomePageID()
{
$interwiki = getInterwiki();
$page_id = str_replace('{NAME}', $_SERVER['REMOTE_USER'], $interwiki['user']);
return cleanID($page_id);
}
/**
* Print the breadcrumbs trace with Bootstrap style
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return bool
*/
public function getBreadcrumbs()
{
global $lang;
global $conf;
//check if enabled
if (!$conf['breadcrumbs']) {
return false;
}
$crumbs = breadcrumbs(); //setup crumb trace
//render crumbs, highlight the last one
print '<ol class="breadcrumb">';
print '<li>' . rtrim($lang['breadcrumb'], ':') . '</li>';
$last = count($crumbs);
$i = 0;
foreach ($crumbs as $id => $name) {
$i++;
print($i == $last) ? '<li class="active">' : '<li>';
tpl_link(wl($id), hsc($name), 'title="' . $id . '"');
print '</li>';
if ($i == $last) {
print '</ol>';
}
}
return true;
}
/**
* Hierarchical breadcrumbs with Bootstrap style
*
* This code was suggested as replacement for the usual breadcrumbs.
* It only makes sense with a deep site structure.
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Nigel McNie <oracle.shinoda@gmail.com>
* @author Sean Coates <sean@caedmon.net>
* @author <fredrik@averpil.com>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @todo May behave strangely in RTL languages
*
* @return bool
*/
public function getYouAreHere()
{
global $conf;
global $ID;
global $lang;
// check if enabled
if (!$conf['youarehere']) {
return false;
}
$parts = explode(':', $ID);
$count = count($parts);
echo '<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">';
echo '<li>' . rtrim($lang['youarehere'], ':') . '</li>';
// always print the startpage
echo '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
tpl_link(wl($conf['start']),
'<span itemprop="name">' . iconify('mdi:home') . '<span class="sr-only">Home</span></span>',
' itemprop="item" title="' . $conf['start'] . '"'
);
echo '<meta itemprop="position" content="1" />';
echo '</li>';
$position = 1;
// print intermediate namespace links
$part = '';
for ($i = 0; $i < $count - 1; $i++) {
$part .= $parts[$i] . ':';
$page = $part;
if ($page == $conf['start']) {
continue;
}
// Skip startpage
$position++;
// output
echo '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
$link = html_wikilink($page);
$link = str_replace(['<span class="curid">', '</span>'], '', $link);
$link = str_replace('<a', '<a itemprop="item" ', $link);
$link = preg_replace('/data-wiki-id="(.+?)"/', '', $link);
$link = str_replace('<a', '<span itemprop="name"><a', $link);
$link = str_replace('</a>', '</a></span>', $link);
echo $link;
echo '<meta itemprop="position" content="' . $position . '" />';
echo '</li>';
}
// print current page, skipping start page, skipping for namespace index
$exists = false;
resolve_pageid('', $page, $exists);
if (isset($page) && $page == $part . $parts[$i]) {
echo '</ol>';
return true;
}
$page = $part . $parts[$i];
if ($page == $conf['start']) {
echo '</ol>';
return true;
}
echo '<li class="active" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
$link = str_replace(['<span class="curid">', '</span>'], '', html_wikilink($page));
$link = str_replace('<a ', '<a itemprop="item" ', $link);
$link = str_replace('<a', '<span itemprop="name"><a', $link);
$link = str_replace('</a>', '</a></span>', $link);
$link = preg_replace('/data-wiki-id="(.+?)"/', '', $link);
echo $link;
echo '<meta itemprop="position" content="' . ++$position . '" />';
echo '</li>';
echo '</ol>';
return true;
}
/**
* Display the page title (and previous namespace page title) on browser titlebar
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @return string
*/
public function getBrowserPageTitle()
{
global $conf, $ACT, $ID;
if ($this->getConf('browserTitleShowNS') && $ACT == 'show') {
$ns_page = '';
$ns_parts = explode(':', $ID);
$ns_pages = [];
$ns_titles = [];
$ns_separator = sprintf(' %s ', $this->getConf('browserTitleCharSepNS'));
if (useHeading('navigation')) {
if (count($ns_parts) > 1) {
foreach ($ns_parts as $ns_part) {
$ns_page .= "$ns_part:";
$ns_pages[] = $ns_page;
}
$ns_pages = array_unique($ns_pages);
foreach ($ns_pages as $ns_page) {
$exists = false;
resolve_pageid(getNS($ns_page), $ns_page, $exists);
$ns_page_title_heading = hsc(p_get_first_heading($ns_page));
$ns_page_title_page = noNSorNS($ns_page);
$ns_page_title = ($exists) ? $ns_page_title_heading : null;
if ($ns_page_title !== $conf['start']) {
$ns_titles[] = $ns_page_title;
}
}
}
resolve_pageid(getNS($ID), $ID, $exists);
if ($exists) {
$ns_titles[] = tpl_pagetitle($ID, true);
} else {
$ns_titles[] = noNS($ID);
}
$ns_titles = array_filter(array_unique($ns_titles));
} else {
$ns_titles = $ns_parts;
}
if ($this->getConf('browserTitleOrderNS') == 'normal') {
$ns_titles = array_reverse($ns_titles);
}
$browser_title = implode($ns_separator, $ns_titles);
} else {
$browser_title = tpl_pagetitle($ID, true);
}
return str_replace(
['@WIKI@', '@TITLE@'],
[strip_tags($conf['title']), $browser_title],
$this->getConf('browserTitle')
);
}
/**
* Return the theme for current namespace
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @return string
*/
public function getThemeForNamespace()
{
global $ID;
$themes_filename = DOKU_CONF . 'bootstrap3.themes.conf';
if (!$this->getConf('themeByNamespace')) {
return [];
}
if (!file_exists($themes_filename)) {
return [];
}
$config = confToHash($themes_filename);
krsort($config);
foreach ($config as $page => $theme) {
if (preg_match("/^$page/", "$ID")) {
list($bootstrap, $bootswatch) = explode('/', $theme);
if ($bootstrap && in_array($bootstrap, ['default', 'optional', 'custom'])) {
return [$bootstrap, $bootswatch];
}
if ($bootstrap == 'bootswatch' && in_array($bootswatch, $this->getBootswatchThemeList())) {
return [$bootstrap, $bootswatch];
}
}
}
return [];
}
/**
* Make a Bootstrap3 Nav
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $html
* @param string $type (= pills, tabs, navbar)
* @param boolean $staked
* @param string $optional_class
* @return string
*/
public function toBootstrapNav($html, $type = '', $stacked = false, $optional_class = '')
{
$classes = [];
$classes[] = 'nav';
$classes[] = $optional_class;
switch ($type) {
case 'navbar':
case 'navbar-nav':
$classes[] = 'navbar-nav';
break;
case 'pills':
case 'tabs':
$classes[] = "nav-$type";
break;
}
if ($stacked) {
$classes[] = 'nav-stacked';
}
$class = implode(' ', $classes);
$output = str_replace(
['<ul class="', '<ul>'],
["<ul class=\"$class ", "<ul class=\"$class\">"],
$html
);
$output = $this->normalizeList($output);
return $output;
}
/**
* Normalize the DokuWiki list items
*
* @todo use Simple DOM HTML library
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @todo use Simple DOM HTML
* @todo FIX SimpleNavi curid
*
* @param string $html
* @return string
*/
public function normalizeList($list)
{
global $ID;
$list = preg_replace_callback('/data-wiki-id="(.+?)"/', [$this, '_replaceWikiCurrentIdCallback'], $list);
$html = new \simple_html_dom;
$html->load($list, true, false);
# Create data-curid HTML5 attribute and unwrap span.curid for pre-Hogfather release
foreach ($html->find('span.curid') as $elm) {
$elm->firstChild()->setAttribute('data-wiki-curid', 'true');
$elm->outertext = str_replace(['<span class="curid">', '</span>'], '', $elm->outertext);
}
# Unwrap div.li element
foreach ($html->find('div.li') as $elm) {
$elm->outertext = str_replace(['<div class="li">', '</div>'], '', $elm->outertext);
}
$list = $html->save();
$html->clear();
unset($html);
$html = new \simple_html_dom;
$html->load($list, true, false);
foreach ($html->find('li') as $elm) {
if ($elm->find('a[data-wiki-curid]')) {
$elm->class .= ' active';
}
}
$list = $html->save();
$html->clear();
unset($html);
# TODO optimize
$list = preg_replace('/<i (.+?)><\/i> <a (.+?)>(.+?)<\/a>/', '<a $2><i $1></i> $3</a>', $list);
$list = preg_replace('/<span (.+?)><\/span> <a (.+?)>(.+?)<\/a>/', '<a $2><span $1></span> $3</a>', $list);
return $list;
}
/**
* Remove data-wiki-id HTML5 attribute
*
* @todo Remove this in future
* @since Hogfather
*
* @param array $matches
*
* @return string
*/
private function _replaceWikiCurrentIdCallback($matches)
{
global $ID;
if ($ID == $matches[1]) {
return 'data-wiki-curid="true"';
}
return '';
}
/**
* Return a Bootstrap NavBar and or drop-down menu
*
* @todo use Simple DOM HTML library
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getNavbar()
{
if ($this->getConf('showNavbar') === 'logged' && !$_SERVER['REMOTE_USER']) {
return false;
}
global $ID;
global $conf;
$navbar = $this->toBootstrapNav(tpl_include_page('navbar', 0, 1, $this->getConf('useACL')), 'navbar');
$navbar = str_replace('urlextern', '', $navbar);
$navbar = preg_replace('/<li class="level([0-9]) node"> (.*)/',
'<li class="level$1 node dropdown"><a href="#" class="dropdown-toggle" data-target="#" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">$2 <span class="caret"></span></a>', $navbar);
$navbar = preg_replace('/<li class="level([0-9]) node active"> (.*)/',
'<li class="level$1 node active dropdown"><a href="#" class="dropdown-toggle" data-target="#" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">$2 <span class="caret"></span></a>', $navbar);
# FIX for Purplenumbers renderer plugin
# TODO use Simple DOM HTML or improve the regex!
if ($conf['renderer_xhtml'] == 'purplenumbers') {
$navbar = preg_replace('/<li class="level1"> (.*)/',
'<li class="level1 dropdown"><a href="#" class="dropdown-toggle" data-target="#" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">$1 <span class="caret"></span></a>', $navbar);
}
$navbar = preg_replace('/<ul class="(.*)">\n<li class="level2(.*)">/',
'<ul class="dropdown-menu" role="menu">' . PHP_EOL . '<li class="level2$2">', $navbar);
return $navbar;
}
/**
* Manipulate Sidebar page to add Bootstrap3 styling
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $sidebar
* @param boolean $return
* @return string
*/
public function normalizeSidebar($sidebar, $return = false)
{
$out = $this->toBootstrapNav($sidebar, 'pills', true);
$out = $this->normalizeContent($out);
$html = new \simple_html_dom;
$html->load($out, true, false);
# TODO 'page-header' will be removed in the next release of Bootstrap
foreach ($html->find('h1, h2, h3, h4, h5, h6') as $elm) {
# Skip panel title on sidebar
if (preg_match('/panel-title/', $elm->class)) {
continue;
}
$elm->class .= ' page-header';
}
$out = $html->save();
$html->clear();
unset($html);
if ($return) {
return $out;
}
echo $out;
}
/**
* Return a drop-down page
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $page name
* @return string
*/
public function getDropDownPage($page)
{
$page = page_findnearest($page, $this->getConf('useACL'));
if (!$page) {
return;
}
$output = $this->normalizeContent($this->toBootstrapNav(tpl_include_page($page, 0, 1, $this->getConf('useACL')), 'pills', true));
$dropdown = '<ul class="nav navbar-nav dw__dropdown_page">' .
'<li class="dropdown dropdown-large">' .
'<a href="#" class="dropdown-toggle" data-toggle="dropdown" title="">' .
p_get_first_heading($page) .
' <span class="caret"></span></a>' .
'<ul class="dropdown-menu dropdown-menu-large" role="menu">' .
'<li><div class="container small">' .
$output .
'</div></li></ul></li></ul>';
return $dropdown;
}
/**
* Include left or right sidebar
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $type left or right sidebar
* @return boolean
*/
public function includeSidebar($type)
{
global $conf;
$left_sidebar = $conf['sidebar'];
$right_sidebar = $this->getConf('rightSidebar');
$left_sidebar_grid = $this->getConf('leftSidebarGrid');
$right_sidebar_grid = $this->getConf('rightSidebarGrid');
if (!$this->getConf('showSidebar')) {
return false;
}
switch ($type) {
case 'left':
if ($this->getConf('sidebarPosition') == 'left') {
$this->sidebarWrapper($left_sidebar, 'dokuwiki__aside', $left_sidebar_grid, 'sidebarheader', 'sidebarfooter');
}
return true;
case 'right':
if ($this->getConf('sidebarPosition') == 'right') {
$this->sidebarWrapper($left_sidebar, 'dokuwiki__aside', $left_sidebar_grid, 'sidebarheader', 'sidebarfooter');
}
if ($this->getConf('showRightSidebar')
&& $this->getConf('sidebarPosition') == 'left') {
$this->sidebarWrapper($right_sidebar, 'dokuwiki__rightaside', $right_sidebar_grid, 'rightsidebarheader', 'rightsidebarfooter');
}
return true;
}
return false;
}
/**
* Wrapper for left or right sidebar
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $sidebar_page
* @param string $sidebar_id
* @param string $sidebar_header
* @param string $sidebar_footer
*/
private function sidebarWrapper($sidebar_page, $sidebar_id, $sidebar_class, $sidebar_header, $sidebar_footer)
{
global $lang;
global $TPL;
@require $this->tplDir . 'tpl/sidebar.php';
}
/**
* Add Bootstrap classes in a DokuWiki content
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param string $content from tpl_content() or from tpl_include_page()
* @return string with Bootstrap styles
*/
public function normalizeContent($content)
{
global $ACT;
global $INPUT;
global $INFO;
# FIX :-\ smile
$content = str_replace(['alt=":-\"', "alt=':-\'"], 'alt=":-&#92;"', $content);
# Workaround for ToDo Plugin
$content = str_replace('checked="checked"', ' checked="checked"', $content);
# Return original content if Simple HTML DOM fail or exceeded page size (default MAX_FILE_SIZE => 600KB)
if (strlen($content) > MAX_FILE_SIZE) {
return $content;
}
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
# Return original content if Simple HTML DOM fail or exceeded page size (default MAX_FILE_SIZE => 600KB)
if (!$html) {
return $content;
}
# Move Current Page ID to <a> element and create data-curid HTML5 attribute (pre-Hogfather release)
foreach ($html->find('.curid') as $elm) {
foreach ($elm->find('a') as $link) {
$link->class .= ' curid';
$link->attr[' data-curid'] = 'true'; # FIX attribute
}
}
# Unwrap span.curid elements
foreach ($html->find('span.curid') as $elm) {
$elm->outertext = str_replace(['<span class="curid">', '</span>'], '', $elm->outertext);
}
# Footnotes
foreach ($html->find('.footnotes') as $elm) {
$elm->outertext = '<hr/>' . $elm->outertext;
}
# Accessibility (a11y)
foreach ($html->find('.a11y') as $elm) {
if (!preg_match('/picker/', $elm->class)) {
$elm->class .= ' sr-only';
}
}
# Fix list overlap in media images
foreach ($html->find('ul, ol') as $elm) {
if (!preg_match('/(nav|dropdown-menu)/', $elm->class)) {
$elm->class .= ' fix-media-list-overlap';
}
}
# Buttons
foreach ($html->find('.button') as $elm) {
if ($elm->tag !== 'form') {
$elm->class .= ' btn';
}
}
foreach ($html->find('[type=button], [type=submit], [type=reset]') as $elm) {
$elm->class .= ' btn btn-default';
}
# Tabs
foreach ($html->find('.tabs') as $elm) {
$elm->class = 'nav nav-tabs';
}
# Tabs (active)
foreach ($html->find('.nav-tabs strong') as $elm) {
$elm->outertext = '<a href="#">' . $elm->innertext . "</a>";
$parent = $elm->parent()->class .= ' active';
}
# Page Heading (h1-h2)
# TODO this class will be removed in Bootstrap >= 4.0 version
foreach ($html->find('h1,h2,h3') as $elm) {
$elm->class .= ' page-header pb-3 mb-4 mt-0'; # TODO replace page-header with border-bottom in BS4
}
# Media Images
foreach ($html->find('img[class^=media]') as $elm) {
$elm->class .= ' img-responsive';
}
# Checkbox
foreach ($html->find('input[type=checkbox]') as $elm) {
$elm->class .= ' checkbox-inline';
}
# Radio button
foreach ($html->find('input[type=radio]') as $elm) {
$elm->class .= ' radio-inline';
}
# Label
foreach ($html->find('label') as $elm) {
$elm->class .= ' control-label';
}
# Form controls
foreach ($html->find('input, select, textarea') as $elm) {
if (!in_array($elm->type, ['submit', 'reset', 'button', 'hidden', 'image', 'checkbox', 'radio'])) {
$elm->class .= ' form-control';
}
}
# Forms
# TODO main form
foreach ($html->find('form') as $elm) {
if (!preg_match('/form-horizontal/', $elm->class)) {
$elm->class .= ' form-inline';
}
}
# Alerts
foreach ($html->find('div.info, div.error, div.success, div.notify') as $elm) {
switch ($elm->class) {
case 'info':
$elm->class = 'alert alert-info';
$elm->innertext = iconify('mdi:information') . ' ' . $elm->innertext;
break;
case 'error':
$elm->class = 'alert alert-danger';
$elm->innertext = iconify('mdi:alert-octagon') . ' ' . $elm->innertext;
break;
case 'success':
$elm->class = 'alert alert-success';
$elm->innertext = iconify('mdi:check-circle') . ' ' . $elm->innertext;
break;
case 'notify':
case 'msg notify':
$elm->class = 'alert alert-warning';
$elm->innertext = iconify('mdi:alert') . ' ' . $elm->innertext;
break;
}
}
# Tables
$table_classes = 'table';
foreach ($this->getConf('tableStyle') as $class) {
if ($class == 'responsive') {
foreach ($html->find('div.table') as $elm) {
$elm->class = 'table-responsive';
}
} else {
$table_classes .= " table-$class";
}
}
foreach ($html->find('table.inline,table.import_failures') as $elm) {
$elm->class .= " $table_classes";
}
foreach ($html->find('div.table') as $elm) {
$elm->class = trim(str_replace('table', '', $elm->class));
}
# Tag and Pagelist (table)
if ($this->getPlugin('tag') || $this->getPlugin('pagelist')) {
foreach ($html->find('table.ul') as $elm) {
$elm->class .= " $table_classes";
}
}
$content = $html->save();
$html->clear();
unset($html);
# ----- Actions -----
# Search
if ($ACT == 'search') {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('fieldset.search-form button[type="submit"]') as $elm) {
$elm->class .= ' btn-primary';
$elm->innertext = iconify('mdi:magnify', ['class' => 'mr-2']) . $elm->innertext;
}
$content = $html->save();
$html->clear();
unset($html);
}
# Index / Sitemap
if ($ACT == 'index') {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('.idx_dir') as $idx => $elm) {
$parent = $elm->parent()->parent();
if (preg_match('/open/', $parent->class)) {
$elm->innertext = iconify('mdi:folder-open', ['class' => 'text-primary mr-2']) . $elm->innertext;
}
if (preg_match('/closed/', $parent->class)) {
$elm->innertext = iconify('mdi:folder', ['class' => 'text-primary mr-2']) . $elm->innertext;
}
}
foreach ($html->find('.idx .wikilink1') as $elm) {
$elm->innertext = iconify('mdi:file-document-outline', ['class' => 'text-muted mr-2']) . $elm->innertext;
}
$content = $html->save();
$html->clear();
unset($html);
}
# Admin Pages
if ($ACT == 'admin') {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
// Set specific icon in Admin Page
if ($INPUT->str('page')) {
if ($admin_pagetitle = $html->find('h1.page-header', 0)) {
$admin_pagetitle->class .= ' ' . hsc($INPUT->str('page'));
}
}
# ACL
if ($INPUT->str('page') == 'acl') {
foreach ($html->find('[name*=cmd[update]]') as $elm) {
$elm->class .= ' btn-success';
if ($elm->tag == 'button') {
$elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
}
}
}
# Popularity
if ($INPUT->str('page') == 'popularity') {
foreach ($html->find('[type=submit]') as $elm) {
$elm->class .= ' btn-primary';
if ($elm->tag == 'button') {
$elm->innertext = iconify('mdi:arrow-right') . ' ' . $elm->innertext;
}
}
}
# Revert Manager
if ($INPUT->str('page') == 'revert') {
foreach ($html->find('[type=submit]') as $idx => $elm) {
if ($idx == 0) {
$elm->class .= ' btn-primary';
if ($elm->tag == 'button') {
$elm->innertext = iconify('mdi:magnify') . ' ' . $elm->innertext;
}
}
if ($idx == 1) {
$elm->class .= ' btn-success';
if ($elm->tag == 'button') {
$elm->innertext = iconify('mdi:refresh') . ' ' . $elm->innertext;
}
}
}
}
# Config
if ($INPUT->str('page') == 'config') {
foreach ($html->find('[type=submit]') as $elm) {
$elm->class .= ' btn-success';
if ($elm->tag == 'button') {
$elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
}
}
foreach ($html->find('#config__manager') as $cm_elm) {
$save_button = '';
foreach ($cm_elm->find('p') as $elm) {
$save_button = '<div class="pull-right">' . $elm->outertext . '</div>';
$elm->outertext = '</div>' . $elm->outertext;
}
foreach ($cm_elm->find('fieldset') as $elm) {
$elm->innertext .= $save_button;
}
}
}
# User Manager
if ($INPUT->str('page') == 'usermanager') {
foreach ($html->find('.notes') as $elm) {
$elm->class = str_replace('notes', '', $elm->class);
}
foreach ($html->find('h2') as $idx => $elm) {
switch ($idx) {
case 0:
$elm->innertext = iconify('mdi:account-multiple') . ' ' . $elm->innertext;
break;
case 1:
$elm->innertext = iconify('mdi:account-plus') . ' ' . $elm->innertext;
break;
case 2:
$elm->innertext = iconify('mdi:account-edit') . ' ' . $elm->innertext;
break;
}
}
foreach ($html->find('.import_users h2') as $elm) {
$elm->innertext = iconify('mdi:account-multiple-plus') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[delete]]') as $elm) {
$elm->class .= ' btn btn-danger';
$elm->innertext = iconify('mdi:account-minus') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[add]]') as $elm) {
$elm->class .= ' btn btn-success';
$elm->innertext = iconify('mdi:plus') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[modify]]') as $elm) {
$elm->class .= ' btn btn-success';
$elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[import]]') as $elm) {
$elm->class .= ' btn btn-primary';
$elm->innertext = iconify('mdi:upload') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[export]]') as $elm) {
$elm->class .= ' btn btn-primary';
$elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[start]]') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-double-left') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[prev]]') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-left') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[next]]') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-right') . ' ' . $elm->innertext;
}
foreach ($html->find('button[name*=fn[last]]') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-double-right') . ' ' . $elm->innertext;
}
}
# Extension Manager
if ($INPUT->str('page') == 'extension') {
foreach ($html->find('.actions') as $elm) {
$elm->class .= ' pl-4 btn-group btn-group-xs';
}
foreach ($html->find('.actions .uninstall') as $elm) {
$elm->class .= ' btn-danger';
$elm->innertext = iconify('mdi:delete') . ' ' . $elm->innertext;
}
foreach ($html->find('.actions .enable') as $elm) {
$elm->class .= ' btn-success';
$elm->innertext = iconify('mdi:check') . ' ' . $elm->innertext;
}
foreach ($html->find('.actions .disable') as $elm) {
$elm->class .= ' btn-warning';
$elm->innertext = iconify('mdi:block-helper') . ' ' . $elm->innertext;
}
foreach ($html->find('.actions .install, .actions .update, .actions .reinstall') as $elm) {
$elm->class .= ' btn-primary';
$elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
}
foreach ($html->find('form.install [type=submit]') as $elm) {
$elm->class .= ' btn btn-success';
$elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
}
foreach ($html->find('form.search [type=submit]') as $elm) {
$elm->class .= ' btn btn-primary';
$elm->innertext = iconify('mdi:cloud-search') . ' ' . $elm->innertext;
}
foreach ($html->find('.permerror') as $elm) {
$elm->class .= ' pull-left';
}
}
# Admin page
if ($INPUT->str('page') == null) {
foreach ($html->find('ul.admin_tasks, ul.admin_plugins') as $admin_task) {
$admin_task->class .= ' list-group';
foreach ($admin_task->find('a') as $item) {
$item->class .= ' list-group-item';
$item->style = 'max-height: 50px'; # TODO remove
}
foreach ($admin_task->find('.icon') as $item) {
if ($item->innertext) {
continue;
}
$item->innertext = iconify('mdi:puzzle', ['class' => 'text-success']);
}
}
foreach ($html->find('h2') as $elm) {
$elm->innertext = iconify('mdi:puzzle', ['class' => 'text-success']) . ' ' . $elm->innertext;
}
foreach ($html->find('ul.admin_plugins') as $admin_plugins) {
$admin_plugins->class .= ' col-sm-4';
foreach ($admin_plugins->find('li') as $idx => $item) {
if ($idx > 0 && $idx % 5 == 0) {
$item->outertext = '</ul><ul class="' . $admin_plugins->class . '">' . $item->outertext;
}
}
}
# DokuWiki logo
if ($admin_version = $html->getElementById('admin__version')) {
$admin_version->innertext = '<div class="dokuwiki__version"><img src="' . DOKU_BASE . 'lib/tpl/dokuwiki/images/logo.png" class="p-2" alt="" width="32" height="32" /> ' . $admin_version->innertext . '</div>';
$template_version = $this->getVersion();
$admin_version->innertext .= '<div class="template__version"><img src="' . tpl_basedir() . 'images/bootstrap.png" class="p-2" height="32" width="32" alt="" />Template ' . $template_version . '</div>';
}
}
$content = $html->save();
$html->clear();
unset($html);
# Configuration Manager Template Sections
if ($INPUT->str('page') == 'config') {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('fieldset[id^="plugin__"]') as $elm) {
/** @var array $matches */
preg_match('/plugin_+(\w+[^_])_+plugin_settings_name/', $elm->id, $matches);
$plugin_name = $matches[1];
if ($extension = plugin_load('helper', 'extension_extension')) {
if ($extension->setExtension($plugin_name)) {
foreach ($elm->find('legend') as $legend) {
$legend->innertext = iconify('mdi:puzzle', ['class' => 'text-success']) . ' ' . $legend->innertext . ' <br/><h6>' . $extension->getDescription() . ' <a class="urlextern" href="' . $extension->getURL() . '" target="_blank">Docs</a></h6>';
}
}
} else {
foreach ($elm->find('legend') as $legend) {
$legend->innertext = iconify('mdi:puzzle', ['class' => 'text-success']) . ' ' . $legend->innertext;
}
}
}
$dokuwiki_configs = [
'#_basic' => 'mdi:settings',
'#_display' => 'mdi:monitor',
'#_authentication' => 'mdi:shield-account',
'#_anti_spam' => 'mdi:block-helper',
'#_editing' => 'mdi:pencil',
'#_links' => 'mdi:link-variant',
'#_media' => 'mdi:folder-image',
'#_notifications' => 'mdi:email',
'#_syndication' => 'mdi:rss',
'#_advanced' => 'mdi:palette-advanced',
'#_network' => 'mdi:network',
];
foreach ($dokuwiki_configs as $selector => $icon) {
foreach ($html->find("$selector legend") as $elm) {
$elm->innertext = iconify($icon) . ' ' . $elm->innertext;
}
}
$content = $html->save();
$html->clear();
unset($html);
$admin_sections = [
// Section => [ Insert Before, Icon ]
'theme' => ['bootstrapTheme', 'mdi:palette'],
'sidebar' => ['sidebarPosition', 'mdi:page-layout-sidebar-left'],
'navbar' => ['inverseNavbar', 'mdi:page-layout-header'],
'semantic' => ['semantic', 'mdi:share-variant'],
'layout' => ['fluidContainer', 'mdi:monitor'],
'toc' => ['tocAffix', 'mdi:view-list'],
'discussion' => ['showDiscussion', 'mdi:comment-text-multiple'],
'avatar' => ['useAvatar', 'mdi:account'],
'cookie_law' => ['showCookieLawBanner', 'mdi:scale-balance'],
'google_analytics' => ['useGoogleAnalytics', 'mdi:google'],
'browser_title' => ['browserTitle', 'mdi:format-title'],
'page' => ['showPageInfo', 'mdi:file'],
];
foreach ($admin_sections as $section => $items) {
$search = $items[0];
$icon = $items[1];
$content = preg_replace(
'/<span class="outkey">(tpl»bootstrap3»' . $search . ')<\/span>/',
'<h3 id="bootstrap3__' . $section . '" class="mt-5">' . iconify($icon) . ' ' . tpl_getLang("config_$section") . '</h3></td><td></td></tr><tr><td class="label"><span class="outkey">$1</span>',
$content
);
}
}
}
# Difference and Draft
if ($ACT == 'diff' || $ACT == 'draft') {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('.diff-lineheader') as $elm) {
$elm->style = 'opacity: 0.5';
$elm->class .= ' text-center px-3';
if ($elm->innertext == '+') {
$elm->class .= ' bg-success';
}
if ($elm->innertext == '-') {
$elm->class .= ' bg-danger';
}
}
foreach ($html->find('.diff_sidebyside .diff-deletedline, .diff_sidebyside .diff-addedline') as $elm) {
$elm->class .= ' w-50';
}
foreach ($html->find('.diff-deletedline') as $elm) {
$elm->class .= ' bg-danger';
}
foreach ($html->find('.diff-addedline') as $elm) {
$elm->class .= ' bg-success';
}
foreach ($html->find('.diffprevrev') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-left') . ' ' . $elm->innertext;
}
foreach ($html->find('.diffnextrev') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-right') . ' ' . $elm->innertext;
}
foreach ($html->find('.diffbothprevrev') as $elm) {
$elm->class .= ' btn btn-default';
$elm->innertext = iconify('mdi:chevron-double-left') . ' ' . $elm->innertext;
}
foreach ($html->find('.minor') as $elm) {
$elm->class .= ' text-muted';
}
$content = $html->save();
$html->clear();
unset($html);
}
# Add icons for Extensions, Actions, etc.
$svg_icon = null;
$iconify_icon = null;
$iconify_attrs = ['class' => 'mr-2'];
if (!$INFO['exists'] && $ACT == 'show') {
$iconify_icon = 'mdi:alert';
$iconify_attrs['style'] = 'color:orange';
}
$menu_class = "\\dokuwiki\\Menu\\Item\\$ACT";
if (class_exists($menu_class, false)) {
$menu_item = new $menu_class;
$svg_icon = $menu_item->getSvg();
}
switch ($ACT) {
case 'admin':
if (($plugin = plugin_load('admin', $INPUT->str('page'))) !== null) {
if (method_exists($plugin, 'getMenuIcon')) {
$svg_icon = $plugin->getMenuIcon();
if (!file_exists($svg_icon)) {
$iconify_icon = 'mdi:puzzle';
$svg_icon = null;
}
} else {
$iconify_icon = 'mdi:puzzle';
$svg_icon = null;
}
}
break;
case 'resendpwd':
$iconify_icon = 'mdi:lock-reset';
break;
case 'denied':
$iconify_icon = 'mdi:block-helper';
$iconify_attrs['style'] = 'color:red';
break;
case 'search':
$iconify_icon = 'mdi:search-web';
break;
case 'preview':
$iconify_icon = 'mdi:file-eye';
break;
case 'diff':
$iconify_icon = 'mdi:file-compare';
break;
case 'showtag':
$iconify_icon = 'mdi:tag-multiple';
break;
case 'draft':
$iconify_icon = 'mdi:android-studio';
break;
}
if ($svg_icon) {
$svg_attrs = ['class' => 'iconify mr-2'];
if ($ACT == 'admin' && $INPUT->str('page') == 'extension') {
$svg_attrs['style'] = 'fill: green;';
}
$svg = SVG::icon($svg_icon, null, '1em', $svg_attrs);
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('h1') as $elm) {
$elm->innertext = $svg . ' ' . $elm->innertext;
break;
}
$content = $html->save();
$html->clear();
unset($html);
}
if ($iconify_icon) {
# Import HTML string
$html = new \simple_html_dom;
$html->load($content, true, false);
foreach ($html->find('h1') as $elm) {
$elm->innertext = iconify($iconify_icon, $iconify_attrs) . $elm->innertext;
break;
}
$content = $html->save();
$html->clear();
unset($html);
}
return $content;
}
/**
* Detect the fluid navbar flag
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
* @return boolean
*/
public function isFluidNavbar()
{
$fluid_container = $this->getConf('fluidContainer');
$fixed_top_nabvar = $this->getConf('fixedTopNavbar');
return ($fluid_container || ($fluid_container && !$fixed_top_nabvar) || (!$fluid_container && !$fixed_top_nabvar));
}
/**
* Calculate automatically the grid size for main container
*
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @return string
*/
public function getContainerGrid()
{
global $ID;
$result = '';
$grids = [
'sm' => ['left' => 0, 'right' => 0],
'md' => ['left' => 0, 'right' => 0],
];
$show_right_sidebar = $this->getConf('showRightSidebar');
$show_left_sidebar = $this->getConf('showSidebar');
$fluid_container = $this->getConf('fluidContainer');
if ($this->getConf('showLandingPage') && (bool) preg_match($this->getConf('landingPages'), $ID)) {
$show_left_sidebar = false;
}
if ($show_left_sidebar) {
foreach (explode(' ', $this->getConf('leftSidebarGrid')) as $grid) {
list($col, $media, $size) = explode('-', $grid);
$grids[$media]['left'] = (int) $size;
}
}
if ($show_right_sidebar) {
foreach (explode(' ', $this->getConf('rightSidebarGrid')) as $grid) {
list($col, $media, $size) = explode('-', $grid);
$grids[$media]['right'] = (int) $size;
}
}
foreach ($grids as $media => $position) {
$left = $position['left'];
$right = $position['right'];
$result .= sprintf('col-%s-%s ', $media, (12 - $left - $right));
}
return $result;
}
/**
* Places the TOC where the function is called
*
* If you use this you most probably want to call tpl_content with
* a false argument
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param bool $return Should the TOC be returned instead to be printed?
* @return string
*/
public function getTOC($return = false)
{
global $TOC;
global $ACT;
global $ID;
global $REV;
global $INFO;
global $conf;
global $INPUT;
$toc = [];
if (is_array($TOC)) {
// if a TOC was prepared in global scope, always use it
$toc = $TOC;
} elseif (($ACT == 'show' || substr($ACT, 0, 6) == 'export') && !$REV && $INFO['exists']) {
// get TOC from metadata, render if neccessary
$meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE);
if (isset($meta['internal']['toc'])) {
$tocok = $meta['internal']['toc'];
} else {
$tocok = true;
}
$toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null;
if (!$tocok || !is_array($toc) || !$conf['tocminheads'] || count($toc) < $conf['tocminheads']) {
$toc = [];
}
} elseif ($ACT == 'admin') {
// try to load admin plugin TOC
/** @var $plugin DokuWiki_Admin_Plugin */
if ($plugin = plugin_getRequestAdminPlugin()) {
$toc = $plugin->getTOC();
$TOC = $toc; // avoid later rebuild
}
}
$toc_check = end($toc);
$toc_undefined = null;
if (isset($toc_check['link']) && !preg_match('/bootstrap/', $toc_check['link'])) {
$toc_undefined = array_pop($toc);
}
\dokuwiki\Extension\Event::createAndTrigger('TPL_TOC_RENDER', $toc, null, false);
if ($ACT == 'admin' && $INPUT->str('page') == 'config') {
$bootstrap3_sections = [
'theme', 'sidebar', 'navbar', 'semantic', 'layout', 'toc',
'discussion', 'avatar', 'cookie_law', 'google_analytics',
'browser_title', 'page',
];
foreach ($bootstrap3_sections as $id) {
$toc[] = [
'link' => "#bootstrap3__$id",
'title' => tpl_getLang("config_$id"),
'type' => 'ul',
'level' => 3,
];
}
}
if ($toc_undefined) {
$toc[] = $toc_undefined;
}
$html = $this->renderTOC($toc);
if ($return) {
return $html;
}
echo $html;
return '';
}
/**
* Return the TOC rendered to XHTML with Bootstrap3 style
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
*
* @param array $toc
* @return string html
*/
private function renderTOC($toc)
{
if (!count($toc)) {
return '';
}
global $lang;
$json_toc = [];
foreach ($toc as $item) {
$json_toc[] = [
'link' => (isset($item['link']) ? $item['link'] : '#' . $item['hid']),
'title' => $item['title'],
'level' => $item['level'],
];
}
$out = '';
$out .= '<script>JSINFO.bootstrap3.toc = ' . json_encode($json_toc) . ';</script>' . DOKU_LF;
if ($this->getConf('tocLayout') !== 'navbar') {
$out .= '<!-- TOC START -->' . DOKU_LF;
$out .= '<div class="dw-toc hidden-print">' . DOKU_LF;
$out .= '<nav id="dw__toc" role="navigation" class="toc-panel panel panel-default small">' . DOKU_LF;
$out .= '<h6 data-toggle="collapse" data-target="#dw__toc .toc-body" title="' . $lang['toc'] . '" class="panel-heading toc-title">' . iconify('mdi:view-list') . ' ';
$out .= '<span>' . $lang['toc'] . '</span>';
$out .= ' <i class="caret"></i></h6>' . DOKU_LF;
$out .= '<div class="panel-body toc-body collapse ' . (!$this->getConf('tocCollapsed') ? 'in' : '') . '">' . DOKU_LF;
$out .= $this->normalizeList(html_buildlist($toc, 'nav toc', 'html_list_toc', 'html_li_default', true)) . DOKU_LF;
$out .= '</div>' . DOKU_LF;
$out .= '</nav>' . DOKU_LF;
$out .= '</div>' . DOKU_LF;
$out .= '<!-- TOC END -->' . DOKU_LF;
}
return $out;
}
private function initToolsMenu()
{
global $ACT;
$tools_menus = [
'user' => ['icon' => 'mdi:account', 'object' => new \dokuwiki\Menu\UserMenu],
'site' => ['icon' => 'mdi:toolbox', 'object' => new \dokuwiki\Menu\SiteMenu],
'page' => ['icon' => 'mdi:file-document-outline', 'object' => new \dokuwiki\template\bootstrap3\Menu\PageMenu],
];
if (defined('DOKU_MEDIADETAIL')) {
$tools_menus['page'] = ['icon' => 'mdi:image', 'object' => new \dokuwiki\template\bootstrap3\Menu\DetailMenu];
}
foreach ($tools_menus as $tool => $data) {
foreach ($data['object']->getItems() as $item) {
$attr = buildAttributes($item->getLinkAttributes());
$active = 'action';
if ($ACT == $item->getType() || ($ACT == 'revisions' && $item->getType() == 'revs') || ($ACT == 'diff' && $item->getType() == 'revs')) {
$active .= ' active';
}
if ($item->getType() == 'shareon') {
$active .= ' dropdown';
}
$html = '<li class="' . $active . '">';
$html .= "<a $attr>";
$html .= \inlineSVG($item->getSvg());
$html .= '<span>' . hsc($item->getLabel()) . '</span>';
$html .= "</a>";
if ($item->getType() == 'shareon') {
$html .= $item->getDropDownMenu();
}
$html .= '</li>';
$tools_menus[$tool]['menu'][$item->getType()]['object'] = $item;
$tools_menus[$tool]['menu'][$item->getType()]['html'] = $html;
}
}
$this->toolsMenu = $tools_menus;
}
public function getToolsMenu()
{
return $this->toolsMenu;
}
public function getToolMenu($tool)
{
return $this->toolsMenu[$tool];
}
public function getToolMenuItem($tool, $item)
{
if (isset($this->toolsMenu[$tool]) && isset($this->toolsMenu[$tool]['menu'][$item])) {
return $this->toolsMenu[$tool]['menu'][$item]['object'];
}
return null;
}
public function getToolMenuItemLink($tool, $item)
{
if (isset($this->toolsMenu[$tool]) && isset($this->toolsMenu[$tool]['menu'][$item])) {
return $this->toolsMenu[$tool]['menu'][$item]['html'];
}
return null;
}
public function getNavbarHeight()
{
switch ($this->getBootswatchTheme()) {
case 'simplex':
case 'superhero':
return 40;
case 'yeti':
return 45;
case 'cerulean':
case 'cosmo':
case 'custom':
case 'cyborg':
case 'lumen':
case 'slate':
case 'spacelab':
case 'solar':
case 'united':
return 50;
case 'darkly':
case 'flatly':
case 'journal':
case 'sandstone':
return 60;
case 'paper':
return 64;
case 'readable':
return 65;
default:
return 50;
}
}
}