xref: /plugin/combo/syntax/link.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1007225e5Sgerardnico<?php
2007225e5Sgerardnico
3007225e5Sgerardnico
437748cd8SNickeaurequire_once(__DIR__ . "/../ComboStrap/PluginUtility.php");
5007225e5Sgerardnico
64cadd4f8SNickeauuse ComboStrap\ArrayUtility;
7*04fd306cSNickeauuse ComboStrap\ButtonTag;
84cadd4f8SNickeauuse ComboStrap\Call;
9531e725cSNickeauuse ComboStrap\CallStack;
10*04fd306cSNickeauuse ComboStrap\DropDownTag;
11*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
12*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
13*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
14*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
15*04fd306cSNickeauuse ComboStrap\FileSystems;
16*04fd306cSNickeauuse ComboStrap\LinkMarkup;
174cadd4f8SNickeauuse ComboStrap\LogUtility;
18*04fd306cSNickeauuse ComboStrap\MarkupRef;
19*04fd306cSNickeauuse ComboStrap\MarkupPath;
20007225e5Sgerardnicouse ComboStrap\PluginUtility;
21531e725cSNickeauuse ComboStrap\TagAttributes;
22c3437056SNickeauuse ComboStrap\ThirdPartyPlugins;
23*04fd306cSNickeauuse ComboStrap\Web\UrlEndpoint;
24007225e5Sgerardnico
25007225e5Sgerardnicoif (!defined('DOKU_INC')) die();
26007225e5Sgerardnico
27007225e5Sgerardnico/**
28007225e5Sgerardnico *
29007225e5Sgerardnico * A link pattern to take over the link of Dokuwiki
30007225e5Sgerardnico * and transform it as a bootstrap link
31007225e5Sgerardnico *
32007225e5Sgerardnico * The handle of the move of link is to be found in the
33007225e5Sgerardnico * admin action {@link action_plugin_combo_linkmove}
34007225e5Sgerardnico *
35*04fd306cSNickeau * popular [[ wiki ]] syntax for linking notes
36*04fd306cSNickeau * and makes it easy to build personal wikis,
37*04fd306cSNickeau * team knowledge bases,
38*04fd306cSNickeau * or something like a Second Brain or a Zettelkasten.
39*04fd306cSNickeau *
40007225e5Sgerardnico */
41007225e5Sgerardnicoclass syntax_plugin_combo_link extends DokuWiki_Syntax_Plugin
42007225e5Sgerardnico{
43007225e5Sgerardnico    const TAG = 'link';
44ef295d81Sgerardnico    const COMPONENT = 'combo_link';
45007225e5Sgerardnico
465f891b7eSNickeau    /**
4785e82846SNickeau     * Disable the link component
4821913ab3SNickeau     */
4921913ab3SNickeau    const CONF_DISABLE_LINK = "disableLink";
5021913ab3SNickeau
5121913ab3SNickeau    /**
525f891b7eSNickeau     * The link Tag
53531e725cSNickeau     * a or p
545f891b7eSNickeau     */
555f891b7eSNickeau    const LINK_TAG = "linkTag";
565f891b7eSNickeau
5721913ab3SNickeau    /**
5821913ab3SNickeau     * Do the link component allows to be spawn on multilines
5921913ab3SNickeau     */
60531e725cSNickeau    const CLICKABLE_ATTRIBUTE = "clickable";
614cadd4f8SNickeau    public const ATTRIBUTE_LABEL = 'label';
624cadd4f8SNickeau    /**
634cadd4f8SNickeau     * The key of the array for the handle cache
644cadd4f8SNickeau     */
65*04fd306cSNickeau    public const MARKUP_REF_ATTRIBUTE = 'ref';
66*04fd306cSNickeau
674cadd4f8SNickeau    public const ATTRIBUTE_IMAGE_IN_LABEL = 'image-in-label';
684cadd4f8SNickeau
694cadd4f8SNickeau    /**
704cadd4f8SNickeau     * A link may have a title or not
714cadd4f8SNickeau     * ie
724cadd4f8SNickeau     * [[path:page]]
734cadd4f8SNickeau     * [[path:page|title]]
744cadd4f8SNickeau     * are valid
754cadd4f8SNickeau     *
764cadd4f8SNickeau     * Get the content until one of this character is found:
774cadd4f8SNickeau     *   * |
784cadd4f8SNickeau     *   * or ]]
794cadd4f8SNickeau     *   * or \n (No line break allowed, too much difficult to debug)
804cadd4f8SNickeau     *   * and not [ (for two links on the same line)
814cadd4f8SNickeau     */
824cadd4f8SNickeau    public const ENTRY_PATTERN_SINGLE_LINE = "\[\[[^\|\]]*(?=[^\n\[]*\]\])";
834cadd4f8SNickeau    public const EXIT_PATTERN = "\]\]";
844cadd4f8SNickeau
854cadd4f8SNickeau
864cadd4f8SNickeau    /**
874cadd4f8SNickeau     * Dokuwiki Link pattern ter info
884cadd4f8SNickeau     * Found in {@link \dokuwiki\Parsing\ParserMode\Internallink}
894cadd4f8SNickeau     */
904cadd4f8SNickeau    const SPECIAL_PATTERN = "\[\[.*?\]\](?!\])";
914cadd4f8SNickeau
924cadd4f8SNickeau    /**
934cadd4f8SNickeau     * The link title attribute (ie popup)
944cadd4f8SNickeau     */
954cadd4f8SNickeau    const TITLE_ATTRIBUTE = "title";
96*04fd306cSNickeau    const STRETCHED_LINK = "stretched-link";
97*04fd306cSNickeau    const CANONICAL = "link";
984cadd4f8SNickeau
994cadd4f8SNickeau
1004cadd4f8SNickeau    /**
1014cadd4f8SNickeau     * Parse the match of a syntax {@link DokuWiki_Syntax_Plugin} handle function
1024cadd4f8SNickeau     * @param $match
1034cadd4f8SNickeau     * @return string[] - an array with the attributes constant `ATTRIBUTE_xxxx` as key
1044cadd4f8SNickeau     *
1054cadd4f8SNickeau     * Code adapted from  {@link Doku_Handler::internallink()}
1064cadd4f8SNickeau     */
1074cadd4f8SNickeau    public static function parse($match): array
1084cadd4f8SNickeau    {
1094cadd4f8SNickeau
1104cadd4f8SNickeau        // Strip the opening and closing markup
111*04fd306cSNickeau        $linkString = preg_replace(array('/^\[\[/', '/]]$/u'), '', $match);
1124cadd4f8SNickeau
1134cadd4f8SNickeau        // Split title from URL
1144cadd4f8SNickeau        $linkArray = explode('|', $linkString, 2);
1154cadd4f8SNickeau
1164cadd4f8SNickeau        // Id
117*04fd306cSNickeau        $attributes[self::MARKUP_REF_ATTRIBUTE] = trim($linkArray[0]);
1184cadd4f8SNickeau
1194cadd4f8SNickeau
1204cadd4f8SNickeau        // Text or image
1214cadd4f8SNickeau        if (!isset($linkArray[1])) {
1224cadd4f8SNickeau            $attributes[self::ATTRIBUTE_LABEL] = null;
1234cadd4f8SNickeau        } else {
1244cadd4f8SNickeau            // An image in the title
125*04fd306cSNickeau            if (preg_match('/^{{[^}]+}}$/', $linkArray[1])) {
1264cadd4f8SNickeau                // If the title is an image, convert it to an array containing the image details
1274cadd4f8SNickeau                $attributes[self::ATTRIBUTE_IMAGE_IN_LABEL] = Doku_Handler_Parse_Media($linkArray[1]);
1284cadd4f8SNickeau            } else {
1294cadd4f8SNickeau                $attributes[self::ATTRIBUTE_LABEL] = $linkArray[1];
1304cadd4f8SNickeau            }
1314cadd4f8SNickeau        }
1324cadd4f8SNickeau
1334cadd4f8SNickeau        return $attributes;
1344cadd4f8SNickeau
1354cadd4f8SNickeau    }
13621913ab3SNickeau
1375f891b7eSNickeau
138007225e5Sgerardnico    /**
139007225e5Sgerardnico     * Syntax Type.
140007225e5Sgerardnico     *
141007225e5Sgerardnico     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
142007225e5Sgerardnico     * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
143007225e5Sgerardnico     */
144007225e5Sgerardnico    function getType()
145007225e5Sgerardnico    {
146007225e5Sgerardnico        return 'substition';
147007225e5Sgerardnico    }
148007225e5Sgerardnico
149007225e5Sgerardnico    /**
150007225e5Sgerardnico     * How Dokuwiki will add P element
151007225e5Sgerardnico     *
152007225e5Sgerardnico     *  * 'normal' - The plugin can be used inside paragraphs
153007225e5Sgerardnico     *  * 'block'  - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs
154007225e5Sgerardnico     *  * 'stack'  - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs
155007225e5Sgerardnico     *
156007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::getPType()
157007225e5Sgerardnico     */
158007225e5Sgerardnico    function getPType()
159007225e5Sgerardnico    {
160007225e5Sgerardnico        return 'normal';
161007225e5Sgerardnico    }
162007225e5Sgerardnico
163007225e5Sgerardnico    /**
164007225e5Sgerardnico     * @return array
165007225e5Sgerardnico     * Allow which kind of plugin inside
166007225e5Sgerardnico     *
167007225e5Sgerardnico     * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs')
168007225e5Sgerardnico     * because we manage self the content and we call self the parser
169007225e5Sgerardnico     */
1704cadd4f8SNickeau    function getAllowedTypes(): array
171007225e5Sgerardnico    {
172007225e5Sgerardnico        return array('substition', 'formatting', 'disabled');
173007225e5Sgerardnico    }
174007225e5Sgerardnico
17537748cd8SNickeau    /**
17637748cd8SNickeau     * @param string $mode
17737748cd8SNickeau     * @return bool
17837748cd8SNickeau     * Accepts inside
17937748cd8SNickeau     */
1804cadd4f8SNickeau    public function accepts($mode): bool
1815f891b7eSNickeau    {
1825f891b7eSNickeau        /**
1835f891b7eSNickeau         * To avoid that the description if it contains a link
1845f891b7eSNickeau         * will be taken by the links mode
1855f891b7eSNickeau         *
1865f891b7eSNickeau         * For instance, [[https://hallo|https://hallo]] will send https://hallo
1875f891b7eSNickeau         * to the external link mode
1885f891b7eSNickeau         */
1895f891b7eSNickeau        $linkModes = [
1905f891b7eSNickeau            "externallink",
1915f891b7eSNickeau            "locallink",
1925f891b7eSNickeau            "internallink",
1935f891b7eSNickeau            "interwikilink",
1945f891b7eSNickeau            "emaillink",
195fc45fbf7Sgerardnico            "emphasis", // double slash can not be used inside to preserve the possibility to write an URL in the description
1965f891b7eSNickeau            //"emphasis_open", // italic use // and therefore take over a link as description which is not handy when copying a tweet
1975f891b7eSNickeau            //"emphasis_close",
1985f891b7eSNickeau            //"acrnonym"
1995f891b7eSNickeau        ];
2005f891b7eSNickeau        if (in_array($mode, $linkModes)) {
2015f891b7eSNickeau            return false;
2025f891b7eSNickeau        } else {
2035f891b7eSNickeau            return true;
2045f891b7eSNickeau        }
2055f891b7eSNickeau    }
2065f891b7eSNickeau
2075f891b7eSNickeau
208007225e5Sgerardnico    /**
209007225e5Sgerardnico     * @see Doku_Parser_Mode::getSort()
210007225e5Sgerardnico     * The mode with the lowest sort number will win out
211007225e5Sgerardnico     */
212007225e5Sgerardnico    function getSort()
213007225e5Sgerardnico    {
214e8b2ff59SNickeau        /**
215e8b2ff59SNickeau         * It should be less than the number
216e8b2ff59SNickeau         * at {@link \dokuwiki\Parsing\ParserMode\Internallink::getSort}
217e8b2ff59SNickeau         * and the like
218e8b2ff59SNickeau         *
219e8b2ff59SNickeau         * For whatever reason, the number below should be less than 100,
220e8b2ff59SNickeau         * otherwise on windows with DokuWiki Stick, the link syntax may be not taken
221e8b2ff59SNickeau         * into account
222e8b2ff59SNickeau         */
223e8b2ff59SNickeau        return 99;
224007225e5Sgerardnico    }
225007225e5Sgerardnico
226007225e5Sgerardnico
227007225e5Sgerardnico    function connectTo($mode)
228007225e5Sgerardnico    {
229d262537cSgerardnico
23037748cd8SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)
23137748cd8SNickeau            &&
23237748cd8SNickeau            $mode !== PluginUtility::getModeFromPluginName(ThirdPartyPlugins::IMAGE_MAPPING_NAME)
23337748cd8SNickeau        ) {
23437748cd8SNickeau
2354cadd4f8SNickeau            $pattern = self::ENTRY_PATTERN_SINGLE_LINE;
2369337a630SNickeau            $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent()));
23737748cd8SNickeau
23821913ab3SNickeau        }
239d262537cSgerardnico
240007225e5Sgerardnico    }
241007225e5Sgerardnico
2425f891b7eSNickeau    public function postConnect()
2435f891b7eSNickeau    {
24421913ab3SNickeau        if (!$this->getConf(self::CONF_DISABLE_LINK, false)) {
2454cadd4f8SNickeau            $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent()));
2465f891b7eSNickeau        }
24721913ab3SNickeau    }
2485f891b7eSNickeau
249007225e5Sgerardnico
250007225e5Sgerardnico    /**
251007225e5Sgerardnico     * The handler for an internal link
252007225e5Sgerardnico     * based on `internallink` in {@link Doku_Handler}
253007225e5Sgerardnico     * The handler call the good renderer in {@link Doku_Renderer_xhtml} with
254007225e5Sgerardnico     * the parameters (ie for instance internallink)
255007225e5Sgerardnico     * @param string $match
256007225e5Sgerardnico     * @param int $state
257007225e5Sgerardnico     * @param int $pos
258007225e5Sgerardnico     * @param Doku_Handler $handler
259007225e5Sgerardnico     * @return array|bool
260007225e5Sgerardnico     */
261007225e5Sgerardnico    function handle($match, $state, $pos, Doku_Handler $handler)
262007225e5Sgerardnico    {
263007225e5Sgerardnico
2645f891b7eSNickeau        switch ($state) {
2655f891b7eSNickeau            case DOKU_LEXER_ENTER:
2664cadd4f8SNickeau                $parsedArray = self::parse($match);
2674cadd4f8SNickeau                $htmlAttributes = TagAttributes::createEmpty(self::TAG);
268*04fd306cSNickeau
2694cadd4f8SNickeau                /**
270*04fd306cSNickeau                 * The markup ref needs to be passed to the
271*04fd306cSNickeau                 * instructions stack (because we support link with a variable as markup ref
272*04fd306cSNickeau                 * via a {@link syntax_plugin_combo_fragment} in a {@link syntax_plugin_combo_iterator}
273*04fd306cSNickeau                 *
274*04fd306cSNickeau                 * The variable is replaced in the {@link syntax_plugin_combo_link::render()}
275*04fd306cSNickeau                 * at runtime while rendering
2764cadd4f8SNickeau                 */
277*04fd306cSNickeau                $markupRef = $parsedArray[self::MARKUP_REF_ATTRIBUTE];
278*04fd306cSNickeau                if ($markupRef !== null) {
279*04fd306cSNickeau                    $htmlAttributes->addComponentAttributeValue(self::MARKUP_REF_ATTRIBUTE, $markupRef);
2804cadd4f8SNickeau                }
2814cadd4f8SNickeau
2824cadd4f8SNickeau
2834cadd4f8SNickeau                /**
2844cadd4f8SNickeau                 * Extra HTML attribute
2854cadd4f8SNickeau                 */
286531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
287531e725cSNickeau                $parent = $callStack->moveToParent();
288007225e5Sgerardnico                $parentName = "";
2894cadd4f8SNickeau                if ($parent !== false) {
290531e725cSNickeau
291531e725cSNickeau                    /**
292531e725cSNickeau                     * Button Link
293531e725cSNickeau                     * Getting the attributes
294531e725cSNickeau                     */
295531e725cSNickeau                    $parentName = $parent->getTagName();
296*04fd306cSNickeau                    if ($parentName == ButtonTag::MARKUP_LONG) {
2974cadd4f8SNickeau                        $htmlAttributes->mergeWithCallStackArray($parent->getAttributes());
29821913ab3SNickeau                    }
299531e725cSNickeau
300531e725cSNickeau                    /**
301531e725cSNickeau                     * Searching Clickable parent
302531e725cSNickeau                     */
303531e725cSNickeau                    $maxLevel = 3;
304531e725cSNickeau                    $level = 0;
305531e725cSNickeau                    while (
306531e725cSNickeau                        $parent != false &&
307531e725cSNickeau                        !$parent->hasAttribute(self::CLICKABLE_ATTRIBUTE) &&
308531e725cSNickeau                        $level < $maxLevel
309531e725cSNickeau                    ) {
310531e725cSNickeau                        $parent = $callStack->moveToParent();
311531e725cSNickeau                        $level++;
3125f891b7eSNickeau                    }
313*04fd306cSNickeau                    if ($parent !== false) {
314531e725cSNickeau                        if ($parent->getAttribute(self::CLICKABLE_ATTRIBUTE)) {
315*04fd306cSNickeau                            $htmlAttributes->addClassName(self::STRETCHED_LINK);
316531e725cSNickeau                            $parent->addClassName("position-relative");
317531e725cSNickeau                            $parent->removeAttribute(self::CLICKABLE_ATTRIBUTE);
3185f891b7eSNickeau                        }
31921913ab3SNickeau                    }
32021913ab3SNickeau
321531e725cSNickeau                }
3224cadd4f8SNickeau                $returnedArray[PluginUtility::STATE] = $state;
3234cadd4f8SNickeau                $returnedArray[PluginUtility::ATTRIBUTES] = $htmlAttributes->toCallStackArray();
3244cadd4f8SNickeau                $returnedArray[PluginUtility::CONTEXT] = $parentName;
3254cadd4f8SNickeau                return $returnedArray;
3264cadd4f8SNickeau
3275f891b7eSNickeau            case DOKU_LEXER_UNMATCHED:
3285f891b7eSNickeau
32932b85071SNickeau                $data = PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler);
3305f891b7eSNickeau                /**
33132b85071SNickeau                 * Delete the separator `|` between the ref and the description if any
3325f891b7eSNickeau                 */
3331fa8c418SNickeau                $tag = CallStack::createFromHandler($handler);
3341fa8c418SNickeau                $parent = $tag->moveToParent();
3351fa8c418SNickeau                if ($parent->getTagName() == self::TAG) {
3365f891b7eSNickeau                    if (strpos($match, '|') === 0) {
33732b85071SNickeau                        $data[PluginUtility::PAYLOAD] = substr($match, 1);
3385f891b7eSNickeau                    }
339007225e5Sgerardnico                }
34032b85071SNickeau                return $data;
341007225e5Sgerardnico
3425f891b7eSNickeau            case DOKU_LEXER_EXIT:
343*04fd306cSNickeau
344531e725cSNickeau                $callStack = CallStack::createFromHandler($handler);
345531e725cSNickeau                $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall();
3464cadd4f8SNickeau
3475f891b7eSNickeau                $openingAttributes = $openingTag->getAttributes();
348531e725cSNickeau                $openingPosition = $openingTag->getKey();
3495f891b7eSNickeau
350531e725cSNickeau                $callStack->moveToEnd();
351531e725cSNickeau                $previousCall = $callStack->previous();
352531e725cSNickeau                $previousCallPosition = $previousCall->getKey();
353531e725cSNickeau                $previousCallContent = $previousCall->getCapturedContent();
354531e725cSNickeau
3554cadd4f8SNickeau                /**
3564cadd4f8SNickeau                 * Link label
3574cadd4f8SNickeau                 * is set if there is no content
3584cadd4f8SNickeau                 * between enter and exit node
3594cadd4f8SNickeau                 */
3604cadd4f8SNickeau                $linkLabel = "";
361531e725cSNickeau                if (
362531e725cSNickeau                    $openingPosition == $previousCallPosition // ie [[id]]
363531e725cSNickeau                    ||
364531e725cSNickeau                    ($openingPosition == $previousCallPosition - 1 && $previousCallContent == "|") // ie [[id|]]
365531e725cSNickeau                ) {
3665f891b7eSNickeau                    // There is no name
367*04fd306cSNickeau                    $markupRef = $openingTag->getAttribute(self::MARKUP_REF_ATTRIBUTE);
368*04fd306cSNickeau                    if ($markupRef !== null) {
369*04fd306cSNickeau                        try {
370*04fd306cSNickeau                            $linkLabel = LinkMarkup::createFromRef($markupRef)
371*04fd306cSNickeau                                ->getDefaultLabel();
372*04fd306cSNickeau                        } catch (ExceptionCompile $e) {
373*04fd306cSNickeau                            LogUtility::error("No default Label can be defined. Error while parsing the markup ref ($markupRef). Error: {$e->getMessage()}",self::CANONICAL, $e);
374*04fd306cSNickeau                        }
375*04fd306cSNickeau
3764cadd4f8SNickeau                    }
3775f891b7eSNickeau                }
3785f891b7eSNickeau                return array(
3795f891b7eSNickeau                    PluginUtility::STATE => $state,
3805f891b7eSNickeau                    PluginUtility::ATTRIBUTES => $openingAttributes,
3814cadd4f8SNickeau                    PluginUtility::PAYLOAD => $linkLabel,
38285e82846SNickeau                    PluginUtility::CONTEXT => $openingTag->getContext()
3835f891b7eSNickeau                );
3845f891b7eSNickeau        }
3855f891b7eSNickeau        return true;
3865f891b7eSNickeau
387007225e5Sgerardnico
388007225e5Sgerardnico    }
389007225e5Sgerardnico
390007225e5Sgerardnico    /**
391007225e5Sgerardnico     * Render the output
392007225e5Sgerardnico     * @param string $format
393007225e5Sgerardnico     * @param Doku_Renderer $renderer
394007225e5Sgerardnico     * @param array $data - what the function handle() return'ed
395007225e5Sgerardnico     * @return boolean - rendered correctly? (however, returned value is not used at the moment)
396007225e5Sgerardnico     * @see DokuWiki_Syntax_Plugin::render()
397007225e5Sgerardnico     *
398007225e5Sgerardnico     *
399007225e5Sgerardnico     */
400c3437056SNickeau    function render($format, Doku_Renderer $renderer, $data): bool
401007225e5Sgerardnico    {
402007225e5Sgerardnico        // The data
403*04fd306cSNickeau        $state = $data[PluginUtility::STATE];
404007225e5Sgerardnico        switch ($format) {
405007225e5Sgerardnico            case 'xhtml':
406007225e5Sgerardnico
407007225e5Sgerardnico                /** @var Doku_Renderer_xhtml $renderer */
408007225e5Sgerardnico                /**
40919b0880dSgerardnico                 * Cache problem may occurs while releasing
410007225e5Sgerardnico                 */
411007225e5Sgerardnico                if (isset($data[PluginUtility::ATTRIBUTES])) {
412531e725cSNickeau                    $callStackAttributes = $data[PluginUtility::ATTRIBUTES];
413007225e5Sgerardnico                } else {
414531e725cSNickeau                    $callStackAttributes = $data;
415007225e5Sgerardnico                }
4165f891b7eSNickeau
417*04fd306cSNickeau                PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::TAG);
4185f891b7eSNickeau
4195f891b7eSNickeau                switch ($state) {
4205f891b7eSNickeau                    case DOKU_LEXER_ENTER:
421531e725cSNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($callStackAttributes, self::TAG);
4224cadd4f8SNickeau
423*04fd306cSNickeau                        $markupRef = $tagAttributes->getValueAndRemove(self::MARKUP_REF_ATTRIBUTE);
424*04fd306cSNickeau                        if ($markupRef === null) {
425*04fd306cSNickeau                            $message = "Internal Error: A link reference was not found";
426*04fd306cSNickeau                            LogUtility::internalError($message);
427*04fd306cSNickeau                            $renderer->doc .= LogUtility::wrapInRedForHtml($message);
4284cadd4f8SNickeau                            return false;
4294cadd4f8SNickeau                        }
430*04fd306cSNickeau                        try {
431*04fd306cSNickeau                            $markupRef = syntax_plugin_combo_variable::replaceVariablesWithValuesFromContext($markupRef);
432*04fd306cSNickeau                            $markupLink = LinkMarkup::createFromRef($markupRef);
433*04fd306cSNickeau                            $markupAttributes = $markupLink->toAttributes();
434*04fd306cSNickeau                        } catch (ExceptionCompile $e) {
435*04fd306cSNickeau                            // uncomment to get the original error stack trace in dev
436*04fd306cSNickeau                            // and see where the exception comes from
437*04fd306cSNickeau                            // Don't forget to comment back
438*04fd306cSNickeau//                            if (PluginUtility::isDevOrTest()) {
439*04fd306cSNickeau//                                throw new ExceptionRuntime("Error on markup ref", self::TAG, 0, $e);
440*04fd306cSNickeau//                            }
441*04fd306cSNickeau
442*04fd306cSNickeau                            /**
443*04fd306cSNickeau                             * Error. Example: unknown inter-wiki ...
444*04fd306cSNickeau                             * We still create the a to be xhtml compliante
445*04fd306cSNickeau                             */
446*04fd306cSNickeau                            $url = UrlEndpoint::createSupportUrl();
447*04fd306cSNickeau                            $markupAttributes = TagAttributes::createEmpty()
448*04fd306cSNickeau                                ->addOutputAttributeValue("href", $url->toString())
449*04fd306cSNickeau                                ->addClassName(LinkMarkup::getHtmlClassNotExist());
450*04fd306cSNickeau                            $renderer->doc .= $markupAttributes->toHtmlEnterTag("a") . $e->getMessage();
451*04fd306cSNickeau
452*04fd306cSNickeau                            LogUtility::warning($e->getMessage(), "link", $e);
453*04fd306cSNickeau                            return false;
4544cadd4f8SNickeau
4554cadd4f8SNickeau                        }
456*04fd306cSNickeau                        // markup attributes is leading because it has already output attribute such as href
457*04fd306cSNickeau                        $markupAttributes->mergeWithCallStackArray($tagAttributes->toCallStackArray());
458*04fd306cSNickeau                        $tagAttributes = $markupAttributes;
459*04fd306cSNickeau
460d262537cSgerardnico
46119b0880dSgerardnico                        /**
4625f891b7eSNickeau                         * Extra styling
46319b0880dSgerardnico                         */
4645f891b7eSNickeau                        $parentTag = $data[PluginUtility::CONTEXT];
4654cadd4f8SNickeau                        $htmlPrefix = "";
4669f4383e9Sgerardnico                        switch ($parentTag) {
46721913ab3SNickeau                            /**
46821913ab3SNickeau                             * Button link
46921913ab3SNickeau                             */
470*04fd306cSNickeau                            case ButtonTag::MARKUP_LONG:
4714cadd4f8SNickeau                                $tagAttributes->addOutputAttributeValue("role", "button");
472*04fd306cSNickeau                                ButtonTag::processButtonAttributesToHtmlAttributes($tagAttributes);
4739f4383e9Sgerardnico                                break;
474*04fd306cSNickeau                            case DropDownTag::TAG:
4754cadd4f8SNickeau                                $tagAttributes->addClassName("dropdown-item");
4764cadd4f8SNickeau                                break;
4774cadd4f8SNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
4784cadd4f8SNickeau                                $tagAttributes->addClassName("navbar-link");
4794cadd4f8SNickeau                                $htmlPrefix = '<div class="navbar-nav">';
4804cadd4f8SNickeau                                break;
4814cadd4f8SNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
4824cadd4f8SNickeau                                $tagAttributes->addClassName("nav-link");
4834cadd4f8SNickeau                                $htmlPrefix = '<li class="nav-item">';
4844cadd4f8SNickeau                                break;
4854cadd4f8SNickeau                            default:
4865f891b7eSNickeau                            case syntax_plugin_combo_badge::TAG:
4879f4383e9Sgerardnico                            case syntax_plugin_combo_cite::TAG:
4889337a630SNickeau                            case syntax_plugin_combo_contentlistitem::DOKU_TAG:
4899f4383e9Sgerardnico                            case syntax_plugin_combo_preformatted::TAG:
4909f4383e9Sgerardnico                                break;
4910a517624Sgerardnico
4929f4383e9Sgerardnico                        }
4939f4383e9Sgerardnico
49419b0880dSgerardnico                        /**
49519b0880dSgerardnico                         * Add it to the rendering
49619b0880dSgerardnico                         */
4974cadd4f8SNickeau                        $renderer->doc .= $htmlPrefix . $tagAttributes->toHtmlEnterTag("a");
4985f891b7eSNickeau                        break;
4995f891b7eSNickeau                    case DOKU_LEXER_UNMATCHED:
50032b85071SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
5015f891b7eSNickeau                        break;
5025f891b7eSNickeau                    case DOKU_LEXER_EXIT:
5035f891b7eSNickeau
5045f891b7eSNickeau                        // if there is no link name defined, we get the name as ref in the payload
5055f891b7eSNickeau                        // otherwise null string
50685e82846SNickeau                        $renderer->doc .= $data[PluginUtility::PAYLOAD];
5075f891b7eSNickeau
508e3d0019cSgerardnico                        // Close the link
50985e82846SNickeau                        $renderer->doc .= "</a>";
510e3d0019cSgerardnico
511e3d0019cSgerardnico                        // Close the html wrapper element
5125f891b7eSNickeau                        $context = $data[PluginUtility::CONTEXT];
5135f891b7eSNickeau                        switch ($context) {
5145f891b7eSNickeau                            case syntax_plugin_combo_navbarcollapse::COMPONENT:
5155f891b7eSNickeau                                $renderer->doc .= '</div>';
5165f891b7eSNickeau                                break;
5175f891b7eSNickeau                            case syntax_plugin_combo_navbargroup::COMPONENT:
5185f891b7eSNickeau                                $renderer->doc .= '</li>';
5195f891b7eSNickeau                                break;
5205f891b7eSNickeau                        }
5215f891b7eSNickeau
5225f891b7eSNickeau                }
523007225e5Sgerardnico                return true;
5245f891b7eSNickeau            case 'metadata':
525007225e5Sgerardnico
5264cadd4f8SNickeau                /**
5274cadd4f8SNickeau                 * @var Doku_Renderer_metadata $renderer
5284cadd4f8SNickeau                 */
5294cadd4f8SNickeau                switch ($state) {
5304cadd4f8SNickeau                    case DOKU_LEXER_ENTER:
531007225e5Sgerardnico                        /**
532007225e5Sgerardnico                         * Keep track of the backlinks ie meta['relation']['references']
533007225e5Sgerardnico                         * @var Doku_Renderer_metadata $renderer
534007225e5Sgerardnico                         */
5354cadd4f8SNickeau                        $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]);
536*04fd306cSNickeau
537*04fd306cSNickeau                        $markupRef = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE);
538*04fd306cSNickeau                        if ($markupRef === null) {
539*04fd306cSNickeau                            LogUtility::internalError("The markup ref was not found for a link.");
5404cadd4f8SNickeau                            return false;
541007225e5Sgerardnico                        }
542*04fd306cSNickeau                        try {
543*04fd306cSNickeau                            $type = MarkupRef::createLinkFromRef($markupRef)
544*04fd306cSNickeau                                ->getSchemeType();
545*04fd306cSNickeau                        } catch (ExceptionCompile $e) {
546*04fd306cSNickeau                            LogUtility::error("The markup ref could not be parsed. Error:{$e->getMessage()}");
547*04fd306cSNickeau                            return false;
548*04fd306cSNickeau                        }
5494cadd4f8SNickeau                        $name = $tagAttributes->getValue(self::ATTRIBUTE_LABEL);
5509f4383e9Sgerardnico
5514cadd4f8SNickeau                        switch ($type) {
5524cadd4f8SNickeau                            case MarkupRef::WIKI_URI:
5534cadd4f8SNickeau                                /**
5544cadd4f8SNickeau                                 * The relative link should be passed (ie the original)
5554cadd4f8SNickeau                                 * Dokuwiki has a default description
5564cadd4f8SNickeau                                 * We can't pass empty or the array(title), it does not work
5574cadd4f8SNickeau                                 */
5584cadd4f8SNickeau                                $descriptionToDelete = "b";
559*04fd306cSNickeau                                $renderer->internallink($markupRef, $descriptionToDelete);
5604cadd4f8SNickeau                                $renderer->doc = substr($renderer->doc, 0, -strlen($descriptionToDelete));
5614cadd4f8SNickeau                                break;
5624cadd4f8SNickeau                            case MarkupRef::WEB_URI:
563*04fd306cSNickeau                                $renderer->externallink($markupRef, $name);
5644cadd4f8SNickeau                                break;
5654cadd4f8SNickeau                            case MarkupRef::LOCAL_URI:
566*04fd306cSNickeau                                $renderer->locallink($markupRef, $name);
5674cadd4f8SNickeau                                break;
5684cadd4f8SNickeau                            case MarkupRef::EMAIL_URI:
569*04fd306cSNickeau                                $renderer->emaillink($markupRef, $name);
5704cadd4f8SNickeau                                break;
5714cadd4f8SNickeau                            case MarkupRef::INTERWIKI_URI:
572*04fd306cSNickeau                                $interWikiSplit = preg_split("/>/", $markupRef);
573*04fd306cSNickeau                                $renderer->interwikilink($markupRef, $name, $interWikiSplit[0], $interWikiSplit[1]);
5744cadd4f8SNickeau                                break;
5754cadd4f8SNickeau                            case MarkupRef::WINDOWS_SHARE_URI:
576*04fd306cSNickeau                                $renderer->windowssharelink($markupRef, $name);
5774cadd4f8SNickeau                                break;
5784cadd4f8SNickeau                            case MarkupRef::VARIABLE_URI:
5794cadd4f8SNickeau                                // No backlinks for link template
5804cadd4f8SNickeau                                break;
5814cadd4f8SNickeau                            default:
582*04fd306cSNickeau                                LogUtility::msg("The markup reference ({$markupRef}) with the type $type was not processed into the metadata");
5839f4383e9Sgerardnico                        }
584007225e5Sgerardnico
585007225e5Sgerardnico                        return true;
5864cadd4f8SNickeau                    case DOKU_LEXER_UNMATCHED:
5874cadd4f8SNickeau                        $renderer->doc .= PluginUtility::renderUnmatched($data);
5884cadd4f8SNickeau                        break;
5895f891b7eSNickeau                }
590007225e5Sgerardnico                break;
591007225e5Sgerardnico
592531e725cSNickeau            case renderer_plugin_combo_analytics::RENDERER_FORMAT:
5935f891b7eSNickeau
594*04fd306cSNickeau                if ($state === DOKU_LEXER_ENTER) {
595007225e5Sgerardnico                    /**
596007225e5Sgerardnico                     *
597007225e5Sgerardnico                     * @var renderer_plugin_combo_analytics $renderer
598007225e5Sgerardnico                     */
5994cadd4f8SNickeau                    $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES]);
600*04fd306cSNickeau                    $ref = $tagAttributes->getValue(self::MARKUP_REF_ATTRIBUTE);
601*04fd306cSNickeau                    try {
602*04fd306cSNickeau                        $markupRef = LinkMarkup::createFromRef($ref);
603*04fd306cSNickeau                    } catch (ExceptionCompile $e) {
604*04fd306cSNickeau                        LogUtility::error("Error while parsing the ref ($ref). Error: {$e->getMessage()}. No further analytics.");
6054cadd4f8SNickeau                        return false;
6064cadd4f8SNickeau                    }
607*04fd306cSNickeau                    $refType = $markupRef->getMarkupRef()->getSchemeType();
6084cadd4f8SNickeau
6094cadd4f8SNickeau
6104cadd4f8SNickeau                    /**
6114cadd4f8SNickeau                     * @param array $stats
6124cadd4f8SNickeau                     * Calculate internal link statistics
6134cadd4f8SNickeau                     */
6144cadd4f8SNickeau                    $stats = &$renderer->stats;
6154cadd4f8SNickeau                    switch ($refType) {
6164cadd4f8SNickeau
6174cadd4f8SNickeau                        case MarkupRef::WIKI_URI:
6184cadd4f8SNickeau
6194cadd4f8SNickeau                            /**
6204cadd4f8SNickeau                             * Internal link count
6214cadd4f8SNickeau                             */
622*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT, $stats)) {
623*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT] = 0;
6244cadd4f8SNickeau                            }
625*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_COUNT]++;
6264cadd4f8SNickeau
6274cadd4f8SNickeau
6284cadd4f8SNickeau                            /**
6294cadd4f8SNickeau                             * Broken link ?
6304cadd4f8SNickeau                             */
631*04fd306cSNickeau                            try {
632*04fd306cSNickeau                                $path = $markupRef->getMarkupRef()->getPath();
633*04fd306cSNickeau                                $linkedPage = MarkupPath::createPageFromPathObject($path);
634*04fd306cSNickeau                                if (!FileSystems::exists($path)) {
635*04fd306cSNickeau                                    $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_BROKEN_COUNT]++;
636*04fd306cSNickeau                                    $stats[renderer_plugin_combo_analytics::INFO][] = "The internal linked page `{$linkedPage}` does not exist";
637*04fd306cSNickeau                                }
638*04fd306cSNickeau                            } catch (ExceptionNotFound $e) {
639*04fd306cSNickeau                                // no local path
6404cadd4f8SNickeau                            }
6414cadd4f8SNickeau
6424cadd4f8SNickeau                            /**
6434cadd4f8SNickeau                             * Calculate link distance
6444cadd4f8SNickeau                             */
6454cadd4f8SNickeau                            global $ID;
646*04fd306cSNickeau                            $id = $linkedPage->getWikiId();
6474cadd4f8SNickeau                            $a = explode(':', getNS($ID));
6484cadd4f8SNickeau                            $b = explode(':', getNS($id));
6494cadd4f8SNickeau                            while (isset($a[0]) && $a[0] == $b[0]) {
6504cadd4f8SNickeau                                array_shift($a);
6514cadd4f8SNickeau                                array_shift($b);
6524cadd4f8SNickeau                            }
6534cadd4f8SNickeau                            $length = count($a) + count($b);
654*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::INTERNAL_LINK_DISTANCE][] = $length;
6554cadd4f8SNickeau                            break;
6564cadd4f8SNickeau
6574cadd4f8SNickeau                        case MarkupRef::WEB_URI:
6584cadd4f8SNickeau
659*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT, $stats)) {
660*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT] = 0;
6614cadd4f8SNickeau                            }
662*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::EXTERNAL_LINK_COUNT]++;
6634cadd4f8SNickeau                            break;
6644cadd4f8SNickeau
6654cadd4f8SNickeau                        case MarkupRef::LOCAL_URI:
6664cadd4f8SNickeau
667*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::LOCAL_LINK_COUNT, $stats)) {
668*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT] = 0;
6694cadd4f8SNickeau                            }
670*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::LOCAL_LINK_COUNT]++;
6714cadd4f8SNickeau                            break;
6724cadd4f8SNickeau
6734cadd4f8SNickeau                        case MarkupRef::INTERWIKI_URI:
6744cadd4f8SNickeau
675*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT, $stats)) {
676*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT] = 0;
6774cadd4f8SNickeau                            }
678*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::INTERWIKI_LINK_COUNT]++;
6794cadd4f8SNickeau                            break;
6804cadd4f8SNickeau
6814cadd4f8SNickeau                        case MarkupRef::EMAIL_URI:
6824cadd4f8SNickeau
683*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::EMAIL_COUNT, $stats)) {
684*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::EMAIL_COUNT] = 0;
6854cadd4f8SNickeau                            }
686*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::EMAIL_COUNT]++;
6874cadd4f8SNickeau                            break;
6884cadd4f8SNickeau
6894cadd4f8SNickeau                        case MarkupRef::WINDOWS_SHARE_URI:
6904cadd4f8SNickeau
691*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT, $stats)) {
692*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT] = 0;
6934cadd4f8SNickeau                            }
694*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::WINDOWS_SHARE_COUNT]++;
6954cadd4f8SNickeau                            break;
6964cadd4f8SNickeau
6974cadd4f8SNickeau                        case MarkupRef::VARIABLE_URI:
6984cadd4f8SNickeau
699*04fd306cSNickeau                            if (!array_key_exists(renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT, $stats)) {
700*04fd306cSNickeau                                $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT] = 0;
7014cadd4f8SNickeau                            }
702*04fd306cSNickeau                            $stats[renderer_plugin_combo_analytics::TEMPLATE_LINK_COUNT]++;
7034cadd4f8SNickeau                            break;
7044cadd4f8SNickeau
7054cadd4f8SNickeau                        default:
7064cadd4f8SNickeau
7074cadd4f8SNickeau                            LogUtility::msg("The link `{$ref}` with the type ($refType)  is not taken into account into the statistics");
7084cadd4f8SNickeau
7094cadd4f8SNickeau                    }
7104cadd4f8SNickeau
7114cadd4f8SNickeau
712007225e5Sgerardnico                    break;
7135f891b7eSNickeau                }
714007225e5Sgerardnico
715007225e5Sgerardnico        }
716*04fd306cSNickeau
717007225e5Sgerardnico        return false;
718007225e5Sgerardnico    }
719007225e5Sgerardnico
720007225e5Sgerardnico
7214cadd4f8SNickeau    /**
7224cadd4f8SNickeau     * Utility function to add a link into the callstack
7234cadd4f8SNickeau     * @param CallStack $callStack
7244cadd4f8SNickeau     * @param TagAttributes $tagAttributes
7254cadd4f8SNickeau     */
726*04fd306cSNickeau    public
727*04fd306cSNickeau    static function addOpenLinkTagInCallStack(CallStack $callStack, TagAttributes $tagAttributes)
7284cadd4f8SNickeau    {
7294cadd4f8SNickeau        $parent = $callStack->moveToParent();
7304cadd4f8SNickeau        $context = "";
7314cadd4f8SNickeau        $attributes = $tagAttributes->toCallStackArray();
7324cadd4f8SNickeau        if ($parent !== false) {
7334cadd4f8SNickeau            $context = $parent->getTagName();
734*04fd306cSNickeau            if ($context === ButtonTag::MARKUP_LONG) {
7354cadd4f8SNickeau                // the link takes by default the data from the button
7364cadd4f8SNickeau                $parentAttributes = $parent->getAttributes();
7374cadd4f8SNickeau                if ($parentAttributes !== null) {
7384cadd4f8SNickeau                    $attributes = ArrayUtility::mergeByValue($parentAttributes, $attributes);
7394cadd4f8SNickeau                }
7404cadd4f8SNickeau            }
7414cadd4f8SNickeau        }
7424cadd4f8SNickeau        $callStack->appendCallAtTheEnd(
7434cadd4f8SNickeau            Call::createComboCall(
7444cadd4f8SNickeau                syntax_plugin_combo_link::TAG,
7454cadd4f8SNickeau                DOKU_LEXER_ENTER,
7464cadd4f8SNickeau                $attributes,
7474cadd4f8SNickeau                $context
7484cadd4f8SNickeau            ));
7494cadd4f8SNickeau    }
7504cadd4f8SNickeau
751*04fd306cSNickeau    public
752*04fd306cSNickeau    static function addExitLinkTagInCallStack(CallStack $callStack)
7534cadd4f8SNickeau    {
7544cadd4f8SNickeau        $callStack->appendCallAtTheEnd(
7554cadd4f8SNickeau            Call::createComboCall(
7564cadd4f8SNickeau                syntax_plugin_combo_link::TAG,
7574cadd4f8SNickeau                DOKU_LEXER_EXIT
7584cadd4f8SNickeau            ));
7594cadd4f8SNickeau    }
760007225e5Sgerardnico}
761007225e5Sgerardnico
762