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