1<?php
2
3namespace ComboStrap;
4
5
6/**
7 * Utility class for breadcrumb
8 *
9 *
10 *
11 * https://en.wikipedia.org/wiki/Breadcrumb_navigation#Websites
12 */
13class BreadcrumbTag
14{
15    /**
16     * The type of breadcrumb
17     *
18     * Navigation is a markup that should be present
19     * only once in a page
20     */
21    public const NAVIGATION_TYPE = "nav";
22    /**
23     * Typography is when a breadcrumb is used in a iterator
24     * for instance as sub-title
25     */
26    public const TYPOGRAPHY_TYPE = "typo";
27    public const MARKUP_BLOCK = "breadcrumb";
28    public const LOGICAL_TAG = "breadcrumb";
29    public const CANONICAL_HIERARCHICAL = "breadcrumb-hierarchical";
30    public const DEPTH_ATTRIBUTE = "depth";
31    const TYPES = [self::TYPOGRAPHY_TYPE, self::NAVIGATION_TYPE];
32
33    /**
34     * Hierarchical breadcrumbs (you are here)
35     *
36     * This will return the Hierarchical breadcrumbs.
37     *
38     * Config:
39     *    - $conf['youarehere'] must be true
40     *    - add $lang['youarehere'] if $printPrefix is true
41     *
42     * Metadata comes from here
43     * https://developers.google.com/search/docs/data-types/breadcrumb
44     *
45     * @param TagAttributes|null $tagAttributes
46     * @return string
47     */
48    public static function toBreadCrumbHtml(TagAttributes $tagAttributes = null): string
49    {
50
51        if ($tagAttributes === null) {
52            $tagAttributes = TagAttributes::createEmpty(self::MARKUP_BLOCK);
53        }
54
55
56        /**
57         * Get the page
58         */
59        $path = \syntax_plugin_combo_iterator::getContextPathForComponentThatMayBeInFragment($tagAttributes);
60        $actualPath = MarkupPath::createPageFromPathObject($path);
61
62        $type = $tagAttributes->getType();
63
64
65        /**
66         * Print in function of the depth
67         */
68        switch ($type) {
69            case self::NAVIGATION_TYPE:
70                /**
71                 * https://www.w3.org/TR/wai-aria-practices/examples/breadcrumb/index.html
72                 * Arial-label Provides a label that describes the type of navigation provided in the nav element.
73                 */
74                $tagAttributes->addOutputAttributeValue("aria-label", "Hierarchical breadcrumb");
75                $htmlOutput = $tagAttributes->toHtmlEnterTag("nav");
76                $htmlOutput .= '<ol class="breadcrumb">';
77
78                $lisHtmlOutput = self::getLiHtmlOutput($actualPath, true);
79                while (true) {
80                    try {
81                        $actualPath = $actualPath->getParent();
82                    } catch (ExceptionNotFound $e) {
83                        break;
84                    }
85                    $liHtmlOutput = self::getLiHtmlOutput($actualPath);
86                    $lisHtmlOutput = $liHtmlOutput . $lisHtmlOutput;
87                }
88                $htmlOutput .= $lisHtmlOutput;
89                // close the breadcrumb
90                $htmlOutput .= '</ol>';
91                $htmlOutput .= '</nav>';
92                return $htmlOutput;
93            case self::TYPOGRAPHY_TYPE:
94
95                try {
96                    $requiredDepth = DataType::toInteger($tagAttributes->getValueAndRemoveIfPresent(self::DEPTH_ATTRIBUTE));
97                } catch (ExceptionBadArgument $e) {
98                    LogUtility::error("We were unable to determine the depth attribute. The depth was set to 1. Error: {$e->getMessage()}");
99                    $requiredDepth = 1;
100                }
101                if ($requiredDepth > 1) {
102                    SnippetSystem::getFromContext()->attachCssInternalStyleSheet("breadcrumb-$type");
103                }
104                $htmlOutput = $tagAttributes->toHtmlEnterTag("span");
105                $lisHtmlOutput = "";
106                $actualDepth = 0;
107                while (true) {
108                    try {
109                        $actualPath = $actualPath->getParent();
110                    } catch (ExceptionNotFound $e) {
111                        break;
112                    }
113                    $actualDepth = $actualDepth + 1;
114                    $nameOrDefault = $actualPath->getNameOrDefault();
115                    $liHtmlOutput = "<span class=\"breadcrumb-$type-item\">$nameOrDefault</span>";
116                    $lisHtmlOutput = $liHtmlOutput . $lisHtmlOutput;
117                    if ($actualDepth >= $requiredDepth) {
118                        break;
119                    }
120                }
121                $htmlOutput .= $lisHtmlOutput;
122                $htmlOutput .= '</span>';
123                return $htmlOutput;
124            default:
125                // internal error
126                LogUtility::error("The breadcrumb type ($type) is unknown");
127                return "";
128
129        }
130
131
132    }
133
134    /**
135     * @param MarkupPath $page
136     * @param bool $current
137     * @param bool $link
138     * @return string - the list item for the page
139     */
140    public static function getLiHtmlOutput(MarkupPath $page, bool $current = false, bool $link = true): string
141    {
142        $liClass = "";
143        $liArial = "";
144        if ($current) {
145            $liClass = " active";
146            /**
147             * https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/
148             * Applied to a link in the breadcrumb set to indicate that it represents the current page.
149             */
150            $liArial = " aria-current=\"page\"";
151        }
152        $liHtmlOutput = "<li class=\"breadcrumb-item$liClass\"$liArial>";
153
154        if (FileSystems::exists($page->getPathObject()) && $current === false) {
155            if ($link) {
156                $liHtmlOutput .= $page->getHtmlAnchorLink(self::CANONICAL_HIERARCHICAL);
157            } else {
158                $liHtmlOutput .= $page->getNameOrDefault();
159            }
160        } else {
161            $liHtmlOutput .= $page->getNameOrDefault();
162        }
163        $liHtmlOutput .= '</li>';
164        return $liHtmlOutput;
165    }
166
167    /**
168     * Same rendering for typographic or navigational breadcrumb
169     * @param TagAttributes $tagAttributes
170     * @return string
171     */
172    public static function render(TagAttributes $tagAttributes): string
173    {
174
175        try {
176            ExecutionContext::getActualOrCreateFromEnv()
177                ->getExecutingMarkupHandler()
178                ->getOutputCacheDependencies()
179                // the output has the data from the requested page
180                ->addDependency(MarkupCacheDependencies::REQUESTED_PAGE_DEPENDENCY)
181                // the data from the requested page is dependent on the name, title or description of the page
182                ->addDependency(MarkupCacheDependencies::PAGE_PRIMARY_META_DEPENDENCY);
183        } catch (ExceptionNotFound $e) {
184            // not a fetcher markup run
185        }
186
187        return BreadcrumbTag::toBreadCrumbHtml($tagAttributes);
188
189    }
190
191    public static function getDefaultBlockAttributes(): array
192    {
193        return [TagAttributes::TYPE_KEY => BreadcrumbTag::NAVIGATION_TYPE];
194    }
195
196    public static function handleEnter(TagAttributes $tagAttributes): array
197    {
198        if ($tagAttributes->getType() === self::TYPOGRAPHY_TYPE) {
199            return [PluginUtility::DISPLAY => Call::INLINE_DISPLAY];
200        } else {
201            return [PluginUtility::DISPLAY => Call::BlOCK_DISPLAY];
202        }
203    }
204
205}
206