xref: /plugin/combo/ComboStrap/MarkupRef.php (revision 738ed353b0e49780521a777daf155242cb131fc9)
14cadd4f8SNickeau<?php
24cadd4f8SNickeau
34cadd4f8SNickeaunamespace ComboStrap;
44cadd4f8SNickeau
504fd306cSNickeauuse ComboStrap\Web\Url;
604fd306cSNickeauuse ComboStrap\Web\UrlEndpoint;
704fd306cSNickeauuse syntax_plugin_combo_variable;
84cadd4f8SNickeau
94cadd4f8SNickeau/**
104cadd4f8SNickeau *
1104fd306cSNickeau * Basically, a class that parse a link/media markup reference and returns an URL.
124cadd4f8SNickeau *
1304fd306cSNickeau * Detailed, the class parses the reference:
1404fd306cSNickeau *   * from a {@link MarkupRef::createMediaFromRef() media markup}
1504fd306cSNickeau *   * or {@link MarkupRef::createLinkFromRef() link markup}
1604fd306cSNickeau * and returns an {@link MarkupRef::getUrl() URL},
1704fd306cSNickeau *
1804fd306cSNickeau * You may determine the {@link MarkupRef::getSchemeType() type of reference}
1904fd306cSNickeau *
2004fd306cSNickeau * For a {@link MarkupRef::WIKI_URI}, the URL returned is:
2104fd306cSNickeau *   * a {@link UrlEndpoint::createFetchUrl() fetch url} for a media
2204fd306cSNickeau *   * a {@link UrlEndpoint::createDokuUrl() doku url} for a link (ie page)
2304fd306cSNickeau *
2404fd306cSNickeau * If this is a {@link MarkupRef::INTERWIKI_URI}, you may also get the {@link MarkupRef::getInterWiki() interwiki instance}
2504fd306cSNickeau * If this is a {@link MarkupRef::WIKI_URI}, you may also get the {@link MarkupRef::getPath() path}
2604fd306cSNickeau *
2704fd306cSNickeau *
2804fd306cSNickeau * Why ?
2904fd306cSNickeau * The parsing function {@link Doku_Handler_Parse_Media} has some flow / problem
3004fd306cSNickeau *    * It keeps the anchor only if there is no query string
3104fd306cSNickeau *    * It takes the first digit as the width (ie media.pdf?page=31 would have a width of 31)
3204fd306cSNickeau *    * `src` is not only the media path but may have a anchor
3304fd306cSNickeau *    * ...
3404fd306cSNickeau *
354cadd4f8SNickeau */
364cadd4f8SNickeauclass MarkupRef
374cadd4f8SNickeau{
3804fd306cSNickeau    public const WINDOWS_SHARE_URI = 'windowsShare';
3904fd306cSNickeau    public const LOCAL_URI = 'local';
4004fd306cSNickeau    public const EMAIL_URI = 'email';
4104fd306cSNickeau    public const WEB_URI = 'external';
4204fd306cSNickeau
4304fd306cSNickeau    /**
4404fd306cSNickeau     * Type of Ref
4504fd306cSNickeau     */
4604fd306cSNickeau    public const INTERWIKI_URI = 'interwiki';
4704fd306cSNickeau    public const WIKI_URI = 'internal';
4804fd306cSNickeau    public const VARIABLE_URI = 'internal_template';
4904fd306cSNickeau    public const REF_ATTRIBUTE = "ref";
504cadd4f8SNickeau
514cadd4f8SNickeau
524cadd4f8SNickeau    /**
5304fd306cSNickeau     * The type of markup ref (ie media or link)
544cadd4f8SNickeau     */
5504fd306cSNickeau    private string $type;
5604fd306cSNickeau    const MEDIA_TYPE = "media";
5704fd306cSNickeau    const LINK_TYPE = "link";
584cadd4f8SNickeau
5904fd306cSNickeau    private string $refScheme;
6004fd306cSNickeau    public const EXTERNAL_MEDIA_CALL_NAME = "external";
6104fd306cSNickeau
6204fd306cSNickeau
6304fd306cSNickeau    private string $ref;
6404fd306cSNickeau    private ?Url $url = null;
6504fd306cSNickeau
6604fd306cSNickeau    private ?Path $path = null;
6704fd306cSNickeau    private ?InterWiki $interWiki = null;
684cadd4f8SNickeau
694cadd4f8SNickeau
704cadd4f8SNickeau    /**
7104fd306cSNickeau     * @throws ExceptionBadSyntax
7204fd306cSNickeau     * @throws ExceptionBadArgument
7304fd306cSNickeau     * @throws ExceptionNotFound
744cadd4f8SNickeau     */
7504fd306cSNickeau    public function __construct($ref, $type)
764cadd4f8SNickeau    {
774cadd4f8SNickeau        $this->ref = $ref;
7804fd306cSNickeau        $this->type = $type;
794cadd4f8SNickeau
8004fd306cSNickeau        $this->url = Url::createEmpty();
814cadd4f8SNickeau
8204fd306cSNickeau        $ref = trim($ref);
834cadd4f8SNickeau
844cadd4f8SNickeau        /**
854cadd4f8SNickeau         * Email validation pattern
864cadd4f8SNickeau         * E-Mail (pattern below is defined in inc/mail.php)
874cadd4f8SNickeau         *
884cadd4f8SNickeau         * Example:
8904fd306cSNickeau         * [[support@combostrap.com?subject=hallo world]]
904cadd4f8SNickeau         * [[support@combostrap.com]]
914cadd4f8SNickeau         */
924cadd4f8SNickeau        $emailRfc2822 = "0-9a-zA-Z!#$%&'*+/=?^_`{|}~-";
934cadd4f8SNickeau        $emailPattern = '[' . $emailRfc2822 . ']+(?:\.[' . $emailRfc2822 . ']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})';
944cadd4f8SNickeau        if (preg_match('<' . $emailPattern . '>', $ref)) {
9504fd306cSNickeau            $this->refScheme = self::EMAIL_URI;
9604fd306cSNickeau            $position = strpos($ref, "?");
9704fd306cSNickeau
9804fd306cSNickeau            if ($position !== false) {
9904fd306cSNickeau                $email = substr($ref, 0, $position);
10004fd306cSNickeau                $queryStringAndFragment = substr($ref, $position + 1);
10104fd306cSNickeau                $this->url = Url::createFromString("mailto:$email");
10204fd306cSNickeau                $this->parseAndAddQueryStringAndFragment($queryStringAndFragment);
10304fd306cSNickeau            } else {
10404fd306cSNickeau                $this->url = Url::createFromString("mailto:$ref");
1054cadd4f8SNickeau            }
10604fd306cSNickeau            return;
1074cadd4f8SNickeau        }
1084cadd4f8SNickeau
1094cadd4f8SNickeau        /**
11004fd306cSNickeau         * Case when the URL is just a full conform URL
11104fd306cSNickeau         *
11204fd306cSNickeau         * Example: `https://` or `ftp://`
1134cadd4f8SNickeau         *
1144cadd4f8SNickeau         * Other scheme are not yet recognized
1154cadd4f8SNickeau         * because it can also be a wiki id
1164cadd4f8SNickeau         * For instance, `mailto:` is also a valid page
11704fd306cSNickeau         *
11804fd306cSNickeau         * same as {@link media_isexternal()}  check only http / ftp scheme
1194cadd4f8SNickeau         */
12004fd306cSNickeau        if (preg_match('#^([a-z0-9\-.+]+?)://#i', $ref)) {
12104fd306cSNickeau            try {
12204fd306cSNickeau                $this->url = Url::createFromString($ref);
12304fd306cSNickeau                $this->refScheme = self::WEB_URI;
12404fd306cSNickeau
12504fd306cSNickeau                /**
12604fd306cSNickeau                 * Authorized scheme only (to not inject code ?)
12704fd306cSNickeau                 */
12804fd306cSNickeau                $authorizedSchemes = self::loadAndGetAuthorizedSchemes();
12904fd306cSNickeau                if (!in_array($this->url->getScheme(), $authorizedSchemes)) {
13004fd306cSNickeau                    throw new ExceptionBadSyntax("The scheme ({$this->url->getScheme()}) of the URL ({$this->url}) is not authorized");
1314cadd4f8SNickeau                }
13204fd306cSNickeau                try {
13304fd306cSNickeau                    $isImage = FileSystems::getMime($this->url)->isImage();
13404fd306cSNickeau                } catch (ExceptionNotFound $e) {
13504fd306cSNickeau                    $isImage = false;
13604fd306cSNickeau                }
13704fd306cSNickeau                if ($isImage) {
13804fd306cSNickeau                    $properties = $this->url->getQueryProperties();
13904fd306cSNickeau                    if (count($properties) >= 1) {
14004fd306cSNickeau                        try {
14104fd306cSNickeau                            /**
14204fd306cSNickeau                             * The first parameter is the `Width X Height`
14304fd306cSNickeau                             */
14404fd306cSNickeau                            $widthAndHeight = array_key_first($properties);
14504fd306cSNickeau                            $xPosition = strpos($widthAndHeight, "x");
14604fd306cSNickeau                            if ($xPosition !== false) {
14704fd306cSNickeau                                $width = DataType::toInteger(substr($widthAndHeight, 0, $xPosition));
14804fd306cSNickeau                                if ($width !== 0) {
14904fd306cSNickeau                                    $this->url->addQueryParameter(Dimension::WIDTH_KEY, $width);
15004fd306cSNickeau                                }
15104fd306cSNickeau                                $height = DataType::toInteger(substr($widthAndHeight, $xPosition + 1));
15204fd306cSNickeau                                $this->url->addQueryParameter(Dimension::HEIGHT_KEY, $height);
15304fd306cSNickeau                            } else {
15404fd306cSNickeau                                $width = DataType::toInteger($widthAndHeight);
15504fd306cSNickeau                                $this->url->addQueryParameter(Dimension::WIDTH_KEY, $width);
15604fd306cSNickeau                            }
15704fd306cSNickeau                            $this->url->deleteQueryParameter($widthAndHeight);
15804fd306cSNickeau                            if ($this->url->hasProperty(MediaMarkup::LINKING_NOLINK_VALUE)) {
15904fd306cSNickeau                                $this->url->addQueryParameter(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE);
16004fd306cSNickeau                                $this->url->deleteQueryParameter(MediaMarkup::LINKING_NOLINK_VALUE);
16104fd306cSNickeau                            }
16204fd306cSNickeau                        } catch (ExceptionBadArgument $e) {
16304fd306cSNickeau                            // not a number/integer
16404fd306cSNickeau                        }
16504fd306cSNickeau                    }
16604fd306cSNickeau                }
16704fd306cSNickeau                return;
16804fd306cSNickeau            } catch (ExceptionBadSyntax $e) {
16904fd306cSNickeau                throw new ExceptionBadSyntax("The url string was not validated as an URL ($ref). Error: {$e->getMessage()}");
17004fd306cSNickeau            }
17104fd306cSNickeau        }
17204fd306cSNickeau
17304fd306cSNickeau        /**
17404fd306cSNickeau         * Windows share link
17504fd306cSNickeau         */
17604fd306cSNickeau        if (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $ref)) {
17704fd306cSNickeau            $this->refScheme = self::WINDOWS_SHARE_URI;
17804fd306cSNickeau            $this->url = LocalPath::createFromPathString($ref)->getUrl();
17904fd306cSNickeau            return;
18004fd306cSNickeau        }
18104fd306cSNickeau
18204fd306cSNickeau        /**
18304fd306cSNickeau         * Only Fragment (also known as local link)
18404fd306cSNickeau         */
18504fd306cSNickeau        if (preg_match('/^#.?/', $ref)) {
18604fd306cSNickeau            $this->refScheme = self::LOCAL_URI;
18704fd306cSNickeau
18804fd306cSNickeau            $fragment = substr($ref, 1);
18904fd306cSNickeau            if ($fragment !== "") {
19004fd306cSNickeau                $fragment = OutlineSection::textToHtmlSectionId($fragment);
19104fd306cSNickeau            }
19204fd306cSNickeau            $this->url = Url::createEmpty()->setFragment($fragment);
19304fd306cSNickeau            $this->path = WikiPath::createRequestedPagePathFromRequest();
19404fd306cSNickeau            return;
1954cadd4f8SNickeau        }
1964cadd4f8SNickeau
1974cadd4f8SNickeau        /**
1984cadd4f8SNickeau         * Interwiki ?
1994cadd4f8SNickeau         */
20004fd306cSNickeau        if (preg_match('/^[a-zA-Z0-9.]+>/u', $ref)) {
20104fd306cSNickeau
20204fd306cSNickeau            $this->refScheme = MarkupRef::INTERWIKI_URI;
20304fd306cSNickeau            switch ($type) {
20404fd306cSNickeau                case self::MEDIA_TYPE:
20504fd306cSNickeau                    $this->interWiki = InterWiki::createMediaInterWikiFromString($ref);
20604fd306cSNickeau                    break;
20704fd306cSNickeau                case self::LINK_TYPE:
20804fd306cSNickeau                    $this->interWiki = InterWiki::createLinkInterWikiFromString($ref);
20904fd306cSNickeau                    break;
21004fd306cSNickeau                default:
21104fd306cSNickeau                    LogUtility::internalError("The type ($type) is unknown, returning a interwiki link ref");
21204fd306cSNickeau                    $this->interWiki = InterWiki::createLinkInterWikiFromString($ref);
21304fd306cSNickeau                    break;
2144cadd4f8SNickeau            }
21504fd306cSNickeau            $this->url = $this->interWiki->toUrl();
21604fd306cSNickeau            return;
21704fd306cSNickeau
2184cadd4f8SNickeau        }
2194cadd4f8SNickeau
22004fd306cSNickeau
2214cadd4f8SNickeau        /**
2224cadd4f8SNickeau         * It can be a link with a ref template
2234cadd4f8SNickeau         */
22404fd306cSNickeau        if (syntax_plugin_combo_variable::isVariable($ref)) {
22504fd306cSNickeau            $this->refScheme = MarkupRef::VARIABLE_URI;
22604fd306cSNickeau            return;
22704fd306cSNickeau        }
22804fd306cSNickeau
22904fd306cSNickeau        /**
23004fd306cSNickeau         * Doku Path
23104fd306cSNickeau         * We parse it
23204fd306cSNickeau         */
23304fd306cSNickeau        $this->refScheme = MarkupRef::WIKI_URI;
23404fd306cSNickeau
23504fd306cSNickeau        $questionMarkPosition = strpos($ref, "?");
23604fd306cSNickeau        $wikiPath = $ref;
23704fd306cSNickeau        $fragment = null;
23804fd306cSNickeau        $queryStringAndAnchorOriginal = null;
23904fd306cSNickeau        if ($questionMarkPosition !== false) {
24004fd306cSNickeau            $wikiPath = substr($ref, 0, $questionMarkPosition);
24104fd306cSNickeau            $queryStringAndAnchorOriginal = substr($ref, $questionMarkPosition + 1);
2424cadd4f8SNickeau        } else {
24304fd306cSNickeau            // We may have only an anchor
24404fd306cSNickeau            $hashTagPosition = strpos($ref, "#");
24504fd306cSNickeau            if ($hashTagPosition !== false) {
24604fd306cSNickeau                $wikiPath = substr($ref, 0, $hashTagPosition);
24704fd306cSNickeau                $fragment = substr($ref, $hashTagPosition + 1);
2484cadd4f8SNickeau            }
2494cadd4f8SNickeau        }
2504cadd4f8SNickeau
2514cadd4f8SNickeau        /**
2524cadd4f8SNickeau         *
25304fd306cSNickeau         * Clean it
2544cadd4f8SNickeau         */
25504fd306cSNickeau        $wikiPath = $this->normalizePath($wikiPath);
2564cadd4f8SNickeau
2574cadd4f8SNickeau        /**
25804fd306cSNickeau         * The URL
25904fd306cSNickeau         * The path is created at the end because it may have a revision
2604cadd4f8SNickeau         */
2614cadd4f8SNickeau        switch ($type) {
26204fd306cSNickeau            case self::MEDIA_TYPE:
26304fd306cSNickeau                $this->url = UrlEndpoint::createFetchUrl();
2644cadd4f8SNickeau                break;
26504fd306cSNickeau            case self::LINK_TYPE:
26604fd306cSNickeau                $this->url = UrlEndpoint::createDokuUrl();
2674cadd4f8SNickeau                break;
2684cadd4f8SNickeau            default:
26904fd306cSNickeau                throw new ExceptionBadArgument("The ref type ($type) is unknown");
27004fd306cSNickeau        }
27104fd306cSNickeau
27204fd306cSNickeau
2734cadd4f8SNickeau        /**
27404fd306cSNickeau         * Parsing Query string if any
2754cadd4f8SNickeau         */
27604fd306cSNickeau        if ($queryStringAndAnchorOriginal !== null) {
27704fd306cSNickeau
27804fd306cSNickeau            $this->parseAndAddQueryStringAndFragment($queryStringAndAnchorOriginal);
27904fd306cSNickeau
28004fd306cSNickeau        }
28104fd306cSNickeau
28204fd306cSNickeau        /**
28304fd306cSNickeau         * The path
28404fd306cSNickeau         */
28504fd306cSNickeau        try {
28604fd306cSNickeau            $rev = $this->url->getQueryPropertyValue(WikiPath::REV_ATTRIBUTE);
28704fd306cSNickeau        } catch (ExceptionNotFound $e) {
28804fd306cSNickeau            $rev = null;
28904fd306cSNickeau        }
29004fd306cSNickeau        /**
29104fd306cSNickeau         * The wiki path may be relative
29204fd306cSNickeau         */
29304fd306cSNickeau        switch ($type) {
29404fd306cSNickeau            case self::MEDIA_TYPE:
29504fd306cSNickeau                $this->path = WikiPath::createMediaPathFromId($wikiPath, $rev);
29670bbd7f1Sgerardnico                $this->url->addQueryParameter(MediaMarkup::$MEDIA_QUERY_PARAMETER, $this->path->getWikiId());
29704fd306cSNickeau                $this->addRevToUrl($rev);
29804fd306cSNickeau
29904fd306cSNickeau                if ($fragment !== null) {
30004fd306cSNickeau                    $this->url->setFragment($fragment);
30104fd306cSNickeau                }
30204fd306cSNickeau
3034cadd4f8SNickeau                break;
30404fd306cSNickeau            case self::LINK_TYPE:
3054cadd4f8SNickeau
30604fd306cSNickeau                /**
30704fd306cSNickeau                 * The path may be an id if it exists
30804fd306cSNickeau                 * otherwise it's a relative path
30904fd306cSNickeau                 * MarkupPath is important because a link to
31004fd306cSNickeau                 * a namespace (ie wikiPath = `ns:`)
31104fd306cSNickeau                 * should become `ns:start`)
31204fd306cSNickeau                 */
31304fd306cSNickeau                $markupPath = MarkupPath::createMarkupFromStringPath($wikiPath);
31404fd306cSNickeau                if (!FileSystems::exists($markupPath) && $wikiPath !== "") {
31504fd306cSNickeau                    // We test for an empty wikiPath string
31604fd306cSNickeau                    // because if the wiki path is the empty string,
31704fd306cSNickeau                    // this is the current requested page
31804fd306cSNickeau                    // An empty id is the root and always exists
31904fd306cSNickeau                    $idPath = MarkupPath::createMarkupFromId($wikiPath);
32004fd306cSNickeau                    if (FileSystems::exists($idPath)) {
32104fd306cSNickeau                        $markupPath = $idPath;
32204fd306cSNickeau                    }
3234cadd4f8SNickeau                }
3244cadd4f8SNickeau
3254cadd4f8SNickeau                /**
32604fd306cSNickeau                 * The path may be a namespace, in the page system
32704fd306cSNickeau                 * the path should then be the index page
3284cadd4f8SNickeau                 */
32904fd306cSNickeau                try {
33004fd306cSNickeau                    $this->path = $markupPath->getPathObject()->toWikiPath();
33104fd306cSNickeau                } catch (ExceptionCompile $e) {
33204fd306cSNickeau                    throw new ExceptionRuntimeInternal("Path should be a wiki path");
33304fd306cSNickeau                }
33404fd306cSNickeau                $this->url->addQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->path->getWikiId());
33504fd306cSNickeau                $this->addRevToUrl($rev);
33604fd306cSNickeau
33704fd306cSNickeau                if ($fragment !== null) {
33804fd306cSNickeau                    $fragment = OutlineSection::textToHtmlSectionId($fragment);
33904fd306cSNickeau                    $this->url->setFragment($fragment);
3404cadd4f8SNickeau                }
3414cadd4f8SNickeau
34204fd306cSNickeau                break;
3434cadd4f8SNickeau            default:
34404fd306cSNickeau                throw new ExceptionBadArgument("The ref type ($type) is unknown");
3454cadd4f8SNickeau        }
34604fd306cSNickeau
3474cadd4f8SNickeau    }
3484cadd4f8SNickeau
3494cadd4f8SNickeau    /**
35004fd306cSNickeau     * @throws ExceptionBadArgument
35104fd306cSNickeau     * @throws ExceptionBadSyntax
35204fd306cSNickeau     * @throws ExceptionNotFound
3534cadd4f8SNickeau     */
3544cadd4f8SNickeau    public
35504fd306cSNickeau    static function createMediaFromRef($refProcessing): MarkupRef
3564cadd4f8SNickeau    {
35704fd306cSNickeau        return new MarkupRef($refProcessing, self::MEDIA_TYPE);
3584cadd4f8SNickeau    }
3594cadd4f8SNickeau
3604cadd4f8SNickeau    /**
36104fd306cSNickeau     * @throws ExceptionBadSyntax
36204fd306cSNickeau     * @throws ExceptionBadArgument
36304fd306cSNickeau     * @throws ExceptionNotFound
3644cadd4f8SNickeau     */
36504fd306cSNickeau    public
36604fd306cSNickeau    static function createLinkFromRef($refProcessing): MarkupRef
3674cadd4f8SNickeau    {
36804fd306cSNickeau        return new MarkupRef($refProcessing, self::LINK_TYPE);
3694cadd4f8SNickeau    }
3704cadd4f8SNickeau
3714cadd4f8SNickeau    // https://www.dokuwiki.org/urlschemes
37204fd306cSNickeau    private static function loadAndGetAuthorizedSchemes(): array
37304fd306cSNickeau    {
37404fd306cSNickeau
37504fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
37604fd306cSNickeau            ->getConfig()
37704fd306cSNickeau            ->getAuthorizedUrlSchemes();
37804fd306cSNickeau
37904fd306cSNickeau
3804cadd4f8SNickeau    }
38104fd306cSNickeau
38204fd306cSNickeau    /**
38304fd306cSNickeau     * In case of manual entry, the function will normalize the path
38404fd306cSNickeau     * @param string $wikiPath - a path entered by a user
38504fd306cSNickeau     * @return string
38604fd306cSNickeau     */
38704fd306cSNickeau    public function normalizePath(string $wikiPath): string
38804fd306cSNickeau    {
38904fd306cSNickeau        if ($wikiPath === "") {
39004fd306cSNickeau            return $wikiPath;
39104fd306cSNickeau        }
39204fd306cSNickeau        // slash to double point
39304fd306cSNickeau        $wikiPath = str_replace(WikiPath::NAMESPACE_SEPARATOR_SLASH, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $wikiPath);
39404fd306cSNickeau
39504fd306cSNickeau        $isNamespacePath = false;
39604fd306cSNickeau        if ($wikiPath[strlen($wikiPath) - 1] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
39704fd306cSNickeau            $isNamespacePath = true;
39804fd306cSNickeau        }
39904fd306cSNickeau        $isPath = false;
40004fd306cSNickeau        if ($wikiPath[0] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
40104fd306cSNickeau            $isPath = true;
40204fd306cSNickeau        }
40304fd306cSNickeau        $pathType = "unknown";
40404fd306cSNickeau        if ($wikiPath[0] === WikiPath::CURRENT_PATH_CHARACTER) {
40504fd306cSNickeau            $pathType = "current";
40604fd306cSNickeau            if (isset($wikiPath[1])) {
40704fd306cSNickeau                if ($wikiPath[1] === WikiPath::CURRENT_PATH_CHARACTER) {
40804fd306cSNickeau                    $pathType = "parent";
40904fd306cSNickeau                }
41004fd306cSNickeau            }
41104fd306cSNickeau        }
41204fd306cSNickeau        /**
41304fd306cSNickeau         * Dokuwiki Compliance
41404fd306cSNickeau         */
41504fd306cSNickeau        $cleanPath = cleanID($wikiPath);
41604fd306cSNickeau        if ($isNamespacePath) {
41704fd306cSNickeau            $cleanPath = "$cleanPath:";
41804fd306cSNickeau        }
41904fd306cSNickeau        switch ($pathType) {
42004fd306cSNickeau            case "current":
42104fd306cSNickeau                if (!$isNamespacePath) {
42204fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PATH_CHARACTER . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $cleanPath;
4234cadd4f8SNickeau                } else {
42404fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PATH_CHARACTER . $cleanPath;
4254cadd4f8SNickeau                }
4264cadd4f8SNickeau                break;
42704fd306cSNickeau            case "parent":
42804fd306cSNickeau                if (!$isNamespacePath) {
42904fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PARENT_PATH_CHARACTER . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $cleanPath;
43004fd306cSNickeau                } else {
43104fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PARENT_PATH_CHARACTER . $cleanPath;
4324cadd4f8SNickeau                }
43304fd306cSNickeau                break;
4344cadd4f8SNickeau        }
43504fd306cSNickeau        if ($isPath) {
43604fd306cSNickeau            $cleanPath = ":$cleanPath";
43704fd306cSNickeau        }
43804fd306cSNickeau        return $cleanPath;
4394cadd4f8SNickeau    }
4404cadd4f8SNickeau
4414cadd4f8SNickeau
4424cadd4f8SNickeau    public
44304fd306cSNickeau    function getUrl(): Url
4444cadd4f8SNickeau    {
44504fd306cSNickeau        return $this->url;
4464cadd4f8SNickeau    }
4474cadd4f8SNickeau
4484cadd4f8SNickeau    /**
44904fd306cSNickeau     * @throws ExceptionNotFound
4504cadd4f8SNickeau     */
4514cadd4f8SNickeau    public
45204fd306cSNickeau    function getPath(): WikiPath
4534cadd4f8SNickeau    {
45404fd306cSNickeau        if ($this->path === null) {
45504fd306cSNickeau            throw new ExceptionNotFound("No path was found");
45604fd306cSNickeau        }
45704fd306cSNickeau        return $this->path;
4584cadd4f8SNickeau    }
4594cadd4f8SNickeau
4604cadd4f8SNickeau    public
46104fd306cSNickeau    function getRef(): string
4624cadd4f8SNickeau    {
4634cadd4f8SNickeau        return $this->ref;
4644cadd4f8SNickeau    }
4654cadd4f8SNickeau
46604fd306cSNickeau    public function getSchemeType(): string
4674cadd4f8SNickeau    {
46804fd306cSNickeau        return $this->refScheme;
4694cadd4f8SNickeau    }
4704cadd4f8SNickeau
4714cadd4f8SNickeau    /**
47204fd306cSNickeau     * @throws ExceptionNotFound
4734cadd4f8SNickeau     */
47404fd306cSNickeau    public function getInterWiki(): InterWiki
4754cadd4f8SNickeau    {
47604fd306cSNickeau        if ($this->interWiki === null) {
47704fd306cSNickeau            throw new ExceptionNotFound("This ref ($this->ref) is not an interWiki.");
47804fd306cSNickeau        }
47904fd306cSNickeau        return $this->interWiki;
4804cadd4f8SNickeau    }
4814cadd4f8SNickeau
48204fd306cSNickeau    private function addRevToUrl($rev = null): void
48304fd306cSNickeau    {
48404fd306cSNickeau        if ($rev !== null) {
48504fd306cSNickeau            $this->url->addQueryParameter(WikiPath::REV_ATTRIBUTE, $rev);
48604fd306cSNickeau        }
48704fd306cSNickeau    }
48804fd306cSNickeau
48904fd306cSNickeau
49004fd306cSNickeau    public function getType(): string
49104fd306cSNickeau    {
49204fd306cSNickeau        return $this->type;
49304fd306cSNickeau    }
49404fd306cSNickeau
49504fd306cSNickeau    /**
49604fd306cSNickeau     * A query parameters value may have a # for the definition of a color
49704fd306cSNickeau     * This process takes it into account
49804fd306cSNickeau     * @param string $queryStringAndFragment
49904fd306cSNickeau     * @return void
50004fd306cSNickeau     */
50104fd306cSNickeau    private function parseAndAddQueryStringAndFragment(string $queryStringAndFragment)
50204fd306cSNickeau    {
50304fd306cSNickeau        /**
50404fd306cSNickeau         * The value $queryStringAndAnchorOriginal
50504fd306cSNickeau         * is kept to create the original queryString
50604fd306cSNickeau         * at the end if we found an anchor
50704fd306cSNickeau         *
50804fd306cSNickeau         * We parse token by token because we allow a hashtag for a hex color
50904fd306cSNickeau         */
51004fd306cSNickeau        $queryStringAndAnchorProcessing = $queryStringAndFragment;
51104fd306cSNickeau        while (strlen($queryStringAndAnchorProcessing) > 0) {
51204fd306cSNickeau
51304fd306cSNickeau            /**
51404fd306cSNickeau             * Capture the token
51504fd306cSNickeau             * and reduce the text
51604fd306cSNickeau             */
51704fd306cSNickeau            $questionMarkPos = strpos($queryStringAndAnchorProcessing, "&");
51804fd306cSNickeau            if ($questionMarkPos !== false) {
51904fd306cSNickeau                $token = substr($queryStringAndAnchorProcessing, 0, $questionMarkPos);
52004fd306cSNickeau                $queryStringAndAnchorProcessing = substr($queryStringAndAnchorProcessing, $questionMarkPos + 1);
5214cadd4f8SNickeau            } else {
52204fd306cSNickeau                $token = $queryStringAndAnchorProcessing;
52304fd306cSNickeau                $queryStringAndAnchorProcessing = "";
5244cadd4f8SNickeau            }
5254cadd4f8SNickeau
52604fd306cSNickeau
52704fd306cSNickeau            /**
52804fd306cSNickeau             * Sizing (wxh)
52904fd306cSNickeau             */
53004fd306cSNickeau            $sizing = [];
53104fd306cSNickeau            if (preg_match('/^([0-9]+)(?:x([0-9]+))?/', $token, $sizing)) {
53204fd306cSNickeau                $this->url->addQueryParameter(Dimension::WIDTH_KEY, $sizing[1]);
53304fd306cSNickeau                if (isset($sizing[2])) {
53404fd306cSNickeau                    $this->url->addQueryParameter(Dimension::HEIGHT_KEY, $sizing[2]);
53504fd306cSNickeau                }
53604fd306cSNickeau                $token = substr($token, strlen($sizing[0]));
53704fd306cSNickeau                if ($token === "") {
53804fd306cSNickeau                    // no anchor behind we continue
53904fd306cSNickeau                    continue;
54004fd306cSNickeau                }
5414cadd4f8SNickeau            }
5424cadd4f8SNickeau
54304fd306cSNickeau            /**
54404fd306cSNickeau             * Linking
54504fd306cSNickeau             */
54604fd306cSNickeau            $found = preg_match('/^(nolink|direct|linkonly|details)/i', $token, $matches);
54704fd306cSNickeau            if ($found) {
54804fd306cSNickeau                $linkingValue = $matches[1];
54904fd306cSNickeau                $this->url->addQueryParameter(MediaMarkup::LINKING_KEY, $linkingValue);
55004fd306cSNickeau                $token = substr($token, strlen($linkingValue));
55104fd306cSNickeau                if ($token == "") {
55204fd306cSNickeau                    // no anchor behind we continue
55304fd306cSNickeau                    continue;
5544cadd4f8SNickeau                }
5554cadd4f8SNickeau            }
5564cadd4f8SNickeau
55704fd306cSNickeau            /**
55804fd306cSNickeau             * Cache
55904fd306cSNickeau             */
56004fd306cSNickeau            $noCacheValue = IFetcherAbs::NOCACHE_VALUE;
56104fd306cSNickeau            $found = preg_match('/^(' . $noCacheValue . ')/i', $token, $matches);
56204fd306cSNickeau            if ($found) {
56304fd306cSNickeau                $this->url->addQueryParameter(IFetcherAbs::CACHE_KEY, $noCacheValue);
56404fd306cSNickeau                $token = substr($token, strlen($noCacheValue));
56504fd306cSNickeau                if ($token == "") {
56604fd306cSNickeau                    // no anchor behind we continue
56704fd306cSNickeau                    continue;
56804fd306cSNickeau                }
56904fd306cSNickeau            }
57004fd306cSNickeau
57104fd306cSNickeau            /**
57204fd306cSNickeau             * Anchor value after a single token case
57304fd306cSNickeau             */
57404fd306cSNickeau            if (strpos($token, '#') === 0) {
57504fd306cSNickeau                $this->url->setFragment(substr($token, 1));
57604fd306cSNickeau                continue;
57704fd306cSNickeau            }
57804fd306cSNickeau
57904fd306cSNickeau            /**
58004fd306cSNickeau             * Key, value
58104fd306cSNickeau             * explode to the first `=`
58204fd306cSNickeau             * in the anchor value, we can have one
58304fd306cSNickeau             *
58404fd306cSNickeau             * Ex with media.pdf#page=31
58504fd306cSNickeau             */
58670bbd7f1Sgerardnico            $tokens = explode("=", $token, 2);
58770bbd7f1Sgerardnico            $key = $tokens[0];
58870bbd7f1Sgerardnico            if (count($tokens) == 2) {
58970bbd7f1Sgerardnico                $value = $tokens[1];
59070bbd7f1Sgerardnico            } else {
59170bbd7f1Sgerardnico                $value = null;
59270bbd7f1Sgerardnico            }
59304fd306cSNickeau
59404fd306cSNickeau            /**
59504fd306cSNickeau             * Case of an anchor after a boolean attribute (ie without =)
59604fd306cSNickeau             * at the end
59704fd306cSNickeau             */
59804fd306cSNickeau            $anchorPosition = strpos($key, '#');
59904fd306cSNickeau            if ($anchorPosition !== false) {
60004fd306cSNickeau                $this->url->setFragment(substr($key, $anchorPosition + 1));
60104fd306cSNickeau                $key = substr($key, 0, $anchorPosition);
60204fd306cSNickeau            }
60304fd306cSNickeau
60404fd306cSNickeau            /**
60504fd306cSNickeau             * Test Anchor on the value
60604fd306cSNickeau             */
60770bbd7f1Sgerardnico            if ($value !== null) {
60804fd306cSNickeau                if (($countHashTag = substr_count($value, "#")) >= 3) {
60904fd306cSNickeau                    LogUtility::msg("The value ($value) of the key ($key) for the link ($this) has $countHashTag `#` characters and the maximum supported is 2.", LogUtility::LVL_MSG_ERROR);
61004fd306cSNickeau                    continue;
61104fd306cSNickeau                }
61204fd306cSNickeau            } else {
61304fd306cSNickeau                /**
61404fd306cSNickeau                 * Boolean attribute
61504fd306cSNickeau                 * (null does not make it)
616*738ed353SNico                 * The boolean true is one by default
61704fd306cSNickeau                 */
618*738ed353SNico                $value = "true";
61904fd306cSNickeau            }
62004fd306cSNickeau
62104fd306cSNickeau            $anchorPosition = false;
62204fd306cSNickeau            $lowerCaseKey = strtolower($key);
62304fd306cSNickeau            if ($lowerCaseKey === TextColor::CSS_ATTRIBUTE) {
62404fd306cSNickeau                /**
62504fd306cSNickeau                 * Special case when color has one color value as hexadecimal #
62604fd306cSNickeau                 * and the hashtag
62704fd306cSNickeau                 */
62804fd306cSNickeau                if (strpos($value, '#') == 0) {
62904fd306cSNickeau                    if (substr_count($value, "#") >= 2) {
63004fd306cSNickeau
63104fd306cSNickeau                        /**
63204fd306cSNickeau                         * The last one
63304fd306cSNickeau                         */
63404fd306cSNickeau                        $anchorPosition = strrpos($value, '#');
63504fd306cSNickeau                    }
63604fd306cSNickeau                    // no anchor then
63704fd306cSNickeau                } else {
63804fd306cSNickeau                    // a color that is not hexadecimal can have an anchor
63904fd306cSNickeau                    $anchorPosition = strpos($value, "#");
64004fd306cSNickeau                }
64104fd306cSNickeau            } else {
64204fd306cSNickeau                // general case
64304fd306cSNickeau                $anchorPosition = strpos($value, "#");
64404fd306cSNickeau            }
64504fd306cSNickeau            if ($anchorPosition !== false) {
64604fd306cSNickeau                $this->url->setFragment(substr($value, $anchorPosition + 1));
64704fd306cSNickeau                $value = substr($value, 0, $anchorPosition);
64804fd306cSNickeau            }
64904fd306cSNickeau
65004fd306cSNickeau            switch ($lowerCaseKey) {
65104fd306cSNickeau                case Dimension::WIDTH_KEY_SHORT: // used in a link w=xxx
65204fd306cSNickeau                    $this->url->addQueryParameter(Dimension::WIDTH_KEY, $value);
65304fd306cSNickeau                    break;
65404fd306cSNickeau                case Dimension::HEIGHT_KEY_SHORT: // used in a link h=xxxx
65504fd306cSNickeau                    $this->url->addQueryParameter(Dimension::HEIGHT_KEY, $value);
65604fd306cSNickeau                    break;
65704fd306cSNickeau                default:
65804fd306cSNickeau                    $this->url->addQueryParameter($key, $value);
65904fd306cSNickeau                    break;
66004fd306cSNickeau            }
66104fd306cSNickeau
66204fd306cSNickeau        }
66304fd306cSNickeau
66404fd306cSNickeau    }
66504fd306cSNickeau
66604fd306cSNickeau    public function __toString()
66704fd306cSNickeau    {
66804fd306cSNickeau        return $this->getRef();
6694cadd4f8SNickeau    }
6704cadd4f8SNickeau
6714cadd4f8SNickeau
6724cadd4f8SNickeau}
673