1<?php
2/**
3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14
15
16use Doku_Renderer_metadata;
17use Doku_Renderer_xhtml;
18use dokuwiki\Extension\PluginTrait;
19use dokuwiki\Utf8\Conversion;
20use syntax_plugin_combo_tooltip;
21
22require_once(__DIR__ . '/PluginUtility.php');
23
24/**
25 *
26 * @package ComboStrap
27 *
28 * Parse the ref found in a markup link
29 * and return an XHTML compliant array
30 * with href, style, ... attributes
31 */
32class MarkupRef
33{
34
35
36    /**
37     * Type of link
38     */
39    const INTERWIKI_URI = 'interwiki';
40    const WINDOWS_SHARE_URI = 'windowsShare';
41    const WEB_URI = 'external';
42
43    const EMAIL_URI = 'email';
44    const LOCAL_URI = 'local';
45    const WIKI_URI = 'internal';
46    const VARIABLE_URI = 'internal_template';
47
48
49    /**
50     * Class added to the type of link
51     * Class have styling rule conflict, they are by default not set
52     * but this configuration permits to turn it back
53     */
54    const CONF_USE_DOKUWIKI_CLASS_NAME = "useDokuwikiLinkClassName";
55    /**
56     * This configuration will set for all internal link
57     * the {@link MarkupRef::PREVIEW_ATTRIBUTE} preview attribute
58     */
59    const CONF_PREVIEW_LINK = "previewLink";
60    const CONF_PREVIEW_LINK_DEFAULT = 0;
61
62
63    const TEXT_ERROR_CLASS = "text-danger";
64
65    /**
66     * The known parameters for an email url
67     */
68    const EMAIL_VALID_PARAMETERS = ["subject"];
69
70    /**
71     * If set, it will show a page preview
72     */
73    const PREVIEW_ATTRIBUTE = "preview";
74    const PREVIEW_TOOLTIP = "preview";
75
76    /**
77     * Highlight Key
78     * Adding this property to the internal query will highlight the words
79     *
80     * See {@link html_hilight}
81     */
82    const SEARCH_HIGHLIGHT_QUERY_PROPERTY = "s";
83
84
85    /**
86     * @var mixed
87     */
88    private $uriType;
89    /**
90     * @var mixed
91     */
92    private $ref;
93
94    /**
95     * @var Page the internal linked page if the link is an internal one
96     */
97    private $linkedPage;
98
99    /**
100     * @var string The value of the title attribute of an anchor
101     */
102    private $title;
103
104
105    /**
106     * The name of the wiki for an inter wiki link
107     * @var string
108     */
109    private $wiki;
110
111
112    /**
113     *
114     * @var false|string
115     */
116    private $schemeUri;
117
118    /**
119     * The uri scheme that can be used inside a page
120     * @var array
121     */
122    private $authorizedSchemes;
123
124
125    /**
126     * @var DokuwikiUrl
127     */
128    private $dokuwikiUrl;
129    /**
130     * @var array|string|null
131     */
132    private $type;
133    /**
134     * @var array
135     */
136    private $interwiki;
137
138    /**
139     * Link constructor.
140     * @param $ref
141     */
142    public function __construct($ref)
143    {
144
145
146        /**
147         * Windows share link
148         */
149        if ($this->uriType == null) {
150            if (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $ref)) {
151                $this->uriType = self::WINDOWS_SHARE_URI;
152                $this->ref = $ref;
153                return;
154            }
155        }
156
157        /**
158         * URI like links section with query and fragment
159         */
160
161        /**
162         * Local
163         */
164        if ($this->uriType == null) {
165            if (preg_match('!^#.+!', $ref)) {
166                $this->uriType = self::LOCAL_URI;
167                $this->ref = $ref;
168            }
169        }
170
171        /**
172         * Email validation pattern
173         * E-Mail (pattern below is defined in inc/mail.php)
174         *
175         * Example:
176         * [[support@combostrap.com?subject=hallo]]
177         * [[support@combostrap.com]]
178         */
179        if ($this->uriType == null) {
180            $emailRfc2822 = "0-9a-zA-Z!#$%&'*+/=?^_`{|}~-";
181            $emailPattern = '[' . $emailRfc2822 . ']+(?:\.[' . $emailRfc2822 . ']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})';
182            if (preg_match('<' . $emailPattern . '>', $ref)) {
183                $this->uriType = self::EMAIL_URI;
184                $this->ref = $ref;
185                // we don't return. The query part is parsed afterwards
186            }
187        }
188
189
190        /**
191         * External (ie only https)
192         */
193        if ($this->uriType == null) {
194            /**
195             * Example: `https://`
196             *
197             * Other scheme are not yet recognized
198             * because it can also be a wiki id
199             * For instance, `mailto:` is also a valid page
200             */
201            if (preg_match('#^([a-z0-9\-\.+]+?)://#i', $ref)) {
202                $this->uriType = self::WEB_URI;
203                $this->schemeUri = strtolower(substr($ref, 0, strpos($ref, ":")));
204                $this->ref = $ref;
205            }
206        }
207
208        /**
209         * Interwiki ?
210         */
211        $refProcessing = $ref;
212        if ($this->uriType == null) {
213            $interwikiPosition = strpos($refProcessing, ">");
214            if ($interwikiPosition !== false) {
215                $this->wiki = strtolower(substr($refProcessing, 0, $interwikiPosition));
216                $refProcessing = substr($refProcessing, $interwikiPosition + 1);
217                $this->ref = $ref;
218                $this->uriType = self::INTERWIKI_URI;
219            }
220        }
221
222        /**
223         * Internal then
224         */
225        if ($this->uriType == null) {
226            /**
227             * It can be a link with a ref template
228             */
229            if (TemplateUtility::isVariable($ref)) {
230                $this->uriType = self::VARIABLE_URI;
231            } else {
232                $this->uriType = self::WIKI_URI;
233            }
234            $this->ref = $ref;
235        }
236
237
238        /**
239         * Url (called ref by dokuwiki)
240         */
241        $this->dokuwikiUrl = DokuwikiUrl::createFromUrl($refProcessing);
242
243
244    }
245
246    public static function createFromPageId($id): MarkupRef
247    {
248        return new MarkupRef(":$id");
249    }
250
251    public static function createFromRef(string $ref): MarkupRef
252    {
253        return new MarkupRef($ref);
254    }
255
256
257    /**
258     * @param $uriType
259     * @return $this
260     */
261    public function setUriType($uriType): MarkupRef
262    {
263        $this->uriType = $uriType;
264        return $this;
265    }
266
267
268    /**
269     *
270     *
271     *
272     * @throws ExceptionCombo
273     */
274    public function toAttributes($logicalTag = \syntax_plugin_combo_link::TAG): TagAttributes
275    {
276
277        $outputAttributes = TagAttributes::createEmpty($logicalTag);
278
279        $type = $this->getUriType();
280
281
282        /**
283         * Add the attribute from the URL
284         * if this is not a `do`
285         */
286
287        switch ($type) {
288            case self::WIKI_URI:
289                if (!$this->dokuwikiUrl->hasQueryParameter("do")) {
290                    foreach ($this->getDokuwikiUrl()->getQueryParameters() as $key => $value) {
291                        if ($key !== self::SEARCH_HIGHLIGHT_QUERY_PROPERTY) {
292                            $outputAttributes->addComponentAttributeValue($key, $value);
293                        }
294                    }
295                }
296                break;
297            case
298            self::EMAIL_URI:
299                foreach ($this->getDokuwikiUrl()->getQueryParameters() as $key => $value) {
300                    if (!in_array($key, self::EMAIL_VALID_PARAMETERS)) {
301                        $outputAttributes->addComponentAttributeValue($key, $value);
302                    }
303                }
304                break;
305        }
306
307
308        global $conf;
309
310        /**
311         * Get the url
312         */
313        $url = $this->getUrl();
314        if (!empty($url)) {
315            $outputAttributes->addOutputAttributeValue("href", $url);
316        }
317
318
319        /**
320         * Processing by type
321         */
322        switch ($this->getUriType()) {
323            case self::INTERWIKI_URI:
324
325                // normal link for the `this` wiki
326                if ($this->getWiki() !== "this") {
327                    PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::INTERWIKI_URI);
328                }
329                /**
330                 * Target
331                 */
332                $interWikiConf = $conf['target']['interwiki'];
333                if (!empty($interWikiConf)) {
334                    $outputAttributes->addOutputAttributeValue('target', $interWikiConf);
335                    $outputAttributes->addOutputAttributeValue('rel', 'noopener');
336                }
337                $outputAttributes->addClassName(self::getHtmlClassInterWikiLink());
338                $wikiClass = "iw_" . preg_replace('/[^_\-a-z0-9]+/i', '_', $this->getWiki());
339                $outputAttributes->addClassName($wikiClass);
340                if (!$this->wikiExists()) {
341                    $outputAttributes->addClassName(self::getHtmlClassNotExist());
342                    $outputAttributes->addOutputAttributeValue("rel", 'nofollow');
343                }
344
345                break;
346            case self::WIKI_URI:
347                /**
348                 * Derived from {@link Doku_Renderer_xhtml::internallink()}
349                 */
350                // https://www.dokuwiki.org/config:target
351                $target = $conf['target']['wiki'];
352                if (!empty($target)) {
353                    $outputAttributes->addOutputAttributeValue('target', $target);
354                }
355                /**
356                 * Internal Page
357                 */
358                $linkedPage = $this->getInternalPage();
359                $outputAttributes->addOutputAttributeValue("data-wiki-id", $linkedPage->getDokuwikiId());
360
361
362                if (!$linkedPage->exists()) {
363
364                    /**
365                     * Red color
366                     */
367                    $outputAttributes->addClassName(self::getHtmlClassNotExist());
368                    $outputAttributes->addOutputAttributeValue("rel", 'nofollow');
369
370                } else {
371
372                    /**
373                     * Internal Link Class
374                     */
375                    $outputAttributes->addClassName(self::getHtmlClassInternalLink());
376
377                    /**
378                     * Link Creation
379                     * Do we need to set the title or the tooltip
380                     * Processing variables
381                     */
382                    $acronym = "";
383
384                    /**
385                     * Preview tooltip
386                     */
387                    $previewConfig = PluginUtility::getConfValue(self::CONF_PREVIEW_LINK, self::CONF_PREVIEW_LINK_DEFAULT);
388                    $preview = $outputAttributes->getBooleanValueAndRemoveIfPresent(self::PREVIEW_ATTRIBUTE, $previewConfig);
389                    if ($preview) {
390                        Tooltip::addToolTipSnippetIfNeeded();
391                        $tooltipHtml = <<<EOF
392<h3>{$linkedPage->getNameOrDefault()}</h3>
393<p>{$linkedPage->getDescriptionOrElseDokuWiki()}</p>
394EOF;
395                        $dataAttributeNamespace = Bootstrap::getDataNamespace();
396                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-toggle", "tooltip");
397                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-placement", "top");
398                        $outputAttributes->addOutputAttributeValue("data{$dataAttributeNamespace}-html", "true");
399                        $outputAttributes->addOutputAttributeValue("title", $tooltipHtml);
400                    }
401
402                    /**
403                     * Low quality Page
404                     * (It has a higher priority than preview and
405                     * the code comes then after)
406                     */
407                    $pageProtectionAcronym = strtolower(PageProtection::ACRONYM);
408                    if ($linkedPage->isLowQualityPage()) {
409
410                        /**
411                         * Add a class to style it differently
412                         * (the acronym is added to the description, later)
413                         */
414                        $acronym = LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM;
415                        $lowerCaseLowQualityAcronym = strtolower(LowQualityPage::LOW_QUALITY_PROTECTION_ACRONYM);
416                        $outputAttributes->addClassName(LowQualityPage::CLASS_NAME . "-combo");
417                        $snippetLowQualityPageId = $lowerCaseLowQualityAcronym;
418                        PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot($snippetLowQualityPageId);
419                        /**
420                         * Note The protection does occur on Javascript level, not on the HTML
421                         * because the created page is valid for a anonymous or logged-in user
422                         * Javascript is controlling
423                         */
424                        if (LowQualityPage::isProtectionEnabled()) {
425
426                            $linkType = LowQualityPage::getLowQualityLinkType();
427                            $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-link", $linkType);
428                            $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-source", $lowerCaseLowQualityAcronym);
429
430                            /**
431                             * Low Quality Page protection javascript is only for warning or login link
432                             */
433                            if (in_array($linkType, [PageProtection::PAGE_PROTECTION_LINK_WARNING, PageProtection::PAGE_PROTECTION_LINK_LOGIN])) {
434                                PageProtection::addPageProtectionSnippet();
435                            }
436
437                        }
438                    }
439
440                    /**
441                     * Late publication has a higher priority than
442                     * the late publication and the is therefore after
443                     * (In case this a low quality page late published)
444                     */
445                    if ($linkedPage->isLatePublication()) {
446                        /**
447                         * Add a class to style it differently if needed
448                         */
449                        $outputAttributes->addClassName(PagePublicationDate::LATE_PUBLICATION_CLASS_NAME . "-combo");
450                        if (PagePublicationDate::isLatePublicationProtectionEnabled()) {
451                            $acronym = PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM;
452                            $lowerCaseLatePublicationAcronym = strtolower(PagePublicationDate::LATE_PUBLICATION_PROTECTION_ACRONYM);
453                            $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-link", PageProtection::PAGE_PROTECTION_LINK_LOGIN);
454                            $outputAttributes->addOutputAttributeValue("data-$pageProtectionAcronym-source", $lowerCaseLatePublicationAcronym);
455                            PageProtection::addPageProtectionSnippet();
456                        }
457
458                    }
459
460                    /**
461                     * Title (ie tooltip vs title html attribute)
462                     */
463                    if (!$outputAttributes->hasAttribute("title")) {
464
465                        /**
466                         * If this is not a link into the same page
467                         */
468                        if (!empty($this->getDokuwikiUrl()->getPath())) {
469                            $description = $linkedPage->getDescriptionOrElseDokuWiki();
470                            if (empty($description)) {
471                                // Rare case
472                                $description = $linkedPage->getH1OrDefault();
473                            }
474                            if (!empty($acronym)) {
475                                $description = $description . " ($acronym)";
476                            }
477                            $outputAttributes->addOutputAttributeValue("title", $description);
478                        }
479
480                    }
481
482                }
483
484                break;
485
486            case self::WINDOWS_SHARE_URI:
487                // https://www.dokuwiki.org/config:target
488                $windowsTarget = $conf['target']['windows'];
489                if (!empty($windowsTarget)) {
490                    $outputAttributes->addOutputAttributeValue('target', $windowsTarget);
491                }
492                $outputAttributes->addClassName("windows");
493                break;
494            case self::LOCAL_URI:
495                break;
496            case self::EMAIL_URI:
497                $outputAttributes->addClassName(self::getHtmlClassEmailLink());
498                break;
499            case self::WEB_URI:
500                if ($conf['relnofollow']) {
501                    $outputAttributes->addOutputAttributeValue("rel", 'nofollow ugc');
502                }
503                // https://www.dokuwiki.org/config:target
504                $externTarget = $conf['target']['extern'];
505                if (!empty($externTarget)) {
506                    $outputAttributes->addOutputAttributeValue('target', $externTarget);
507                    $outputAttributes->addOutputAttributeValue("rel", 'noopener');
508                }
509                if ($this->type === null) {
510                    /**
511                     * Default class for default external link
512                     * To not interfere with other external link style
513                     * For instance, {@link \syntax_plugin_combo_share}
514                     */
515                    $outputAttributes->addClassName(self::getHtmlClassExternalLink());
516                }
517                break;
518            default:
519                /**
520                 * May be any external link
521                 * such as {@link \syntax_plugin_combo_share}
522                 */
523                break;
524
525        }
526
527        /**
528         * An email URL and title
529         * may be already encoded because of the vanguard configuration
530         *
531         * The url is not treated as an attribute
532         * because the transformation function encodes the value
533         * to mitigate XSS
534         *
535         */
536        if ($this->getUriType() == self::EMAIL_URI) {
537            $emailAddress = $this->obfuscateEmail($this->dokuwikiUrl->getPath());
538            $outputAttributes->addOutputAttributeValue("title", $emailAddress);
539        }
540
541        /**
542         * Return
543         */
544        return $outputAttributes;
545
546
547    }
548
549
550    /**
551     * Return the type of link from an ID
552     *
553     * @return string a `TYPE_xxx` constant
554     */
555    public
556    function getUriType(): string
557    {
558        return $this->uriType;
559    }
560
561
562    /**
563     * @return Page - the internal page or an error if the link is not an internal one
564     */
565    public
566    function getInternalPage(): Page
567    {
568        if ($this->linkedPage == null) {
569            if ($this->getUriType() == self::WIKI_URI) {
570                // if there is no path, this is the actual page
571                $pathOrId = $this->dokuwikiUrl->getPath();
572
573                $this->linkedPage = Page::createPageFromNonQualifiedPath($pathOrId);
574
575            } else {
576                throw new \RuntimeException("You can't ask the internal page id from a link that is not an internal one");
577            }
578        }
579        return $this->linkedPage;
580    }
581
582    public
583    function getRef()
584    {
585        return $this->ref;
586    }
587
588    /**
589     * The label inside the anchor tag if there is none
590     * @param false $navigation
591     * @return string|null
592     */
593    public function getLabel(bool $navigation = false): ?string
594    {
595
596        switch ($this->getUriType()) {
597            case self::WIKI_URI:
598                if ($navigation) {
599                    return $this->getInternalPage()->getNameOrDefault();
600                } else {
601                    return $this->getInternalPage()->getTitleOrDefault();
602                }
603
604            case self::EMAIL_URI:
605
606                global $conf;
607                $email = $this->dokuwikiUrl->getPath();
608                switch ($conf['mailguard']) {
609                    case 'none' :
610                        return $email;
611                    case 'visible' :
612                    default :
613                        $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
614                        return strtr($email, $obfuscate);
615                }
616            case self::INTERWIKI_URI:
617                return $this->dokuwikiUrl->getPath();
618            case self::LOCAL_URI:
619                return $this->dokuwikiUrl->getFragment();
620            default:
621                return $this->getRef();
622        }
623    }
624
625    /**
626     * @param $title - the value of the title attribute of the anchor
627     */
628    public
629    function setTitle($title)
630    {
631        $this->title = $title;
632    }
633
634
635    /**
636     * @throws ExceptionCombo
637     * @var string $targetEnvironmentAmpersand
638     * By default, all data are encoded
639     * at {@link TagAttributes::encodeToHtmlValue()}
640     * therefore the default is non-encoded
641     *
642     */
643    public function getUrl()
644    {
645
646        switch ($this->getUriType()) {
647            case self::WIKI_URI:
648                $page = $this->getInternalPage();
649
650                /**
651                 * Styling attribute
652                 * may be passed via parameters
653                 * for internal link
654                 * We don't want the styling attribute
655                 * in the URL
656                 *
657                 * We will not overwrite the parameters if this is an dokuwiki
658                 * action link (with the `do` property)
659                 */
660                if ($this->dokuwikiUrl->hasQueryParameter("do")) {
661
662                    $absoluteUrl = Site::shouldUrlBeAbsolute();
663                    $url = wl(
664                        $page->getDokuwikiId(),
665                        $this->dokuwikiUrl->getQueryParameters(),
666                        $absoluteUrl
667                    );
668
669                } else {
670
671                    /**
672                     * No parameters by default known
673                     */
674                    $url = $page->getCanonicalUrl(
675                        [],
676                        false
677                    );
678
679                    /**
680                     * The search term
681                     * Code adapted found at {@link Doku_Renderer_xhtml::internallink()}
682                     * We can't use the previous {@link wl function}
683                     * because it encode too much
684                     */
685                    $searchTerms = $this->dokuwikiUrl->getQueryParameter(self::SEARCH_HIGHLIGHT_QUERY_PROPERTY);
686                    if ($searchTerms !== null) {
687                        $url .= DokuwikiUrl::AMPERSAND_CHARACTER;
688                        PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot("search-hit");
689                        if (is_array($searchTerms)) {
690                            /**
691                             * To verify, do we really need the []
692                             * to get an array in php ?
693                             */
694                            $searchTermsQuery = [];
695                            foreach ($searchTerms as $searchTerm) {
696                                $searchTermsQuery[] = "s[]=$searchTerm";
697                            }
698                            $url .= implode(DokuwikiUrl::AMPERSAND_CHARACTER, $searchTermsQuery);
699                        } else {
700                            $url .= "s=$searchTerms";
701                        }
702                    }
703
704
705                }
706                if ($this->dokuwikiUrl->getFragment() != null) {
707                    /**
708                     * pageutils (transform a fragment in section id)
709                     */
710                    $check = false;
711                    $url .= '#' . sectionID($this->dokuwikiUrl->getFragment(), $check);
712                }
713                break;
714            case self::INTERWIKI_URI:
715                $wiki = $this->wiki;
716                $extendedPath = $this->dokuwikiUrl->getPath();
717                if ($this->dokuwikiUrl->getFragment() !== null) {
718                    $extendedPath .= "#{$this->dokuwikiUrl->getFragment()}";
719                }
720                $url = $this->interWikiRefToUrl($wiki, $extendedPath);
721                break;
722            case self::WINDOWS_SHARE_URI:
723                $url = str_replace('\\', '/', $this->getRef());
724                $url = 'file:///' . $url;
725                break;
726            case self::EMAIL_URI:
727                /**
728                 * An email link is `<email>`
729                 * {@link Emaillink::connectTo()}
730                 * or
731                 * {@link PluginTrait::email()
732                 */
733                // common.php#obfsucate implements the $conf['mailguard']
734                $uri = $this->getDokuwikiUrl()->getPath();
735                $uri = $this->obfuscateEmail($uri);
736                $uri = urlencode($uri);
737                $queryParameters = $this->getDokuwikiUrl()->getQueryParameters();
738                if (sizeof($queryParameters) > 0) {
739                    $uri .= "?";
740                    foreach ($queryParameters as $key => $value) {
741                        $value = urlencode($value);
742                        $key = urlencode($key);
743                        if (in_array($key, self::EMAIL_VALID_PARAMETERS)) {
744                            $uri .= "$key=$value";
745                        }
746                    }
747                }
748                $url = 'mailto:' . $uri;
749                break;
750            case self::LOCAL_URI:
751                $check = false;
752                $url = '#' . sectionID($this->ref, $check);
753                break;
754            case self::WEB_URI:
755                /**
756                 * Default is external
757                 * For instance, {@link \syntax_plugin_combo_share} link
758                 */
759                /**
760                 * Authorized scheme only
761                 * to not inject code
762                 */
763                if (is_null($this->authorizedSchemes)) {
764                    // https://www.dokuwiki.org/urlschemes
765                    $this->authorizedSchemes = getSchemes();
766                    $this->authorizedSchemes[] = "whatsapp";
767                    $this->authorizedSchemes[] = "mailto";
768                }
769                if (!in_array($this->schemeUri, $this->authorizedSchemes)) {
770                    throw new ExceptionCombo("The scheme ($this->schemeUri) is not authorized as uri");
771                } else {
772                    $url = $this->ref;
773                }
774                break;
775            case self::VARIABLE_URI:
776                throw new ExceptionCombo("A template variable uri ($this->ref) can not give back an url, it should be first be replaced");
777            default:
778                throw new ExceptionCombo("The structure of the reference ($this->ref) is unknown");
779        }
780
781
782        return $url;
783    }
784
785    public function getWiki(): ?string
786    {
787        return $this->wiki;
788    }
789
790
791    public
792    function getScheme()
793    {
794        return $this->schemeUri;
795    }
796
797
798    private
799    function wikiExists(): bool
800    {
801        $wikis = getInterwiki();
802        return key_exists($this->wiki, $wikis);
803    }
804
805    private
806    function obfuscateEmail($email, $inAttribute = true): string
807    {
808        /**
809         * adapted from {@link obfuscate()} in common.php
810         */
811        global $conf;
812
813        $mailGuard = $conf['mailguard'];
814        if ($mailGuard === "hex" && $inAttribute) {
815            $mailGuard = "visible";
816        }
817        switch ($mailGuard) {
818            case 'visible' :
819                $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
820                return strtr($email, $obfuscate);
821
822            case 'hex' :
823                return Conversion::toHtml($email, true);
824
825            case 'none' :
826            default :
827                return $email;
828        }
829    }
830
831
832    public
833    function isRelative(): bool
834    {
835        return strpos($this->path, ':') !== 0;
836    }
837
838    public
839    function getDokuwikiUrl(): DokuwikiUrl
840    {
841        return $this->dokuwikiUrl;
842    }
843
844
845    public
846    static function getHtmlClassInternalLink(): string
847    {
848        $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
849        if ($oldClassName) {
850            return "wikilink1";
851        } else {
852            return "link-internal";
853        }
854    }
855
856    public
857    static function getHtmlClassEmailLink(): string
858    {
859        $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
860        if ($oldClassName) {
861            return "mail";
862        } else {
863            return "link-mail";
864        }
865    }
866
867    public
868    static function getHtmlClassInterWikiLink(): string
869    {
870        $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
871        if ($oldClassName) {
872            return "interwiki";
873        } else {
874            return "link-interwiki";
875        }
876    }
877
878    public
879    static function getHtmlClassExternalLink(): string
880    {
881        $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
882        if ($oldClassName) {
883            return "urlextern";
884        } else {
885            return "link-external";
886        }
887    }
888
889//FYI: exist in dokuwiki is "wikilink1 but we let the control to the user
890    public
891    static function getHtmlClassNotExist(): string
892    {
893        $oldClassName = PluginUtility::getConfValue(self::CONF_USE_DOKUWIKI_CLASS_NAME);
894        if ($oldClassName) {
895            return "wikilink2";
896        } else {
897            return self::TEXT_ERROR_CLASS;
898        }
899    }
900
901    public
902    function __toString()
903    {
904        return $this->ref;
905    }
906
907    private
908    function getEmailObfuscationConfiguration()
909    {
910        global $conf;
911        return $conf['mailguard'];
912    }
913
914    /**
915     * @param string $shortcut
916     * @param string $reference
917     * @return mixed|string
918     * Adapted  from {@link Doku_Renderer_xhtml::_resolveInterWiki()}
919     * @noinspection DuplicatedCode
920     */
921    private function interWikiRefToUrl(string &$shortcut, string $reference)
922    {
923
924        if ($this->interwiki === null) {
925            $this->interwiki = getInterwiki();
926        }
927
928        // Get interwiki URL
929        if (isset($this->interwiki[$shortcut])) {
930            $url = $this->interwiki[$shortcut];
931        } elseif (isset($this->interwiki['default'])) {
932            $shortcut = 'default';
933            $url = $this->interwiki[$shortcut];
934        } else {
935            // not parsable interwiki outputs '' to make sure string manipulation works
936            $shortcut = '';
937            $url = '';
938        }
939
940        //split into hash and url part
941        $hash = strrchr($reference, '#');
942        if ($hash) {
943            $reference = substr($reference, 0, -strlen($hash));
944            $hash = substr($hash, 1);
945        }
946
947        //replace placeholder
948        if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) {
949            //use placeholders
950            $url = str_replace('{URL}', rawurlencode($reference), $url);
951            //wiki names will be cleaned next, otherwise urlencode unsafe chars
952            $url = str_replace('{NAME}', ($url[0] === ':') ? $reference :
953                preg_replace_callback('/[[\\\\\]^`{|}#%]/', function ($match) {
954                    return rawurlencode($match[0]);
955                }, $reference), $url);
956            $parsed = parse_url($reference);
957            if (empty($parsed['scheme'])) $parsed['scheme'] = '';
958            if (empty($parsed['host'])) $parsed['host'] = '';
959            if (empty($parsed['port'])) $parsed['port'] = 80;
960            if (empty($parsed['path'])) $parsed['path'] = '';
961            if (empty($parsed['query'])) $parsed['query'] = '';
962            $url = strtr($url, [
963                '{SCHEME}' => $parsed['scheme'],
964                '{HOST}' => $parsed['host'],
965                '{PORT}' => $parsed['port'],
966                '{PATH}' => $parsed['path'],
967                '{QUERY}' => $parsed['query'],
968            ]);
969        } else if ($url != '') {
970            // make sure when no url is defined, we keep it null
971            // default
972            $url = $url . rawurlencode($reference);
973        }
974        //handle as wiki links
975        if ($url[0] === ':') {
976            $urlParam = null;
977            $id = $url;
978            if (strpos($url, '?') !== false) {
979                list($id, $urlParam) = explode('?', $url, 2);
980            }
981            $url = wl(cleanID($id), $urlParam);
982            $exists = page_exists($id);
983        }
984        if ($hash) $url .= '#' . rawurlencode($hash);
985
986        return $url;
987    }
988
989
990}
991