1<?php
2
3namespace ComboStrap;
4
5
6use ComboStrap\Web\UrlEndpoint;
7use Doku_Handler;
8use syntax_plugin_combo_link;
9
10
11class PermalinkTag
12{
13
14
15    public const GENERATED_TYPE = "generated";
16    public const CANONICAL = PermalinkTag::TAG;
17    public const NAMED_TYPE = "named";
18    public const TAG = "permalink";
19    public const FRAGMENT_ATTRIBUTE = "fragment";
20
21    public static function handleEnterSpecial(TagAttributes $attributes, int $state, Doku_Handler $handler): array
22    {
23
24        $callStack = CallStack::createFromHandler($handler);
25        $type = $attributes->getValueAndRemoveIfPresent(TagAttributes::TYPE_KEY);
26        if ($type == null) {
27            $type = self::GENERATED_TYPE;
28        } else {
29            $type = strtolower($type);
30        }
31
32        $strict = $attributes->getBooleanValueAndRemoveIfPresent(TagAttributes::STRICT, true);
33
34        /**
35         * Cache key dependencies
36         */
37        try {
38            ExecutionContext::getActualOrCreateFromEnv()
39                ->getExecutingMarkupHandler()
40                ->getOutputCacheDependencies()
41                ->addDependency(MarkupCacheDependencies::REQUESTED_PAGE_DEPENDENCY);
42        } catch (ExceptionNotFound $e) {
43            // not a fetcher markup run
44        }
45
46        try {
47            $requestedPage = MarkupPath::createFromRequestedPage();
48        } catch (ExceptionNotFound $e) {
49            return self::handleError(
50                "No requested page was found",
51                $strict,
52                $callStack
53            );
54        }
55        $fragment = $attributes->getValueAndRemoveIfPresent(self::FRAGMENT_ATTRIBUTE);
56        switch ($type) {
57            case self::GENERATED_TYPE:
58
59
60                try {
61                    $permanentValue = self::getPermalinkId($requestedPage);
62                } catch (ExceptionNotFound $e) {
63                    return self::handleError(
64                        "The page id has not yet been set",
65                        $strict,
66                        $callStack
67                    );
68                }
69
70                $url = UrlEndpoint::createBaseUrl()
71                    ->setPath("/$permanentValue")
72                    ->toAbsoluteUrl();
73
74                /** @noinspection DuplicatedCode */
75                if ($fragment !== null) {
76                    $fragment = OutlineSection::textToHtmlSectionId($fragment);
77                    $url->setFragment($fragment);
78                }
79                $attributes->addComponentAttributeValue(syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE, $url);
80                $attributes->addOutputAttributeValue("rel", "nofollow");
81                syntax_plugin_combo_link::addOpenLinkTagInCallStack($callStack, $attributes);
82                if ($state === DOKU_LEXER_SPECIAL) {
83                    self::addLinkContentInCallStack($callStack, $url);
84                    self::closeLinkInCallStack($callStack);
85                }
86                return [];
87            case self::NAMED_TYPE:
88                try {
89                    $requestedPage->getCanonical();
90                } catch (ExceptionNotFound $e) {
91                    $withIcon = false; // no icon in handle, error should be send to renderer
92                    $documentationUrlForCanonical = PluginUtility::getDocumentationHyperLink(Canonical::PROPERTY_NAME, "canonical value", $withIcon);
93                    $errorMessage = "The page ($requestedPage) does not have a $documentationUrlForCanonical. We can't create a named permalink";
94                    return self::handleError($errorMessage, $strict, $callStack);
95                }
96
97                $urlPath = PageUrlPath::createForPage($requestedPage)
98                    ->getUrlPathFromType(PageUrlType::CONF_VALUE_CANONICAL_PATH);
99                $urlId = WikiPath::removeRootSepIfPresent($urlPath); // delete the root sep (ie :)
100                $canonicalUrl = UrlEndpoint::createDokuUrl()
101                    ->setQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $urlId)
102                    ->toAbsoluteUrl();
103                /** @noinspection DuplicatedCode */
104                if ($fragment !== null) {
105                    $fragment = OutlineSection::textToHtmlSectionId($fragment);
106                    $canonicalUrl->setFragment($fragment);
107                }
108                $attributes->addComponentAttributeValue(syntax_plugin_combo_link::MARKUP_REF_ATTRIBUTE, $canonicalUrl);
109                $attributes->addOutputAttributeValue("rel", "nofollow");
110                syntax_plugin_combo_link::addOpenLinkTagInCallStack($callStack, $attributes);
111                if ($state === DOKU_LEXER_SPECIAL) {
112                    self::addLinkContentInCallStack($callStack, $canonicalUrl);
113                    self::closeLinkInCallStack($callStack);
114                }
115                return [];
116            default:
117                return self::handleError(
118                    "The permalink type ({$attributes->getType()} is unknown.",
119                    $strict,
120                    $callStack
121                );
122
123        }
124    }
125
126    public static function handleError(string $errorMessage, bool $strict, CallStack $callStack): array
127    {
128
129        $returnArray = [];
130        if ($strict) {
131            $returnArray[PluginUtility::EXIT_MESSAGE] = $errorMessage;
132        }
133        $returnArray[PluginUtility::EXIT_CODE] = 1;
134
135        /**
136         * If this is a button, we cache it
137         */
138        $parent = $callStack->moveToParent();
139        if ($parent !== false && $parent->getTagName() === ButtonTag::MARKUP_LONG) {
140            $parent->addAttribute(Display::DISPLAY, Display::DISPLAY_NONE_VALUE);
141        }
142
143        return $returnArray;
144    }
145
146    public static function getKnownTypes(): array
147    {
148        return [PermalinkTag::NAMED_TYPE, PermalinkTag::GENERATED_TYPE];
149    }
150
151    public static function addLinkContentInCallStack(CallStack $callStack, string $url)
152    {
153        $callStack->appendCallAtTheEnd(
154            Call::createComboCall(
155                syntax_plugin_combo_link::TAG,
156                DOKU_LEXER_UNMATCHED,
157                [],
158                null,
159                null,
160                $url
161            ));
162    }
163
164    public static function handeExit(Doku_Handler $handler)
165    {
166        $callStack = CallStack::createFromHandler($handler);
167        $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall();
168        if ($openingCall->getExitCode() === 0) {
169            // no error
170            self::closeLinkInCallStack($callStack);
171        }
172    }
173
174    public static function closeLinkInCallStack(CallStack $callStack)
175    {
176        $callStack->appendCallAtTheEnd(
177            Call::createComboCall(
178                syntax_plugin_combo_link::TAG,
179                DOKU_LEXER_EXIT
180            ));
181    }
182
183    public static function renderEnterSpecialXhtml(array $data): string
184    {
185        $errorMessage = $data[PluginUtility::EXIT_MESSAGE] ?? null;
186        if (!empty($errorMessage)) {
187            LogUtility::warning($errorMessage, PermalinkTag::CANONICAL);
188            return "<span class=\"text-warning\">{$errorMessage}</span>";
189        }
190        return "";
191    }
192
193    /**
194     * @throws ExceptionNotFound
195     */
196    public static function getPermalinkId(MarkupPath $requestedPage): string
197    {
198
199        $pageId = $requestedPage->getPageIdAbbr();
200        return PageUrlPath::encodePageId($pageId);
201    }
202}
203