getConf(self::CONF_DISABLE_LINK, false) && $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME) ) { $pattern = self::ENTRY_PATTERN_SINGLE_LINE; $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); } } public function postConnect() { if (!$this->getConf(self::CONF_DISABLE_LINK, false)) { $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); } } /** * The handler for an internal link * based on `internallink` in {@link Doku_Handler} * The handler call the good renderer in {@link Doku_Renderer_xhtml} with * the parameters (ie for instance internallink) * @param string $match * @param int $state * @param int $pos * @param Doku_Handler $handler * @return array|bool */ function handle($match, $state, $pos, Doku_Handler $handler) { switch ($state) { case DOKU_LEXER_ENTER: $parsedArray = self::parse($match); $htmlAttributes = TagAttributes::createEmpty(self::TAG); /** * The markup ref needs to be passed to the * instructions stack (because we support link with a variable as markup ref * via a {@link syntax_plugin_combo_fragment} in a {@link syntax_plugin_combo_iterator} * * The variable is replaced in the {@link syntax_plugin_combo_link::render()} * at runtime while rendering */ $markupRef = $parsedArray[self::MARKUP_REF_ATTRIBUTE]; if ($markupRef !== null) { $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef); } /** * Extra HTML attribute */ $callStack = CallStack::createFromHandler($handler); $parent = $callStack->moveToParent(); $parentName = ""; if ($parent !== false) { /** * Button Link * Getting the attributes */ $parentName = $parent->getTagName(); if ($parentName == ButtonTag::MARKUP_LONG) { $htmlAttributes->mergeWithCallStackArray($parent->getAttributes()); } /** * Searching Clickable parent */ $maxLevel = 3; $level = 0; while ( $parent != false && !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) && $level < $maxLevel ) { $parent = $callStack->moveToParent(); $level++; } if ($parent !== false) { if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) { $htmlAttributes->addClassName(self::STRETCHED_LINK); $parent->addClassName("position-relative"); $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE); } } } $returnedArray[PluginUtility::STATE] = $state; $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray(); $returnedArray[PluginUtility::CONTEXT] = $parentName; return $returnedArray; case DOKU_LEXER_UNMATCHED: $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); /** * Delete the separator `|` between the ref and the description if any */ $tag = CallStack::createFromHandler($handler); $parent = $tag->moveToParent(); if ($parent->getTagName() == self::TAG) { if (strpos($match, '|') === 0) { $data[PluginUtility::PAYLOAD] = substr($match, 1); } } return $data; case DOKU_LEXER_EXIT: $callStack = CallStack::createFromHandler($handler); $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); $openingAttributes = $openingTag->getAttributes(); $openingPosition = $openingTag->getKey(); $callStack->moveToEnd(); $previousCall = $callStack->previous(); $previousCallPosition = $previousCall->getKey(); $previousCallContent = $previousCall->getCapturedContent(); /** * Link label * is set if there is no content * between enter and exit node */ $linkLabel = ""; if ( $openingPosition == $previousCallPosition // ie [[id]] || ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]] ) { // There is no name $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE); if ($markupRef !== null) { try { $linkLabel = LinkMarkup::createFromRef($markupRef) ->getDefaultLabel(); } catch (ExceptionCompile $e) { LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}", self::CANONICAL, $e); } } } return array( PluginUtility::STATE => $state, PluginUtility::ATTRIBUTES => $openingAttributes, PluginUtility::PAYLOAD => $linkLabel, PluginUtility::CONTEXT => $openingTag->getContext() ); } return true; } /** * Render the output * @param string $format * @param Doku_Renderer $renderer * @param array $data - what the function handle() return'ed * @return boolean - rendered correctly? (however, returned value is not used at the moment) * @see DokuWiki_Syntax_Plugin::render() * * */ function render($format, Doku_Renderer $renderer, $data): bool { // The data $state = $data[PluginUtility::STATE]; switch ($format) { case 'xhtml': /** @var Doku_Renderer_xhtml $renderer */ /** * Cache problem may occurs while releasing */ if (isset($data[PluginUtility::ATTRIBUTES])) { $callStackAttributes = $data[PluginUtility::ATTRIBUTES]; } else { $callStackAttributes = $data; } PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG); switch ($state) { case DOKU_LEXER_ENTER: $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG); $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE); if ($markupRef === null) { $message = "Internal Error: A link reference was not found"; LogUtility::internalError($message); $renderer->doc .= LogUtility::wrapInRedForHtml($message); return false; } try { $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef); $markupLink = LinkMarkup::createFromRef($markupRef); $markupAttributes = $markupLink->toAttributes(); } catch (ExceptionCompile $e) { // uncomment to get the original error stack trace in dev // and see where the exception comes from // Don't forget to comment back // if (PluginUtility::isDevOrTest()) { // throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e); // } /** * Error. Example: unknown inter-wiki ... * We still create the a to be xhtml compliante */ $url = UrlEndpoint::createSupportUrl(); $markupAttributes = TagAttributes::createEmpty() ->addOutputAttributeValue("href", $url->toString()) ->addClassName(LinkMarkup::getHtmlClassNotExist()); $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage(); LogUtility::warning($e->getMessage(), "link", $e); return false; } // markup attributes is leading because it has already output attribute such as href $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray()); $tagAttributes = $markupAttributes; /** * Extra styling */ $parentTag = $data[PluginUtility::CONTEXT]; $htmlPrefix = ""; switch ($parentTag) { /** * Button link */ case ButtonTag::MARKUP_LONG: $tagAttributes->addOutputAttributeValue("role", "button"); ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes); break; case DropDownTag::TAG: $tagAttributes->addClassName("dropdown-item"); break; case syntax_plugin_combo_navbarcollapse::COMPONENT: $tagAttributes->addClassName("navbar-link"); $htmlPrefix = ''; break; case syntax_plugin_combo_navbargroup::COMPONENT: $renderer->doc .= ''; break; } } return true; case 'metadata': /** * @var Doku_Renderer_metadata $renderer */ switch ($state) { case DOKU_LEXER_ENTER: /** * Keep track of the backlinks ie meta['relation']['references'] * @var Doku_Renderer_metadata $renderer */ $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); if ($markupRef === null) { LogUtility::internalError("The markup ref was not found for a link."); return false; } try { $type = MarkupRef::createLinkFromRef($markupRef) ->getSchemeType(); } catch (ExceptionCompile $e) { LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}"); return false; } $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL); switch ($type) { case MarkupRef::WIKI_URI: /** * The relative link should be passed (ie the original) * Dokuwiki has a default description * We can't pass empty or the array(title), it does not work */ $descriptionToDelete = "b"; $renderer->internallink($markupRef, $descriptionToDelete); $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete)); break; case MarkupRef::WEB_URI: $renderer->externallink($markupRef, $name); break; case MarkupRef::LOCAL_URI: $renderer->locallink($markupRef, $name); break; case MarkupRef::EMAIL_URI: $renderer->emaillink($markupRef, $name); break; case MarkupRef::INTERWIKI_URI: $interWikiSplit = preg_split("/>/", $markupRef); $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]); break; case MarkupRef::WINDOWS_SHARE_URI: $renderer->windowssharelink($markupRef, $name); break; case MarkupRef::VARIABLE_URI: // No backlinks for link template break; default: LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata"); } return true; case DOKU_LEXER_UNMATCHED: $renderer->doc .= PluginUtility::renderUnmatched($data); break; } break; case renderer_plugin_combo_analytics::RENDERER_FORMAT: if ($state === DOKU_LEXER_ENTER) { /** * * @var renderer_plugin_combo_analytics $renderer */ $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]); $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE); try { $markupRef = LinkMarkup::createFromRef($ref); } catch (ExceptionCompile $e) { LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics."); return false; } $refType = $markupRef->getMarkupRef()->getSchemeType(); /** * @param array $stats * Calculate internal link statistics */ $stats = &$renderer->stats; switch ($refType) { case MarkupRef::WIKI_URI: /** * Internal link count */ if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++; /** * Broken link ? */ try { $path = $markupRef->getMarkupRef()->getPath(); $linkedPage = MarkupPath::createPageFromPathObject($path); if (!FileSystems::exists($path)) { $internalLinkBroken = $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] ?? 0; $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT] = $internalLinkBroken + 1; $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist"; } } catch (ExceptionNotFound $e) { // no local path } /** * Calculate link distance */ global $ID; $id = $linkedPage->getWikiId(); $a = explode(':', getNS($ID)); $b = explode(':', getNS($id)); while (isset($a[0]) && $a[0] == $b[0]) { array_shift($a); array_shift($b); } $length = count($a) + count($b); $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length; break; case MarkupRef::WEB_URI: if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++; break; case MarkupRef::LOCAL_URI: if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++; break; case MarkupRef::INTERWIKI_URI: if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++; break; case MarkupRef::EMAIL_URI: if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++; break; case MarkupRef::WINDOWS_SHARE_URI: if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++; break; case MarkupRef::VARIABLE_URI: if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) { $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0; } $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++; break; default: LogUtility::msg("The link `{$ref}` with the type ($refType) is not taken into account into the statistics"); } break; } } return false; } /** * Utility function to add a link into the callstack * @param CallStack $callStack * @param TagAttributes $tagAttributes */ public static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes) { $parent = $callStack->moveToParent(); $context = ""; $attributes = $tagAttributes->toCallStackArray(); if ($parent !== false) { $context = $parent->getTagName(); if ($context === ButtonTag::MARKUP_LONG) { // the link takes by default the data from the button $parentAttributes = $parent->getAttributes(); if ($parentAttributes !== null) { $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes); } } } $callStack->appendCallAtTheEnd( Call::createComboCall( syntax_plugin_combo_link::TAG, DOKU_LEXER_ENTER, $attributes, $context )); } public static function addExitLinkTagInCallStack(CallStack $callStack) { $callStack->appendCallAtTheEnd( Call::createComboCall( syntax_plugin_combo_link::TAG, DOKU_LEXER_EXIT )); } }