xref: /template/mikio/mikio.php (revision 2b4583abab306b8eb94c98b5ea77d885731fe2f3)
1<?php
2
3namespace dokuwiki\template\mikio;
4
5/**
6 * DokuWiki Mikio Template
7 *
8 * @link    http://dokuwiki.org/template:mikio
9 * @author  James Collins <james.collins@outlook.com.au>
10 * @license MIT License (https://raw.githubusercontent.com/nomadjimbob/Mikio/master/LICENSE)
11 */
12
13if (!defined('DOKU_INC')) die();
14
15require_once('inc/simple_html_dom.php');
16
17class Template {
18  public $tplDir  = '';
19  public $baseDir = '';
20
21
22    /**
23     * Class constructor
24     *
25     * @author  James Collins <james.collins@outlook.com.au>
26     */
27    public function __construct() {
28      $this->tplDir  = tpl_incdir();
29      $this->baseDir = tpl_basedir();
30
31      $this->_registerHooks();
32     }
33
34
35    /**
36     * Get the singleton instance
37     *
38     * @return Template
39     */
40    public static function getInstance()
41    {
42
43        static $instance = null;
44
45        if ($instance === null) {
46            $instance = new Template();
47        }
48
49        return $instance;
50
51    }
52
53    /**
54     * Register themes DokuWiki hooks
55     *
56     * @author  James Collins <james.collins@outlook.com.au>
57     */
58    private function _registerHooks() {
59        global $EVENT_HANDLER;
60
61         $events_dispatcher = array(
62            'TPL_METAHEADER_OUTPUT'     => 'metaheadersHandler',
63            // 'TPL_CONTENT_DISPLAY'       => 'contentHandler',
64        );
65
66        foreach ($events_dispatcher as $event => $method) {
67            $EVENT_HANDLER->register_hook($event, 'BEFORE', $this, $method);
68        }
69    }
70
71
72    /**
73     * DokuWiki META Header event handler
74     *
75     * @author  James Collins <james.collins@outlook.com.au>
76     */
77    public function metaHeadersHandler(\Doku_Event $event) {
78        $stylesheets    = array();
79        $scripts        = array();
80
81        if($this->getConf('useTheme') != '') {
82            if(file_exists($this->tplDir . 'themes/' . $this->getConf('useTheme') . '/style.css')) {
83                $stylesheets[] = $this->baseDir . 'themes/' . $this->getConf('useTheme') . '/style.css';
84            }
85            if(file_exists($this->tplDir . 'themes/' . $this->getConf('useTheme') . '/script.js')) {
86                $scripts[] = $this->baseDir . 'themes/' . $this->getConf('useTheme') . '/script.js';
87            }
88        }
89
90        if($this->getConf('includeFontAwesome') == true) $stylesheets[] = $this->baseDir . 'assets/fontawesome/css/all.min.css';
91
92        $scripts[] = $this->baseDir . 'assets/bootstrap/popper.min.js';
93        $scripts[] = $this->baseDir . 'assets/bootstrap/bootstrap.min.js';
94        $stylesheets[] = $this->baseDir . 'assets/bootstrap/bootstrap.min.css';
95        $stylesheets[] = $this->baseDir . 'assets/mikio.css';
96
97        foreach ($stylesheets as $style) {
98            array_unshift($event->data['link'], array(
99                'type' => 'text/css',
100                'rel'  => 'stylesheet',
101                'href' => $style
102            ));
103        }
104
105        foreach ($scripts as $script) {
106            $event->data['script'][] = array(
107                 'type'  => 'text/javascript',
108              '_data' => '',
109              'src'   => $script
110          );
111      }
112    }
113
114
115    /**
116     * DokuWiki content event handler
117     *
118     * @author  James Collins <james.collins@outlook.com.au>
119     */
120    public function contentHandler(\Doku_Event $event)
121    {
122        $event->data = $this->parseContent($event->data);
123    }
124
125
126    /**
127     * Parse configuration options
128     *
129     * @author  James Collins <james.collins@outlook.com.au>
130     *
131     * @param   string  $key        The configuration key to retreive
132     * @param   mixed   $default    If key doesn't exist, return this value
133     * @return  mixed               Parsed value of configuration
134     */
135    public function getConf($key, $default = false) {
136        global $ACT, $conf;
137
138        $value = tpl_getConf($key, $default);
139
140        switch($key) {
141
142            case 'navbar':  // TODO is this needed?
143                $value = explode(',', $value);
144                break;
145
146            case 'showSidebar':
147                if ($ACT !== 'show') {
148                    return false;
149                }
150
151                return page_findnearest($conf['sidebar'], $this->getConf('useACL'));
152
153            case 'navbarMenuStyle':
154                if($value != 'text') {
155                    if(!$this->getConf('useFontAwesome')) {
156                        return 'text';
157                    }
158                }
159
160            break;
161
162            case 'navbarMenuPosition':
163                if($value == 'right') {
164                    return 'ml-md-auto';
165                }
166
167                return '';
168
169            case 'breadcrumbsLoc':
170                if(!$this->getConf('useHeroTitle') && $value == 'hero') {
171                    return 'top';
172                }
173
174                if($value != 'top' && $value != 'hero' && $value != 'page') {
175                    return 'page';
176                }
177
178                break;
179        }
180
181        return $value;
182    }
183
184
185    /**
186     * Icon
187     *
188     * @author  James Collins <james.collins@outlook.com.au>
189     *
190     * @param   string  $type       The type of icon to return
191     * @return  string              HTML for icon element
192     */
193    public function icon($type) {
194        if($this->getConf('useFontAwesome')) {
195            return '<i class="fa fa-' . $type . '" aria-hidden="true"></i>';
196        }
197
198        return '';
199    }
200
201
202    /**
203     * Print the Navbar menu title/icon
204     *
205     * @author  James Collins <james.collins@outlook.com.au>
206     *
207     * @param   string  $type       The type of icon to return
208     * @return  string              HTML for icon element
209     */
210    public function navbarMenuTitle($title, $icon) {
211        global $lang;
212
213        $menu = '';
214
215        if($this->getConf('navbarMenuStyle') != 'text') {
216            $menu .= $this->icon($icon);
217        }
218
219        if($this->getConf('navbarMenuStyle') != 'icon') {
220            $menu .= $lang[$title];
221        }
222
223        echo $menu;
224    }
225
226
227     /**
228     * Add class to first DOM element
229     *
230     * @author  James Collins <james.collins@outlook.com.au>
231     *
232     * @param   string  $content    HTML DOM
233     * @param   string  $class      Class to add DOM elements
234     * @return  string              HTML DOM with class added
235     */
236    public function elementAddClass($html, $class) {
237        preg_match('/class.*?".*?"/', $html, $matches);
238        if(count($matches) > 0) {
239            preg_match('/[" ]'.$class.'[" ]/', $matches[0], $matches);
240            if(count($matches) == 0) {
241                return preg_replace('/(class.*?=.*?")/', '${1}'.$class.' ', $html, 1);
242            }
243        } else {
244            return preg_replace('/>/', 'class="'.$class.'">', $html, 1);
245        }
246
247        return $html;
248    }
249
250
251    /**
252     * Include Page
253     *
254     * @author  James Collins <james.collins@outlook.com.au>
255     *
256     * @param   string  $location
257     * @param   boolean $return
258     * @return  string
259     */
260    public function includePage($location, $return = false)
261    {
262
263        $content = '';
264
265        if($content === '') $content = tpl_include_page($location, 0, 1, $this->getConf('useACL'));
266
267        if($content === '') return '';
268
269        $content = $this->parseContent($content);
270
271        if($return) return $content;
272
273        print $content;
274        return '';
275    }
276
277    public function includeLoggedIn() {
278        if (!empty($_SERVER['REMOTE_USER'])) {
279            echo '<li class="user navbar-text text-nowrap">';
280            tpl_userinfo(); /* 'Logged in as ...' */
281            echo '</li>';
282        }
283    }
284
285
286    /**
287     * Include Menus
288     *
289     * @author  James Collins <james.collins@outlook.com.au>
290     *
291     * @param   string  $location
292     */
293    public function includeMenu($location) {
294        global $lang;
295        global $USERINFO;
296
297        $conf = $this->getConf('navbarIconsText');
298        $dropdown = $this->getConf('navbarUseDropdown');
299        $hideIcons = ($conf == 'text');
300        $hideText = ($conf == 'icons');
301        $guestMode = $this->getConf('navbarGuestHide') && ($USERINFO == false);
302
303        if(!$hideText && !$hideIcons) $hideText = false;
304
305        if(!$guestMode) {
306            // Page tools
307            $items = (new \dokuwiki\Menu\PageMenu())->getItems();
308            print '<li id="dokuwiki__pagetools" class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' . (!$hideIcons ? $this->icon('file') : '') . (!$hideText ? $lang['page_tools'] : '') . '</a><div class="dropdown-menu dropdown-menu-right">';
309            foreach($items as $item) {
310                if($item->getType() != 'top') {
311                    print '<a class="' . ($dropdown ? 'dropdown-item' : 'nav-item nav-link') . '" href="'.$item->getLink().'" title="'.$item->getTitle().'">';
312                    if(!$hideIcons) print '<span class="icon">'.inlineSVG($item->getSvg()).'</span>';
313                    if(!$hideText || $dropdown) print '<span>' . $item->getLabel() . '</span>';
314                    print '</a>';
315                }
316            }
317            if($dropdown) print '</div></li>';
318
319            // Site tools
320            $items = (new \dokuwiki\Menu\SiteMenu())->getItems('action ');
321
322            print '<li id="dokuwiki__sitetools" class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' . (!$hideIcons ? $this->icon('gear') : '') . (!$hideText ? $lang['site_tools'] : '') . '</a><div class="dropdown-menu dropdown-menu-right">';
323            foreach($items as $item) {
324                print '<a class="' . ($dropdown ? 'dropdown-item' : 'nav-item nav-link') . '" href="'.$item->getLink().'" title="'.$item->getTitle().'">';
325                if(!$hideIcons) print '<span class="icon">'.inlineSVG($item->getSvg()).'</span>';
326                if(!$hideText || $dropdown) print '<span>' . $item->getLabel() . '</span>';
327                print '</a>';
328            }
329            if($dropdown) print '</div></li>';
330        }
331
332        // User tools
333        $items = (new \dokuwiki\Menu\UserMenu())->getItems('action');
334        if(!$guestMode) print '<li id="dokuwiki__usertools" class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' . (!$hideIcons ? $this->icon('user') : '') . (!$hideText ? $lang['user_tools'] : '') . '</a><div class="dropdown-menu dropdown-menu-right">';
335        foreach($items as $item) {
336            if(!$guestMode || $item->getType() == 'login') {
337                print '<a class="' . (($dropdown && !$guestMode) ? 'dropdown-item' : 'nav-item nav-link') . '" href="'.$item->getLink().'" title="'.$item->getTitle().'">';
338                if(!$hideIcons) print '<span class="icon">'.inlineSVG($item->getSvg()).'</span>';
339                if(!$hideText || ($dropdown && !$guestMode)) print '<span>' . $item->getLabel() . '</span>';
340                print '</a>';
341            }
342        }
343        if($dropdown && !$guestMode) print '</div></li>';
344
345    }
346
347    /**
348     * Include Sidebar
349     *
350     * @author  James Collins <james.collins@outlook.com.au>
351     *
352     * @param   string  $type       Sidebar type
353     * @return  boolean             If sidebar was added
354     */
355    public function includeSidebar($type) {
356        global $conf;
357        global $ID;
358
359        $useACL = true; // Add these as config options?
360        $propagate = true;
361        $checkPropagate = true; // Add these as config options?
362
363        switch($type) {
364            case 'left':
365                if($this->getConf('showSidebar') && page_findnearest($conf['sidebar'], $useACL) != false && p_get_metadata($ID, 'nosidebar', false) == false) {
366                    $sidebar = tpl_includeFile('sidebarheader.html', false);
367
368                    $sidebar .= $this->includeSearch('sidebar-top', false);
369
370                    $confSidebar = tpl_include_page($conf['sidebar'], false, $propagate, $useACL);
371                    if($checkPropagate && $confSidebar == '') {
372                        $confSidebar = tpl_include_page($conf['sidebar'], false, false, $useACL);
373                    }
374                    $sidebar .= $confSidebar;
375
376                    $sidebar .= $this->includeSearch('sidebar-bottom', false);
377                    $sidebar .= tpl_includeFile('sidebarfooter.html', false);
378
379                    if($sidebar != '') {
380                        print '<aside class="col-md-2">' . $sidebar . '</aside>';
381                    }
382
383                    return true;
384                }
385
386                return false;
387        }
388
389        return false;
390    }
391
392    /**
393     * Include Page Tools
394     *
395     * @author  James Collins <james.collins@outlook.com.au>
396     *
397     * @param   string  $location       Page tools location
398     * @return  boolean             If page tools was added
399     */
400    public function includePageTools($location) {
401        $id = '';
402        $group_class = 'btn-group';
403
404        if((!$this->getConf('hidePageTools') && $location == 'side') || (!$this->getConf('hidePageToolsFooter') && $location == 'footer')) {
405            if($location == 'side') {
406                $id = 'dw__pagetools';
407                $group_class = 'btn-group-vertical';
408            }
409
410            print '<nav id="' . $id . '" class="hidden-print dw__pagetools">';
411                print '<div class="' . $group_class . '">';
412
413                $items = (new \dokuwiki\Menu\PageMenu())->getItems();
414                foreach($items as $item) {
415                    print '<a class="btn btn-sm btn-light" href="'.$item->getLink().'" title="'.$item->getTitle().'">'
416                    .'<span class="icon">'.inlineSVG($item->getSvg()).'</span>'
417                    . '<span class="a11y">'.$item->getLabel().'</span>'
418                    . '</a>';
419                }
420
421                print '</div>';
422            print '</nav>';
423        }
424    }
425
426    /**
427     * Include Search
428     *
429     * @author  James Collins <james.collins@outlook.com.au>
430     *
431     * @param   string  $location   Search location
432     * @return  boolean             If search was added
433     */
434    public function includeSearch($location, $print=true) {
435        global $lang;
436
437        //<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
438        $out = '';
439
440        if($location == $this->getConf('navbarSearchPosition') || ($location == 'footer' && $this->getConf('showSearchInFooter')) || ($location == 'sidebar-top' && $this->getConf('showSearchInSidebar') == 'top') || ($location == 'sidebar-bottom' && $this->getConf('showSearchInSidebar') == 'bottom')) {
441            $out .= '<form action="' . wl($ID) . '" accept-charset="utf-8" class="form-inline search" id="dw__search" method="get" role="search">';
442                $out .= '<div class="input-group"><input id="sqsearch" autocomplete="off" type="search" placeholder="' . $lang['btn_search'] . '" value="' . (($ACT == 'search') ? htmlspecialchars($QUERY) : '') . '" accesskey="f" name="q" class="form-control" title="[F]" style="height:auto"/>';
443                    $out .= '<div class="input-group-append"><button class="btn btn-secondary" type="submit" title="' .  $lang['btn_search'] . '">';
444                        $out .= $this->icon('search'); //$lang['btn_search'];  // TODO show icon if conf says and font awesome installed
445                    $out .= '</button></div></div>';
446                    $out .= '<input type="hidden" name="do" value="search" />';
447            $out .= '</form>';
448        }
449
450        if($print) {
451            print $out;
452            return '';
453        }
454
455        return $out;
456    }
457
458
459    /**
460     * Include Custom menus
461     *
462     * @author  James Collins <james.collins@outlook.com.au>
463     *
464     * @param   string  $location   menu location
465     * @return  boolean             If menu was added
466     */
467    public function includeCustomMenu($location, $addOuter=true) {
468        if(($location == 'navbar' && $this->getConf('showCustomPagesInNavbar')) || ($location == 'footer' && $this->getConf('showCustomPagesInFooter'))) {
469            if($addOuter) {
470                print '<ul class="nav">';
471            }
472
473            $menuList = $this->getConf('navbarCustomPages');
474
475            if($menuList != '') {
476                $menuList = explode(',', $menuList);
477
478                foreach($menuList as $item) {
479                    $i = strpos($item, '|');
480                    if($i !== false) {
481                        $url = $this->getLink(trim(substr($item, 0, $i)));
482                        $title = trim(substr($item, $i + 1));
483
484                        print('<li class="nav-item"><a href="' . $url . '" class="nav-link">' . $title . '</a></li>');
485                    }
486                }
487            }
488
489            if($addOuter) {
490                print '</ul>';
491            }
492        }
493    }
494
495    /**
496     * Print out breadcrumbs
497     *
498     * @author  James Collins <james.collins@outlook.com.au>
499     *
500     * @param   string  $location   Location of breadcrumbs
501     */
502    public function includeBreadcrumbs($location) {
503        if($location == $this->getConf('breadcrumbsLoc')) {
504            global $conf;
505
506            print '<div class="mikio-breadcrumbs">';
507
508            if($conf['breadcrumbs']) {
509                tpl_breadcrumbs();
510            }
511
512            if($conf['youarehere']) {
513                tpl_youarehere();
514            }
515
516            print '</div>';
517        }
518    }
519
520
521    /**
522     * Print out hero
523     *
524     * @author  James Collins <james.collins@outlook.com.au>
525     */
526    public function includeHero() {
527        global $ACT;
528        global $INFO;
529
530        // file_put_contents('output.txt', print_r($INFO, true));
531
532        if($ACT == 'show') {
533            if($this->getConf('useHeroTitle')) {
534                print '<div class="mikio-hero d-flex flex-row">';
535                    print '<div class="mikio-hero-text flex-grow-1">';
536                        $this->includeBreadcrumbs('hero');
537                        print '<h1 id="mikio-hero-title">';
538                            print tpl_pagetitle(null, true).' ';    // No idea why this requires a blank space afterwards to work?
539                        print '</h1>';
540                        print '<h2 class="mikio-hero-subtext">';
541                            // print $this->heroSubtitle;       // TODO scrape page for hero subtitle
542                        print '</h2>';
543                    print '</div>';
544
545
546                    $hero_image = tpl_getMediaFile(array(':' . $INFO['namespace'] . ':hero.png', ':' . $INFO['namespace'] . ':hero.jpg', ':hero.png', ':hero.jpg', ':wiki:hero.png', ':wiki:hero.jpg', 'images/hero.png', 'images/hero.jpg'), false);
547                    if($hero_image != '') $hero_image = ' style="background-image:url(\''.$hero_image.'\');"';
548
549                    print '<div class="mikio-hero-image"' . $hero_image . '></div>';
550                print '</div>';
551            }
552        }
553    }
554
555
556    /**
557     * Print out TOC
558     *
559     * @author  James Collins <james.collins@outlook.com.au>
560     */
561    public function includeTOC($location) {
562        if($this->getConf('tocfullheight') && $location === 'full') {
563            $toc = tpl_toc(true);
564
565            if($toc != '') {
566                print '<div class="mikio-toc mikio-toc-full">';
567                print $toc;
568                print '</div>';
569            }
570        } else if(!$this->getConf('tocfullheight') && $location === 'float') {
571            $toc = tpl_toc(true);
572
573            if($toc != '') {
574                print '<div class="mikio-toc mikio-toc-float">';
575                print $toc;
576                print '</div>';
577            }
578        }
579    }
580
581
582    /**
583     * Parse HTML for bootstrap
584     *
585     * @author  James Collins <james.collins@outlook.com.au>
586     *
587     * @param   string  $content    HTML content to parse
588     * @return  string              Parsed HTML for bootstrap
589     */
590    public function parseContent($content) {
591        $html = new \simple_html_dom;
592        $html->load($content, true, false);
593
594        # Return original content if Simple HTML DOM fail or exceeded page size (default MAX_FILE_SIZE => 600KB)
595        if (!$html) {
596            return $content;
597        }
598
599        # Hide page title if hero is enabled
600        if($this->getConf('useHeroTitle')) {
601            $pageTitle = tpl_pagetitle(null, true);
602
603            foreach($html->find('h1,h2,h3,h4') as $elm) {
604                if($elm->innertext == $pageTitle) {
605                    $elm->innertext = '';
606                    break;
607                }
608            }
609        }
610
611        # Hero subtitle
612        foreach($html->find('p') as $elm) {
613            $i = stripos($elm->innertext, '~~hero-subtitle');
614            if($i !== false) {
615                $j = strpos($elm->innertext, '~~', $i + 2);
616                if($j !== false) {
617                    if($j > $i + 16) {
618                        $subtitle = substr($elm->innertext, $i + 16, $j - $i - 16);
619                        foreach($html->find('.mikio-hero-subtext') as $subtitleElm) {
620                           $subtitleElm->innertext = $subtitle;
621                        }
622                    }
623
624                    $elm->innertext = substr($elm->innertext, $j + 2);
625                    break;
626                }
627            }
628        }
629
630        # Buttons
631        foreach ($html->find('.button') as $elm) {
632            if ($elm->tag == 'form') {
633                continue;
634            }
635            $elm->class .= ' btn';
636        }
637
638        foreach ($html->find('[type=button], [type=submit], [type=reset]') as $elm) {
639            if(stripos($elm->class, 'btn') === false) {
640                $elm->class .= ' btn btn-outline-secondary';
641            }
642        }
643
644        # Section Edit Button
645        foreach ($html->find('.btn_secedit [type=submit]') as $elm) {
646            $elm->class .= ' btn-sm';
647        }
648
649        # Section Edit icons
650        foreach ($html->find('.secedit.editbutton_section button') as $elm) {
651            $elm->innertext = '<i class="fa fa-edit" aria-hidden="true"></i> ' . $elm->innertext;
652        }
653
654        $content = $html->save();
655
656        $html->clear();
657        unset($html);
658
659        return $content;
660    }
661
662
663    /*** GET LINK ***/
664    public function getLink($str) {
665        $i = strpos($str, '://');
666        if($i !== false) return $str;
667
668        return wl($str);
669    }
670}
671
672global $TEMPLATE;
673
674$TEMPLATE = \dokuwiki\template\mikio\Template::getInstance();