1<?php
2
3namespace dokuwiki\template\sprintdoc;
4
5/**
6 * Class Template
7 *
8 * provides additional logic for the sprintdoc template
9 *
10 * @package dokuwiki\template\sprintdoc
11 */
12class Template {
13
14    /** @var array loaded plugins */
15    protected $plugins = array(
16        'sqlite' => null,
17        'tagging' => null,
18        'magicmatcher' => null,
19        'tplinc' => null,
20        'sitemapnavi' => null,
21    );
22
23    /** @var string the type of special navigation to use */
24    protected $nav = '';
25
26
27    /**
28     * Get the singleton instance
29     *
30     * @return Template
31     */
32    public static function getInstance() {
33        static $instance = null;
34        if($instance === null) $instance = new Template();
35        return $instance;
36    }
37
38    /**
39     * Template constructor.
40     */
41    protected function __construct() {
42        $this->initializePlugins();
43        $this->initNavigationCookie();
44
45        /** @var \Doku_Event_Handler */
46        global $EVENT_HANDLER;
47        $EVENT_HANDLER->register_hook('PLUGIN_TPLINC_LOCATIONS_SET', 'BEFORE', $this, 'registerIncludes');
48    }
49
50    /**
51     * Load all the plugins we support directly
52     */
53    protected function initializePlugins() {
54        $this->plugins['sqlite'] = plugin_load('helper', 'sqlite');
55        if($this->plugins['sqlite']) {
56            $this->plugins['tagging'] = plugin_load('helper', 'tagging');
57            $this->plugins['magicmatcher'] = plugin_load('syntax', 'magicmatcher_issuelist');
58        }
59        $this->plugins['tplinc'] = plugin_load('helper', 'tplinc');
60        $this->plugins['sitemapnavi'] = plugin_load('helper', 'sitemapnavi');
61    }
62
63    /**
64     * Makes include position info available to the tplinc plugin
65     *
66     * @param \Doku_Event $event
67     */
68    public function registerIncludes(\Doku_Event $event) {
69        $event->data['footer'] = 'Footer below the page content';
70        $event->data['sidebarfooter'] = 'Footer below the sidebar';
71        $event->data['sidebarheader'] = 'Header above the sidebar';
72        $event->data['navtop'] = 'Additional navigation items at the top';
73        $event->data['navbottom'] = 'Additional navigation items at the bottom';
74    }
75
76    /**
77     * Get the content to include from the tplinc plugin
78     *
79     * prefix and postfix are only added when there actually is any content
80     *
81     * @param string $location
82     * @param string $pre prepend this before the content
83     * @param string $post append this to the content
84     * @return string
85     */
86    public function getInclude($location, $pre = '', $post = '') {
87        if(!$this->plugins['tplinc']) return '';
88        $content = $this->plugins['tplinc']->renderIncludes($location);
89        if($content === '') return '';
90        return $pre . $content . $post;
91    }
92
93    /**
94     * Sets a cookie to remember the requested special navigation
95     */
96    protected function initNavigationCookie() {
97        if ($this->plugins['sitemapnavi'] === null) return;
98        global $INPUT;
99
100        $nav = $INPUT->str('nav');
101        if($nav) {
102            set_doku_pref('nav', $nav);
103            $this->nav = $INPUT->str('nav');
104        } else {
105            $this->nav = get_doku_pref('nav', 'sidebar');
106        }
107    }
108
109    /**
110     * Return the navigation for the sidebar
111     *
112     * Defaults to the standard sidebar mechanism, but supports also the sitemapnavi plugin
113     *
114     * @return string
115     */
116    public function getNavigation() {
117        global $ID;
118        global $conf;
119
120        // id of the current sidebar, each sidebar must have its own state
121        $header = sprintf('<div id="sidebarId" class="%s"></div>', md5(page_findnearest($conf['sidebar'])));
122        // add tabs if multiple navigation types available
123        if ($this->plugins['sitemapnavi'] !== null) {
124            $header .= '<ul class="sidebar-tabs">';
125            $header .= '<li class="' . ($this->nav === 'sidebar' ? 'active' : '') . '">' .
126                '<a href="' . wl($ID, ['nav' => 'sidebar']) . '">'.tpl_getLang('nav_sidebar').'</a></li>';
127            $header .= '<li class="' . ($this->nav === 'sitemap' ? 'active' : '') . '">' .
128                '<a href="' . wl($ID, ['nav' => 'sitemap']) . '">'.tpl_getLang('nav_sitemap').'</a></li>';
129            $header .= '</ul>';
130        }
131
132        // decide what to show
133        if ($this->nav === 'sitemap') {
134            // site tree created by sitemapnavi plugin
135            $nav = '<nav class="nav-sitemapnavi" id="plugin__sitemapnavi">';
136            $nav .= $this->plugins['sitemapnavi']->getSiteMap(':');
137            $nav .= '</nav>';
138        } else {
139            // main navigation, loaded from standard sidebar, fixed up by javascript
140            $nav = '<nav class="nav-main">';
141            // immeadiately hide the navigation (if javascript available)
142            // it will be restyled and made visible again by our script later
143            $nav .= '<script type="application/javascript">
144                        document.getElementsByClassName("nav-main")[0].style.visibility = "hidden";
145                     </script>';
146
147            $nav .= $this->getInclude('navtop');
148            $nav .= tpl_include_page($conf['sidebar'], false, true);
149            $nav .= $this->getInclude('navbottom');
150            $nav .= '</nav>';
151        }
152
153        return $header . $nav;
154    }
155
156    /**
157     * Default class defining is the sidebar should collapse
158     *
159     * @return string
160     */
161    public function fullWidthClass() {
162        global $ACT;
163        // no auto collapsing? empty class
164        if (!tpl_getConf('autocollapse')) return '';
165        // mode show? empty class
166        if ($ACT === "show") return '';
167        // anything else? wide content
168        return 'wide-content ';
169    }
170
171    /**
172     * Get all the tabs to display
173     *
174     * @return array
175     */
176    public function getMetaBoxTabs() {
177        global $lang, $INFO;
178        $tabs = array();
179
180        $toc = tpl_toc(true);
181        if($toc) {
182            $tabs[] = array(
183                'id' => 'spr__tab-toc',
184                'label' => $lang['toc'],
185                'tab' => $toc,
186                'count' => null,
187            );
188        }
189
190        if($this->plugins['tagging']) {
191            $tabs[] = array(
192                'id' => 'spr__tab-tags',
193                'label' => tpl_getLang('tab_tags'),
194                'tab' => $this->plugins['tagging']->tpl_tags(false),
195                'count' => count($this->plugins['tagging']->findItems(array('pid' => $INFO['id']), 'tag')),
196            );
197        }
198
199        if ($this->plugins['magicmatcher']) {
200            $tabs[] = array(
201                'id' => 'spr__tab-issues',
202                'label' => tpl_getLang('tab_issues'),
203                'tab' => $this->plugins['magicmatcher']->getIssueListHTML(),
204                'count' =>  $this->plugins['magicmatcher']->getCountIssues(),
205            );
206        }
207
208        return $tabs;
209    }
210
211    /**
212     * Creates an image tag and includes the first found image correctly resized
213     *
214     * @param string $tag
215     * @param array $attributes
216     * @param int $w
217     * @param int $h
218     * @param bool $crop
219     * @return string
220     */
221    public static function getResizedImgTag($tag, $attributes, $w, $h, $crop = true) {
222        $attr = '';
223        $medias = array();
224
225        // the attribute having an array defines where the image goes
226        foreach($attributes as $attribute => $data) {
227            if(is_array($data)) {
228                $medias = $data;
229                $attr = $attribute;
230            }
231        }
232        // if the image attribute could not be found return
233        if(!$attr || !$medias) return '';
234
235        // try all medias until an existing one is found
236        $media = '';
237        foreach($medias as $media) {
238            if(file_exists(mediaFN($media))) break;
239            $media = '';
240        }
241        if($media === '') return '';
242
243        // replace the array
244        $media = ml($media, array('w' => $w, 'h' => $h, 'crop' => (int) $crop), true, '&');
245        $attributes[$attr] = $media;
246
247        // return the full tag
248        return '<' . $tag . ' ' . buildAttributes($attributes) . ' />' . "\n";
249    }
250
251    /**
252     * Embed the main logo
253     *
254     * Tries a few different locations
255     */
256    public function mainLogo() {
257        global $conf;
258
259        // homepage logo should not link to itself (BITV accessibility requirement)
260        $linkit = (strcmp(wl(), $_SERVER['REQUEST_URI']) !== 0);
261        if($linkit) {
262            $title = $conf['title'] . tpl_getLang('adjunct_linked_logo_text');
263        } else {
264            $title = tpl_getLang('adjunct_start_logo_text') . $conf['title'];
265        }
266
267        $desktop = self::getResizedImgTag(
268            'img',
269            array(
270                'class' => 'mobile-hide',
271                'src' => array('wiki:logo-wide.svg', 'wiki:logo.svg', 'wiki:logo-wide.png', 'wiki:logo.png'),
272                'alt' => $title,
273            ),
274            0, 250, false
275        );
276        $mobile = self::getResizedImgTag(
277            'img',
278            array(
279                'class' => 'mobile-only',
280                'src' => array(
281                    'wiki:logo-32x32.svg', 'wiki:favicon.svg', 'wiki:logo-square.svg', 'wiki:logo.svg',
282                    'wiki:logo-32x32.png', 'wiki:favicon.png', 'wiki:logo-square.png', 'wiki:logo.png'
283                ),
284                'alt' => $title,
285            ),
286            32, 32
287        );
288
289        // homepage logo should not link to itself (BITV accessibility requirement)
290        if($linkit) {
291            tpl_link(wl(), $desktop, 'accesskey="h" title="[H]"');
292            tpl_link(wl(), $mobile, 'accesskey="h" title="[H]"');
293        } else {
294            echo $desktop;
295            echo $mobile;
296        }
297    }
298
299    /**
300     * Add the current mode information to the hierarchical breadcrumbs
301     */
302    public function breadcrumbSuffix() {
303        global $ACT;
304        global $lang;
305        global $INPUT;
306        global $ID;
307        global $conf;
308        global $IMG;
309        if($ACT == 'show') return;
310
311        // find an apropriate label for the current mode
312        if($ACT) {
313            $label = tpl_getLang('mode_' . $ACT);
314            if(!$label) {
315                if(isset($lang['btn_' . $ACT])) {
316                    $label = $lang['btn_' . $ACT];
317                } else {
318                    $label = $ACT;
319                }
320            }
321        } else {
322            // actually we would need to create a proper namespace breadcrumb path here,
323            // but this is the most simplest thing we can do for now
324            if(defined('DOKU_MEDIADETAIL')) {
325                $label = hsc(noNS($IMG));
326            } else {
327                return;
328            }
329        }
330
331        if($ACT == 'admin' && $INPUT->has('page')) {
332            $link = wl($ID, array('do' => 'admin'));
333            echo '<bdi> : <a href="' . $link . '"><strong>' . $label . '</strong></a></bdi>';
334
335            /** @var \DokuWiki_Admin_Plugin $plugin */
336            $plugin = plugin_load('admin', $INPUT->str('page'));
337            if(!$plugin) return;
338
339            $label = $plugin->getMenuText($conf['lang']);
340        }
341
342        echo '<bdi><span class="curid"> : <strong>' . $label . '</strong></span></bdi>';
343    }
344}
345