1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\TagAttribute\StyleAttribute;
7use Doku_Handler;
8
9
10class PageExplorerTag
11{
12    public const PAGE_TYPE = "page";
13    public const FOLDER_ICON = "images:page-explorer-folder";
14    /**
15     * Page canonical and tag pattern
16     */
17    public const CANONICAL = "page-explorer";
18    public const HOME_TYPE = "home";
19    public const PAGE_ATTRIBUTES = "page-attributes";
20    public const INDEX_INSTRUCTIONS = "index-instructions";
21    public const NAMESPACE_ATTRIBUTES = "namespace-attributes";
22    public const TYPE_TREE = "tree";
23    public const PAGE_INSTRUCTIONS = "page-instructions";
24    public const PARENT_ATTRIBUTES = "parent-attributes";
25    public const INDEX_ATTRIBUTES = "index-attributes";
26    public const NAMESPACE_INSTRUCTIONS = "namespace-instructions";
27    /**
28     * Tag in Dokuwiki cannot have a `-`
29     * This is the last part of the class
30     */
31    public const LOGICAL_TAG = self::PAGE_EXPLORER_MARKUP;
32    /**
33     * Namespace attribute
34     * that contains the namespace information
35     * (ie
36     *   * a namespace path
37     *   * or current, for the namespace of the current requested page
38     */
39    public const ATTR_NAMESPACE = "ns";
40    public const LEVEL_UP_ICON = "images:page-explorer-icons8-level-up";
41
42    public const NTOC_MARKUP = "ntoc";
43    public const PAGE_EXPLORER_MARKUP = "page-explorer";
44
45    public const PARENT_INSTRUCTIONS = "parent-instructions";
46    /**
47     * Attributes on the home node
48     */
49    public const LIST_TYPE = "list";
50    public const INDEX_TAG = "index";
51    /**
52     * The pattern
53     */
54    public const INDEX_HOME_TAG = "home";
55    /**
56     * Tag in Dokuwiki cannot have a `-`
57     * This is the last part of the class
58     */
59    public const LOGICAL_INDEX_TAG = "pageexplorerhome";
60    public const INDEXES_TAG = [PageExplorerTag::INDEX_HOME_TAG, PageExplorerTag::INDEX_TAG];
61    /**
62     * Implementation of the namespace
63     *   * ie the button (in a collapsible menu) http://localhost:63342/bootstrap-5.0.1-examples/sidebars/index.html
64     *   * or the directory in a list menu
65     *
66     * This syntax is not a classic syntax plugin
67     * The instructions are captured at the {@link DOKU_LEXER_END}
68     * state of {@link syntax_plugin_combo_pageexplorer::handle()}
69     * to create/generate the namespaces
70     *
71     */
72    public const NAMESPACE_LOGICAL_TAG = "pageexplorernamespace";
73    public const NAMESPACE_SHORT_TAG = "ns";
74    public const NAMESPACE_ITEM_TAG = "ns-item";
75    public const NAMESPACE_LONG_TAG = "namespace";
76    /**
77     * Tag in Dokuwiki cannot have a `-`
78     * This is the last part of the class
79     */
80    public const PAGE_LOGICAL_TAG = self::PAGE_TAG;
81    /**
82     * The pattern
83     */
84    public const PAGE_TAG = "page";
85    public const PAGE_ITEM_TAG = "page-item";
86    const PARENT_TAG = "parent";
87
88
89    /**
90     * @param string $html
91     * @param MarkupPath $page
92     * @param array $data - the data array from the handler
93     * @param string $type
94     */
95    public static function treeProcessLeaf(string &$html, MarkupPath $page, array $data, string $type)
96    {
97        /**
98         * In callstack instructions
99         * <li>
100         *   $instructions
101         * </li>
102         */
103        $pageAttributes = $data[self::PAGE_ATTRIBUTES];
104        $pageInstructions = $data[self::PAGE_INSTRUCTIONS];
105        if ($pageInstructions === null && $pageAttributes !== null) {
106            return;
107        }
108        if (!FileSystems::exists($page->getPathObject())) {
109            LogUtility::error("The given leaf page ($page) does not exist and was not added to the page-explorer tree", self::CANONICAL);
110            return;
111        }
112        if ($page->isHidden()) {
113            return;
114        }
115
116        $listItemEnterTag = TagAttributes::createEmpty()
117            ->setLogicalTag(self::CANONICAL . "-tree-$type")
118            ->toHtmlEnterTag("li");
119
120        $listItemContent = "";
121        if ($pageInstructions !== null) {
122            try {
123                $listItemContent = MarkupRenderUtility::renderInstructionsToXhtml($pageInstructions, $page->getMetadataForRendering());
124            } catch (ExceptionCompile $e) {
125                LogUtility::error("Error while rendering the leaf. Error: {$e->getMessage()}", self::CANONICAL);
126                return;
127            }
128        } else {
129            try {
130                $listItemContent = LinkMarkup::createFromPageIdOrPath($page->getWikiId())
131                    ->toAttributes()
132                    ->toHtmlEnterTag("a");
133                $listItemContent .= "{$page->getNameOrDefault()}</a>";
134            } catch (ExceptionCompile $e) {
135                LogUtility::error("Error while rendering the default tree page. Error: {$e->getMessage()}", self::CANONICAL);
136            }
137        }
138        /** @noinspection PhpUnnecessaryCurlyVarSyntaxInspection */
139        $html .= "{$listItemEnterTag}{$listItemContent}</li>";
140
141    }
142
143    /**
144     * @param WikiPath $namespacePath
145     * @return string the last part with a uppercase letter and where underscore became a space
146     */
147    public static function toNamespaceName(WikiPath $namespacePath): string
148    {
149        try {
150            return ucfirst(trim(str_replace("_", " ", $namespacePath->getLastNameWithoutExtension())));
151        } catch (ExceptionNotFound $e) {
152            // root
153            return "";
154        }
155    }
156
157    /**
158     * A class prefix added in elements
159     * @param string $type
160     * @return string
161     */
162    public static function getClassPrefix(string $type): string
163    {
164        return self::CANONICAL . "-$type";
165    }
166
167    public static function handleExit(Doku_Handler $handler)
168    {
169        $callStack = CallStack::createFromHandler($handler);
170
171        /**
172         * Capture the instructions for
173         * {@link syntax_plugin_combo_pageexplorerpage}
174         * {@link syntax_plugin_combo_pageexplorernamespace}
175         * {@link syntax_plugin_combo_pageexplorernamehome}
176         */
177        $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
178        /**
179         * @var Call[] $namespaceInstructions
180         * @var array $namespaceAttributes
181         */
182        $namespaceInstructions = null;
183        $namespaceAttributes = null;
184        /**
185         * @var Call[] $templatePageInstructions
186         * @var array $pageAttributes
187         */
188        $templatePageInstructions = null;
189        $pageAttributes = null;
190        /**
191         * @var Call[] $templateHomeInstructions
192         * @var array $homeAttributes
193         */
194        $templateHomeInstructions = null;
195        $homeAttributes = null;
196        /**
197         * The instructions for the parent item in a page explorer list
198         * if any
199         * @var Call[] $parentInstructions
200         * @var array $parentAttributes
201         */
202        $parentInstructions = null;
203        $parentAttributes = null;
204        /**
205         * @var Call[] $actualInstructionsStack
206         */
207        $actualInstructionsStack = [];
208        while ($callStack->next()) {
209            $actualCall = $callStack->getActualCall();
210            $tagName = $actualCall->getTagName();
211            switch ($actualCall->getState()) {
212                case DOKU_LEXER_ENTER:
213                    switch ($tagName) {
214                        case self::PAGE_LOGICAL_TAG:
215                            $pageAttributes = $actualCall->getAttributes();
216                            continue 3;
217                        case self::NAMESPACE_LOGICAL_TAG:
218                            $namespaceAttributes = $actualCall->getAttributes();
219                            continue 3;
220                        case self::LOGICAL_INDEX_TAG:
221                            $homeAttributes = $actualCall->getAttributes();
222                            continue 3;
223                        case self::PARENT_TAG:
224                            $parentAttributes = $actualCall->getAttributes();
225                            continue 3;
226                        default:
227                            $actualInstructionsStack[] = $actualCall->toCallArray();
228                            continue 3;
229                    }
230                case DOKU_LEXER_EXIT:
231                    switch ($tagName) {
232                        case self::PAGE_LOGICAL_TAG:
233                            $templatePageInstructions = $actualInstructionsStack;
234                            $actualInstructionsStack = [];
235                            continue 3;
236                        case self::NAMESPACE_LOGICAL_TAG:
237                            $namespaceInstructions = $actualInstructionsStack;
238                            $actualInstructionsStack = [];
239                            continue 3;
240                        case self::LOGICAL_INDEX_TAG:
241                            $templateHomeInstructions = $actualInstructionsStack;
242                            $actualInstructionsStack = [];
243                            continue 3;
244                        case PageExplorerTag::PARENT_TAG:
245                            $parentInstructions = $actualInstructionsStack;
246                            $actualInstructionsStack = [];
247                            continue 3;
248                        default:
249                            $actualInstructionsStack[] = $actualCall->toCallArray();
250                            continue 3;
251
252                    }
253                default:
254                    $actualInstructionsStack[] = $actualCall->toCallArray();
255                    break;
256
257            }
258        }
259        /**
260         * Remove all callstack from the opening tag
261         */
262        $callStack->deleteAllCallsAfter($openingTag);
263
264        /**
265         * The container/block should have at minimum an enter tag
266         * to be able to set the {@link action_plugin_combo_sideslotpostprocessing} auto collapsible
267         * parameter
268         */
269        $openingTag->setPluginData(PageExplorerTag::NAMESPACE_INSTRUCTIONS, $namespaceInstructions);
270        $openingTag->setPluginData(PageExplorerTag::NAMESPACE_ATTRIBUTES, $namespaceAttributes);
271        $openingTag->setPluginData(PageExplorerTag::PAGE_INSTRUCTIONS, $templatePageInstructions);
272        $openingTag->setPluginData(PageExplorerTag::PAGE_ATTRIBUTES, $pageAttributes);
273        $openingTag->setPluginData(PageExplorerTag::INDEX_INSTRUCTIONS, $templateHomeInstructions);
274        $openingTag->setPluginData(PageExplorerTag::INDEX_ATTRIBUTES, $homeAttributes);
275        $openingTag->setPluginData(PageExplorerTag::PARENT_INSTRUCTIONS, $parentInstructions);
276        $openingTag->setPluginData(PageExplorerTag::PARENT_ATTRIBUTES, $parentAttributes);
277    }
278
279    public static function renderEnterTag(TagAttributes $pageExplorerTagAttributes, array $data)
280    {
281
282        $returnedXhtml = "";
283        /**
284         * Id (id is mandatory for toggle)
285         */
286        $id = $pageExplorerTagAttributes->getValue(TagAttributes::ID_KEY);
287        if ($id === null) {
288            $id = IdManager::getOrCreate()->generateNewHtmlIdForComponent(PageExplorerTag::CANONICAL);
289            $pageExplorerTagAttributes->setComponentAttributeValue(TagAttributes::ID_KEY, $id);
290        }
291
292        $executionContext = ExecutionContext::getActualOrCreateFromEnv();
293
294
295        try {
296
297            /**
298             * {@link MarkupCacheDependencies::PAGE_PRIMARY_META_DEPENDENCY}
299             * The cache output is composed of primary metadata
300             * (If it changes, the content change)
301             *
302             * {@link MarkupCacheDependencies::PAGE_SYSTEM_DEPENDENCY}
303             * The content depend on the file system tree
304             * (if a file is added or deleted, the content will change)
305             */
306            $executionContext
307                ->getExecutingMarkupHandler()
308                ->getOutputCacheDependencies()
309                ->addDependency(MarkupCacheDependencies::PAGE_PRIMARY_META_DEPENDENCY)
310                ->addDependency(MarkupCacheDependencies::PAGE_SYSTEM_DEPENDENCY);
311        } catch (ExceptionNotFound $e) {
312            // no fetcher markup running
313            // ie markup
314        }
315
316        /**
317         * Context
318         */
319        $requestedContextPath = $executionContext->getContextPath();
320
321        /**
322         * NameSpacePath determination
323         */
324        $pageExplorerType = $pageExplorerTagAttributes->getType();
325        $namespaceAttribute = $pageExplorerTagAttributes->getValueAndRemove(PageExplorerTag::ATTR_NAMESPACE);
326        if ($namespaceAttribute !== null) {
327            WikiPath::addNamespaceEndSeparatorIfNotPresent($namespaceAttribute);
328            $namespacePath = WikiPath::createMarkupPathFromPath($namespaceAttribute);
329        } else {
330            try {
331                $namespacePath = $requestedContextPath->getParent();
332            } catch (ExceptionNotFound $e) {
333                $namespacePath = WikiPath::createRootNamespacePathOnMarkupDrive();
334            }
335            try {
336                $executionContext
337                    ->getExecutingMarkupHandler()
338                    ->getOutputCacheDependencies()
339                    ->addDependency(MarkupCacheDependencies::REQUESTED_NAMESPACE_DEPENDENCY);
340            } catch (ExceptionNotFound $e) {
341                // ok
342            }
343        }
344
345
346        /**
347         * Class Prefix
348         */
349        $componentClassPrefix = PageExplorerTag::getClassPrefix($pageExplorerType);
350
351
352        /**
353         * Rendering
354         */
355        switch ($pageExplorerType) {
356            default:
357            case PageExplorerTag::LIST_TYPE:
358
359                /**
360                 * Class
361                 */
362                $classContainer = "list-group";
363                $classItem = "list-group-item";
364
365                /**
366                 * Css
367                 */
368                $executionContext
369                    ->getSnippetSystem()
370                    ->attachCssInternalStyleSheet($componentClassPrefix);
371
372                /**
373                 * Create the enter content list tag
374                 */
375                $returnedXhtml .= $pageExplorerTagAttributes
376                    ->addClassName($classContainer)
377                    ->removeAttributeIfPresent(TagAttributes::WIKI_ID)
378                    ->setLogicalTag(PageExplorerTag::CANONICAL)
379                    ->toHtmlEnterTag("ul");
380
381
382                /**
383                 * Home
384                 */
385                $indexInstructions = $data[PageExplorerTag::INDEX_INSTRUCTIONS];
386                $indexAttributes = $data[PageExplorerTag::INDEX_ATTRIBUTES];
387                $currentIndexPage = MarkupPath::createPageFromPathObject($namespacePath);
388                if (!($indexInstructions === null && $indexAttributes !== null)) {
389
390                    if (FileSystems::exists($currentIndexPage)) {
391
392
393                        $indexTagAttributes = TagAttributes::createFromCallStackArray($indexAttributes);
394
395
396                        /**
397                         * Enter home tag
398                         */
399                        $indexPageType = "index";
400                        $returnedXhtml .= $indexTagAttributes
401                            ->addClassName($classItem)
402                            ->setLogicalTag(PageExplorerTag::CANONICAL . "-{$pageExplorerType}-{$indexPageType}")
403                            ->toHtmlEnterTag("li");
404                        /**
405                         * Content
406                         */
407                        if ($indexInstructions !== null) {
408                            try {
409                                $returnedXhtml .= MarkupRenderUtility::renderInstructionsToXhtml($indexInstructions, $currentIndexPage->getMetadataForRendering());
410                            } catch (ExceptionCompile $e) {
411                                $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the home. Error: {$e->getMessage()}");
412                            }
413                        } else {
414                            try {
415                                $returnedXhtml .= LinkMarkup::createFromPageIdOrPath($currentIndexPage->getWikiId())
416                                    ->toAttributes()
417                                    ->toHtmlEnterTag("a");
418                                $returnedXhtml .= "{$currentIndexPage->getNameOrDefault()}</a>";
419                            } catch (ExceptionCompile $e) {
420                                $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the default home. Error: {$e->getMessage()}");
421                            }
422                        }
423                        /**
424                         * End home tag
425                         */
426                        $returnedXhtml .= "</li>";
427                    }
428
429                }
430
431                /**
432                 * Parent ?
433                 */
434                $parentInstructions = $data[PageExplorerTag::PARENT_INSTRUCTIONS];
435                $parentAttributes = $data[PageExplorerTag::PARENT_ATTRIBUTES];
436                if (!($parentInstructions === null && $indexAttributes !== null)) {
437                    try {
438                        $parentPage = $currentIndexPage->getParent();
439                        if ($parentPage->exists()) {
440
441                            $parentTagAttributes = TagAttributes::createFromCallStackArray($parentAttributes);
442                            /**
443                             * Enter parent tag
444                             */
445                            $pageType = "parent";
446                            $returnedXhtml .= $parentTagAttributes
447                                ->addClassName($classItem)
448                                ->setLogicalTag(PageExplorerTag::CANONICAL . "-{$pageExplorerType}-{$pageType}")
449                                ->toHtmlEnterTag("li");
450                            /**
451                             * Content
452                             */
453                            if ($parentInstructions !== null) {
454                                try {
455                                    $returnedXhtml .= MarkupRenderUtility::renderInstructionsToXhtml($parentInstructions, $parentPage->getMetadataForRendering());
456                                } catch (ExceptionCompile $e) {
457                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the parent instructions. Error: {$e->getMessage()}");
458                                }
459                            } else {
460                                try {
461                                    $parentWikiId = $parentPage->getPathObject()->getWikiId();
462                                    $returnedXhtml .= LinkMarkup::createFromPageIdOrPath($parentWikiId)
463                                        ->toAttributes()
464                                        ->toHtmlEnterTag("a");
465                                    $returnedXhtml .= Icon::createFromComboResource(PageExplorerTag::LEVEL_UP_ICON)
466                                        ->toHtml();
467                                    $returnedXhtml .= " {$parentPage->getNameOrDefault()}</a>";
468                                } catch (ExceptionCompile $e) {
469                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the default parent. Error: {$e->getMessage()}");
470                                }
471                            }
472                            /**
473                             * End parent tag
474                             */
475                            $returnedXhtml .= "</li>";
476                        }
477                    } catch (ExceptionNotFound $e) {
478                        // no parent page
479                    }
480
481                }
482
483                /**
484                 * Children (Namespaces/Pages)
485                 */
486
487                $namespaceEnterTag = TagAttributes::createFromCallStackArray($data[PageExplorerTag::NAMESPACE_ATTRIBUTES])
488                    ->addClassName($classItem)
489                    ->setLogicalTag(PageExplorerTag::CANONICAL . "-{$pageExplorerType}-namespace")
490                    ->toHtmlEnterTag("li");
491                $pageEnterTag = TagAttributes::createFromCallStackArray($data[PageExplorerTag::PAGE_ATTRIBUTES])
492                    ->addClassName($classItem)
493                    ->setLogicalTag(PageExplorerTag::CANONICAL . "-{$pageExplorerType}-page")
494                    ->toHtmlEnterTag("li");
495
496
497                $pageInstructions = $data[PageExplorerTag::PAGE_INSTRUCTIONS];
498                $pageAttributes = $data[PageExplorerTag::PAGE_ATTRIBUTES];
499                $namespaceInstructions = $data[PageExplorerTag::NAMESPACE_INSTRUCTIONS];
500                $namespaceAttributes = $data[PageExplorerTag::NAMESPACE_ATTRIBUTES];
501
502                $pageNum = 0;
503                foreach (FileSystems::getChildrenContainer($namespacePath) as $subNamespacePath) {
504
505                    // Namespace
506                    if (!($namespaceInstructions === null && $namespaceAttributes !== null)) {
507                        try {
508                            $subNamespacePage = MarkupPath::getIndexPageFromNamespace($subNamespacePath->toAbsoluteId());
509                        } catch (ExceptionBadSyntax $e) {
510                            LogUtility::msg("Bad syntax for the namespace $namespacePath. Error: {$e->getMessage()}", LogUtility::LVL_MSG_ERROR, PageExplorerTag::CANONICAL);
511                            return false;
512                        }
513                        if ($subNamespacePage->isHidden()) {
514                            continue;
515                        }
516                        if ($subNamespacePage->exists()) {
517                            /**
518                             * SubNamespace Enter tag
519                             */
520                            $returnedXhtml .= $namespaceEnterTag;
521
522                            /**
523                             * SubNamespace Content
524                             */
525                            if ($namespaceInstructions !== null) {
526                                try {
527                                    $returnedXhtml .= MarkupRenderUtility::renderInstructionsToXhtml($namespaceInstructions, $subNamespacePage->getMetadataForRendering());
528                                } catch (ExceptionCompile $e) {
529                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the sub-namespace. Error: {$e->getMessage()}");
530                                }
531                            } else {
532                                try {
533                                    $returnedXhtml .= LinkMarkup::createFromPageIdOrPath($subNamespacePage->getWikiId())
534                                        ->toAttributes()
535                                        ->toHtmlEnterTag("a");
536                                    $returnedXhtml .= Icon::createFromComboResource(PageExplorerTag::FOLDER_ICON)
537                                        ->toHtml();
538                                    $returnedXhtml .= " {$subNamespacePage->getNameOrDefault()}</a>";
539                                } catch (ExceptionCompile $e) {
540                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the default namespace. Error: {$e->getMessage()}");
541                                }
542
543                            }
544                            /**
545                             * SubNamespace Exit tag
546                             */
547                            $returnedXhtml .= "</li>";
548                        }
549
550                    }
551
552                }
553
554                $childrenLeaf = FileSystems::getChildrenLeaf($namespacePath);
555                foreach ($childrenLeaf as $childPagePath) {
556                    $childPage = MarkupPath::createPageFromPathObject($childPagePath);
557                    if ($childPage->isHidden()) {
558                        continue;
559                    }
560                    if (!($pageInstructions === null && $pageAttributes !== null)) {
561                        $pageNum++;
562                        if ($currentIndexPage !== null
563                            && $childPagePath->getWikiId() !== $currentIndexPage->getWikiId()
564                            && FileSystems::exists($childPagePath)
565                        ) {
566                            /**
567                             * Page Enter tag
568                             */
569                            $returnedXhtml .= $pageEnterTag;
570                            /**
571                             * Page Content
572                             */
573                            if ($pageInstructions !== null) {
574
575                                try {
576                                    $returnedXhtml .= MarkupRenderUtility::renderInstructionsToXhtmlFromPage($pageInstructions, $childPage);
577                                } catch (ExceptionCompile $e) {
578                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the page. Error: {$e->getMessage()}");
579                                }
580                            } else {
581                                try {
582                                    $returnedXhtml .= LinkMarkup::createFromPageIdOrPath($childPagePath->getWikiId())
583                                        ->toAttributes()
584                                        ->toHtmlEnterTag("a");
585                                    $returnedXhtml .= "{$childPage->getNameOrDefault()}</a>";
586                                } catch (ExceptionCompile $e) {
587                                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the default page. Error: {$e->getMessage()}");
588                                }
589                            }
590                            /**
591                             * Page Exit tag
592                             */
593                            $returnedXhtml .= "</li>";
594                        }
595                    }
596                }
597
598
599                /**
600                 * End container tag
601                 */
602                $returnedXhtml .= "</ul>";
603                return $returnedXhtml;
604
605            case PageExplorerTag::TYPE_TREE:
606
607                /**
608                 * Printing the tree
609                 *
610                 * data-wiki-id, needed for the
611                 * javascript that open the tree
612                 * to the actual page
613                 */
614                $namespaceId = $namespacePath->getWikiId();
615                if (!blank($namespaceId)) {
616                    $pageExplorerTagAttributes->addOutputAttributeValue("data-" . TagAttributes::WIKI_ID, $namespaceId);
617                } else {
618                    $pageExplorerTagAttributes->addBooleanOutputAttributeValue("data-" . TagAttributes::WIKI_ID);
619                }
620
621
622                $snippetId = PageExplorerTag::CANONICAL . "-" . $pageExplorerType;
623                /**
624                 * Open the tree until the current page
625                 * and make it active
626                 */
627                PluginUtility::getSnippetManager()->attachJavascriptFromComponentId($snippetId);
628                /**
629                 * Styling
630                 */
631                PluginUtility::getSnippetManager()->attachCssInternalStyleSheet($snippetId);
632                $returnedXhtml .= $pageExplorerTagAttributes->toHtmlEnterTag("nav") . DOKU_LF;
633                $returnedXhtml .= "<ul>" . DOKU_LF;
634
635                try {
636                    $tree = PathTreeNode::buildTreeViaFileSystemChildren($namespacePath);
637                    self::treeProcessTree($returnedXhtml, $tree, $data);
638                } catch (ExceptionBadSyntax $e) {
639                    $returnedXhtml .= LogUtility::wrapInRedForHtml("Error while rendering the tree sub-namespace. Error: {$e->getMessage()}");
640                }
641
642                $returnedXhtml .= "</ul>";
643                $returnedXhtml .= "</nav>";
644                return $returnedXhtml;
645
646        }
647    }
648
649    /**
650     * Process the
651     * @param string $html - the callstack
652     * @param PathTreeNode $pathTreeNode
653     * @param array $data - the data array from the handler
654     * @throws ExceptionBadSyntax
655     */
656    public static
657    function treeProcessTree(string &$html, PathTreeNode $pathTreeNode, array $data)
658    {
659
660        /**
661         * Home Page first
662         * @var MarkupPath $homePage
663         */
664        $homePage = null;
665        /**
666         * @var TreeNode[] $containerTreeNodes
667         */
668        $containerTreeNodes = [];
669        /**
670         * @var MarkupPath[] $nonHomePages
671         */
672        $nonHomePages = [];
673
674        /**
675         * Scanning the tree node to
676         * categorize the children as home, page or namespace (container)
677         * @var PathTreeNode[] $children
678         */
679        $children = $pathTreeNode->getChildren();
680        foreach ($children as $child) {
681
682            /**
683             * Namespace
684             */
685            if ($child->hasChildren()) {
686
687                $containerTreeNodes[] = $child;
688
689            } else {
690                /**
691                 * Page
692                 */
693                $page = MarkupPath::createPageFromPathObject($child->getPath());
694                if ($page->isIndexPage()) {
695                    $homePage = $page;
696                } else {
697                    $nonHomePages[] = $page;
698                }
699
700            }
701
702        }
703
704        /**
705         * First the home page
706         */
707        if ($homePage !== null) {
708            PageExplorerTag::treeProcessLeaf($html, $homePage, $data, PageExplorerTag::HOME_TYPE);
709        }
710
711        /**
712         * The subdirectories
713         */
714        $namespaceInstructions = $data[PageExplorerTag::NAMESPACE_INSTRUCTIONS];
715        foreach ($containerTreeNodes as $containerTreeNode) {
716
717            /**
718             * @var WikiPath $containerPath
719             */
720            $containerPath = $containerTreeNode->getPath();
721
722            /**
723             * Entering: Creating in instructions form
724             * the same as in markup form
725             *
726             * <li>
727             *    <button data-bs-target="#id" data-bs-collapse="collapse">
728             *      Label
729             *    </button>
730             *    <div id="id">
731             *      <ul>
732             *        <li></li>
733             *        <li></li>
734             *        ....
735             *    </div>
736             *    ...
737             */
738            $html .= "<li>";
739
740
741            /**
742             * Keep the id unique on the page in order to be able to collapse
743             * the good HTML node
744             */
745            $namespaceId = ExecutionContext::getActualOrCreateFromEnv()->getIdManager()->generateNewHtmlIdForComponent("page-explorer-{$containerPath->getWikiId()}");
746            $id = StyleAttribute::addComboStrapSuffix($namespaceId);
747            $html .= TagAttributes::createEmpty()
748                ->addOutputAttributeValue("data-bs-target", "#$id")
749                ->addOutputAttributeValue("data-" . TagAttributes::WIKI_ID, $containerPath->getWikiId())
750                ->addOutputAttributeValue("data-bs-toggle", "collapse")
751                ->addOutputAttributeValue("aria-expanded", "false")
752                ->addClassName("btn")
753                ->addClassName("align-items-center")
754                ->addClassName("rounded")
755                ->toHtmlEnterTag("button");
756
757            // Button label
758
759            $subHomePage = MarkupPath::getIndexPageFromNamespace($containerPath->toAbsoluteId());
760            if ($subHomePage->exists()) {
761                if ($namespaceInstructions !== null) {
762                    try {
763                        $html .= MarkupRenderUtility::renderInstructionsToXhtml($namespaceInstructions, $subHomePage->getMetadataForRendering());
764                    } catch (ExceptionCompile $e) {
765                        $html .= LogUtility::wrapInRedForHtml("Error while rendering the child directory. Error: {$e->getMessage()}");
766                    }
767                } else {
768                    $html .= $subHomePage->getNameOrDefault();
769                }
770            } else {
771                $namespaceName = PageExplorerTag::toNamespaceName($containerPath);
772                $html .= $namespaceName;
773            }
774            // End button
775            $html .= "</button>";
776
777            /**
778             * Sub and Recursion
779             */
780            $html .= TagAttributes::createEmpty()
781                ->addClassName("collapse")
782                ->addOutputAttributeValue(TagAttributes::ID_KEY, "$id")
783                ->toHtmlEnterTag("div");
784            $html .= "<ul>";
785            self::treeProcessTree($html, $containerTreeNode, $data);
786            $html .= "</ul>";
787            $html .= "</div>";
788
789            /**
790             * Closing: Creating in instructions form
791             * the same as in markup form
792             *
793             *   </$pageExplorerTreeListTag>
794             * </$pageExplorerTreeTag>
795             */
796            $html .= "</li>";
797
798        }
799
800        /**
801         * Then the other pages
802         */
803        foreach ($nonHomePages as $page) {
804            PageExplorerTag::treeProcessLeaf($html, $page, $data, PageExplorerTag::PAGE_TYPE);
805        }
806
807
808    }
809}
810
811