Some of the repetitive violations were suppressed.
86.78KiB; PHP | 2021-01-14 00:07:56+01 | SLOC 2133
1
<?php
2
3
namespace dokuwiki\template\bootstrap3;
4
5
/**
6
 * DokuWiki Bootstrap3 Template: Template Class
7
 *
8
 * @link     http://dokuwiki.org/template:bootstrap3
9
 * @author   Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
10
 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
11
 */
12
13 4
class Template
14
{
15
16
    private $plugins      = array();
17
    private $confMetadata = array();
18
    private $toolsMenu    = array();
19
20
    public $tplDir  = '';
21
    public $baseDir = '';
22
23 11
    public function __construct()
24
    {
25
26
        global $JSINFO;
27
        global $INPUT;
28
        global $ACT;
29
        global $INFO;
30
31
        $this->tplDir  = tpl_incdir();
32
        $this->baseDir = tpl_basedir();
33
34
        $this->registerHooks();
35
        $this->initPlugins();
36
        $this->initToolsMenu();
37
        $this->loadConfMetadata();
38
39
        // Get the template info (useful for debug)
40
        if (isset($INFO['isadmin']) && $INPUT->str('do') && $INPUT->str('do') == 'check') {
41
            msg('Template version ' . $this->getVersion(), 1, '', '', MSG_ADMINS_ONLY);
42
        }
43
44
        // Populate JSINFO object
45
        $JSINFO['bootstrap3'] = array(
46
            'mode'   => $ACT,
47
            'toc'    => array(),
48
            'config' => array(
49
                'collapsibleSections'        => (int) $this->getConf('collapsibleSections'),
50
                'fixedTopNavbar'             => (int) $this->getConf('fixedTopNavbar'),
51
                'showSemanticPopup'          => (int) $this->getConf('showSemanticPopup'),
52
                'sidebarOnNavbar'            => (int) $this->getConf('sidebarOnNavbar'),
53
                'tagsOnTop'                  => (int) $this->getConf('tagsOnTop'),
54
                'tocAffix'                   => (int) $this->getConf('tocAffix'),
55
                'tocCollapseOnScroll'        => (int) $this->getConf('tocCollapseOnScroll'),
56
                'tocCollapsed'               => (int) $this->getConf('tocCollapsed'),
57
                'tocLayout'                  => $this->getConf('tocLayout'),
58
                'useAnchorJS'                => (int) $this->getConf('useAnchorJS'),
59
                'useAlternativeToolbarIcons' => (int) $this->getConf('useAlternativeToolbarIcons'),
60
            ),
61
        );
62
63
        if ($ACT == 'admin') {
64
            $JSINFO['bootstrap3']['admin'] = hsc($INPUT->str('page'));
65
        }
66
67
        if (!defined('MAX_FILE_SIZE')) {
68
            if ($pagesize = $this->getConf('domParserMaxPageSize')) {
69
                define('MAX_FILE_SIZE', $pagesize);
70
            }
71
        }
72
    }
73
74
    public function getVersion()
75
    {
76
        $template_info    = confToHash($this->tplDir . 'template.info.txt');
77
        $template_version = 'v' . $template_info['date'];
78
79
        if (isset($template_info['build'])) {
80
            $template_version .= ' (' . $template_info['build'] . ')';
81
        }
82
83
        return $template_version;
84
    }
85
86
    private function registerHooks()
87
    {
88
        /** @var \Doku_Event_Handler */
89
        global $EVENT_HANDLER;
90
91
        $events_dispatcher = array(
92
            'FORM_QUICKSEARCH_OUTPUT'       => 'searchHandler',
93
            'FORM_SEARCH_OUTPUT'            => 'searchHandler',
94
            'HTML_DRAFTFORM_OUTPUT'         => 'draftFormHandler',
95
            'HTML_EDITFORM_OUTPUT'          => 'editFormHandler',
96
            'HTML_LOGINFORM_OUTPUT'         => 'accountFormHandler',
97
            'HTML_RESENDPWDFORM_OUTPUT'     => 'accountFormHandler',
98
            'HTML_PROFILEDELETEFORM_OUTPUT' => 'accountFormHandler',
99
            'HTML_RECENTFORM_OUTPUT'        => 'revisionsFormHandler',
100
            'HTML_REGISTERFORM_OUTPUT'      => 'accountFormHandler',
101
            'HTML_REVISIONSFORM_OUTPUT'     => 'revisionsFormHandler',
102
            'HTML_SUBSCRIBEFORM_OUTPUT'     => 'accountFormHandler',
103
            'HTML_UPDATEPROFILEFORM_OUTPUT' => 'accountFormHandler',
104
            'PLUGIN_TAG_LINK'               => 'tagPluginHandler',
105
            'PLUGIN_TPLINC_LOCATIONS_SET'   => 'tplIncPluginHandler',
106
            'SEARCH_QUERY_FULLPAGE'         => 'searchHandler',
107
            'SEARCH_QUERY_PAGELOOKUP'       => 'searchHandler',
108
            'SEARCH_RESULT_FULLPAGE'        => 'searchHandler',
109
            'SEARCH_RESULT_PAGELOOKUP'      => 'searchHandler',
110
            'TPL_CONTENT_DISPLAY'           => 'contentHandler',
111
            'TPL_METAHEADER_OUTPUT'         => 'metaheadersHandler',
112
113
        );
114
115
        foreach ($events_dispatcher as $event => $method) {
116
            $EVENT_HANDLER->register_hook($event, 'BEFORE', $this, $method);
117
        }
118
    }
119
120 1
    public function accountFormHandler(\Doku_Event $event)
121
    {
122
        foreach ($event->data->_content as $key => $item) {
123
            if (is_array($item) && isset($item['_elem'])) {
124
                $title_icon   = 'account';
125
                $button_class = 'btn btn-success';
126
                $button_icon  = 'arrow-right';
127
128
                switch ($event->name) {
129
                    case 'HTML_LOGINFORM_OUTPUT':
130
                        $title_icon  = 'account';
131
                        $button_icon = 'lock';
132
                        break;
133
                    case 'HTML_UPDATEPROFILEFORM_OUTPUT':
134
                        $title_icon = 'account-card-details-outline';
135
                        break;
136
                    case 'HTML_PROFILEDELETEFORM_OUTPUT':
137
                        $title_icon   = 'account-remove';
138
                        $button_class = 'btn btn-danger';
139
                        break;
140
                    case 'HTML_REGISTERFORM_OUTPUT':
141
                        $title_icon = 'account-plus';
142
                        break;
143
                    case 'HTML_SUBSCRIBEFORM_OUTPUT':
144
                        $title_icon = null;
145
                        break;
146
                    case 'HTML_RESENDPWDFORM_OUTPUT':
147
                        $title_icon = 'lock-reset';
148
                        break;
149
                }
150
151
                // Legend
152
                if ($item['_elem'] == 'openfieldset') {
153
                    $event->data->_content[$key]['_legend'] = (($title_icon) ? iconify("mdi:$title_icon") : '') . ' ' . $event->data->_content[$key]['_legend'];
154
                }
155
156
                // Save button
157
                if (isset($item['type']) && $item['type'] == 'submit') {
158
                    $event->data->_content[$key]['class'] = " $button_class";
159
                    $event->data->_content[$key]['value'] = (($button_icon) ? iconify("mdi:$button_icon") : '') . ' ' . $event->data->_content[$key]['value'];
160
                }
161
            }
162
        }
163
    }
164
165
    /**
166
     * Handle HTML_DRAFTFORM_OUTPUT event
167
     *
168
     * @param \Doku_Event $event Event handler
169
     *
170
     * @return void
171
     **/
172
    public function draftFormHandler(\Doku_Event $event)
173
    {
174
        foreach ($event->data->_content as $key => $item) {
175
            if (is_array($item) && isset($item['_elem'])) {
176
                if ($item['_action'] == 'draftdel') {
177
                    $event->data->_content[$key]['class'] = ' btn btn-danger';
178
                    $event->data->_content[$key]['value'] = iconify('mdi:close') . ' ' . $event->data->_content[$key]['value'];
179
                }
180
181
                if ($item['_action'] == 'recover') {
182
                    $event->data->_content[$key]['value'] = iconify('mdi:refresh') . ' ' . $event->data->_content[$key]['value'];
183
                }
184
185
                if ($item['_action'] == 'show') {
186
                    $event->data->_content[$key]['value'] = iconify('mdi:arrow-left') . ' ' . $event->data->_content[$key]['value'];
187
                }
188
            }
189
        }
190
    }
191
192
    /**
193
     * Handle HTML_EDITFORM_OUTPUT and HTML_DRAFTFORM_OUTPUT event
194
     *
195
     * @param \Doku_Event $event Event handler
196
     *
197
     * @return void
198
     **/
199
    public function editFormHandler(\Doku_Event $event)
200
    {
201
        foreach ($event->data->_content as $key => $item) {
202
            if (is_array($item) && isset($item['_elem'])) {
203
                // Save button
204
                if ($item['_action'] == 'save') {
205
                    $event->data->_content[$key]['class'] = ' btn btn-success';
206
                    $event->data->_content[$key]['value'] = iconify('mdi:content-save') . ' ' . $event->data->_content[$key]['value'];
207
                }
208
209
                // Preview and Show buttons
210
                if ($item['_action'] == 'preview' || $item['_action'] == 'show') {
211
                    $event->data->_content[$key]['value'] = iconify('mdi:file-document-outline') . ' ' . $event->data->_content[$key]['value'];
212
                }
213
214
                // Cancel button
215
                if ($item['_action'] == 'cancel') {
216
                    $event->data->_content[$key]['value'] = iconify('mdi:arrow-left') . ' ' . $event->data->_content[$key]['value'];
217
                }
218
            }
219
        }
220
    }
221
222
    /**
223
     * Handle HTML_REVISIONSFORM_OUTPUT and HTML_RECENTFORM_OUTPUT events
224
     *
225
     * @param \Doku_Event $event Event handler
226
     *
227
     * @return void
228
     **/
229 1
    public function revisionsFormHandler(\Doku_Event $event)
230
    {
231
        foreach ($event->data->_content as $key => $item) {
232
            // Revision form
233
            if (is_array($item) && isset($item['_elem'])) {
234
                if ($item['_elem'] == 'opentag' && $item['_tag'] == 'span' && strstr($item['class'], 'sizechange')) {
235
                    if (strstr($item['class'], 'positive')) {
236
                        $event->data->_content[$key]['class'] .= ' label label-success';
237
                    }
238
239
                    if (strstr($item['class'], 'negative')) {
240
                        $event->data->_content[$key]['class'] .= ' label label-danger';
241
                    }
242
                }
243
244
                // Recent form
245
                if ($item['_elem'] == 'opentag' && $item['_tag'] == 'li' && strstr($item['class'], 'minor')) {
246
                    $event->data->_content[$key]['class'] .= ' text-muted';
247
                }
248
            }
249
        }
250
    }
251
252
    public function contentHandler(\Doku_Event $event)
253
    {
254
        $event->data = $this->normalizeContent($event->data);
255
    }
256
257
    public function searchHandler(\Doku_Event $event)
258
    {
259
        //dbg(print_r($event, 1));
260
261
        if ($event->name == 'SEARCH_RESULT_PAGELOOKUP') {
262
            array_unshift($event->data['listItemContent'], iconify('mdi:file-document-outline', array('title' => hsc($event->data['page']))) . ' ');
263
        }
264
265
        if ($event->name == 'SEARCH_RESULT_FULLPAGE') {
266
            $event->data['resultBody']['meta'] = str_replace(
267
                array('<span class="lastmod">', '<span class="hits">'),
268
                array('<span class="lastmod">' . iconify('mdi:calendar') . ' ', '<span class="hits"' . iconify('mdi:poll') . ' '),
269
                '<small>' . $event->data['resultBody']['meta'] . '</small>'
270
            );
271
        }
272
    }
273
274
    /**
275
     * Load the template assets (Bootstrap, AnchorJS, etc)
276
     *
277
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
278
     * @todo    Move the specific-padding size of Bootswatch template in template.less
279
     *
280
     * @param  \Doku_Event $event
281
     */
282 1
    public function metaheadersHandler(\Doku_Event $event)
283
    {
284
285
        global $ACT;
286 1
        global $INPUT;
287
288
        $fixed_top_navbar = $this->getConf('fixedTopNavbar');
289
290
        if ($google_analitycs = $this->getGoogleAnalitycs()) {
291
            $event->data['script'][] = array(
292
                'type'  => 'text/javascript',
293
                '_data' => $google_analitycs,
294
            );
295
        }
296
297
        // Apply some FIX
298
        if ($ACT || defined('DOKU_MEDIADETAIL')) {
299
            // Default Padding
300
            $navbar_padding = 20;
301
302
            if ($fixed_top_navbar) {
303
                $navbar_height = $this->getNavbarHeight();
304
                $navbar_padding += $navbar_height;
305
            }
306
307
            $styles = array();
308
309
            // TODO implement in css.php dispatcher
310
311
            $styles[] = "body { margin-top: {$navbar_padding}px; }";
312
            $styles[] = ' #dw__toc.affix { top: ' . ($navbar_padding - 10) . 'px; position: fixed !important; }';
313
314
            if ($this->getConf('tocCollapseSubSections')) {
315
                $styles[] = ' #dw__toc .nav .nav .nav { display: none; }';
316
            }
317
318
            $event->data['style'][] = array(
319
                'type'  => 'text/css',
320
                '_data' => '@media screen { ' . implode(" ", $styles) . ' }',
321
            );
322
        }
323
    }
324
325
    public function tagPluginHandler(\Doku_Event $event)
326
    {
327
        $event->data['class'] .= ' tag label label-default mx-1';
328
        $event->data['title'] = iconify('mdi:tag-text-outline') . ' ' . $event->data['title'];
329
    }
330
331
    public function tplIncPluginHandler(\Doku_Event $event)
332
    {
333
        $event->data['header']             = 'Header of page below the navbar (header)';
334
        $event->data['topheader']          = 'Top Header of page (topheader)';
335
        $event->data['pagefooter']         = 'Footer below the page content (pagefooter)';
336
        $event->data['pageheader']         = 'Header above the page content (pageheader)';
337
        $event->data['sidebarfooter']      = 'Footer below the sidebar (sidebarfooter)';
338
        $event->data['sidebarheader']      = 'Header above the sidebar (sidebarheader)';
339
        $event->data['rightsidebarfooter'] = 'Footer below the right-sidebar (rightsidebarfooter)';
340
        $event->data['rightsidebarheader'] = 'Header above the right-sidebar (rightsidebarheader)';
341
    }
342
343
    private function initPlugins()
344
    {
345
        $this->plugins['tplinc']       = plugin_load('helper', 'tplinc');
346
        $this->plugins['tag']          = plugin_load('helper', 'tag');
347
        $this->plugins['userhomepage'] = plugin_load('helper', 'userhomepage');
348
        $this->plugins['translation']  = plugin_load('helper', 'translation');
349
        $this->plugins['pagelist']     = plugin_load('helper', 'pagelist');
350
    }
351
352
    public function getPlugin($plugin)
353
    {
354
        if (plugin_isdisabled($plugin)) {
355
            return false;
356
        }
357
358
        if (!isset($this->plugins[$plugin])) {
359
            return false;
360
        }
361
362
        return $this->plugins[$plugin];
363
    }
364
365
    /**
366
     * Get the singleton instance
367
     *
368
     * @return Template
369
     */
370
    public static function getInstance()
371
    {
372
        static $instance = null;
373
374
        if ($instance === null) {
375
            $instance = new self;
376
        }
377
378
        return $instance;
379
    }
380
381
    /**
382
     * Get the content to include from the tplinc plugin
383
     *
384
     * prefix and postfix are only added when there actually is any content
385
     *
386
     * @param string $location
387
     * @return string
388
     */
389 2
    public function includePage($location, $return = false)
390
    {
391
392
        $content = '';
393
394
        if ($plugin = $this->getPlugin('tplinc')) {
395
            $content = $plugin->renderIncludes($location);
396
        }
397
398
        if ($content === '') {
399
            $content = tpl_include_page($location, 0, 1, $this->getConf('useACL'));
400
        }
401
402
        if ($content === '') {
403
            return '';
404
        }
405
406
        $content = $this->normalizeContent($content);
407
408
        if ($return) {
409
            return $content;
410
        }
411
412
        echo $content;
413
        return '';
414
    }
415
416
    /**
417
     * Get the template configuration metadata
418
     *
419
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
420
     *
421
     * @param   string $key
422
     * @return  array|string
423
     */
424
    public function getConfMetadata($key = null)
425
    {
426
        if ($key && isset($this->confMetadata[$key])) {
427
            return $this->confMetadata[$key];
428
        }
429
430
        return null;
431
    }
432
433
    private function loadConfMetadata()
434
    {
435
        $meta = array();
436
        $file = $this->tplDir . 'conf/metadata.php';
437
438
        include $file;
439
440
        $this->confMetadata = $meta;
441
    }
442
443
    /**
444
     * Simple wrapper for tpl_getConf
445
     *
446
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
447
     *
448
     * @param   string  $key
449
     * @param   mixed   $default value
450
     * @return  mixed
451
     */
452 6
    public function getConf($key, $default = false)
453
    {
454 1
        global $ACT, $INFO, $ID, $conf;
455
456
        $value = tpl_getConf($key, $default);
457
458
        switch ($key) {
459
            case 'useAvatar':
460
461
                if ($value == 'off') {
462
                    return false;
463
                }
464
465
                return $value;
466
467
            case 'bootstrapTheme':
468
469
                @list($theme, $bootswatch) = $this->getThemeForNamespace();
470
                if ($theme) {
471
                    return $theme;
472
                }
473
474
                return $value;
475
476
            case 'bootswatchTheme':
477
478
                @list($theme, $bootswatch) = $this->getThemeForNamespace();
479
                if ($bootswatch) {
480
                    return $bootswatch;
481
                }
482
483
                return $value;
484
485
            case 'showTools':
486
            case 'showSearchForm':
487
            case 'showPageTools':
488
            case 'showEditBtn':
489
            case 'showAddNewPage':
490
491
                return $value !== 'never' && ($value == 'always' || !empty($_SERVER['REMOTE_USER']));
492
493
            case 'showAdminMenu':
494
495
                return $value && ($INFO['isadmin'] || $INFO['ismanager']);
496
497
            case 'hideLoginLink':
498
            case 'showLoginOnFooter':
499
500
                return ($value && !isset($_SERVER['REMOTE_USER']));
501
502
            case 'showCookieLawBanner':
503
504
                return $value && page_findnearest(tpl_getConf('cookieLawBannerPage'), $this->getConf('useACL')) && ($ACT == 'show');
505
506
            case 'showSidebar':
507
508
                if ($ACT !== 'show') {
509
                    return false;
510
                }
511
512
                if ($this->getConf('showLandingPage')) {
513
                    return false;
514
                }
515
516
                return page_findnearest($conf['sidebar'], $this->getConf('useACL'));
517
518
            case 'showRightSidebar':
519
520
                if ($ACT !== 'show') {
521
                    return false;
522
                }
523
524
                if ($this->getConf('sidebarPosition') == 'right') {
525
                    return false;
526
                }
527
528
                return page_findnearest(tpl_getConf('rightSidebar'), $this->getConf('useACL'));
529
530
            case 'showLandingPage':
531
532
                return ($value && (bool) preg_match_all($this->getConf('landingPages'), $ID));
533
534
            case 'pageOnPanel':
535
536
                if ($this->getConf('showLandingPage')) {
537
                    return false;
538
                }
539
540
                return $value;
541
542
            case 'showThemeSwitcher':
543
544
                return $value && ($this->getConf('bootstrapTheme') == 'bootswatch');
545
546
            case 'tocCollapseSubSections':
547
548
                if (!$this->getConf('tocAffix')) {
549
                    return false;
550
                }
551
552
                return $value;
553
554
            case 'schemaOrgType':
555
556
                if ($semantic = plugin_load('helper', 'semantic')) {
557
                    if (method_exists($semantic, 'getSchemaOrgType')) {
558
                        return $semantic->getSchemaOrgType();
559
                    }
560
                }
561
562
                return $value;
563
564
            case 'tocCollapseOnScroll':
565
566
                if ($this->getConf('tocLayout') !== 'default') {
567
                    return false;
568
                }
569
570
                return $value;
571
        }
572
573
        $metadata = $this->getConfMetadata($key);
574
575
        if (isset($metadata[0])) {
576
            switch ($metadata[0]) {
577
                case 'regex':
578
                    return '/' . $value . '/';
579
                case 'multicheckbox':
580
                    return explode(',', $value);
581
            }
582
        }
583
584
        return $value;
585
    }
586
587
    /**
588
     * Return the Bootswatch.com theme lists defined in metadata.php
589
     *
590
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
591
     *
592
     * @return  array
593
     */
594
    public function getBootswatchThemeList()
595
    {
596
        $bootswatch_themes = $this->getConfMetadata('bootswatchTheme');
597
        return $bootswatch_themes['_choices'];
598
    }
599
600
    /**
601
     * Get a Gravatar, Libravatar, Office365/EWS URL or local ":user" DokuWiki namespace
602
     *
603
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
604
     *
605
     * @param   string  $username  User ID
606
     * @param   string  $email     The email address
607
     * @param   string  $size      Size in pixels, defaults to 80px [ 1 - 2048 ]
608
     * @param   string  $d         Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
609
     * @param   string  $r         Maximum rating (inclusive) [ g | pg | r | x ]
610
     *
611
     * @return  string
612
     */
613 3
    public function getAvatar($username, $email, $size = 80, $d = 'mm', $r = 'g')
614
    {
615
        global $INFO;
616
617
        $avatar_url      = '';
618
        $avatar_provider = $this->getConf('useAvatar');
619
620
        if (!$avatar_provider) {
621
            return false;
622
        }
623
624
        if ($avatar_provider == 'local') {
625
626
            $interwiki = getInterwiki();
627
            $user_url  = str_replace('{NAME}', $username, $interwiki['user']);
628
            $logo_size = array();
629
            $logo      = tpl_getMediaFile(array("$user_url.png", "$user_url.jpg", 'images/avatar.png'), false, $logo_size);
630
631
            return $logo;
632
        }
633
634
        if ($avatar_provider == 'activedirectory') {
635
            $logo = "data:image/jpeg;base64," . base64_encode($INFO['userinfo']['thumbnailphoto']);
636
637
            return $logo;
638
        }
639
640
        $email = strtolower(trim($email));
641
642
        if ($avatar_provider == 'office365') {
643
            $office365_url = rtrim($this->getConf('office365URL'), '/');
644
            $avatar_url    = $office365_url . '/owa/service.svc/s/GetPersonaPhoto?email=' . $email . '&size=HR' . $size . 'x' . $size;
645
        }
646
647
        if ($avatar_provider == 'gravatar' || $avatar_provider == 'libavatar') {
648
            $gravatar_url  = rtrim($this->getConf('gravatarURL'), '/') . '/';
649
            $libavatar_url = rtrim($this->getConf('libavatarURL'), '/') . '/';
650
651
            switch ($avatar_provider) {
652
                case 'gravatar':
653
                    $avatar_url = $gravatar_url;
654
                    break;
655
                case 'libavatar':
656
                    $avatar_url = $libavatar_url;
657
                    break;
658
            }
659
660
            $avatar_url .= md5($email);
661
            $avatar_url .= "?s=$size&d=$d&r=$r";
662
        }
663
664
        if ($avatar_url) {
665
            $media_link = ml("$avatar_url&.jpg", array('cache' => 'recache', 'w' => $size, 'h' => $size));
666
            return $media_link;
667
        }
668
669
        return false;
670
    }
671
672
    /**
673
     * Return template classes
674
     *
675
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
676
     * @see tpl_classes();
677
     *
678
     * @return string
679
     **/
680
    public function getClasses()
681
    {
682
        $page_on_panel    = $this->getConf('pageOnPanel');
683
        $bootstrap_theme  = $this->getConf('bootstrapTheme');
684
        $bootswatch_theme = $this->getBootswatchTheme();
685
686
        $classes   = array();
687
        $classes[] = (($bootstrap_theme == 'bootswatch') ? $bootswatch_theme : $bootstrap_theme);
688
        $classes[] = trim(tpl_classes());
689
690
        if ($page_on_panel) {
691
            $classes[] = 'dw-page-on-panel';
692
        }
693
694
        if (!$this->getConf('tableFullWidth')) {
695
            $classes[] = 'dw-table-width';
696
        }
697
698
        if ($this->isFluidNavbar()) {
699
            $classes[] = 'dw-fluid-container';
700
        }
701
702
        return implode(' ', $classes);
703
    }
704
705
    /**
706
     * Return the current Bootswatch theme
707
     *
708
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
709
     *
710
     * @return  string
711
     */
712
    public function getBootswatchTheme()
713
    {
714 1
        global $INPUT;
715
716
        $bootswatch_theme = $this->getConf('bootswatchTheme');
717
718
        if ($this->getConf('showThemeSwitcher')) {
719
            if (get_doku_pref('bootswatchTheme', null) !== null && get_doku_pref('bootswatchTheme', null) !== '') {
720
                $bootswatch_theme = get_doku_pref('bootswatchTheme', null);
721
            }
722
        }
723
        return $bootswatch_theme;
724
    }
725
726
    /**
727
     * Return only the available Bootswatch.com themes
728
     *
729
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
730
     *
731
     * @return  array
732
     */
733
    public function getAvailableBootswatchThemes()
734
    {
735
        return array_diff($this->getBootswatchThemeList(), $this->getConf('hideInThemeSwitcher'));
736
    }
737
738
    /**
739
     * Print some info about the current page
740
     *
741
     * @author  Andreas Gohr <andi@splitbrain.org>
742
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
743
     *
744
     * @param   bool $ret return content instead of printing it
745
     * @return  bool|string
746
     */
747 3
    public function getPageInfo($ret = false)
748
    {
749
        global $conf;
750
        global $lang;
751
        global $INFO;
752 1
        global $ID;
753
754
        // return if we are not allowed to view the page
755
        if (!auth_quickaclcheck($ID)) {
756
            return false;
757
        }
758
759
        // prepare date and path
760 1
        $fn = $INFO['filepath'];
761
762
        if (!$conf['fullpath']) {
763
            if ($INFO['rev']) {
764
                $fn = str_replace(fullpath($conf['olddir']) . '/', '', $fn);
765 1
            } else {
ElseExpression The method getPageInfo uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (kritika/PHPMD) Filter like this
766
                $fn = str_replace(fullpath($conf['datadir']) . '/', '', $fn);
767
            }
768
        }
769
770
        $date_format = $this->getConf('pageInfoDateFormat');
771
        $page_info   = $this->getConf('pageInfo');
772
773
        $fn   = utf8_decodeFN($fn);
774
        $date = (($date_format == 'dformat')
775
            ? dformat($INFO['lastmod'])
776
            : datetime_h($INFO['lastmod']));
777
778
        // print it
779
        if ($INFO['exists']) {
780
            $fn_full = $fn;
781
782
            if (!in_array('extension', $page_info)) {
783
                $fn = str_replace(array('.txt.gz', '.txt'), '', $fn);
784
            }
785
786
            $out = '<ul class="list-inline">';
787
788
            if (in_array('filename', $page_info)) {
789
                $out .= '<li>' . iconify('mdi:file-document-outline', array('class' => 'text-muted')) . ' <span title="' . $fn_full . '">' . $fn . '</span></li>';
790
            }
791
792
            if (in_array('date', $page_info)) {
793
                $out .= '<li>' . iconify('mdi:calendar', array('class' => 'text-muted')) . ' ' . $lang['lastmod'] . ' <span title="' . dformat($INFO['lastmod']) . '">' . $date . '</span></li>';
794
            }
795
796
            if (in_array('editor', $page_info)) {
797
                if (isset($INFO['editor'])) {
798
                    $user = editorinfo($INFO['editor']);
799
800
                    if ($this->getConf('useAvatar')) {
801
                        global $auth;
802
                        $user_data = $auth->getUserData($INFO['editor']);
803
804
                        $avatar_img = $this->getAvatar($INFO['editor'], $user_data['mail'], 16);
805
                        $user_img   = '<img src="' . $avatar_img . '" alt="" width="16" height="16" class="img-rounded" /> ';
806
                        $user       = str_replace(array('iw_user', 'interwiki'), '', $user);
807
                        $user       = $user_img . "<bdi>$user<bdi>";
808
                    }
809
810
                    $out .= '<li class="text-muted">' . $lang['by'] . ' <bdi>' . $user . '</bdi></li>';
811 1
                } else {
812
                    $out .= '<li>(' . $lang['external_edit'] . ')</li>';
813
                }
814
            }
815
816
            if ($INFO['locked'] && in_array('locked', $page_info)) {
817
                $out .= '<li>' . iconify('mdi:lock', array('class' => 'text-muted')) . ' ' . $lang['lockedby'] . ' ' . editorinfo($INFO['locked']) . '</li>';
818
            }
819
820
            $out .= '</ul>';
821
822
            if ($ret) {
823
                return $out;
824 1
            } else {
825
                echo $out;
826
                return true;
827
            }
828
        }
829
830
        return false;
831
    }
832
833
    /**
834
     * Prints the global message array in Bootstrap style
835
     *
836
     * @author Andreas Gohr <andi@splitbrain.org>
837
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
838
     *
839
     * @see html_msgarea()
840
     */
841 3
    public function getMessageArea()
842
    {
843
844
        global $MSG, $MSG_shown;
845
846
        /** @var array $MSG */
847
        // store if the global $MSG has already been shown and thus HTML output has been started
848
        $MSG_shown = true;
849
850
        // Check if translation is outdate
851
        if ($this->getConf('showTranslation') && $translation = $this->getPlugin('translation')) {
852 1
            global $ID;
853
854
            if ($translation->istranslatable($ID)) {
855
                $translation->checkage();
856
            }
857
        }
858
859
        if (!isset($MSG)) {
860
            return;
861
        }
862
863
        $shown = array();
864
865
        foreach ($MSG as $msg) {
866
            $hash = md5($msg['msg']);
867
            if (isset($shown[$hash])) {
868
                continue;
869
            }
870
            // skip double messages
871
872
            if (info_msg_allowed($msg)) {
873
                switch ($msg['lvl']) {
874
                    case 'info':
875
                        $level = 'info';
876
                        $icon  = 'mdi:information';
877
                        break;
878
879
                    case 'error':
880
                        $level = 'danger';
881
                        $icon  = 'mdi:alert-octagon';
882
                        break;
883
884
                    case 'notify':
885
                        $level = 'warning';
886
                        $icon  = 'mdi:alert';
887
                        break;
888
889
                    case 'success':
890
                        $level = 'success';
891
                        $icon  = 'mdi:check-circle';
892
                        break;
893
                }
894
895
                print '<div class="alert alert-' . $level . '">';
896
                print iconify($icon, array('class' => 'mr-2'));
897
                print $msg['msg'];
898
                print '</div>';
899
            }
900
901
            $shown[$hash] = 1;
902
        }
903
904
        unset($GLOBALS['MSG']);
905
    }
906
907
    /**
908
     * Get the license (link or image)
909
     *
910
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
911
     *
912
     * @param  string  $type ("link" or "image")
913
     * @param  integer $size of image
914
     * @param  bool    $return or print
915
     * @return string
916
     */
917 1
    public function getLicense($type = 'link', $size = 24, $return = false)
918
    {
919
920
        global $conf, $license, $lang;
921
922
        $target = $conf['target']['extern'];
923
        $lic    = $license[$conf['license']];
924
        $output = '';
925
926
        if (!$lic) {
927
            return '';
928
        }
929
930
        if ($type == 'link') {
931
            $output .= $lang['license'] . '<br/>';
932
        }
933
934
        $license_url  = $lic['url'];
935
        $license_name = $lic['name'];
936
937
        $output .= '<a href="' . $license_url . '" title="' . $license_name . '" target="' . $target . '" itemscope itemtype="http://schema.org/CreativeWork" itemprop="license" rel="license" class="license">';
938
939
        if ($type == 'image') {
940
            foreach (explode('-', $conf['license']) as $license_img) {
941
                if ($license_img == 'publicdomain') {
942
                    $license_img = 'pd';
943
                }
944
945
                $output .= '<img src="' . tpl_basedir() . "images/license/$license_img.png" . '" width="' . $size . '" height="' . $size . '" alt="' . $license_img . '" /> ';
946
            }
947 1
        } else {
948
            $output .= $lic['name'];
949
        }
950
951
        $output .= '</a>';
952
953
        if ($return) {
954
            return $output;
955
        }
956
957
        echo $output;
958
        return '';
959
    }
960
961
    /**
962
     * Add Google Analytics
963
     *
964
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
965
     *
966
     * @return  string
967
     */
968 4
    public function getGoogleAnalitycs()
969
    {
970
        global $INFO;
971 1
        global $ID;
972
973
        if (!$this->getConf('useGoogleAnalytics')) {
974
            return false;
975
        }
976
977
        if (!$google_analitycs_id = $this->getConf('googleAnalyticsTrackID')) {
978
            return false;
979
        }
980
981
        if ($this->getConf('googleAnalyticsNoTrackAdmin') && $INFO['isadmin']) {
982
            return false;
983
        }
984
985
        if ($this->getConf('googleAnalyticsNoTrackUsers') && isset($_SERVER['REMOTE_USER'])) {
986
            return false;
987
        }
988
989
        if (tpl_getConf('googleAnalyticsNoTrackPages')) {
990
            if (preg_match_all($this->getConf('googleAnalyticsNoTrackPages'), $ID)) {
991
                return false;
992
            }
993
        }
994
995
        $out = DOKU_LF;
996
        $out .= '// Google Analytics' . DOKU_LF;
997
        $out .= "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
998
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
999
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
1000
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');" . DOKU_LF;
1001
1002
        $out .= 'ga("create", "' . $google_analitycs_id . '", "auto");' . DOKU_LF;
1003
        $out .= 'ga("send", "pageview");' . DOKU_LF;
1004
1005
        if ($this->getConf('googleAnalyticsAnonymizeIP')) {
1006
            $out .= 'ga("set", "anonymizeIp", true);' . DOKU_LF;
1007
        }
1008
1009
        if ($this->getConf('googleAnalyticsTrackActions')) {
1010
            $out .= 'ga("send", "event", "DokuWiki", JSINFO.bootstrap3.mode);' . DOKU_LF;
1011
        }
1012
1013
        $out .= '// End Google Analytics' . DOKU_LF;
1014
1015
        return $out;
1016
    }
1017
1018
    /**
1019
     * Return the user home-page link
1020
     *
1021
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1022
     *
1023
     * @return  string
1024
     */
1025
    public function getUserHomePageLink()
1026
    {
1027
        return wl($this->getUserHomePageID());
1028
    }
1029
1030
    /**
1031
     * Return the user home-page ID
1032
     *
1033
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1034
     *
1035
     * @return  string
1036
     */
1037 1
    public function getUserHomePageID()
1038
    {
1039
        $interwiki = getInterwiki();
1040
        $page_id   = str_replace('{NAME}', $_SERVER['REMOTE_USER'], $interwiki['user']);
1041
1042
        return cleanID($page_id);
1043
    }
1044
1045
    /**
1046
     * Print the breadcrumbs trace with Bootstrap style
1047
     *
1048
     * @author Andreas Gohr <andi@splitbrain.org>
1049
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1050
     *
1051
     * @return bool
1052
     */
1053 1
    public function getBreadcrumbs()
1054
    {
1055
        global $lang;
1056
        global $conf;
1057
1058
        //check if enabled
1059
        if (!$conf['breadcrumbs']) {
1060
            return false;
1061
        }
1062
1063
        $crumbs = breadcrumbs(); //setup crumb trace
1064
1065
        //render crumbs, highlight the last one
1066
        print '<ol class="breadcrumb">';
1067
        print '<li>' . rtrim($lang['breadcrumb'], ':') . '</li>';
1068
1069
        $last = count($crumbs);
1070 1
        $i    = 0;
1071
1072
        foreach ($crumbs as $id => $name) {
1073
            $i++;
1074
1075
            print($i == $last) ? '<li class="active">' : '<li>';
1076
            tpl_link(wl($id), hsc($name), 'title="' . $id . '"');
1077
            print '</li>';
1078
1079
            if ($i == $last) {
1080
                print '</ol>';
1081
            }
1082
        }
1083
1084
        return true;
1085
    }
1086
1087
    /**
1088
     * Hierarchical breadcrumbs with Bootstrap style
1089
     *
1090
     * This code was suggested as replacement for the usual breadcrumbs.
1091
     * It only makes sense with a deep site structure.
1092
     *
1093
     * @author Andreas Gohr <andi@splitbrain.org>
1094
     * @author Nigel McNie <oracle.shinoda@gmail.com>
1095
     * @author Sean Coates <sean@caedmon.net>
1096
     * @author <fredrik@averpil.com>
1097
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1098
     * @todo   May behave strangely in RTL languages
1099
     *
1100
     * @return bool
1101
     */
1102 1
    public function getYouAreHere()
1103
    {
1104
        global $conf;
1105 1
        global $ID;
1106
        global $lang;
1107
1108
        // check if enabled
1109
        if (!$conf['youarehere']) {
1110
            return false;
1111
        }
1112
1113
        $parts = explode(':', $ID);
1114
        $count = count($parts);
1115
1116
        echo '<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">';
1117
        echo '<li>' . rtrim($lang['youarehere'], ':') . '</li>';
1118
1119
        // always print the startpage
1120
        echo '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
1121
1122
        tpl_link(wl($conf['start']),
1123
            '<span itemprop="name">' . iconify('mdi:home') . '<span class="sr-only">Home</span></span>',
1124
            ' itemprop="item"  title="' . $conf['start'] . '"'
1125
        );
1126
1127
        echo '<meta itemprop="position" content="1" />';
1128
        echo '</li>';
1129
1130
        $position = 1;
1131
1132
        // print intermediate namespace links
1133
        $part = '';
1134
1135
        for ($i = 0; $i < $count - 1; $i++) {
1136
            $part .= $parts[$i] . ':';
1137
            $page = $part;
1138
1139
            if ($page == $conf['start']) {
1140
                continue;
1141
            }
1142
            // Skip startpage
1143
1144
            $position++;
1145
1146
            // output
1147
            echo '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
1148
1149
            $link = html_wikilink($page);
1150
            $link = str_replace(array('<span class="curid">', '</span>'), '', $link);
1151
            $link = str_replace('<a', '<a itemprop="item" ', $link);
1152
            $link = preg_replace('/data-wiki-id="(.+?)"/', '', $link);
1153
            $link = str_replace('<a', '<span itemprop="name"><a', $link);
1154
            $link = str_replace('</a>', '</a></span>', $link);
1155
1156
            echo $link;
1157
            echo '<meta itemprop="position" content="' . $position . '" />';
1158
            echo '</li>';
1159
        }
1160
1161
        // print current page, skipping start page, skipping for namespace index
1162
        $exists = false;
1163
        resolve_pageid('', $page, $exists);
1164
1165
        if (isset($page) && $page == $part . $parts[$i]) {
1166
            echo '</ol>';
1167
            return true;
1168
        }
1169
1170
        $page = $part . $parts[$i];
1171
1172
        if ($page == $conf['start']) {
1173
            echo '</ol>';
1174
            return true;
1175
        }
1176
1177
        echo '<li class="active" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
1178
1179
        $link = str_replace(array('<span class="curid">', '</span>'), '', html_wikilink($page));
1180
        $link = str_replace('<a ', '<a itemprop="item" ', $link);
1181
        $link = str_replace('<a', '<span itemprop="name"><a', $link);
1182
        $link = str_replace('</a>', '</a></span>', $link);
1183
        $link = preg_replace('/data-wiki-id="(.+?)"/', '', $link);
1184
1185
        echo $link;
1186
        echo '<meta itemprop="position" content="' . ++$position . '" />';
1187
        echo '</li>';
1188
        echo '</ol>';
1189
1190
        return true;
1191
    }
1192
1193
    /**
1194
     * Display the page title (and previous namespace page title) on browser titlebar
1195
     *
1196
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1197
     * @return string
1198
     */
1199 1
    public function getBrowserPageTitle()
1200
    {
1201 1
        global $conf, $ACT, $ID;
1202
1203
        if ($this->getConf('browserTitleShowNS') && $ACT == 'show') {
1204
            $ns_page      = '';
1205
            $ns_parts     = explode(':', $ID);
1206
            $ns_pages     = array();
1207
            $ns_titles    = array();
1208
            $ns_separator = sprintf(' %s ', $this->getConf('browserTitleCharSepNS'));
1209
1210
            if (useHeading('navigation')) {
1211
                if (count($ns_parts) > 1) {
1212
                    foreach ($ns_parts as $ns_part) {
1213
                        $ns_page .= "$ns_part:";
1214
                        $ns_pages[] = $ns_page;
1215
                    }
1216
1217
                    $ns_pages = array_unique($ns_pages);
1218
1219
                    foreach ($ns_pages as $ns_page) {
1220
                        $exists = false;
1221
                        resolve_pageid(getNS($ns_page), $ns_page, $exists);
1222
1223 1
                        $ns_page_title_heading = hsc(p_get_first_heading($ns_page));
1224 1
                        $ns_page_title_page    = noNSorNS($ns_page);
1225
                        $ns_page_title         = ($exists) ? $ns_page_title_heading : null;
1226
1227
                        if ($ns_page_title !== $conf['start']) {
1228
                            $ns_titles[] = $ns_page_title;
1229
                        }
1230
                    }
1231
                }
1232
1233
                resolve_pageid(getNS($ID), $ID, $exists);
1234
1235
                if ($exists) {
1236
                    $ns_titles[] = tpl_pagetitle($ID, true);
1237 1
                } else {
1238
                    $ns_titles[] = noNS($ID);
1239
                }
1240
1241
                $ns_titles = array_filter(array_unique($ns_titles));
1242 1
            } else {
1243
                $ns_titles = $ns_parts;
1244
            }
1245
1246
            if ($this->getConf('browserTitleOrderNS') == 'normal') {
1247
                $ns_titles = array_reverse($ns_titles);
1248
            }
1249
1250
            $browser_title = implode($ns_separator, $ns_titles);
1251 1
        } else {
1252
            $browser_title = tpl_pagetitle($ID, true);
1253
        }
1254
1255
        return str_replace(
1256
            array('@WIKI@', '@TITLE@'),
1257
            array(strip_tags($conf['title']), $browser_title),
1258
            $this->getConf('browserTitle')
1259
        );
1260
    }
1261
1262
    /**
1263
     * Return the theme for current namespace
1264
     *
1265
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1266
     * @return string
1267
     */
1268
    public function getThemeForNamespace()
1269
    {
1270
        global $ID;
1271
1272
        $themes_filename = DOKU_CONF . 'bootstrap3.themes.conf';
1273
1274
        if (!$this->getConf('themeByNamespace')) {
1275
            return array();
1276
        }
1277
1278
        if (!file_exists($themes_filename)) {
1279
            return array();
1280
        }
1281
1282
        $config = confToHash($themes_filename);
1283
        krsort($config);
1284
1285
        foreach ($config as $page => $theme) {
1286
            if (preg_match("/^$page/", "$ID")) {
1287
                list($bootstrap, $bootswatch) = explode('/', $theme);
1288
1289
                if ($bootstrap && in_array($bootstrap, array('default', 'optional', 'custom'))) {
1290
                    return array($bootstrap, $bootswatch);
1291
                }
1292
1293
                if ($bootstrap == 'bootswatch' && in_array($bootswatch, $this->getBootswatchThemeList())) {
1294
                    return array($bootstrap, $bootswatch);
1295
                }
1296
            }
1297
        }
1298
1299
        return array();
1300
    }
1301
1302
    /**
1303
     * Make a Bootstrap3 Nav
1304
     *
1305
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1306
     *
1307
     * @param   string   $html
1308
     * @param   string   $type (= pills, tabs, navbar)
1309
     * @param   boolean  $staked
1310
     * @param   string   $optional_class
1311
     * @return  string
1312
     */
1313 2
    public function toBootstrapNav($html, $type = '', $stacked = false, $optional_class = '')
1314
    {
1315
        $classes = array();
1316
1317
        $classes[] = 'nav';
1318
        $classes[] = $optional_class;
1319
1320
        switch ($type) {
1321
            case 'navbar':
1322
            case 'navbar-nav':
1323
                $classes[] = 'navbar-nav';
1324
                break;
1325
            case 'pills':
1326
            case 'tabs':
1327
                $classes[] = "nav-$type";
1328
                break;
1329
        }
1330
1331
        if ($stacked) {
1332
            $classes[] = 'nav-stacked';
1333
        }
1334
1335
        $class = implode(' ', $classes);
1336
1337
        $output = str_replace(
1338
            array('<ul class="', '<ul>'),
1339
            array("<ul class=\"$class ", "<ul class=\"$class\">"),
1340
            $html
1341
        );
1342
1343
        $output = $this->normalizeList($output);
1344
1345
        return $output;
1346
    }
1347
1348
    /**
1349
     * Normalize the DokuWiki list items
1350
     *
1351
     * @todo    use Simple DOM HTML library
1352
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1353
     * @todo    use Simple DOM HTML
1354
     * @todo    FIX SimpleNavi curid
1355
     *
1356
     * @param   string  $html
1357
     * @return  string
1358
     */
1359
    public function normalizeList($list)
1360
    {
1361
1362 1
        global $ID;
1363
1364
        $list = preg_replace_callback('/data-wiki-id="(.+?)"/', array($this, '_replaceWikiCurrentIdCallback'), $list);
1365
1366
        $html = new \simple_html_dom;
1367
        $html->load($list, true, false);
1368
1369
        # Create data-curid HTML5 attribute and unwrap span.curid for pre-Hogfather release
1370
        foreach ($html->find('span.curid') as $elm) {
1371
            $elm->firstChild()->setAttribute('data-wiki-curid', 'true');
1372
            $elm->outertext = str_replace(array('<span class="curid">', '</span>'), '', $elm->outertext);
1373
        }
1374
1375
        # Unwrap div.li element
1376
        foreach ($html->find('div.li') as $elm) {
1377
            $elm->outertext = str_replace(array('<div class="li">', '</div>'), '', $elm->outertext);
1378
        }
1379
1380
        $list = $html->save();
1381
        $html->clear();
1382
        unset($html);
1383
1384
        $html = new \simple_html_dom;
1385
        $html->load($list, true, false);
1386
1387
        foreach ($html->find('li') as $elm) {
1388
            if ($elm->find('a[data-wiki-curid]')) {
1389
                $elm->class .= ' active';
1390
            }
1391
        }
1392
1393
        $list = $html->save();
1394
        $html->clear();
1395
        unset($html);
1396
1397
        # TODO optimize
1398
        $list = preg_replace('/<i (.+?)><\/i> <a (.+?)>(.+?)<\/a>/', '<a $2><i $1></i> $3</a>', $list);
1399
        $list = preg_replace('/<span (.+?)><\/span> <a (.+?)>(.+?)<\/a>/', '<a $2><span $1></span> $3</a>', $list);
1400
1401
        return $list;
1402
    }
1403
1404
    /**
1405
     * Remove data-wiki-id HTML5 attribute
1406
     *
1407
     * @todo Remove this in future
1408
     * @since Hogfather
1409
     *
1410
     * @param array $matches
1411
     *
1412
     * @return string
1413
     */
1414 2
    private function _replaceWikiCurrentIdCallback($matches)
1415
    {
1416
1417
        global $ID;
1418
1419
        if ($ID == $matches[1]) {
1420
            return 'data-wiki-curid="true"';
1421
        }
1422
1423
        return '';
1424
1425
    }
1426
1427
    /**
1428
     * Return a Bootstrap NavBar and or drop-down menu
1429
     *
1430
     * @todo    use Simple DOM HTML library
1431
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1432
     *
1433
     * @return  string
1434
     */
1435 1
    public function getNavbar()
1436
    {
1437
        if ($this->getConf('showNavbar') === 'logged' && !$_SERVER['REMOTE_USER']) {
1438
            return false;
1439
        }
1440
1441 1
        global $ID;
1442
        global $conf;
1443
1444
        $navbar = $this->toBootstrapNav(tpl_include_page('navbar', 0, 1, $this->getConf('useACL')), 'navbar');
1445
1446
        $navbar = str_replace('urlextern', '', $navbar);
1447
1448
        $navbar = preg_replace('/<li class="level([0-9]) node"> (.*)/',
1449
            '<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);
1450
1451
        $navbar = preg_replace('/<li class="level([0-9]) node active"> (.*)/',
1452
            '<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);
1453
1454
        # FIX for Purplenumbers renderer plugin
1455
        # TODO use Simple DOM HTML or improve the regex!
1456
        if ($conf['renderer_xhtml'] == 'purplenumbers') {
1457
            $navbar = preg_replace('/<li class="level1"> (.*)/',
1458
                '<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);
1459
        }
1460
1461
        $navbar = preg_replace('/<ul class="(.*)">\n<li class="level2(.*)">/',
1462
            '<ul class="dropdown-menu" role="menu">' . PHP_EOL . '<li class="level2$2">', $navbar);
1463
1464
        return $navbar;
1465
    }
1466
1467
    /**
1468
     * Manipulate Sidebar page to add Bootstrap3 styling
1469
     *
1470
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1471
     *
1472
     * @param   string   $sidebar
1473
     * @param   boolean  $return
1474
     * @return  string
1475
     */
1476 1
    public function normalizeSidebar($sidebar, $return = false)
1477
    {
1478
        $out = $this->toBootstrapNav($sidebar, 'pills', true);
1479
        $out = $this->normalizeContent($out);
1480
1481
        $html = new \simple_html_dom;
1482
        $html->load($out, true, false);
1483
1484
        # TODO 'page-header' will be removed in the next release of Bootstrap
1485
        foreach ($html->find('h1, h2, h3, h4, h5, h6') as $elm) {
1486
1487
            # Skip panel title on sidebar
1488
            if (preg_match('/panel-title/', $elm->class)) {
1489
                continue;
1490
            }
1491
1492
            $elm->class .= ' page-header';
1493
        }
1494
1495
        $out = $html->save();
1496
        $html->clear();
1497
        unset($html);
1498
1499
        if ($return) {
1500
            return $out;
1501
        }
1502
1503
        echo $out;
1504
    }
1505
1506
    /**
1507
     * Return a drop-down page
1508
     *
1509
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1510
     *
1511
     * @param   string  $page name
1512
     * @return  string
1513
     */
1514
    public function getDropDownPage($page)
1515
    {
1516
1517
        $page = page_findnearest($page, $this->getConf('useACL'));
1518
1519
        if (!$page) {
1520
            return;
1521
        }
1522
1523
        $output   = $this->normalizeContent($this->toBootstrapNav(tpl_include_page($page, 0, 1, $this->getConf('useACL')), 'pills', true));
1524
        $dropdown = '<ul class="nav navbar-nav dw__dropdown_page">' .
1525
        '<li class="dropdown dropdown-large">' .
1526
        '<a href="#" class="dropdown-toggle" data-toggle="dropdown" title="">' .
1527
        p_get_first_heading($page) .
1528
            ' <span class="caret"></span></a>' .
1529
            '<ul class="dropdown-menu dropdown-menu-large" role="menu">' .
1530
            '<li><div class="container small">' .
1531
            $output .
1532
            '</div></li></ul></li></ul>';
1533
1534
        return $dropdown;
1535
    }
1536
1537
    /**
1538
     * Include left or right sidebar
1539
     *
1540
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1541
     *
1542
     * @param   string  $type left or right sidebar
1543
     * @return  boolean
1544
     */
1545
    public function includeSidebar($type)
1546
    {
1547
        global $conf;
1548
1549
        $left_sidebar       = $conf['sidebar'];
1550
        $right_sidebar      = $this->getConf('rightSidebar');
1551
        $left_sidebar_grid  = $this->getConf('leftSidebarGrid');
1552
        $right_sidebar_grid = $this->getConf('rightSidebarGrid');
1553
1554
        if (!$this->getConf('showSidebar')) {
1555
            return false;
1556
        }
1557
1558
        switch ($type) {
1559
            case 'left':
1560
1561
                if ($this->getConf('sidebarPosition') == 'left') {
1562
                    $this->sidebarWrapper($left_sidebar, 'dokuwiki__aside', $left_sidebar_grid, 'sidebarheader', 'sidebarfooter');
1563
                }
1564
1565
                return true;
1566
1567
            case 'right':
1568
1569
                if ($this->getConf('sidebarPosition') == 'right') {
1570
                    $this->sidebarWrapper($left_sidebar, 'dokuwiki__aside', $left_sidebar_grid, 'sidebarheader', 'sidebarfooter');
1571
                }
1572
1573
                if ($this->getConf('showRightSidebar')
1574
                    && $this->getConf('sidebarPosition') == 'left') {
1575
                    $this->sidebarWrapper($right_sidebar, 'dokuwiki__rightaside', $right_sidebar_grid, 'rightsidebarheader', 'rightsidebarfooter');
1576
                }
1577
1578
                return true;
1579
        }
1580
1581
        return false;
1582
    }
1583
1584
    /**
1585
     * Wrapper for left or right sidebar
1586
     *
1587
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1588
     *
1589
     * @param  string  $sidebar_page
1590
     * @param  string  $sidebar_id
1591
     * @param  string  $sidebar_header
1592
     * @param  string  $sidebar_footer
1593
     */
1594 10
    private function sidebarWrapper($sidebar_page, $sidebar_id, $sidebar_class, $sidebar_header, $sidebar_footer)
1595
    {
1596 1
        global $lang;
1597 1
        global $TPL;
1598
1599
        @require $this->tplDir . 'tpl/sidebar.php';
1600
    }
1601
1602
    /**
1603
     * Add Bootstrap classes in a DokuWiki content
1604
     *
1605
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
1606
     *
1607
     * @param   string  $content from tpl_content() or from tpl_include_page()
1608
     * @return  string  with Bootstrap styles
1609
     */
1610 7
    public function normalizeContent($content)
1611
    {
1612
        global $ACT;
1613
        global $INPUT;
1614
        global $INFO;
1615
1616
        # FIX :-\ smile
1617
        $content = str_replace(array('alt=":-\"', "alt=':-\'"), 'alt=":-&#92;"', $content);
1618
1619
        # Workaround for ToDo Plugin
1620
        $content = str_replace('checked="checked"', ' checked="checked"', $content);
1621
1622
        # Return original content if Simple HTML DOM fail or exceeded page size (default MAX_FILE_SIZE => 600KB)
1623
        if (strlen($content) > MAX_FILE_SIZE) {
1624
            return $content;
1625
        }
1626
1627
        # Import HTML string
1628
        $html = new \simple_html_dom;
1629
        $html->load($content, true, false);
1630
1631
        # Return original content if Simple HTML DOM fail or exceeded page size (default MAX_FILE_SIZE => 600KB)
1632
        if (!$html) {
1633
            return $content;
1634
        }
1635
1636
        # Move Current Page ID to <a> element and create data-curid HTML5 attribute (pre-Hogfather release)
1637
        foreach ($html->find('.curid') as $elm) {
1638
            foreach ($elm->find('a') as $link) {
1639
                $link->class .= ' curid';
1640
                $link->attr[' data-curid'] = 'true'; # FIX attribute
1641
            }
1642
        }
1643
1644
        # Unwrap span.curid elements
1645
        foreach ($html->find('span.curid') as $elm) {
1646
            $elm->outertext = str_replace(array('<span class="curid">', '</span>'), '', $elm->outertext);
1647
        }
1648
1649
        # Footnotes
1650
        foreach ($html->find('.footnotes') as $elm) {
1651
            $elm->outertext = '<hr/>' . $elm->outertext;
1652
        }
1653
1654
        # Accessibility (a11y)
1655
        foreach ($html->find('.a11y') as $elm) {
1656
            if (preg_match('/picker/', $elm->class)) {
1657
                continue;
1658
            }
1659
            $elm->class .= ' sr-only';
1660
        }
1661
1662
        # Fix list overlap in media images
1663
        foreach ($html->find('ul, ol') as $elm) {
1664
            if (preg_match('/(nav|dropdown-menu)/', $elm->class)) {
1665
                continue;
1666
            }
1667
            $elm->class .= ' fix-media-list-overlap';
1668
        }
1669
1670
        # Buttons
1671
        foreach ($html->find('.button') as $elm) {
1672
            if ($elm->tag == 'form') {
1673
                continue;
1674
            }
1675
            $elm->class .= ' btn';
1676
        }
1677
1678
        foreach ($html->find('[type=button], [type=submit], [type=reset]') as $elm) {
1679
            $elm->class .= ' btn btn-default';
1680
        }
1681
1682
        # Section Edit Button
1683
        foreach ($html->find('.btn_secedit [type=submit]') as $elm) {
1684
            $elm->class .= ' btn btn-xs btn-default';
1685
        }
1686
1687
        # Section Edit icons
1688
        foreach ($html->find('.secedit.editbutton_section button') as $elm) {
1689
            $elm->innertext = iconify('mdi:pencil') . ' ' . $elm->innertext;
1690
        }
1691
1692
        foreach ($html->find('.secedit.editbutton_table button') as $elm) {
1693
            $elm->innertext = iconify('mdi:table') . ' ' . $elm->innertext;
1694
        }
1695
1696
        # Tabs
1697
        foreach ($html->find('.tabs') as $elm) {
1698
            $elm->class = 'nav nav-tabs';
1699
        }
1700
1701
        # Tabs (active)
1702
        foreach ($html->find('.nav-tabs strong') as $elm) {
1703
            $elm->outertext = '<a href="#">' . $elm->innertext . "</a>";
1704
            $parent         = $elm->parent()->class .= ' active';
1705
        }
1706
1707
        # Page Heading (h1-h2)
1708
        # TODO this class will be removed in Bootstrap >= 4.0 version
1709
        foreach ($html->find('h1,h2,h3') as $elm) {
1710
            $elm->class .= ' page-header pb-3 mb-4 mt-5'; # TODO replace page-header with border-bottom in BS4
1711
        }
1712
1713
        # Media Images
1714
        foreach ($html->find('img[class^=media]') as $elm) {
1715
            $elm->class .= ' img-responsive';
1716
        }
1717
1718
        # Checkbox
1719
        foreach ($html->find('input[type=checkbox]') as $elm) {
1720
            $elm->class .= ' checkbox-inline';
1721
        }
1722
1723
        # Radio button
1724
        foreach ($html->find('input[type=radio]') as $elm) {
1725
            $elm->class .= ' radio-inline';
1726
        }
1727
1728
        # Label
1729
        foreach ($html->find('label') as $elm) {
1730
            $elm->class .= ' control-label';
1731
        }
1732
1733
        # Form controls
1734
        foreach ($html->find('input, select, textarea') as $elm) {
1735
            if (in_array($elm->type, array('submit', 'reset', 'button', 'hidden', 'image', 'checkbox', 'radio'))) {
1736
                continue;
1737
            }
1738
            $elm->class .= ' form-control';
1739
        }
1740
1741
        # Forms
1742
        # TODO main form
1743
        foreach ($html->find('form') as $elm) {
1744
            if (preg_match('/form-horizontal/', $elm->class)) {
1745
                continue;
1746
            }
1747
            $elm->class .= ' form-inline';
1748
        }
1749
1750
        # Alerts
1751
        foreach ($html->find('div.info, div.error, div.success, div.notify') as $elm) {
1752
            switch ($elm->class) {
1753
                case 'info':
1754
                    $elm->class     = 'alert alert-info';
1755
                    $elm->innertext = iconify('mdi:information') . ' ' . $elm->innertext;
1756
                    break;
1757
1758
                case 'error':
1759
                    $elm->class     = 'alert alert-danger';
1760
                    $elm->innertext = iconify('mdi:alert-octagon') . ' ' . $elm->innertext;
1761
                    break;
1762
1763
                case 'success':
1764
                    $elm->class     = 'alert alert-success';
1765
                    $elm->innertext = iconify('mdi:check-circle') . ' ' . $elm->innertext;
1766
                    break;
1767
1768
                case 'notify':
1769
                case 'msg notify':
1770
                    $elm->class     = 'alert alert-warning';
1771
                    $elm->innertext = iconify('mdi:alert') . ' ' . $elm->innertext;
1772
                    break;
1773
            }
1774
        }
1775
1776
        # Tables
1777
1778
        $table_classes = 'table';
1779
1780
        foreach ($this->getConf('tableStyle') as $class) {
1781
            if ($class == 'responsive') {
1782
                foreach ($html->find('div.table') as $elm) {
1783
                    $elm->class = 'table-responsive';
1784
                }
1785 1
            } else {
1786
                $table_classes .= " table-$class";
1787
            }
1788
        }
1789
1790
        foreach ($html->find('table.inline,table.import_failures') as $elm) {
1791
            $elm->class .= " $table_classes";
1792
        }
1793
1794
        foreach ($html->find('div.table') as $elm) {
1795
            $elm->class = trim(str_replace('table', '', $elm->class));
1796
        }
1797
1798
        # Tag and Pagelist (table)
1799
1800
        if ($this->getPlugin('tag') || $this->getPlugin('pagelist')) {
1801
            foreach ($html->find('table.ul') as $elm) {
1802
                $elm->class .= " $table_classes";
1803
            }
1804
        }
1805
1806
        $content = $html->save();
1807
1808
        $html->clear();
1809
        unset($html);
1810
1811
        # ----- Actions -----
1812
1813
        # Search
1814
1815
        if ($ACT == 'search') {
1816
            # Import HTML string
1817
            $html = new \simple_html_dom;
1818
            $html->load($content, true, false);
1819
1820
            foreach ($html->find('fieldset.search-form button[type="submit"]') as $elm) {
1821
                $elm->class .= ' btn-primary';
1822
                $elm->innertext = iconify('mdi:magnify', array('class' => 'mr-2')) . $elm->innertext;
1823
            }
1824
1825
            $content = $html->save();
1826
1827
            $html->clear();
1828
            unset($html);
1829
        }
1830
1831
        # Index / Sitemap
1832
1833
        if ($ACT == 'index') {
1834
            # Import HTML string
1835
            $html = new \simple_html_dom;
1836
            $html->load($content, true, false);
1837
1838
            foreach ($html->find('.idx_dir') as $idx => $elm) {
1839
                $parent = $elm->parent()->parent();
1840
1841
                if (preg_match('/open/', $parent->class)) {
1842
                    $elm->innertext = iconify('mdi:folder-open', array('class' => 'text-primary mr-2')) . $elm->innertext;
1843
                }
1844
1845
                if (preg_match('/closed/', $parent->class)) {
1846
                    $elm->innertext = iconify('mdi:folder', array('class' => 'text-primary mr-2')) . $elm->innertext;
1847
                }
1848
            }
1849
1850
            foreach ($html->find('.idx .wikilink1') as $elm) {
1851
                $elm->innertext = iconify('mdi:file-document-outline', array('class' => 'text-muted mr-2')) . $elm->innertext;
1852
            }
1853
1854
            $content = $html->save();
1855
1856
            $html->clear();
1857
            unset($html);
1858
        }
1859
1860
        # Admin Pages
1861
1862
        if ($ACT == 'admin') {
1863
            # Import HTML string
1864
            $html = new \simple_html_dom;
1865
            $html->load($content, true, false);
1866
1867
            // Set specific icon in Admin Page
1868
            if ($INPUT->str('page')) {
1869
                if ($admin_pagetitle = $html->find('h1.page-header', 0)) {
1870
                    $admin_pagetitle->class .= ' ' . hsc($INPUT->str('page'));
1871
                }
1872
            }
1873
1874
            # ACL
1875
1876
            if ($INPUT->str('page') == 'acl') {
1877
                foreach ($html->find('[name*=cmd[update]]') as $elm) {
1878
                    $elm->class .= ' btn-success';
1879
                    if ($elm->tag == 'button') {
1880
                        $elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
1881
                    }
1882
                }
1883
            }
1884
1885
            # Popularity
1886
1887
            if ($INPUT->str('page') == 'popularity') {
1888
                foreach ($html->find('[type=submit]') as $elm) {
1889
                    $elm->class .= ' btn-primary';
1890
1891
                    if ($elm->tag == 'button') {
1892
                        $elm->innertext = iconify('mdi:arrow-right') . ' ' . $elm->innertext;
1893
                    }
1894
                }
1895
            }
1896
1897
            # Revert Manager
1898
1899
            if ($INPUT->str('page') == 'revert') {
1900
                foreach ($html->find('[type=submit]') as $idx => $elm) {
1901
                    if ($idx == 0) {
1902
                        $elm->class .= ' btn-primary';
1903
                        if ($elm->tag == 'button') {
1904
                            $elm->innertext = iconify('mdi:magnify') . ' ' . $elm->innertext;
1905
                        }
1906
                    }
1907
1908
                    if ($idx == 1) {
1909
                        $elm->class .= ' btn-success';
1910
                        if ($elm->tag == 'button') {
1911
                            $elm->innertext = iconify('mdi:refresh') . ' ' . $elm->innertext;
1912
                        }
1913
                    }
1914
                }
1915
            }
1916
1917
            # Config
1918
1919
            if ($INPUT->str('page') == 'config') {
1920
                foreach ($html->find('[type=submit]') as $elm) {
1921
                    $elm->class .= ' btn-success';
1922
                    if ($elm->tag == 'button') {
1923
                        $elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
1924
                    }
1925
                }
1926
1927
                foreach ($html->find('#config__manager') as $cm_elm) {
1928
                    $save_button = '';
1929
1930
                    foreach ($cm_elm->find('p') as $elm) {
1931
                        $save_button    = '<div class="pull-right">' . $elm->outertext . '</div>';
1932
                        $elm->outertext = '</div>' . $elm->outertext;
1933
                    }
1934
1935
                    foreach ($cm_elm->find('fieldset') as $elm) {
1936
                        $elm->innertext .= $save_button;
1937
                    }
1938
                }
1939
            }
1940
1941
            # User Manager
1942
1943
            if ($INPUT->str('page') == 'usermanager') {
1944
                foreach ($html->find('.notes') as $elm) {
1945
                    $elm->class = str_replace('notes', '', $elm->class);
1946
                }
1947
1948
                foreach ($html->find('h2') as $idx => $elm) {
1949
                    switch ($idx) {
1950
                        case 0:
1951
                            $elm->innertext = iconify('mdi:account-multiple') . ' ' . $elm->innertext;
1952
                            break;
1953
                        case 1:
1954
                            $elm->innertext = iconify('mdi:account-plus') . ' ' . $elm->innertext;
1955
                            break;
1956
                        case 2:
1957
                            $elm->innertext = iconify('mdi:account-edit') . ' ' . $elm->innertext;
1958
                            break;
1959
                    }
1960
                }
1961
1962
                foreach ($html->find('.import_users h2') as $elm) {
1963
                    $elm->innertext = iconify('mdi:account-multiple-plus') . ' ' . $elm->innertext;
1964
                }
1965
1966
                foreach ($html->find('button[name*=fn[delete]]') as $elm) {
1967
                    $elm->class .= ' btn btn-danger';
1968
                    $elm->innertext = iconify('mdi:account-minus') . ' ' . $elm->innertext;
1969
                }
1970
1971
                foreach ($html->find('button[name*=fn[add]]') as $elm) {
1972
                    $elm->class .= ' btn btn-success';
1973
                    $elm->innertext = iconify('mdi:plus') . ' ' . $elm->innertext;
1974
                }
1975
1976
                foreach ($html->find('button[name*=fn[modify]]') as $elm) {
1977
                    $elm->class .= ' btn btn-success';
1978
                    $elm->innertext = iconify('mdi:content-save') . ' ' . $elm->innertext;
1979
                }
1980
1981
                foreach ($html->find('button[name*=fn[import]]') as $elm) {
1982
                    $elm->class .= ' btn btn-primary';
1983
                    $elm->innertext = iconify('mdi:upload') . ' ' . $elm->innertext;
1984
                }
1985
1986
                foreach ($html->find('button[name*=fn[export]]') as $elm) {
1987
                    $elm->class .= ' btn btn-primary';
1988
                    $elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
1989
                }
1990
1991
                foreach ($html->find('button[name*=fn[start]]') as $elm) {
1992
                    $elm->class .= ' btn btn-default';
1993
                    $elm->innertext = iconify('mdi:chevron-double-left') . ' ' . $elm->innertext;
1994
                }
1995
1996
                foreach ($html->find('button[name*=fn[prev]]') as $elm) {
1997
                    $elm->class .= ' btn btn-default';
1998
                    $elm->innertext = iconify('mdi:chevron-left') . ' ' . $elm->innertext;
1999
                }
2000
2001
                foreach ($html->find('button[name*=fn[next]]') as $elm) {
2002
                    $elm->class .= ' btn btn-default';
2003
                    $elm->innertext = iconify('mdi:chevron-right') . ' ' . $elm->innertext;
2004
                }
2005
2006
                foreach ($html->find('button[name*=fn[last]]') as $elm) {
2007
                    $elm->class .= ' btn btn-default';
2008
                    $elm->innertext = iconify('mdi:chevron-double-right') . ' ' . $elm->innertext;
2009
                }
2010
            }
2011
2012
            # Extension Manager
2013
2014
            if ($INPUT->str('page') == 'extension') {
2015
                foreach ($html->find('.actions') as $elm) {
2016
                    $elm->class .= ' pl-4 btn-group btn-group-xs';
2017
                }
2018
2019
                foreach ($html->find('.actions .uninstall') as $elm) {
2020
                    $elm->class .= ' btn-danger';
2021
                    $elm->innertext = iconify('mdi:delete') . ' ' . $elm->innertext;
2022
                }
2023
2024
                foreach ($html->find('.actions .enable') as $elm) {
2025
                    $elm->class .= ' btn-success';
2026
                    $elm->innertext = iconify('mdi:check') . ' ' . $elm->innertext;
2027
                }
2028
2029
                foreach ($html->find('.actions .disable') as $elm) {
2030
                    $elm->class .= ' btn-warning';
2031
                    $elm->innertext = iconify('mdi:block-helper') . ' ' . $elm->innertext;
2032
                }
2033
2034
                foreach ($html->find('.actions .install, .actions .update, .actions .reinstall') as $elm) {
2035
                    $elm->class .= ' btn-primary';
2036
                    $elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
2037
                }
2038
2039
                foreach ($html->find('form.install [type=submit]') as $elm) {
2040
                    $elm->class .= ' btn btn-success';
2041
                    $elm->innertext = iconify('mdi:download') . ' ' . $elm->innertext;
2042
                }
2043
2044
                foreach ($html->find('form.search [type=submit]') as $elm) {
2045
                    $elm->class .= ' btn btn-primary';
2046
                    $elm->innertext = iconify('mdi:cloud-search') . ' ' . $elm->innertext;
2047
                }
2048
2049
                foreach ($html->find('.permerror') as $elm) {
2050
                    $elm->class .= ' pull-left';
2051
                }
2052
            }
2053
2054
            # Admin page
2055
            if ($INPUT->str('page') == null) {
2056
                foreach ($html->find('ul.admin_tasks, ul.admin_plugins') as $admin_task) {
2057
                    $admin_task->class .= ' list-group';
2058
2059
                    foreach ($admin_task->find('a') as $item) {
2060
                        $item->class .= ' list-group-item';
2061
                        $item->style = 'max-height: 50px'; # TODO remove
2062
                    }
2063
2064
                    foreach ($admin_task->find('.icon') as $item) {
2065
                        if ($item->innertext) {
2066
                            continue;
2067
                        }
2068
2069
                        $item->innertext = iconify('mdi:puzzle', array('class' => 'text-success'));
2070
                    }
2071
                }
2072
2073
                foreach ($html->find('h2') as $elm) {
2074
                    $elm->innertext = iconify('mdi:puzzle', array('class' => 'text-success')) . ' ' . $elm->innertext;
2075
                }
2076
2077
                foreach ($html->find('ul.admin_plugins') as $admin_plugins) {
2078
                    $admin_plugins->class .= ' col-sm-4';
2079
                    foreach ($admin_plugins->find('li') as $idx => $item) {
2080
                        if ($idx > 0 && $idx % 5 == 0) {
2081
                            $item->outertext = '</ul><ul class="' . $admin_plugins->class . '">' . $item->outertext;
2082
                        }
2083
                    }
2084
                }
2085
2086
                # DokuWiki logo
2087
                if ($admin_version = $html->getElementById('admin__version')) {
2088
                    $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>';
2089
2090
                    $template_version = $this->getVersion();
2091
2092
                    $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>';
2093
                }
2094
            }
2095
2096
            $content = $html->save();
2097
2098
            $html->clear();
2099
            unset($html);
2100
2101
            # Configuration Manager Template Sections
2102
            if ($INPUT->str('page') == 'config') {
2103
                # Import HTML string
2104
                $html = new \simple_html_dom;
2105
                $html->load($content, true, false);
2106
2107
                foreach ($html->find('fieldset[id^="plugin__"]') as $elm) {
2108
2109
                    /** @var array $matches */
2110
                    preg_match('/plugin_+(\w+[^_])_+plugin_settings_name/', $elm->id, $matches);
2111
2112
                    $plugin_name = $matches[1];
2113
2114
                    if ($extension = plugin_load('helper', 'extension_extension')) {
2115
                        if ($extension->setExtension($plugin_name)) {
2116
                            foreach ($elm->find('legend') as $legend) {
2117
                                $legend->innertext = iconify('mdi:puzzle', array('class' => 'text-success')) . ' ' . $legend->innertext . ' <br/><h6>' . $extension->getDescription() . ' <a class="urlextern" href="' . $extension->getURL() . '" target="_blank">Docs</a></h6>';
2118
                            }
2119
                        }
2120 1
                    } else {
2121
                        foreach ($elm->find('legend') as $legend) {
2122
                            $legend->innertext = iconify('mdi:puzzle', array('class' => 'text-success')) . ' ' . $legend->innertext;
2123
                        }
2124
                    }
2125
                }
2126
2127
                $dokuwiki_configs = array(
2128
                    '#_basic'          => 'mdi:settings',
2129
                    '#_display'        => 'mdi:monitor',
2130
                    '#_authentication' => 'mdi:shield-account',
2131
                    '#_anti_spam'      => 'mdi:block-helper',
2132
                    '#_editing'        => 'mdi:pencil',
2133
                    '#_links'          => 'mdi:link-variant',
2134
                    '#_media'          => 'mdi:folder-image',
2135
                    '#_notifications'  => 'mdi:email',
2136
                    '#_syndication'    => 'mdi:rss',
2137
                    '#_advanced'       => 'mdi:palette-advanced',
2138
                    '#_network'        => 'mdi:network',
2139
                );
2140
2141
                foreach ($dokuwiki_configs as $selector => $icon) {
2142
                    foreach ($html->find("$selector legend") as $elm) {
2143
                        $elm->innertext = iconify($icon) . ' ' . $elm->innertext;
2144
                    }
2145
                }
2146
2147
                $content = $html->save();
2148
2149
                $html->clear();
2150
                unset($html);
2151
2152
                $admin_sections = array(
2153
                    // Section                  Insert Before           Icon
2154
                    'theme'            => array('bootstrapTheme', 'mdi:palette'),
2155
                    'sidebar'          => array('sidebarPosition', 'mdi:page-layout-sidebar-left'),
2156
                    'navbar'           => array('inverseNavbar', 'mdi:page-layout-header'),
2157
                    'semantic'         => array('semantic', 'mdi:share-variant'),
2158
                    'layout'           => array('fluidContainer', 'mdi:monitor'),
2159
                    'toc'              => array('tocAffix', 'mdi:view-list'),
2160
                    'discussion'       => array('showDiscussion', 'mdi:comment-text-multiple'),
2161
                    'avatar'           => array('useAvatar', 'mdi:account'),
2162
                    'cookie_law'       => array('showCookieLawBanner', 'mdi:scale-balance'),
2163
                    'google_analytics' => array('useGoogleAnalytics', 'mdi:google'),
2164
                    'browser_title'    => array('browserTitle', 'mdi:format-title'),
2165
                    'page'             => array('showPageInfo', 'mdi:file'),
2166
                );
2167
2168
                foreach ($admin_sections as $section => $items) {
2169
                    $search = $items[0];
2170
                    $icon   = $items[1];
2171
2172
                    $content = preg_replace(
2173
                        '/<span class="outkey">(tpl»bootstrap3»' . $search . ')<\/span>/',
2174
                        '<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>',
2175
                        $content
2176
                    );
2177
                }
2178
            }
2179
        }
2180
2181
        # Difference and Draft
2182
2183
        if ($ACT == 'diff' || $ACT == 'draft') {
2184
            # Import HTML string
2185
            $html = new \simple_html_dom;
2186
            $html->load($content, true, false);
2187
2188
            foreach ($html->find('.diff-lineheader') as $elm) {
2189
                $elm->style = 'opacity: 0.5';
2190
                $elm->class .= ' text-center px-3';
2191
2192
                if ($elm->innertext == '+') {
2193
                    $elm->class .= ' bg-success';
2194
                }
2195
                if ($elm->innertext == '-') {
2196
                    $elm->class .= ' bg-danger';
2197
                }
2198
            }
2199
2200
            foreach ($html->find('.diff_sidebyside .diff-deletedline, .diff_sidebyside .diff-addedline') as $elm) {
2201
                $elm->class .= ' w-50';
2202
            }
2203
2204
            foreach ($html->find('.diff-deletedline') as $elm) {
2205
                $elm->class .= ' bg-danger';
2206
            }
2207
2208
            foreach ($html->find('.diff-addedline') as $elm) {
2209
                $elm->class .= ' bg-success';
2210
            }
2211
2212
            foreach ($html->find('.diffprevrev') as $elm) {
2213
                $elm->class .= ' btn btn-default';
2214
                $elm->innertext = iconify('mdi:chevron-left') . ' ' . $elm->innertext;
2215
            }
2216
2217
            foreach ($html->find('.diffnextrev') as $elm) {
2218
                $elm->class .= ' btn btn-default';
2219
                $elm->innertext = iconify('mdi:chevron-right') . ' ' . $elm->innertext;
2220
            }
2221
2222
            foreach ($html->find('.diffbothprevrev') as $elm) {
2223
                $elm->class .= ' btn btn-default';
2224
                $elm->innertext = iconify('mdi:chevron-double-left') . ' ' . $elm->innertext;
2225
            }
2226
2227
            foreach ($html->find('.minor') as $elm) {
2228
                $elm->class .= ' text-muted';
2229
            }
2230
2231
            $content = $html->save();
2232
2233
            $html->clear();
2234
            unset($html);
2235
        }
2236
2237
        # Add icons for Extensions, Actions, etc.
2238
2239
        $svg_icon      = null;
2240
        $iconify_icon  = null;
2241
        $iconify_attrs = array('class' => 'mr-2');
2242
2243
        if (!$INFO['exists'] && $ACT == 'show') {
2244
            $iconify_icon           = 'mdi:alert';
2245
            $iconify_attrs['style'] = 'color:orange';
2246
        }
2247
2248
        $menu_class = "\\dokuwiki\\Menu\\Item\\$ACT";
2249
2250
        if (class_exists($menu_class, false)) {
2251
            $menu_item = new $menu_class;
2252
            $svg_icon  = $menu_item->getSvg();
2253
        }
2254
2255
        switch ($ACT) {
2256
            case 'admin':
2257
2258
                if (($plugin = plugin_load('admin', $INPUT->str('page'))) !== null) {
2259
                    if (method_exists($plugin, 'getMenuIcon')) {
2260
                        $svg_icon = $plugin->getMenuIcon();
2261
2262
                        if (!file_exists($svg_icon)) {
2263
                            $iconify_icon = 'mdi:puzzle';
2264
                            $svg_icon     = null;
2265
                        }
2266 1
                    } else {
2267
                        $iconify_icon = 'mdi:puzzle';
2268
                        $svg_icon     = null;
2269
                    }
2270
                }
2271
2272
                break;
2273
2274
            case 'resendpwd':
2275
                $iconify_icon = 'mdi:lock-reset';
2276
                break;
2277
2278
            case 'denied':
2279
                $iconify_icon           = 'mdi:block-helper';
2280
                $iconify_attrs['style'] = 'color:red';
2281
                break;
2282
2283
            case 'search':
2284
                $iconify_icon = 'mdi:search-web';
2285
                break;
2286
2287
            case 'preview':
2288
                $iconify_icon = 'mdi:file-eye';
2289
                break;
2290
2291
            case 'diff':
2292
                $iconify_icon = 'mdi:file-compare';
2293
                break;
2294
2295
            case 'showtag':
2296
                $iconify_icon = 'mdi:tag-multiple';
2297
                break;
2298
2299
            case 'draft':
2300
                $iconify_icon = 'mdi:android-studio';
2301
                break;
2302
2303
        }
2304
2305
        if ($svg_icon) {
2306
            $svg_attrs = array('class' => 'iconify mr-2');
2307
2308
            if ($ACT == 'admin' && $INPUT->str('page') == 'extension') {
2309
                $svg_attrs['style'] = 'fill: green;';
2310
            }
2311
2312 1
            $svg = SVG::icon($svg_icon, null, '1em', $svg_attrs);
2313
2314
            # Import HTML string
2315
            $html = new \simple_html_dom;
2316
            $html->load($content, true, false);
2317
2318
            foreach ($html->find('h1') as $elm) {
2319
                $elm->innertext = $svg . ' ' . $elm->innertext;
2320
                break;
2321
            }
2322
2323
            $content = $html->save();
2324
2325
            $html->clear();
2326
            unset($html);
2327
        }
2328
2329
        if ($iconify_icon) {
2330
            # Import HTML string
2331
            $html = new \simple_html_dom;
2332
            $html->load($content, true, false);
2333
2334
            foreach ($html->find('h1') as $elm) {
2335
                $elm->innertext = iconify($iconify_icon, $iconify_attrs) . $elm->innertext;
2336
                break;
2337
            }
2338
2339
            $content = $html->save();
2340
2341
            $html->clear();
2342
            unset($html);
2343
        }
2344
2345
        return $content;
2346
    }
2347
2348
    /**
2349
     * Detect the fluid navbar flag
2350
     *
2351
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
2352
     * @return boolean
2353
     */
2354
    public function isFluidNavbar()
2355
    {
2356
        $fluid_container  = $this->getConf('fluidContainer');
2357
        $fixed_top_nabvar = $this->getConf('fixedTopNavbar');
2358
2359
        return ($fluid_container || ($fluid_container && !$fixed_top_nabvar) || (!$fluid_container && !$fixed_top_nabvar));
2360
    }
2361
2362
    /**
2363
     * Calculate automatically the grid size for main container
2364
     *
2365
     * @author  Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
2366
     *
2367
     * @return  string
2368
     */
2369
    public function getContainerGrid()
2370
    {
2371
        global $ID;
2372
2373
        $result = '';
2374
2375
        $grids = array(
2376
            'sm' => array('left' => 0, 'right' => 0),
2377
            'md' => array('left' => 0, 'right' => 0),
2378
        );
2379
2380
        $show_right_sidebar = $this->getConf('showRightSidebar');
2381
        $show_left_sidebar  = $this->getConf('showSidebar');
2382 1
        $fluid_container    = $this->getConf('fluidContainer');
2383
2384
        if ($this->getConf('showLandingPage') && (bool) preg_match($this->getConf('landingPages'), $ID)) {
2385
            $show_left_sidebar = false;
2386
        }
2387
2388
        if ($show_left_sidebar) {
2389
            foreach (explode(' ', $this->getConf('leftSidebarGrid')) as $grid) {
2390
                list($col, $media, $size) = explode('-', $grid);
2391
                $grids[$media]['left']    = (int) $size;
2392
            }
2393
        }
2394
2395
        if ($show_right_sidebar) {
2396
            foreach (explode(' ', $this->getConf('rightSidebarGrid')) as $grid) {
2397
                list($col, $media, $size) = explode('-', $grid);
2398
                $grids[$media]['right']   = (int) $size;
2399
            }
2400
        }
2401
2402
        foreach ($grids as $media => $position) {
2403
            $left  = $position['left'];
2404
            $right = $position['right'];
2405
            $result .= sprintf('col-%s-%s ', $media, (12 - $left - $right));
2406
        }
2407
2408
        return $result;
2409
    }
2410
2411
    /**
2412
     * Places the TOC where the function is called
2413
     *
2414
     * If you use this you most probably want to call tpl_content with
2415
     * a false argument
2416
     *
2417
     * @author Andreas Gohr <andi@splitbrain.org>
2418
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
2419
     *
2420
     * @param bool $return Should the TOC be returned instead to be printed?
2421
     * @return string
2422
     */
2423 3
    public function getTOC($return = false)
2424
    {
2425
        global $TOC;
2426
        global $ACT;
2427
        global $ID;
2428
        global $REV;
2429
        global $INFO;
2430
        global $conf;
2431
        global $INPUT;
2432
2433
        $toc = array();
2434
2435
        if (is_array($TOC)) {
2436
            // if a TOC was prepared in global scope, always use it
2437
            $toc = $TOC;
2438
        } elseif (($ACT == 'show' || substr($ACT, 0, 6) == 'export') && !$REV && $INFO['exists']) {
2439
            // get TOC from metadata, render if neccessary
2440
            $meta = p_get_metadata($ID, '', METADATA_RENDER_USING_CACHE);
2441
            if (isset($meta['internal']['toc'])) {
2442
                $tocok = $meta['internal']['toc'];
2443
            } else {
2444
                $tocok = true;
2445
            }
2446
            $toc = isset($meta['description']['tableofcontents']) ? $meta['description']['tableofcontents'] : null;
2447
            if (!$tocok || !is_array($toc) || !$conf['tocminheads'] || count($toc) < $conf['tocminheads']) {
2448
                $toc = array();
2449
            }
2450
        } elseif ($ACT == 'admin') {
2451
            // try to load admin plugin TOC
2452
            /** @var $plugin DokuWiki_Admin_Plugin */
2453
            if ($plugin = plugin_getRequestAdminPlugin()) {
2454
                $toc = $plugin->getTOC();
2455
                $TOC = $toc; // avoid later rebuild
2456
            }
2457
        }
2458
2459
        $toc_check = end($toc);
2460
        $toc_undefined = null;
2461
2462
        if (!preg_match('/bootstrap/', $toc_check['link'])) {
2463
            $toc_undefined = array_pop($toc);
2464
        }
2465
2466
2467
        trigger_event('TPL_TOC_RENDER', $toc, null, false);
2468
2469
        if ($ACT == 'admin' && $INPUT->str('page') == 'config') {
2470
            $bootstrap3_sections = array(
2471
                'theme', 'sidebar', 'navbar', 'semantic', 'layout', 'toc',
2472
                'discussion', 'avatar', 'cookie_law', 'google_analytics',
2473
                'browser_title', 'page',
2474
            );
2475
2476
            foreach ($bootstrap3_sections as $id) {
2477
                $toc[] = array(
2478
                    'link'  => "#bootstrap3__$id",
2479
                    'title' => tpl_getLang("config_$id"),
2480
                    'type'  => 'ul',
2481
                    'level' => 3,
2482
                );
2483
            }
2484
        }
2485
2486
        if ($toc_undefined) {
2487
            $toc[] = $toc_undefined;
2488
        }
2489
2490
        $html = $this->renderTOC($toc);
2491
2492
        if ($return) {
2493
            return $html;
2494
        }
2495
2496
        echo $html;
2497
        return '';
2498
    }
2499
2500
    /**
2501
     * Return the TOC rendered to XHTML with Bootstrap3 style
2502
     *
2503
     * @author Andreas Gohr <andi@splitbrain.org>
2504
     * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com>
2505
     *
2506
     * @param array $toc
2507
     * @return string html
2508
     */
2509
    private function renderTOC($toc)
2510
    {
2511
        if (!count($toc)) {
2512
            return '';
2513
        }
2514
2515
        global $lang;
2516
2517
        $json_toc = array();
2518
2519
        foreach ($toc as $item) {
2520
            $json_toc[] = array(
2521
                'link'  => (isset($item['link']) ? $item['link'] : '#' . $item['hid']),
2522
                'title' => $item['title'],
2523
                'level' => $item['level'],
2524
            );
2525
        }
2526
2527
        $out = '';
2528
        $out .= '<script>JSINFO.bootstrap3.toc = ' . json_encode($json_toc) . ';</script>' . DOKU_LF;
2529
2530
        if ($this->getConf('tocLayout') !== 'navbar') {
2531
            $out .= '<!-- TOC START -->' . DOKU_LF;
2532
            $out .= '<div class="dw-toc hidden-print">' . DOKU_LF;
2533
            $out .= '<nav id="dw__toc" role="navigation" class="toc-panel panel panel-default small">' . DOKU_LF;
2534
            $out .= '<h6 data-toggle="collapse" data-target="#dw__toc .toc-body" title="' . $lang['toc'] . '" class="panel-heading toc-title">' . iconify('mdi:view-list') . ' ';
2535
            $out .= '<span>' . $lang['toc'] . '</span>';
2536
            $out .= ' <i class="caret"></i></h6>' . DOKU_LF;
2537
            $out .= '<div class="panel-body  toc-body collapse ' . (!$this->getConf('tocCollapsed') ? 'in' : '') . '">' . DOKU_LF;
2538
            $out .= $this->normalizeList(html_buildlist($toc, 'nav toc', 'html_list_toc', 'html_li_default', true)) . DOKU_LF;
2539
            $out .= '</div>' . DOKU_LF;
2540
            $out .= '</nav>' . DOKU_LF;
2541
            $out .= '</div>' . DOKU_LF;
2542
            $out .= '<!-- TOC END -->' . DOKU_LF;
2543
        }
2544
2545
        return $out;
2546
    }
2547
2548
    private function initToolsMenu()
2549
    {
2550
        global $ACT;
2551
2552
        $tools_menus = array(
2553
            'user' => array('icon' => 'mdi:account', 'object' => new \dokuwiki\Menu\UserMenu),
2554
            'site' => array('icon' => 'mdi:toolbox', 'object' => new \dokuwiki\Menu\SiteMenu),
2555
            'page' => array('icon' => 'mdi:file-document-outline', 'object' => new \dokuwiki\template\bootstrap3\Menu\PageMenu),
2556
        );
2557
2558
        if (defined('DOKU_MEDIADETAIL')) {
2559
            $tools_menus['page'] = array('icon' => 'mdi:image', 'object' => new \dokuwiki\template\bootstrap3\Menu\DetailMenu);
2560
        }
2561
2562
        foreach ($tools_menus as $tool => $data) {
2563
            foreach ($data['object']->getItems() as $item) {
2564
                $attr   = buildAttributes($item->getLinkAttributes());
2565
                $active = 'action';
2566
2567
                if ($ACT == $item->getType() || ($ACT == 'revisions' && $item->getType() == 'revs') || ($ACT == 'diff' && $item->getType() == 'revs')) {
2568
                    $active .= ' active';
2569
                }
2570
2571
                if ($item->getType() == 'shareon') {
2572
                    $active .= ' dropdown';
2573
                }
2574
2575
                $html = '<li class="' . $active . '">';
2576
                $html .= "<a $attr>";
2577
                $html .= \inlineSVG($item->getSvg());
2578
                $html .= '<span>' . hsc($item->getLabel()) . '</span>';
2579
                $html .= "</a>";
2580
2581
                if ($item->getType() == 'shareon') {
2582
                    $html .= $item->getDropDownMenu();
2583
                }
2584
2585
                $html .= '</li>';
2586
2587
                $tools_menus[$tool]['menu'][$item->getType()]['object'] = $item;
2588
                $tools_menus[$tool]['menu'][$item->getType()]['html']   = $html;
2589
            }
2590
        }
2591
2592
        $this->toolsMenu = $tools_menus;
2593
    }
2594
2595
    public function getToolsMenu()
2596
    {
2597
        return $this->toolsMenu;
2598
    }
2599
2600
    public function getToolMenu($tool)
2601
    {
2602
        return $this->toolsMenu[$tool];
2603
    }
2604
2605
    public function getToolMenuItem($tool, $item)
2606
    {
2607
        if (isset($this->toolsMenu[$tool]) && isset($this->toolsMenu[$tool]['menu'][$item])) {
2608
            return $this->toolsMenu[$tool]['menu'][$item]['object'];
2609
        }
2610
        return null;
2611
    }
2612
2613
    public function getToolMenuItemLink($tool, $item)
2614
    {
2615
        if (isset($this->toolsMenu[$tool]) && isset($this->toolsMenu[$tool]['menu'][$item])) {
2616
            return $this->toolsMenu[$tool]['menu'][$item]['html'];
2617
        }
2618
        return null;
2619
    }
2620
2621
    public function getNavbarHeight()
2622
    {
2623
        switch ($this->getBootswatchTheme()) {
2624
            case 'simplex':
2625
            case 'superhero':
2626
                return 40;
2627
2628
            case 'yeti':
2629
                return 45;
2630
2631
            case 'cerulean':
2632
            case 'cosmo':
2633
            case 'custom':
2634
            case 'cyborg':
2635
            case 'lumen':
2636
            case 'slate':
2637
            case 'spacelab':
2638
            case 'solar':
2639
            case 'united':
2640
                return 50;
2641
2642
            case 'darkly':
2643
            case 'flatly':
2644
            case 'journal':
2645
            case 'sandstone':
2646
                return 60;
2647
2648
            case 'paper':
2649
                return 64;
2650
2651
            case 'readable':
2652
                return 65;
2653
2654
            default:
2655
                return 50;
2656
        }
2657
    }
2658
}