1<?php
2/**
3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14
15
16use ComboStrap\Meta\Api\Metadata;
17use ComboStrap\TagAttribute\StyleAttribute;
18use Doku_Renderer;
19use DokuWiki_Admin_Plugin;
20use syntax_plugin_combo_toc;
21
22class Toc extends Metadata
23{
24
25
26    const CANONICAL = syntax_plugin_combo_toc::CANONICAL;
27    private ?array $tocData = null;
28
29
30    public static function createForRequestedPage(): Toc
31    {
32        return self::createForPage(MarkupPath::createFromRequestedPage());
33    }
34
35    public static function getClass(): string
36    {
37        return StyleAttribute::addComboStrapSuffix(self::CANONICAL);
38    }
39
40    /**
41     * @throws ExceptionBadArgument - if the TOC is not an array
42     * @throws ExceptionNotFound - if the TOC variable was not found
43     */
44    public static function createFromGlobalVariable(): Toc
45    {
46        global $TOC;
47        if ($TOC === null) {
48            throw new ExceptionNotFound("No global TOC variable found");
49        }
50        return (new Toc())
51            ->setValue($TOC);
52    }
53
54    public static function createEmpty(): Toc
55    {
56        return new Toc();
57    }
58
59
60    public function toXhtml(): string
61    {
62
63        $this->buildCheck();
64
65        if ($this->tocData === null) {
66            return "";
67        }
68
69        PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::CANONICAL);
70
71        $toc = $this->tocData;
72
73        $tocMinHeads = Site::getTocMinHeadings();
74        if (count($toc) < $tocMinHeads) {
75            return "";
76        }
77
78        /**
79         * Adding toc number style
80         */
81        try {
82            $css = Outline::getCssNumberingRulesFor(Outline::TOC_NUMBERING);
83            PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(Outline::TOC_NUMBERING, $css);
84        } catch (ExceptionNotEnabled $e) {
85            // not enabled
86        } catch (ExceptionBadSyntax $e) {
87            LogUtility::error("The toc numbering type was unknown", self::CANONICAL);
88        }
89
90        /**
91         * Creating the html
92         */
93        global $lang;
94
95        // To keep track of the HTML level (levels may be badly encoded)
96        $htmlLevel = 0;
97        $previousLevel = 0;
98        $topTocLevel = Site::getTopTocLevel();
99        $ulMarkup = "";
100        foreach ($toc as $tocItem) {
101
102            $actualLevel = $tocItem["level"];
103
104            /**
105             * Skipping to the first top level
106             */
107            if ($actualLevel < $topTocLevel) {
108                $previousLevel = $actualLevel;
109                continue;
110            }
111
112            /**
113             * Closing
114             */
115            $levelDiff = $previousLevel - $actualLevel;
116            switch (true) {
117                case $levelDiff === 0 && (!empty($ulMarkup)):
118                    /**
119                     * Same level
120                     */
121                    $ulMarkup .= "</li>";
122                    break;
123                case ($actualLevel < $previousLevel && !empty($ulMarkup)):
124                    /**
125                     * One or multiple level up
126                     * (from 4 to 2)
127                     */
128                    $htmlLevel += $levelDiff;
129                    $ulMarkup .= str_repeat("</li></ul>", $levelDiff);
130                    $ulMarkup .= "</li>";
131                    break;
132                default:
133                    /**
134                     * One level down
135                     * (We can't go multiple at once)
136                     */
137                    $htmlLevel -= 1;
138                    $ulMarkup .= "<ul>";
139                    break;
140            }
141
142            $href = $tocItem['link'];
143            $label = $tocItem['title'];
144            $tocLevelClass = StyleAttribute::addComboStrapSuffix("toc-level-$actualLevel");
145            $ulMarkup .= "<li><a href=\"$href\" class=\"$tocLevelClass\">$label</a>";
146            /**
147             * Close
148             */
149            $previousLevel = $actualLevel;
150        }
151        // grand closing
152        $ulMarkup .= str_repeat("</li></ul>", abs($htmlLevel));
153        $tocHeaderLang = $lang['toc'];
154        $tocHeaderClass = StyleAttribute::addComboStrapSuffix("toc-header");
155        return <<<EOF
156<p class="$tocHeaderClass">$tocHeaderLang</p>
157$ulMarkup
158EOF;
159
160
161    }
162
163
164    /**
165     * @param Doku_Renderer $renderer
166     * @return bool if the toc need to be shown
167     *
168     * From {@link Doku_Renderer::notoc()}
169     * $this->info['toc'] = false;
170     * when
171     * ~~NOTOC~~
172     */
173    public static function showToc(Doku_Renderer $renderer): bool
174    {
175
176        global $ACT;
177
178        /**
179         * Search page, no toc
180         */
181        if ($ACT === 'search') {
182            return false;
183        }
184
185        /**
186         * If this is another template such as Dokuwiki, we get two TOC.
187         */
188        if (!Site::isStrapTemplate()) {
189            return false;
190        }
191
192        /**
193         * On the admin page
194         */
195        if ($ACT === 'admin') {
196
197            global $INPUT;
198            $plugin = null;
199            $class = $INPUT->str('page');
200            if (!empty($class)) {
201
202                $pluginList = plugin_list('admin');
203
204                if (in_array($class, $pluginList)) {
205                    // attempt to load the plugin
206                    /** @var $plugin DokuWiki_Admin_Plugin */
207                    $plugin = plugin_load('admin', $class);
208                }
209
210                if ($plugin !== null) {
211                    global $TOC;
212                    if (!is_array($TOC)) $TOC = $plugin->getTOC(); //if TOC wasn't requested yet
213                    if (!is_array($TOC)) {
214                        return false;
215                    } else {
216                        return true;
217                    }
218
219                }
220
221            }
222
223        }
224
225        // return it if set otherwise return true
226        return $renderer->info['toc'] ?? true;
227
228    }
229
230    public function shouldTocBePrinted(): bool
231    {
232        global $conf;
233        return $conf['tocminheads'] && count($this->tocData) >= $conf['tocminheads'];
234    }
235
236    public static function createForPage($page): Toc
237    {
238        return (new Toc())
239            ->setResource($page);
240    }
241
242
243    /**
244     * @throws ExceptionBadArgument
245     */
246    public function setValue($value): Toc
247    {
248        if (!is_array($value)) {
249            throw new ExceptionBadArgument("The toc value ($value) is not an array");
250        }
251        $this->tocData = $value;
252        /**
253         * We don't set the global TOC because
254         * if the global TOC is set {@link tpl_admin()}, will not
255         * ask the toc to the admin plugin
256         */
257//        global $TOC;
258//        $TOC = $value;
259        return $this;
260    }
261
262    public function valueIsNotNull(): bool
263    {
264        return $this->tocData !== null;
265    }
266
267    static     public function getDataType(): string
268    {
269        return DataType::ARRAY_VALUE;
270    }
271
272    static public function getDescription(): string
273    {
274        return "Table of Contents";
275    }
276
277    static public function getLabel(): string
278    {
279        return "The table of content for the page";
280    }
281
282    public static function getName(): string
283    {
284        return "toc";
285    }
286
287    static public function getPersistenceType(): string
288    {
289        return Metadata::DERIVED_METADATA;
290    }
291
292    static public function isMutable(): bool
293    {
294        return true;
295    }
296
297    public function setFromStoreValueWithoutException($value): Metadata
298    {
299        $this->tocData = $value;
300        return $this;
301        // We can't modify the toc of dokuwiki
302        // This data shows how to get the table of content from dokuwiki
303        // $description = $metaDataStore->getCurrentFromName("description");
304        // if($description!==null) {
305        //    $this->tocData = $description["tableofcontents"];
306        // }
307
308    }
309
310    /**
311     * @return array
312     * @throws ExceptionNotFound
313     */
314    public function getValue(): array
315    {
316        $this->buildCheck();
317        if ($this->tocData === null) {
318            throw new ExceptionNotFound("No toc");
319        }
320        return $this->tocData;
321    }
322
323    public function getDefaultValue(): array
324    {
325        return [];
326    }
327}
328