xref: /template/strap/ComboStrap/LinkMarkup.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau/**
3*04fd306cSNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4*04fd306cSNickeau *
5*04fd306cSNickeau * This source code is licensed under the GPL license found in the
6*04fd306cSNickeau * COPYING  file in the root directory of this source tree.
7*04fd306cSNickeau *
8*04fd306cSNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9*04fd306cSNickeau * @author   ComboStrap <support@combostrap.com>
10*04fd306cSNickeau *
11*04fd306cSNickeau */
12*04fd306cSNickeau
13*04fd306cSNickeaunamespace ComboStrap;
14*04fd306cSNickeau
15*04fd306cSNickeau
16*04fd306cSNickeauuse ComboStrap\Meta\Field\PageTemplateName;
17*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
18*04fd306cSNickeauuse Doku_Renderer_xhtml;
19*04fd306cSNickeauuse dokuwiki\Extension\PluginTrait;
20*04fd306cSNickeauuse dokuwiki\Utf8\Conversion;
21*04fd306cSNickeauuse syntax_plugin_combo_link;
22*04fd306cSNickeau
23*04fd306cSNickeau
24*04fd306cSNickeau/**
25*04fd306cSNickeau *
26*04fd306cSNickeau * @package ComboStrap
27*04fd306cSNickeau *
28*04fd306cSNickeau * Parse the ref found in a markup link
29*04fd306cSNickeau * and return an {@link LinkMarkup::toAttributes()} array for an anchor (a)
30*04fd306cSNickeau * with href, style, ... attributes
31*04fd306cSNickeau *
32*04fd306cSNickeau */
33*04fd306cSNickeauclass LinkMarkup
34*04fd306cSNickeau{
35*04fd306cSNickeau
36*04fd306cSNickeau
37*04fd306cSNickeau    /**
38*04fd306cSNickeau     * Class added to the type of link
39*04fd306cSNickeau     * Class have styling rule conflict, they are by default not set
40*04fd306cSNickeau     * but this configuration permits to turn it back
41*04fd306cSNickeau     */
42*04fd306cSNickeau    const CONF_USE_DOKUWIKI_CLASS_NAME = "useDokuwikiLinkClassName";
43*04fd306cSNickeau
44*04fd306cSNickeau    /**
45*04fd306cSNickeau     * This configuration will set for all internal link
46*04fd306cSNickeau     * the {@link LinkMarkup::PREVIEW_ATTRIBUTE} preview attribute
47*04fd306cSNickeau     */
48*04fd306cSNickeau    const CONF_PREVIEW_LINK = "previewLink";
49*04fd306cSNickeau    const CONF_PREVIEW_LINK_DEFAULT = 0;
50*04fd306cSNickeau
51*04fd306cSNickeau
52*04fd306cSNickeau    const TEXT_ERROR_CLASS = "text-danger";
53*04fd306cSNickeau
54*04fd306cSNickeau    /**
55*04fd306cSNickeau     * The known parameters for an email url
56*04fd306cSNickeau     * The other are styling attribute :)
57*04fd306cSNickeau     */
58*04fd306cSNickeau    const EMAIL_VALID_PARAMETERS = ["subject"];
59*04fd306cSNickeau
60*04fd306cSNickeau    /**
61*04fd306cSNickeau     * If set, it will show a page preview
62*04fd306cSNickeau     */
63*04fd306cSNickeau    const PREVIEW_ATTRIBUTE = "preview";
64*04fd306cSNickeau
65*04fd306cSNickeau
66*04fd306cSNickeau    /**
67*04fd306cSNickeau     * Highlight Key
68*04fd306cSNickeau     * Adding this property to the internal query will highlight the words
69*04fd306cSNickeau     *
70*04fd306cSNickeau     * See {@link html_hilight}
71*04fd306cSNickeau     */
72*04fd306cSNickeau    const SEARCH_HIGHLIGHT_QUERY_PROPERTY = "s";
73*04fd306cSNickeau    const DATA_WIKI_ID = "data-wiki-id";
74*04fd306cSNickeau
75*04fd306cSNickeau    /**
76*04fd306cSNickeau     * For styling on the anchor tag (ie a)
77*04fd306cSNickeau     */
78*04fd306cSNickeau    public const ANCHOR_HTML_SNIPPET_ID = "anchor-branding";
79*04fd306cSNickeau
80*04fd306cSNickeau    /**
81*04fd306cSNickeau     * Url properties
82*04fd306cSNickeau     * that are not seen as styling properties
83*04fd306cSNickeau     * for a page
84*04fd306cSNickeau     * We could also build the {@link FetcherPage}
85*04fd306cSNickeau     * and see which attributes were not taken ?
86*04fd306cSNickeau     */
87*04fd306cSNickeau    const PROTECTED_URL_PROPERTY = [
88*04fd306cSNickeau        self::SEARCH_HIGHLIGHT_QUERY_PROPERTY,
89*04fd306cSNickeau        DokuWikiId::DOKUWIKI_ID_ATTRIBUTE,
90*04fd306cSNickeau        PageTemplateName::PROPERTY_NAME,
91*04fd306cSNickeau        FetcherPage::PURGE
92*04fd306cSNickeau    ];
93*04fd306cSNickeau
94*04fd306cSNickeau
95*04fd306cSNickeau    private MarkupRef $markupRef;
96*04fd306cSNickeau
97*04fd306cSNickeau
98*04fd306cSNickeau    private TagAttributes $stylingAttributes;
99*04fd306cSNickeau
100*04fd306cSNickeau    /**
101*04fd306cSNickeau     * Link constructor.
102*04fd306cSNickeau     * @param String $ref
103*04fd306cSNickeau     * @throws ExceptionBadArgument
104*04fd306cSNickeau     * @throws ExceptionBadSyntax
105*04fd306cSNickeau     * @throws ExceptionNotFound
106*04fd306cSNickeau     */
107*04fd306cSNickeau    public function __construct(string $ref)
108*04fd306cSNickeau    {
109*04fd306cSNickeau
110*04fd306cSNickeau        $this->stylingAttributes = TagAttributes::createEmpty(syntax_plugin_combo_link::TAG);
111*04fd306cSNickeau
112*04fd306cSNickeau        $this->markupRef = MarkupRef::createLinkFromRef($ref);
113*04fd306cSNickeau
114*04fd306cSNickeau        $this->collectStylingAttributeInUrl();
115*04fd306cSNickeau
116*04fd306cSNickeau
117*04fd306cSNickeau    }
118*04fd306cSNickeau
119*04fd306cSNickeau    public static function createFromPageIdOrPath($id): LinkMarkup
120*04fd306cSNickeau    {
121*04fd306cSNickeau        WikiPath::addRootSeparatorIfNotPresent($id);
122*04fd306cSNickeau        try {
123*04fd306cSNickeau            return new LinkMarkup($id);
124*04fd306cSNickeau        } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
125*04fd306cSNickeau            throw new ExceptionRuntime("Internal error: an id should be a good reference");
126*04fd306cSNickeau        }
127*04fd306cSNickeau    }
128*04fd306cSNickeau
129*04fd306cSNickeau    /**
130*04fd306cSNickeau     * @throws ExceptionBadArgument
131*04fd306cSNickeau     * @throws ExceptionBadSyntax
132*04fd306cSNickeau     * @throws ExceptionNotFound
133*04fd306cSNickeau     */
134*04fd306cSNickeau    public static function createFromRef(string $ref): LinkMarkup
135*04fd306cSNickeau    {
136*04fd306cSNickeau        return new LinkMarkup($ref);
137*04fd306cSNickeau    }
138*04fd306cSNickeau
139*04fd306cSNickeau    public static function getHtmlClassLocalLink(): string
140*04fd306cSNickeau    {
141*04fd306cSNickeau        return "link-local";
142*04fd306cSNickeau    }
143*04fd306cSNickeau
144*04fd306cSNickeau
145*04fd306cSNickeau    /**
146*04fd306cSNickeau     *
147*04fd306cSNickeau     * @throws ExceptionNotFound
148*04fd306cSNickeau     */
149*04fd306cSNickeau    public function toAttributes(): TagAttributes
150*04fd306cSNickeau    {
151*04fd306cSNickeau
152*04fd306cSNickeau        $outputAttributes = $this->stylingAttributes;
153*04fd306cSNickeau
154*04fd306cSNickeau
155*04fd306cSNickeau        $url = $this->getMarkupRef()->getUrl();
156*04fd306cSNickeau        $outputAttributes->addOutputAttributeValue("href", $url->toString());
157*04fd306cSNickeau
158*04fd306cSNickeau        /**
159*04fd306cSNickeau         * The search term
160*04fd306cSNickeau         * Code adapted found at {@link Doku_Renderer_xhtml::internallink()}
161*04fd306cSNickeau         * We can't use the previous {@link wl function}
162*04fd306cSNickeau         * because it encode too much
163*04fd306cSNickeau         */
164*04fd306cSNickeau        $snippetSystem = PluginUtility::getSnippetManager();
165*04fd306cSNickeau        if ($url->hasProperty(self::SEARCH_HIGHLIGHT_QUERY_PROPERTY)) {
166*04fd306cSNickeau            $snippetSystem->attachCssInternalStyleSheet("search-hit");
167*04fd306cSNickeau        }
168*04fd306cSNickeau
169*04fd306cSNickeau        /**
170*04fd306cSNickeau         * Default Link Color
171*04fd306cSNickeau         * Saturation and lightness comes from the
172*04fd306cSNickeau         * Note:
173*04fd306cSNickeau         *   * blue color of Bootstrap #0d6efd s: 98, l: 52
174*04fd306cSNickeau         *   * blue color of twitter #1d9bf0 s: 88, l: 53
175*04fd306cSNickeau         *   * reddit gray with s: 16, l : 31
176*04fd306cSNickeau         *   * the text is s: 11, l: 15
177*04fd306cSNickeau         * We choose the gray/tone rendering to be close to black
178*04fd306cSNickeau         * the color of the text
179*04fd306cSNickeau         */
180*04fd306cSNickeau        try {
181*04fd306cSNickeau            $primaryColor = ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getPrimaryColor();
182*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
183*04fd306cSNickeau            $primaryColor = null;
184*04fd306cSNickeau        }
185*04fd306cSNickeau        if (Site::isBrandingColorInheritanceEnabled() && $primaryColor !== null) {
186*04fd306cSNickeau
187*04fd306cSNickeau            $primaryColorText = ColorSystem::toTextColor($primaryColor);
188*04fd306cSNickeau            $primaryColorHoverText = ColorSystem::toTextHoverColor($primaryColor);
189*04fd306cSNickeau            /**
190*04fd306cSNickeau             * There is also a link primary
191*04fd306cSNickeau             * https://getbootstrap.com/docs/5.2/helpers/colored-links/
192*04fd306cSNickeau             */
193*04fd306cSNickeau            $aCss = <<<EOF
194*04fd306cSNickeau.link-primary { color: {$primaryColorText->toRgbHex()}; }
195*04fd306cSNickeau.link-primary:hover { color: {$primaryColorHoverText->toRgbHex()}; }
196*04fd306cSNickeaumain a { color: {$primaryColorText->toRgbHex()}; }
197*04fd306cSNickeaumain a:hover { color: {$primaryColorHoverText->toRgbHex()}; }
198*04fd306cSNickeauEOF;
199*04fd306cSNickeau            SnippetSystem::getFromContext()->attachCssInternalStylesheet(self::ANCHOR_HTML_SNIPPET_ID, $aCss);
200*04fd306cSNickeau
201*04fd306cSNickeau        }
202*04fd306cSNickeau
203*04fd306cSNickeau
204*04fd306cSNickeau        global $conf;
205*04fd306cSNickeau
206*04fd306cSNickeau
207*04fd306cSNickeau        /**
208*04fd306cSNickeau         * Processing by type
209*04fd306cSNickeau         */
210*04fd306cSNickeau        switch ($this->getMarkupRef()->getSchemeType()) {
211*04fd306cSNickeau            case MarkupRef::INTERWIKI_URI:
212*04fd306cSNickeau                try {
213*04fd306cSNickeau                    $interWiki = $this->getMarkupRef()->getInterWiki();
214*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
215*04fd306cSNickeau                    LogUtility::internalError("The interwiki should be available. We were unable to create the link attributes.");
216*04fd306cSNickeau                    return $outputAttributes;
217*04fd306cSNickeau                }
218*04fd306cSNickeau                // normal link for the `this` wiki
219*04fd306cSNickeau                if ($interWiki->getWiki() !== "this") {
220*04fd306cSNickeau                    $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI);
221*04fd306cSNickeau                }
222*04fd306cSNickeau                $cssRules = $interWiki->getDefaultCssRules();
223*04fd306cSNickeau                $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI, $cssRules);
224*04fd306cSNickeau                try {
225*04fd306cSNickeau                    $cssRules = $interWiki->getSpecificCssRules();
226*04fd306cSNickeau                    $snippetSystem->attachCssInternalStyleSheet(MarkupRef::INTERWIKI_URI . "-" . $interWiki->getWiki(), $cssRules);
227*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
228*04fd306cSNickeau                    // no media find for the wiki
229*04fd306cSNickeau                }
230*04fd306cSNickeau                /**
231*04fd306cSNickeau                 * Target
232*04fd306cSNickeau                 */
233*04fd306cSNickeau                $interWikiConf = $conf['target']['interwiki'];
234*04fd306cSNickeau                if (!empty($interWikiConf)) {
235*04fd306cSNickeau                    $outputAttributes->addOutputAttributeValue('target', $interWikiConf);
236*04fd306cSNickeau                    $outputAttributes->addOutputAttributeValue('rel', 'noopener');
237*04fd306cSNickeau                }
238*04fd306cSNickeau                $outputAttributes->addClassName($interWiki->getComponentClass());
239*04fd306cSNickeau                $outputAttributes->addClassName($interWiki->getSubComponentClass());
240*04fd306cSNickeau                break;
241*04fd306cSNickeau            case MarkupRef::WIKI_URI:
242*04fd306cSNickeau                /**
243*04fd306cSNickeau                 * Derived from {@link Doku_Renderer_xhtml::internallink()}
244*04fd306cSNickeau                 */
245*04fd306cSNickeau                // https://www.dokuwiki.org/config:target
246*04fd306cSNickeau                $target = $conf['target']['wiki'];
247*04fd306cSNickeau                if (!empty($target)) {
248*04fd306cSNickeau                    $outputAttributes->addOutputAttributeValue('target', $target);
249*04fd306cSNickeau                }
250*04fd306cSNickeau                /**
251*04fd306cSNickeau                 * Internal Page
252*04fd306cSNickeau                 */
253*04fd306cSNickeau                try {
254*04fd306cSNickeau                    $dokuPath = $this->getMarkupRef()->getPath();
255*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
256*04fd306cSNickeau                    throw new ExceptionNotFound("We were unable to process the internal link dokuwiki id on the link. The path was not found. Error: {$e->getMessage()}");
257*04fd306cSNickeau                }
258*04fd306cSNickeau                $page = MarkupPath::createPageFromPathObject($dokuPath);
259*04fd306cSNickeau                $outputAttributes->addOutputAttributeValue(self::DATA_WIKI_ID, $dokuPath->getWikiId());
260*04fd306cSNickeau
261*04fd306cSNickeau
262*04fd306cSNickeau                if (!FileSystems::exists($dokuPath)) {
263*04fd306cSNickeau
264*04fd306cSNickeau                    /**
265*04fd306cSNickeau                     * Red color
266*04fd306cSNickeau                     * if not `do=edit`
267*04fd306cSNickeau                     */
268*04fd306cSNickeau                    if (!$this->markupRef->getUrl()->hasProperty("do")) {
269*04fd306cSNickeau                        $outputAttributes->addClassName(self::getHtmlClassNotExist());
270*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("rel", 'nofollow');
271*04fd306cSNickeau                    }
272*04fd306cSNickeau
273*04fd306cSNickeau                } else {
274*04fd306cSNickeau
275*04fd306cSNickeau                    /**
276*04fd306cSNickeau                     * Internal Link Class
277*04fd306cSNickeau                     */
278*04fd306cSNickeau                    $outputAttributes->addClassName(self::getHtmlClassInternalLink());
279*04fd306cSNickeau
280*04fd306cSNickeau                    /**
281*04fd306cSNickeau                     * Link Creation
282*04fd306cSNickeau                     * Do we need to set the title or the tooltip
283*04fd306cSNickeau                     * Processing variables
284*04fd306cSNickeau                     */
285*04fd306cSNickeau                    $acronym = "";
286*04fd306cSNickeau
287*04fd306cSNickeau                    /**
288*04fd306cSNickeau                     * Preview tooltip
289*04fd306cSNickeau                     */
290*04fd306cSNickeau                    $previewConfig = SiteConfig::getConfValue(self::CONF_PREVIEW_LINK, self::CONF_PREVIEW_LINK_DEFAULT);
291*04fd306cSNickeau                    $preview = $outputAttributes->hasComponentAttributeAndRemove(self::PREVIEW_ATTRIBUTE);
292*04fd306cSNickeau                    if ($preview || $previewConfig === 1) {
293*04fd306cSNickeau                        Tooltip::addToolTipSnippetIfNeeded();
294*04fd306cSNickeau                        // We use as heading, the name and not the title of the resource because otherwise it would be to lengthy
295*04fd306cSNickeau                        $tooltipHtml = <<<EOF
296*04fd306cSNickeau<h3>{$page->getNameOrDefault()}</h3>
297*04fd306cSNickeau<p>{$page->getDescriptionOrElseDokuWiki()}</p>
298*04fd306cSNickeauEOF;
299*04fd306cSNickeau                        $dataAttributeNamespace = Bootstrap::getDataNamespace();
300*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-toggle", "tooltip");
301*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-placement", "top");
302*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-html", "true");
303*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("title", $tooltipHtml);
304*04fd306cSNickeau                    }
305*04fd306cSNickeau
306*04fd306cSNickeau                    /**
307*04fd306cSNickeau                     * Low quality Page
308*04fd306cSNickeau                     * (It has a higher priority than preview and
309*04fd306cSNickeau                     * the code comes then after)
310*04fd306cSNickeau                     */
311*04fd306cSNickeau                    if ($page->isLowQualityPage()) {
312*04fd306cSNickeau
313*04fd306cSNickeau                        /**
314*04fd306cSNickeau                         * Add a class to style it differently
315*04fd306cSNickeau                         * (the acronym is added to the description, later)
316*04fd306cSNickeau                         */
317*04fd306cSNickeau                        $acronym = LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM;
318*04fd306cSNickeau                        $lowerCaseLowQualityAcronym = strtolower(LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM);
319*04fd306cSNickeau                        $outputAttributes->addClassName(StyleAttribute::addComboStrapSuffix(LowQualityPage::CLASS_SUFFIX));
320*04fd306cSNickeau                        $snippetLowQualityPageId = $lowerCaseLowQualityAcronym;
321*04fd306cSNickeau                        $snippetSystem->attachCssInternalStyleSheet($snippetLowQualityPageId);
322*04fd306cSNickeau                        /**
323*04fd306cSNickeau                         * Note The protection does occur on Javascript level, not on the HTML
324*04fd306cSNickeau                         * because the created page is valid for a anonymous or logged-in user
325*04fd306cSNickeau                         * Javascript is controlling
326*04fd306cSNickeau                         */
327*04fd306cSNickeau                        if (LowQualityPage::isProtectionEnabled()) {
328*04fd306cSNickeau
329*04fd306cSNickeau                            $linkType = LowQualityPage::getLowQualityLinkType();
330*04fd306cSNickeau                            $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, $linkType);
331*04fd306cSNickeau                            $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLowQualityAcronym);
332*04fd306cSNickeau
333*04fd306cSNickeau                            /**
334*04fd306cSNickeau                             * Low Quality Page protection javascript is only for warning or login link
335*04fd306cSNickeau                             */
336*04fd306cSNickeau                            if (in_array($linkType, [PageProtection::PAGE_PROTECTION_LINK_WARNING, PageProtection::PAGE_PROTECTION_LINK_LOGIN])) {
337*04fd306cSNickeau                                PageProtection::addPageProtectionSnippet();
338*04fd306cSNickeau                            }
339*04fd306cSNickeau
340*04fd306cSNickeau                        }
341*04fd306cSNickeau                    }
342*04fd306cSNickeau
343*04fd306cSNickeau                    /**
344*04fd306cSNickeau                     * Late publication has a higher priority than
345*04fd306cSNickeau                     * the late publication and the is therefore after
346*04fd306cSNickeau                     * (In case this a low quality page late published)
347*04fd306cSNickeau                     */
348*04fd306cSNickeau                    if ($page->isLatePublication()) {
349*04fd306cSNickeau                        /**
350*04fd306cSNickeau                         * Add a class to style it differently if needed
351*04fd306cSNickeau                         */
352*04fd306cSNickeau                        $className = StyleAttribute::addComboStrapSuffix(PagePublicationDate::LATE_PUBLICATION_CLASS_PREFIX_NAME);
353*04fd306cSNickeau                        $outputAttributes->addClassName($className);
354*04fd306cSNickeau                        if (PagePublicationDate::isLatePublicationProtectionEnabled()) {
355*04fd306cSNickeau                            $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_LINK);
356*04fd306cSNickeau                            $outputAttributes->removeOutputAttributeIfPresent(PageProtection::DATA_PP_SOURCE);
357*04fd306cSNickeau                            $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_LINK, PageProtection::PAGE_PROTECTION_LINK_LOGIN);
358*04fd306cSNickeau                            $acronym = PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM;
359*04fd306cSNickeau                            $lowerCaseLatePublicationAcronym = strtolower(PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM);
360*04fd306cSNickeau                            $outputAttributes->addOutputAttributeValue(PageProtection::DATA_PP_SOURCE, $lowerCaseLatePublicationAcronym);
361*04fd306cSNickeau                            PageProtection::addPageProtectionSnippet();
362*04fd306cSNickeau                        }
363*04fd306cSNickeau
364*04fd306cSNickeau                    }
365*04fd306cSNickeau
366*04fd306cSNickeau                    /**
367*04fd306cSNickeau                     * Title (ie tooltip vs title html attribute)
368*04fd306cSNickeau                     */
369*04fd306cSNickeau                    if (!$outputAttributes->hasAttribute("title")) {
370*04fd306cSNickeau
371*04fd306cSNickeau                        $description = PageDescription::createForPage($page)->getValueOrDefault();
372*04fd306cSNickeau                        if (!empty($acronym)) {
373*04fd306cSNickeau                            $description = $description . " ($acronym)";
374*04fd306cSNickeau                        }
375*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("title", $description);
376*04fd306cSNickeau
377*04fd306cSNickeau                    }
378*04fd306cSNickeau
379*04fd306cSNickeau                }
380*04fd306cSNickeau
381*04fd306cSNickeau                break;
382*04fd306cSNickeau
383*04fd306cSNickeau            case MarkupRef::WINDOWS_SHARE_URI:
384*04fd306cSNickeau                // https://www.dokuwiki.org/config:target
385*04fd306cSNickeau                $windowsTarget = $conf['target']['windows'];
386*04fd306cSNickeau                if (!empty($windowsTarget)) {
387*04fd306cSNickeau                    $outputAttributes->addOutputAttributeValue('target', $windowsTarget);
388*04fd306cSNickeau                }
389*04fd306cSNickeau                $outputAttributes->addClassName("windows");
390*04fd306cSNickeau                break;
391*04fd306cSNickeau            case MarkupRef::LOCAL_URI:
392*04fd306cSNickeau                $outputAttributes->addClassName(self::getHtmlClassLocalLink());
393*04fd306cSNickeau                if (!$outputAttributes->hasAttribute("title")) {
394*04fd306cSNickeau                    $description = ucfirst($this->markupRef->getUrl()->getFragment());
395*04fd306cSNickeau                    if ($description !== "") {
396*04fd306cSNickeau                        $description = str_replace("_", " ", $description);
397*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("title", $description);
398*04fd306cSNickeau                    }
399*04fd306cSNickeau                }
400*04fd306cSNickeau                break;
401*04fd306cSNickeau            case MarkupRef::EMAIL_URI:
402*04fd306cSNickeau                $outputAttributes->addClassName(self::getHtmlClassEmailLink());
403*04fd306cSNickeau                /**
404*04fd306cSNickeau                 * An email link is `<email>`
405*04fd306cSNickeau                 * {@link Emaillink::connectTo()}
406*04fd306cSNickeau                 * or
407*04fd306cSNickeau                 * {@link PluginTrait::email()
408*04fd306cSNickeau                 */
409*04fd306cSNickeau                // common.php#obfsucate implements the $conf['mailguard']
410*04fd306cSNickeau                $uri = $url->getPath();
411*04fd306cSNickeau                $uri = $this->obfuscateEmail($uri);
412*04fd306cSNickeau                $uri = urlencode($uri);
413*04fd306cSNickeau                $queryParameters = $url->getQueryProperties();
414*04fd306cSNickeau                if (sizeof($queryParameters) > 0) {
415*04fd306cSNickeau                    $uri .= "?";
416*04fd306cSNickeau                    foreach ($queryParameters as $key => $value) {
417*04fd306cSNickeau                        $value = urlencode($value);
418*04fd306cSNickeau                        $key = urlencode($key);
419*04fd306cSNickeau                        if (in_array($key, self::EMAIL_VALID_PARAMETERS)) {
420*04fd306cSNickeau                            $uri .= "$key=$value";
421*04fd306cSNickeau                        }
422*04fd306cSNickeau                    }
423*04fd306cSNickeau                }
424*04fd306cSNickeau                // replace href
425*04fd306cSNickeau                $outputAttributes->removeOutputAttributeIfPresent("href");
426*04fd306cSNickeau                $outputAttributes->addOutputAttributeValue("href", 'mailto:' . $uri);
427*04fd306cSNickeau                break;
428*04fd306cSNickeau            case MarkupRef::WEB_URI:
429*04fd306cSNickeau                /**
430*04fd306cSNickeau                 * It may be a absolute url
431*04fd306cSNickeau                 * that points to the local website
432*04fd306cSNickeau                 * (case of the {@link \syntax_plugin_combo_permalink}
433*04fd306cSNickeau                 */
434*04fd306cSNickeau                if ($url->isExternal()) {
435*04fd306cSNickeau
436*04fd306cSNickeau                    if ($conf['relnofollow']) {
437*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("rel", 'nofollow ugc');
438*04fd306cSNickeau                    }
439*04fd306cSNickeau                    // https://www.dokuwiki.org/config:target
440*04fd306cSNickeau                    $externTarget = $conf['target']['extern'];
441*04fd306cSNickeau                    if (!empty($externTarget)) {
442*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue('target', $externTarget);
443*04fd306cSNickeau                        $outputAttributes->addOutputAttributeValue("rel", 'noopener');
444*04fd306cSNickeau                    }
445*04fd306cSNickeau                    /**
446*04fd306cSNickeau                     * Default class for default external link
447*04fd306cSNickeau                     * To not interfere with other external link style
448*04fd306cSNickeau                     * For instance, {@link \syntax_plugin_combo_share}
449*04fd306cSNickeau                     */
450*04fd306cSNickeau                    $outputAttributes->addClassName(self::getHtmlClassExternalLink());
451*04fd306cSNickeau                }
452*04fd306cSNickeau                break;
453*04fd306cSNickeau            default:
454*04fd306cSNickeau                /**
455*04fd306cSNickeau                 * May be any external link
456*04fd306cSNickeau                 * such as {@link \syntax_plugin_combo_share}
457*04fd306cSNickeau                 */
458*04fd306cSNickeau                break;
459*04fd306cSNickeau
460*04fd306cSNickeau        }
461*04fd306cSNickeau
462*04fd306cSNickeau        /**
463*04fd306cSNickeau         * An email URL and title
464*04fd306cSNickeau         * may be already encoded because of the vanguard configuration
465*04fd306cSNickeau         *
466*04fd306cSNickeau         * The url is not treated as an attribute
467*04fd306cSNickeau         * because the transformation function encodes the value
468*04fd306cSNickeau         * to mitigate XSS
469*04fd306cSNickeau         *
470*04fd306cSNickeau         */
471*04fd306cSNickeau        if ($this->getMarkupRef()->getSchemeType() == MarkupRef::EMAIL_URI) {
472*04fd306cSNickeau            $emailAddress = $this->obfuscateEmail($this->markupRef->getUrl()->getPath());
473*04fd306cSNickeau            $outputAttributes->addOutputAttributeValue("title", $emailAddress);
474*04fd306cSNickeau        }
475*04fd306cSNickeau
476*04fd306cSNickeau
477*04fd306cSNickeau        /**
478*04fd306cSNickeau         * Return
479*04fd306cSNickeau         */
480*04fd306cSNickeau        return $outputAttributes;
481*04fd306cSNickeau
482*04fd306cSNickeau
483*04fd306cSNickeau    }
484*04fd306cSNickeau
485*04fd306cSNickeau
486*04fd306cSNickeau    /**
487*04fd306cSNickeau     * The label inside the anchor tag if there is none
488*04fd306cSNickeau     * @param false $navigation
489*04fd306cSNickeau     * @return string
490*04fd306cSNickeau     * @throws ExceptionNotFound|ExceptionBadArgument
491*04fd306cSNickeau     *
492*04fd306cSNickeau     */
493*04fd306cSNickeau    public
494*04fd306cSNickeau    function getDefaultLabel(bool $navigation = false): string
495*04fd306cSNickeau    {
496*04fd306cSNickeau
497*04fd306cSNickeau        switch ($this->getMarkupRef()->getSchemeType()) {
498*04fd306cSNickeau            case MarkupRef::WIKI_URI:
499*04fd306cSNickeau                $page = $this->getPage();
500*04fd306cSNickeau                if ($navigation) {
501*04fd306cSNickeau                    return ResourceName::createForResource($page)->getValueOrDefault();
502*04fd306cSNickeau                } else {
503*04fd306cSNickeau                    return PageTitle::createForMarkup($page)->getValueOrDefault();
504*04fd306cSNickeau                }
505*04fd306cSNickeau            case MarkupRef::EMAIL_URI:
506*04fd306cSNickeau                global $conf;
507*04fd306cSNickeau                $email = $this->markupRef->getUrl()->getPath();
508*04fd306cSNickeau                switch ($conf['mailguard']) {
509*04fd306cSNickeau                    case 'none' :
510*04fd306cSNickeau                        return $email;
511*04fd306cSNickeau                    case 'visible' :
512*04fd306cSNickeau                    default :
513*04fd306cSNickeau                        $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
514*04fd306cSNickeau                        return strtr($email, $obfuscate);
515*04fd306cSNickeau                }
516*04fd306cSNickeau            case MarkupRef::INTERWIKI_URI:
517*04fd306cSNickeau                try {
518*04fd306cSNickeau                    $path = $this->markupRef->getInterWiki()->toUrl()->getPath();
519*04fd306cSNickeau                    if ($path[0] === "/") {
520*04fd306cSNickeau                        return substr($path, 1);
521*04fd306cSNickeau                    } else {
522*04fd306cSNickeau                        return $path;
523*04fd306cSNickeau                    }
524*04fd306cSNickeau                } catch (ExceptionBadSyntax|ExceptionNotFound $e) {
525*04fd306cSNickeau                    return "interwiki";
526*04fd306cSNickeau                }
527*04fd306cSNickeau            case MarkupRef::LOCAL_URI:
528*04fd306cSNickeau                return $this->markupRef->getUrl()->getFragment();
529*04fd306cSNickeau            default:
530*04fd306cSNickeau                return $this->markupRef->getRef();
531*04fd306cSNickeau        }
532*04fd306cSNickeau    }
533*04fd306cSNickeau
534*04fd306cSNickeau
535*04fd306cSNickeau    private
536*04fd306cSNickeau    function obfuscateEmail($email, $inAttribute = true): string
537*04fd306cSNickeau    {
538*04fd306cSNickeau        /**
539*04fd306cSNickeau         * adapted from {@link obfuscate()} in common.php
540*04fd306cSNickeau         */
541*04fd306cSNickeau        global $conf;
542*04fd306cSNickeau
543*04fd306cSNickeau        $mailGuard = $conf['mailguard'];
544*04fd306cSNickeau        if ($mailGuard === "hex" && $inAttribute) {
545*04fd306cSNickeau            $mailGuard = "visible";
546*04fd306cSNickeau        }
547*04fd306cSNickeau        switch ($mailGuard) {
548*04fd306cSNickeau            case 'visible' :
549*04fd306cSNickeau                $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
550*04fd306cSNickeau                return strtr($email, $obfuscate);
551*04fd306cSNickeau
552*04fd306cSNickeau            case 'hex' :
553*04fd306cSNickeau                return Conversion::toHtml($email, true);
554*04fd306cSNickeau
555*04fd306cSNickeau            case 'none' :
556*04fd306cSNickeau            default :
557*04fd306cSNickeau                return $email;
558*04fd306cSNickeau        }
559*04fd306cSNickeau    }
560*04fd306cSNickeau
561*04fd306cSNickeau
562*04fd306cSNickeau    /**
563*04fd306cSNickeau     * @return bool
564*04fd306cSNickeau     * @deprecated should not be here ref does not have the notion of relative
565*04fd306cSNickeau     */
566*04fd306cSNickeau    public
567*04fd306cSNickeau    function isRelative(): bool
568*04fd306cSNickeau    {
569*04fd306cSNickeau        return strpos($this->getMarkupRef()->getRef(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) !== 0;
570*04fd306cSNickeau    }
571*04fd306cSNickeau
572*04fd306cSNickeau    public
573*04fd306cSNickeau    function getMarkupRef(): MarkupRef
574*04fd306cSNickeau    {
575*04fd306cSNickeau        return $this->markupRef;
576*04fd306cSNickeau    }
577*04fd306cSNickeau
578*04fd306cSNickeau
579*04fd306cSNickeau    public
580*04fd306cSNickeau    static function getHtmlClassInternalLink(): string
581*04fd306cSNickeau    {
582*04fd306cSNickeau        $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
583*04fd306cSNickeau        if ($oldClassName) {
584*04fd306cSNickeau            return "wikilink1";
585*04fd306cSNickeau        } else {
586*04fd306cSNickeau            return "link-internal";
587*04fd306cSNickeau        }
588*04fd306cSNickeau    }
589*04fd306cSNickeau
590*04fd306cSNickeau    public
591*04fd306cSNickeau    static function getHtmlClassEmailLink(): string
592*04fd306cSNickeau    {
593*04fd306cSNickeau        $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
594*04fd306cSNickeau        if ($oldClassName) {
595*04fd306cSNickeau            return "mail";
596*04fd306cSNickeau        } else {
597*04fd306cSNickeau            return "link-mail";
598*04fd306cSNickeau        }
599*04fd306cSNickeau    }
600*04fd306cSNickeau
601*04fd306cSNickeau    public static function getHtmlClassExternalLink(): string
602*04fd306cSNickeau    {
603*04fd306cSNickeau        $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
604*04fd306cSNickeau        if ($oldClassName) {
605*04fd306cSNickeau            return "urlextern";
606*04fd306cSNickeau        } else {
607*04fd306cSNickeau            return "link-external";
608*04fd306cSNickeau        }
609*04fd306cSNickeau    }
610*04fd306cSNickeau
611*04fd306cSNickeau//FYI: exist in dokuwiki is "wikilink1 but we let the control to the user
612*04fd306cSNickeau    public
613*04fd306cSNickeau    static function getHtmlClassNotExist(): string
614*04fd306cSNickeau    {
615*04fd306cSNickeau        $oldClassName = SiteConfig::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
616*04fd306cSNickeau        if ($oldClassName) {
617*04fd306cSNickeau            return "wikilink2";
618*04fd306cSNickeau        } else {
619*04fd306cSNickeau            return self::TEXT_ERROR_CLASS;
620*04fd306cSNickeau        }
621*04fd306cSNickeau    }
622*04fd306cSNickeau
623*04fd306cSNickeau    public
624*04fd306cSNickeau    function __toString()
625*04fd306cSNickeau    {
626*04fd306cSNickeau        return $this->getMarkupRef()->getRef();
627*04fd306cSNickeau    }
628*04fd306cSNickeau
629*04fd306cSNickeau
630*04fd306cSNickeau    /**
631*04fd306cSNickeau     * @throws ExceptionNotFound
632*04fd306cSNickeau     */
633*04fd306cSNickeau    private
634*04fd306cSNickeau    function getPage(): MarkupPath
635*04fd306cSNickeau    {
636*04fd306cSNickeau        return MarkupPath::createPageFromPathObject($this->getMarkupRef()->getPath());
637*04fd306cSNickeau    }
638*04fd306cSNickeau
639*04fd306cSNickeau    /**
640*04fd306cSNickeau     * Styling attribute
641*04fd306cSNickeau     * may be passed via parameters
642*04fd306cSNickeau     * for internal link
643*04fd306cSNickeau     * We don't want the styling attribute
644*04fd306cSNickeau     * in the URL
645*04fd306cSNickeau     */
646*04fd306cSNickeau    private
647*04fd306cSNickeau    function collectStylingAttributeInUrl()
648*04fd306cSNickeau    {
649*04fd306cSNickeau
650*04fd306cSNickeau
651*04fd306cSNickeau        /**
652*04fd306cSNickeau         * We will not overwrite the parameters if this is an dokuwiki
653*04fd306cSNickeau         * action link (with the `do` property)
654*04fd306cSNickeau         */
655*04fd306cSNickeau        if ($this->markupRef->getUrl()->hasProperty("do")) {
656*04fd306cSNickeau            return;
657*04fd306cSNickeau        }
658*04fd306cSNickeau
659*04fd306cSNickeau        /**
660*04fd306cSNickeau         * Add the attribute from the URL
661*04fd306cSNickeau         * if this is not a `do`
662*04fd306cSNickeau         */
663*04fd306cSNickeau        switch ($this->markupRef->getSchemeType()) {
664*04fd306cSNickeau            case MarkupRef::WIKI_URI:
665*04fd306cSNickeau                foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) {
666*04fd306cSNickeau                    if (!in_array($key, self::PROTECTED_URL_PROPERTY)) {
667*04fd306cSNickeau                        $this->getMarkupRef()->getUrl()->deleteQueryParameter($key);
668*04fd306cSNickeau                        if (!TagAttributes::isEmptyValue($value)) {
669*04fd306cSNickeau                            $this->stylingAttributes->addComponentAttributeValue($key, $value);
670*04fd306cSNickeau                        } else {
671*04fd306cSNickeau                            $this->stylingAttributes->addEmptyComponentAttributeValue($key);
672*04fd306cSNickeau                        }
673*04fd306cSNickeau                    }
674*04fd306cSNickeau                }
675*04fd306cSNickeau                break;
676*04fd306cSNickeau            case
677*04fd306cSNickeau            MarkupRef::EMAIL_URI:
678*04fd306cSNickeau                foreach ($this->getMarkupRef()->getUrl()->getQueryProperties() as $key => $value) {
679*04fd306cSNickeau                    if (!in_array($key, self::EMAIL_VALID_PARAMETERS)) {
680*04fd306cSNickeau                        $this->stylingAttributes->addComponentAttributeValue($key, $value);
681*04fd306cSNickeau                    }
682*04fd306cSNickeau                }
683*04fd306cSNickeau                break;
684*04fd306cSNickeau        }
685*04fd306cSNickeau
686*04fd306cSNickeau    }
687*04fd306cSNickeau
688*04fd306cSNickeau    /**
689*04fd306cSNickeau     * @return TagAttributes - the unknown attributes in a url are collected as styling attributes if this not a do query
690*04fd306cSNickeau     * by {@link LinkMarkup::collectStylingAttributeInUrl()}
691*04fd306cSNickeau     */
692*04fd306cSNickeau    public
693*04fd306cSNickeau    function getStylingAttributes(): TagAttributes
694*04fd306cSNickeau    {
695*04fd306cSNickeau        return $this->stylingAttributes;
696*04fd306cSNickeau    }
697*04fd306cSNickeau
698*04fd306cSNickeau
699*04fd306cSNickeau}
700