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