xref: /plugin/combo/ComboStrap/MarkupRef.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
14cadd4f8SNickeau<?php
24cadd4f8SNickeau
34cadd4f8SNickeaunamespace ComboStrap;
44cadd4f8SNickeau
5*04fd306cSNickeauuse ComboStrap\Web\Url;
6*04fd306cSNickeauuse ComboStrap\Web\UrlEndpoint;
7*04fd306cSNickeauuse syntax_plugin_combo_variable;
84cadd4f8SNickeau
94cadd4f8SNickeau/**
104cadd4f8SNickeau *
11*04fd306cSNickeau * Basically, a class that parse a link/media markup reference and returns an URL.
124cadd4f8SNickeau *
13*04fd306cSNickeau * Detailed, the class parses the reference:
14*04fd306cSNickeau *   * from a {@link MarkupRef::createMediaFromRef() media markup}
15*04fd306cSNickeau *   * or {@link MarkupRef::createLinkFromRef() link markup}
16*04fd306cSNickeau * and returns an {@link MarkupRef::getUrl() URL},
17*04fd306cSNickeau *
18*04fd306cSNickeau * You may determine the {@link MarkupRef::getSchemeType() type of reference}
19*04fd306cSNickeau *
20*04fd306cSNickeau * For a {@link MarkupRef::WIKI_URI}, the URL returned is:
21*04fd306cSNickeau *   * a {@link UrlEndpoint::createFetchUrl() fetch url} for a media
22*04fd306cSNickeau *   * a {@link UrlEndpoint::createDokuUrl() doku url} for a link (ie page)
23*04fd306cSNickeau *
24*04fd306cSNickeau * If this is a {@link MarkupRef::INTERWIKI_URI}, you may also get the {@link MarkupRef::getInterWiki() interwiki instance}
25*04fd306cSNickeau * If this is a {@link MarkupRef::WIKI_URI}, you may also get the {@link MarkupRef::getPath() path}
26*04fd306cSNickeau *
27*04fd306cSNickeau *
28*04fd306cSNickeau * Why ?
29*04fd306cSNickeau * The parsing function {@link Doku_Handler_Parse_Media} has some flow / problem
30*04fd306cSNickeau *    * It keeps the anchor only if there is no query string
31*04fd306cSNickeau *    * It takes the first digit as the width (ie media.pdf?page=31 would have a width of 31)
32*04fd306cSNickeau *    * `src` is not only the media path but may have a anchor
33*04fd306cSNickeau *    * ...
34*04fd306cSNickeau *
354cadd4f8SNickeau */
364cadd4f8SNickeauclass MarkupRef
374cadd4f8SNickeau{
38*04fd306cSNickeau    public const WINDOWS_SHARE_URI = 'windowsShare';
39*04fd306cSNickeau    public const LOCAL_URI = 'local';
40*04fd306cSNickeau    public const EMAIL_URI = 'email';
41*04fd306cSNickeau    public const WEB_URI = 'external';
42*04fd306cSNickeau
43*04fd306cSNickeau    /**
44*04fd306cSNickeau     * Type of Ref
45*04fd306cSNickeau     */
46*04fd306cSNickeau    public const INTERWIKI_URI = 'interwiki';
47*04fd306cSNickeau    public const WIKI_URI = 'internal';
48*04fd306cSNickeau    public const VARIABLE_URI = 'internal_template';
49*04fd306cSNickeau    public const REF_ATTRIBUTE = "ref";
504cadd4f8SNickeau
514cadd4f8SNickeau
524cadd4f8SNickeau    /**
53*04fd306cSNickeau     * The type of markup ref (ie media or link)
544cadd4f8SNickeau     */
55*04fd306cSNickeau    private string $type;
56*04fd306cSNickeau    const MEDIA_TYPE = "media";
57*04fd306cSNickeau    const LINK_TYPE = "link";
584cadd4f8SNickeau
59*04fd306cSNickeau    private string $refScheme;
60*04fd306cSNickeau    public const EXTERNAL_MEDIA_CALL_NAME = "external";
61*04fd306cSNickeau
62*04fd306cSNickeau
63*04fd306cSNickeau    private string $ref;
64*04fd306cSNickeau    private ?Url $url = null;
65*04fd306cSNickeau
66*04fd306cSNickeau    private ?Path $path = null;
67*04fd306cSNickeau    private ?InterWiki $interWiki = null;
684cadd4f8SNickeau
694cadd4f8SNickeau
704cadd4f8SNickeau    /**
71*04fd306cSNickeau     * @throws ExceptionBadSyntax
72*04fd306cSNickeau     * @throws ExceptionBadArgument
73*04fd306cSNickeau     * @throws ExceptionNotFound
744cadd4f8SNickeau     */
75*04fd306cSNickeau    public function __construct($ref, $type)
764cadd4f8SNickeau    {
774cadd4f8SNickeau        $this->ref = $ref;
78*04fd306cSNickeau        $this->type = $type;
794cadd4f8SNickeau
80*04fd306cSNickeau        $this->url = Url::createEmpty();
814cadd4f8SNickeau
82*04fd306cSNickeau        $ref = trim($ref);
834cadd4f8SNickeau
844cadd4f8SNickeau        /**
854cadd4f8SNickeau         * Email validation pattern
864cadd4f8SNickeau         * E-Mail (pattern below is defined in inc/mail.php)
874cadd4f8SNickeau         *
884cadd4f8SNickeau         * Example:
89*04fd306cSNickeau         * [[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)) {
95*04fd306cSNickeau            $this->refScheme = self::EMAIL_URI;
96*04fd306cSNickeau            $position = strpos($ref, "?");
97*04fd306cSNickeau
98*04fd306cSNickeau            if ($position !== false) {
99*04fd306cSNickeau                $email = substr($ref, 0, $position);
100*04fd306cSNickeau                $queryStringAndFragment = substr($ref, $position + 1);
101*04fd306cSNickeau                $this->url = Url::createFromString("mailto:$email");
102*04fd306cSNickeau                $this->parseAndAddQueryStringAndFragment($queryStringAndFragment);
103*04fd306cSNickeau            } else {
104*04fd306cSNickeau                $this->url = Url::createFromString("mailto:$ref");
1054cadd4f8SNickeau            }
106*04fd306cSNickeau            return;
1074cadd4f8SNickeau        }
1084cadd4f8SNickeau
1094cadd4f8SNickeau        /**
110*04fd306cSNickeau         * Case when the URL is just a full conform URL
111*04fd306cSNickeau         *
112*04fd306cSNickeau         * 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
117*04fd306cSNickeau         *
118*04fd306cSNickeau         * same as {@link media_isexternal()}  check only http / ftp scheme
1194cadd4f8SNickeau         */
120*04fd306cSNickeau        if (preg_match('#^([a-z0-9\-.+]+?)://#i', $ref)) {
121*04fd306cSNickeau            try {
122*04fd306cSNickeau                $this->url = Url::createFromString($ref);
123*04fd306cSNickeau                $this->refScheme = self::WEB_URI;
124*04fd306cSNickeau
125*04fd306cSNickeau                /**
126*04fd306cSNickeau                 * Authorized scheme only (to not inject code ?)
127*04fd306cSNickeau                 */
128*04fd306cSNickeau                $authorizedSchemes = self::loadAndGetAuthorizedSchemes();
129*04fd306cSNickeau                if (!in_array($this->url->getScheme(), $authorizedSchemes)) {
130*04fd306cSNickeau                    throw new ExceptionBadSyntax("The scheme ({$this->url->getScheme()}) of the URL ({$this->url}) is not authorized");
1314cadd4f8SNickeau                }
132*04fd306cSNickeau                try {
133*04fd306cSNickeau                    $isImage = FileSystems::getMime($this->url)->isImage();
134*04fd306cSNickeau                } catch (ExceptionNotFound $e) {
135*04fd306cSNickeau                    $isImage = false;
136*04fd306cSNickeau                }
137*04fd306cSNickeau                if ($isImage) {
138*04fd306cSNickeau                    $properties = $this->url->getQueryProperties();
139*04fd306cSNickeau                    if (count($properties) >= 1) {
140*04fd306cSNickeau                        try {
141*04fd306cSNickeau                            /**
142*04fd306cSNickeau                             * The first parameter is the `Width X Height`
143*04fd306cSNickeau                             */
144*04fd306cSNickeau                            $widthAndHeight = array_key_first($properties);
145*04fd306cSNickeau                            $xPosition = strpos($widthAndHeight, "x");
146*04fd306cSNickeau                            if ($xPosition !== false) {
147*04fd306cSNickeau                                $width = DataType::toInteger(substr($widthAndHeight, 0, $xPosition));
148*04fd306cSNickeau                                if($width!==0) {
149*04fd306cSNickeau                                    $this->url->addQueryParameter(Dimension::WIDTH_KEY, $width);
150*04fd306cSNickeau                                }
151*04fd306cSNickeau                                $height = DataType::toInteger(substr($widthAndHeight, $xPosition+1));
152*04fd306cSNickeau                                $this->url->addQueryParameter(Dimension::HEIGHT_KEY,$height);
153*04fd306cSNickeau                            } else {
154*04fd306cSNickeau                                $width = DataType::toInteger($widthAndHeight);
155*04fd306cSNickeau                                $this->url->addQueryParameter(Dimension::WIDTH_KEY,$width);
156*04fd306cSNickeau                            }
157*04fd306cSNickeau                            $this->url->deleteQueryParameter($widthAndHeight);
158*04fd306cSNickeau                            if($this->url->hasProperty(MediaMarkup::LINKING_NOLINK_VALUE)){
159*04fd306cSNickeau                                $this->url->addQueryParameter(MediaMarkup::LINKING_KEY,MediaMarkup::LINKING_NOLINK_VALUE);
160*04fd306cSNickeau                                $this->url->deleteQueryParameter(MediaMarkup::LINKING_NOLINK_VALUE);
161*04fd306cSNickeau                            }
162*04fd306cSNickeau                        } catch (ExceptionBadArgument $e) {
163*04fd306cSNickeau                            // not a number/integer
164*04fd306cSNickeau                        }
165*04fd306cSNickeau                    }
166*04fd306cSNickeau                }
167*04fd306cSNickeau                return;
168*04fd306cSNickeau            } catch (ExceptionBadSyntax $e) {
169*04fd306cSNickeau                throw new ExceptionBadSyntax("The url string was not validated as an URL ($ref). Error: {$e->getMessage()}");
170*04fd306cSNickeau            }
171*04fd306cSNickeau        }
172*04fd306cSNickeau
173*04fd306cSNickeau        /**
174*04fd306cSNickeau         * Windows share link
175*04fd306cSNickeau         */
176*04fd306cSNickeau        if (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $ref)) {
177*04fd306cSNickeau            $this->refScheme = self::WINDOWS_SHARE_URI;
178*04fd306cSNickeau            $this->url = LocalPath::createFromPathString($ref)->getUrl();
179*04fd306cSNickeau            return;
180*04fd306cSNickeau        }
181*04fd306cSNickeau
182*04fd306cSNickeau        /**
183*04fd306cSNickeau         * Only Fragment (also known as local link)
184*04fd306cSNickeau         */
185*04fd306cSNickeau        if (preg_match('/^#.?/', $ref)) {
186*04fd306cSNickeau            $this->refScheme = self::LOCAL_URI;
187*04fd306cSNickeau
188*04fd306cSNickeau            $fragment = substr($ref, 1);
189*04fd306cSNickeau            if ($fragment !== "") {
190*04fd306cSNickeau                $fragment = OutlineSection::textToHtmlSectionId($fragment);
191*04fd306cSNickeau            }
192*04fd306cSNickeau            $this->url = Url::createEmpty()->setFragment($fragment);
193*04fd306cSNickeau            $this->path = WikiPath::createRequestedPagePathFromRequest();
194*04fd306cSNickeau            return;
1954cadd4f8SNickeau        }
1964cadd4f8SNickeau
1974cadd4f8SNickeau        /**
1984cadd4f8SNickeau         * Interwiki ?
1994cadd4f8SNickeau         */
200*04fd306cSNickeau        if (preg_match('/^[a-zA-Z0-9.]+>/u', $ref)) {
201*04fd306cSNickeau
202*04fd306cSNickeau            $this->refScheme = MarkupRef::INTERWIKI_URI;
203*04fd306cSNickeau            switch ($type) {
204*04fd306cSNickeau                case self::MEDIA_TYPE:
205*04fd306cSNickeau                    $this->interWiki = InterWiki::createMediaInterWikiFromString($ref);
206*04fd306cSNickeau                    break;
207*04fd306cSNickeau                case self::LINK_TYPE:
208*04fd306cSNickeau                    $this->interWiki = InterWiki::createLinkInterWikiFromString($ref);
209*04fd306cSNickeau                    break;
210*04fd306cSNickeau                default:
211*04fd306cSNickeau                    LogUtility::internalError("The type ($type) is unknown, returning a interwiki link ref");
212*04fd306cSNickeau                    $this->interWiki = InterWiki::createLinkInterWikiFromString($ref);
213*04fd306cSNickeau                    break;
2144cadd4f8SNickeau            }
215*04fd306cSNickeau            $this->url = $this->interWiki->toUrl();
216*04fd306cSNickeau            return;
217*04fd306cSNickeau
2184cadd4f8SNickeau        }
2194cadd4f8SNickeau
220*04fd306cSNickeau
2214cadd4f8SNickeau        /**
2224cadd4f8SNickeau         * It can be a link with a ref template
2234cadd4f8SNickeau         */
224*04fd306cSNickeau        if (syntax_plugin_combo_variable::isVariable($ref)) {
225*04fd306cSNickeau            $this->refScheme = MarkupRef::VARIABLE_URI;
226*04fd306cSNickeau            return;
227*04fd306cSNickeau        }
228*04fd306cSNickeau
229*04fd306cSNickeau        /**
230*04fd306cSNickeau         * Doku Path
231*04fd306cSNickeau         * We parse it
232*04fd306cSNickeau         */
233*04fd306cSNickeau        $this->refScheme = MarkupRef::WIKI_URI;
234*04fd306cSNickeau
235*04fd306cSNickeau        $questionMarkPosition = strpos($ref, "?");
236*04fd306cSNickeau        $wikiPath = $ref;
237*04fd306cSNickeau        $fragment = null;
238*04fd306cSNickeau        $queryStringAndAnchorOriginal = null;
239*04fd306cSNickeau        if ($questionMarkPosition !== false) {
240*04fd306cSNickeau            $wikiPath = substr($ref, 0, $questionMarkPosition);
241*04fd306cSNickeau            $queryStringAndAnchorOriginal = substr($ref, $questionMarkPosition + 1);
2424cadd4f8SNickeau        } else {
243*04fd306cSNickeau            // We may have only an anchor
244*04fd306cSNickeau            $hashTagPosition = strpos($ref, "#");
245*04fd306cSNickeau            if ($hashTagPosition !== false) {
246*04fd306cSNickeau                $wikiPath = substr($ref, 0, $hashTagPosition);
247*04fd306cSNickeau                $fragment = substr($ref, $hashTagPosition + 1);
2484cadd4f8SNickeau            }
2494cadd4f8SNickeau        }
2504cadd4f8SNickeau
2514cadd4f8SNickeau        /**
2524cadd4f8SNickeau         *
253*04fd306cSNickeau         * Clean it
2544cadd4f8SNickeau         */
255*04fd306cSNickeau        $wikiPath = $this->normalizePath($wikiPath);
2564cadd4f8SNickeau
2574cadd4f8SNickeau        /**
258*04fd306cSNickeau         * The URL
259*04fd306cSNickeau         * The path is created at the end because it may have a revision
2604cadd4f8SNickeau         */
2614cadd4f8SNickeau        switch ($type) {
262*04fd306cSNickeau            case self::MEDIA_TYPE:
263*04fd306cSNickeau                $this->url = UrlEndpoint::createFetchUrl();
2644cadd4f8SNickeau                break;
265*04fd306cSNickeau            case self::LINK_TYPE:
266*04fd306cSNickeau                $this->url = UrlEndpoint::createDokuUrl();
2674cadd4f8SNickeau                break;
2684cadd4f8SNickeau            default:
269*04fd306cSNickeau                throw new ExceptionBadArgument("The ref type ($type) is unknown");
270*04fd306cSNickeau        }
271*04fd306cSNickeau
272*04fd306cSNickeau
2734cadd4f8SNickeau        /**
274*04fd306cSNickeau         * Parsing Query string if any
2754cadd4f8SNickeau         */
276*04fd306cSNickeau        if ($queryStringAndAnchorOriginal !== null) {
277*04fd306cSNickeau
278*04fd306cSNickeau            $this->parseAndAddQueryStringAndFragment($queryStringAndAnchorOriginal);
279*04fd306cSNickeau
280*04fd306cSNickeau        }
281*04fd306cSNickeau
282*04fd306cSNickeau        /**
283*04fd306cSNickeau         * The path
284*04fd306cSNickeau         */
285*04fd306cSNickeau        try {
286*04fd306cSNickeau            $rev = $this->url->getQueryPropertyValue(WikiPath::REV_ATTRIBUTE);
287*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
288*04fd306cSNickeau            $rev = null;
289*04fd306cSNickeau        }
290*04fd306cSNickeau        /**
291*04fd306cSNickeau         * The wiki path may be relative
292*04fd306cSNickeau         */
293*04fd306cSNickeau        switch ($type) {
294*04fd306cSNickeau            case self::MEDIA_TYPE:
295*04fd306cSNickeau                $this->path = WikiPath::createMediaPathFromId($wikiPath, $rev);
296*04fd306cSNickeau                $this->url->addQueryParameter(FetcherTraitWikiPath::$MEDIA_QUERY_PARAMETER, $this->path->getWikiId());
297*04fd306cSNickeau                $this->addRevToUrl($rev);
298*04fd306cSNickeau
299*04fd306cSNickeau                if ($fragment !== null) {
300*04fd306cSNickeau                    $this->url->setFragment($fragment);
301*04fd306cSNickeau                }
302*04fd306cSNickeau
3034cadd4f8SNickeau                break;
304*04fd306cSNickeau            case self::LINK_TYPE:
3054cadd4f8SNickeau
306*04fd306cSNickeau                /**
307*04fd306cSNickeau                 * The path may be an id if it exists
308*04fd306cSNickeau                 * otherwise it's a relative path
309*04fd306cSNickeau                 * MarkupPath is important because a link to
310*04fd306cSNickeau                 * a namespace (ie wikiPath = `ns:`)
311*04fd306cSNickeau                 * should become `ns:start`)
312*04fd306cSNickeau                 */
313*04fd306cSNickeau                $markupPath = MarkupPath::createMarkupFromStringPath($wikiPath);
314*04fd306cSNickeau                if (!FileSystems::exists($markupPath) && $wikiPath !== "") {
315*04fd306cSNickeau                    // We test for an empty wikiPath string
316*04fd306cSNickeau                    // because if the wiki path is the empty string,
317*04fd306cSNickeau                    // this is the current requested page
318*04fd306cSNickeau                    // An empty id is the root and always exists
319*04fd306cSNickeau                    $idPath = MarkupPath::createMarkupFromId($wikiPath);
320*04fd306cSNickeau                    if (FileSystems::exists($idPath)) {
321*04fd306cSNickeau                        $markupPath = $idPath;
322*04fd306cSNickeau                    }
3234cadd4f8SNickeau                }
3244cadd4f8SNickeau
3254cadd4f8SNickeau                /**
326*04fd306cSNickeau                 * The path may be a namespace, in the page system
327*04fd306cSNickeau                 * the path should then be the index page
3284cadd4f8SNickeau                 */
329*04fd306cSNickeau                try {
330*04fd306cSNickeau                    $this->path = $markupPath->getPathObject()->toWikiPath();
331*04fd306cSNickeau                } catch (ExceptionCompile $e) {
332*04fd306cSNickeau                    throw new ExceptionRuntimeInternal("Path should be a wiki path");
333*04fd306cSNickeau                }
334*04fd306cSNickeau                $this->url->addQueryParameter(DokuwikiId::DOKUWIKI_ID_ATTRIBUTE, $this->path->getWikiId());
335*04fd306cSNickeau                $this->addRevToUrl($rev);
336*04fd306cSNickeau
337*04fd306cSNickeau                if ($fragment !== null) {
338*04fd306cSNickeau                    $fragment = OutlineSection::textToHtmlSectionId($fragment);
339*04fd306cSNickeau                    $this->url->setFragment($fragment);
3404cadd4f8SNickeau                }
3414cadd4f8SNickeau
342*04fd306cSNickeau                break;
3434cadd4f8SNickeau            default:
344*04fd306cSNickeau                throw new ExceptionBadArgument("The ref type ($type) is unknown");
3454cadd4f8SNickeau        }
346*04fd306cSNickeau
3474cadd4f8SNickeau    }
3484cadd4f8SNickeau
3494cadd4f8SNickeau    /**
350*04fd306cSNickeau     * @throws ExceptionBadArgument
351*04fd306cSNickeau     * @throws ExceptionBadSyntax
352*04fd306cSNickeau     * @throws ExceptionNotFound
3534cadd4f8SNickeau     */
3544cadd4f8SNickeau    public
355*04fd306cSNickeau    static function createMediaFromRef($refProcessing): MarkupRef
3564cadd4f8SNickeau    {
357*04fd306cSNickeau        return new MarkupRef($refProcessing, self::MEDIA_TYPE);
3584cadd4f8SNickeau    }
3594cadd4f8SNickeau
3604cadd4f8SNickeau    /**
361*04fd306cSNickeau     * @throws ExceptionBadSyntax
362*04fd306cSNickeau     * @throws ExceptionBadArgument
363*04fd306cSNickeau     * @throws ExceptionNotFound
3644cadd4f8SNickeau     */
365*04fd306cSNickeau    public
366*04fd306cSNickeau    static function createLinkFromRef($refProcessing): MarkupRef
3674cadd4f8SNickeau    {
368*04fd306cSNickeau        return new MarkupRef($refProcessing, self::LINK_TYPE);
3694cadd4f8SNickeau    }
3704cadd4f8SNickeau
3714cadd4f8SNickeau    // https://www.dokuwiki.org/urlschemes
372*04fd306cSNickeau    private static function loadAndGetAuthorizedSchemes(): array
373*04fd306cSNickeau    {
374*04fd306cSNickeau
375*04fd306cSNickeau        return ExecutionContext::getActualOrCreateFromEnv()
376*04fd306cSNickeau            ->getConfig()
377*04fd306cSNickeau            ->getAuthorizedUrlSchemes();
378*04fd306cSNickeau
379*04fd306cSNickeau
3804cadd4f8SNickeau    }
381*04fd306cSNickeau
382*04fd306cSNickeau    /**
383*04fd306cSNickeau     * In case of manual entry, the function will normalize the path
384*04fd306cSNickeau     * @param string $wikiPath - a path entered by a user
385*04fd306cSNickeau     * @return string
386*04fd306cSNickeau     */
387*04fd306cSNickeau    public function normalizePath(string $wikiPath): string
388*04fd306cSNickeau    {
389*04fd306cSNickeau        if ($wikiPath === "") {
390*04fd306cSNickeau            return $wikiPath;
391*04fd306cSNickeau        }
392*04fd306cSNickeau        // slash to double point
393*04fd306cSNickeau        $wikiPath = str_replace(WikiPath::NAMESPACE_SEPARATOR_SLASH, WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $wikiPath);
394*04fd306cSNickeau
395*04fd306cSNickeau        $isNamespacePath = false;
396*04fd306cSNickeau        if ($wikiPath[strlen($wikiPath) - 1] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
397*04fd306cSNickeau            $isNamespacePath = true;
398*04fd306cSNickeau        }
399*04fd306cSNickeau        $isPath = false;
400*04fd306cSNickeau        if ($wikiPath[0] === WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
401*04fd306cSNickeau            $isPath = true;
402*04fd306cSNickeau        }
403*04fd306cSNickeau        $pathType = "unknown";
404*04fd306cSNickeau        if ($wikiPath[0] === WikiPath::CURRENT_PATH_CHARACTER) {
405*04fd306cSNickeau            $pathType = "current";
406*04fd306cSNickeau            if (isset($wikiPath[1])) {
407*04fd306cSNickeau                if ($wikiPath[1] === WikiPath::CURRENT_PATH_CHARACTER) {
408*04fd306cSNickeau                    $pathType = "parent";
409*04fd306cSNickeau                }
410*04fd306cSNickeau            }
411*04fd306cSNickeau        }
412*04fd306cSNickeau        /**
413*04fd306cSNickeau         * Dokuwiki Compliance
414*04fd306cSNickeau         */
415*04fd306cSNickeau        $cleanPath = cleanID($wikiPath);
416*04fd306cSNickeau        if ($isNamespacePath) {
417*04fd306cSNickeau            $cleanPath = "$cleanPath:";
418*04fd306cSNickeau        }
419*04fd306cSNickeau        switch ($pathType) {
420*04fd306cSNickeau            case "current":
421*04fd306cSNickeau                if (!$isNamespacePath) {
422*04fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PATH_CHARACTER . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $cleanPath;
4234cadd4f8SNickeau                } else {
424*04fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PATH_CHARACTER . $cleanPath;
4254cadd4f8SNickeau                }
4264cadd4f8SNickeau                break;
427*04fd306cSNickeau            case "parent":
428*04fd306cSNickeau                if (!$isNamespacePath) {
429*04fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PARENT_PATH_CHARACTER . WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $cleanPath;
430*04fd306cSNickeau                } else {
431*04fd306cSNickeau                    $cleanPath = WikiPath::CURRENT_PARENT_PATH_CHARACTER . $cleanPath;
4324cadd4f8SNickeau                }
433*04fd306cSNickeau                break;
4344cadd4f8SNickeau        }
435*04fd306cSNickeau        if ($isPath) {
436*04fd306cSNickeau            $cleanPath = ":$cleanPath";
437*04fd306cSNickeau        }
438*04fd306cSNickeau        return $cleanPath;
4394cadd4f8SNickeau    }
4404cadd4f8SNickeau
4414cadd4f8SNickeau
4424cadd4f8SNickeau    public
443*04fd306cSNickeau    function getUrl(): Url
4444cadd4f8SNickeau    {
445*04fd306cSNickeau        return $this->url;
4464cadd4f8SNickeau    }
4474cadd4f8SNickeau
4484cadd4f8SNickeau    /**
449*04fd306cSNickeau     * @throws ExceptionNotFound
4504cadd4f8SNickeau     */
4514cadd4f8SNickeau    public
452*04fd306cSNickeau    function getPath(): WikiPath
4534cadd4f8SNickeau    {
454*04fd306cSNickeau        if ($this->path === null) {
455*04fd306cSNickeau            throw new ExceptionNotFound("No path was found");
456*04fd306cSNickeau        }
457*04fd306cSNickeau        return $this->path;
4584cadd4f8SNickeau    }
4594cadd4f8SNickeau
4604cadd4f8SNickeau    public
461*04fd306cSNickeau    function getRef(): string
4624cadd4f8SNickeau    {
4634cadd4f8SNickeau        return $this->ref;
4644cadd4f8SNickeau    }
4654cadd4f8SNickeau
466*04fd306cSNickeau    public function getSchemeType(): string
4674cadd4f8SNickeau    {
468*04fd306cSNickeau        return $this->refScheme;
4694cadd4f8SNickeau    }
4704cadd4f8SNickeau
4714cadd4f8SNickeau    /**
472*04fd306cSNickeau     * @throws ExceptionNotFound
4734cadd4f8SNickeau     */
474*04fd306cSNickeau    public function getInterWiki(): InterWiki
4754cadd4f8SNickeau    {
476*04fd306cSNickeau        if ($this->interWiki === null) {
477*04fd306cSNickeau            throw new ExceptionNotFound("This ref ($this->ref) is not an interWiki.");
478*04fd306cSNickeau        }
479*04fd306cSNickeau        return $this->interWiki;
4804cadd4f8SNickeau    }
4814cadd4f8SNickeau
482*04fd306cSNickeau    private function addRevToUrl($rev = null): void
483*04fd306cSNickeau    {
484*04fd306cSNickeau        if ($rev !== null) {
485*04fd306cSNickeau            $this->url->addQueryParameter(WikiPath::REV_ATTRIBUTE, $rev);
486*04fd306cSNickeau        }
487*04fd306cSNickeau    }
488*04fd306cSNickeau
489*04fd306cSNickeau
490*04fd306cSNickeau    public function getType(): string
491*04fd306cSNickeau    {
492*04fd306cSNickeau        return $this->type;
493*04fd306cSNickeau    }
494*04fd306cSNickeau
495*04fd306cSNickeau    /**
496*04fd306cSNickeau     * A query parameters value may have a # for the definition of a color
497*04fd306cSNickeau     * This process takes it into account
498*04fd306cSNickeau     * @param string $queryStringAndFragment
499*04fd306cSNickeau     * @return void
500*04fd306cSNickeau     */
501*04fd306cSNickeau    private function parseAndAddQueryStringAndFragment(string $queryStringAndFragment)
502*04fd306cSNickeau    {
503*04fd306cSNickeau        /**
504*04fd306cSNickeau         * The value $queryStringAndAnchorOriginal
505*04fd306cSNickeau         * is kept to create the original queryString
506*04fd306cSNickeau         * at the end if we found an anchor
507*04fd306cSNickeau         *
508*04fd306cSNickeau         * We parse token by token because we allow a hashtag for a hex color
509*04fd306cSNickeau         */
510*04fd306cSNickeau        $queryStringAndAnchorProcessing = $queryStringAndFragment;
511*04fd306cSNickeau        while (strlen($queryStringAndAnchorProcessing) > 0) {
512*04fd306cSNickeau
513*04fd306cSNickeau            /**
514*04fd306cSNickeau             * Capture the token
515*04fd306cSNickeau             * and reduce the text
516*04fd306cSNickeau             */
517*04fd306cSNickeau            $questionMarkPos = strpos($queryStringAndAnchorProcessing, "&");
518*04fd306cSNickeau            if ($questionMarkPos !== false) {
519*04fd306cSNickeau                $token = substr($queryStringAndAnchorProcessing, 0, $questionMarkPos);
520*04fd306cSNickeau                $queryStringAndAnchorProcessing = substr($queryStringAndAnchorProcessing, $questionMarkPos + 1);
5214cadd4f8SNickeau            } else {
522*04fd306cSNickeau                $token = $queryStringAndAnchorProcessing;
523*04fd306cSNickeau                $queryStringAndAnchorProcessing = "";
5244cadd4f8SNickeau            }
5254cadd4f8SNickeau
526*04fd306cSNickeau
527*04fd306cSNickeau            /**
528*04fd306cSNickeau             * Sizing (wxh)
529*04fd306cSNickeau             */
530*04fd306cSNickeau            $sizing = [];
531*04fd306cSNickeau            if (preg_match('/^([0-9]+)(?:x([0-9]+))?/', $token, $sizing)) {
532*04fd306cSNickeau                $this->url->addQueryParameter(Dimension::WIDTH_KEY, $sizing[1]);
533*04fd306cSNickeau                if (isset($sizing[2])) {
534*04fd306cSNickeau                    $this->url->addQueryParameter(Dimension::HEIGHT_KEY, $sizing[2]);
535*04fd306cSNickeau                }
536*04fd306cSNickeau                $token = substr($token, strlen($sizing[0]));
537*04fd306cSNickeau                if ($token === "") {
538*04fd306cSNickeau                    // no anchor behind we continue
539*04fd306cSNickeau                    continue;
540*04fd306cSNickeau                }
5414cadd4f8SNickeau            }
5424cadd4f8SNickeau
543*04fd306cSNickeau            /**
544*04fd306cSNickeau             * Linking
545*04fd306cSNickeau             */
546*04fd306cSNickeau            $found = preg_match('/^(nolink|direct|linkonly|details)/i', $token, $matches);
547*04fd306cSNickeau            if ($found) {
548*04fd306cSNickeau                $linkingValue = $matches[1];
549*04fd306cSNickeau                $this->url->addQueryParameter(MediaMarkup::LINKING_KEY, $linkingValue);
550*04fd306cSNickeau                $token = substr($token, strlen($linkingValue));
551*04fd306cSNickeau                if ($token == "") {
552*04fd306cSNickeau                    // no anchor behind we continue
553*04fd306cSNickeau                    continue;
5544cadd4f8SNickeau                }
5554cadd4f8SNickeau            }
5564cadd4f8SNickeau
557*04fd306cSNickeau            /**
558*04fd306cSNickeau             * Cache
559*04fd306cSNickeau             */
560*04fd306cSNickeau            $noCacheValue = IFetcherAbs::NOCACHE_VALUE;
561*04fd306cSNickeau            $found = preg_match('/^(' . $noCacheValue . ')/i', $token, $matches);
562*04fd306cSNickeau            if ($found) {
563*04fd306cSNickeau                $this->url->addQueryParameter(IFetcherAbs::CACHE_KEY, $noCacheValue);
564*04fd306cSNickeau                $token = substr($token, strlen($noCacheValue));
565*04fd306cSNickeau                if ($token == "") {
566*04fd306cSNickeau                    // no anchor behind we continue
567*04fd306cSNickeau                    continue;
568*04fd306cSNickeau                }
569*04fd306cSNickeau            }
570*04fd306cSNickeau
571*04fd306cSNickeau            /**
572*04fd306cSNickeau             * Anchor value after a single token case
573*04fd306cSNickeau             */
574*04fd306cSNickeau            if (strpos($token, '#') === 0) {
575*04fd306cSNickeau                $this->url->setFragment(substr($token, 1));
576*04fd306cSNickeau                continue;
577*04fd306cSNickeau            }
578*04fd306cSNickeau
579*04fd306cSNickeau            /**
580*04fd306cSNickeau             * Key, value
581*04fd306cSNickeau             * explode to the first `=`
582*04fd306cSNickeau             * in the anchor value, we can have one
583*04fd306cSNickeau             *
584*04fd306cSNickeau             * Ex with media.pdf#page=31
585*04fd306cSNickeau             */
586*04fd306cSNickeau            list($key, $value) = explode("=", $token, 2);
587*04fd306cSNickeau
588*04fd306cSNickeau            /**
589*04fd306cSNickeau             * Case of an anchor after a boolean attribute (ie without =)
590*04fd306cSNickeau             * at the end
591*04fd306cSNickeau             */
592*04fd306cSNickeau            $anchorPosition = strpos($key, '#');
593*04fd306cSNickeau            if ($anchorPosition !== false) {
594*04fd306cSNickeau                $this->url->setFragment(substr($key, $anchorPosition + 1));
595*04fd306cSNickeau                $key = substr($key, 0, $anchorPosition);
596*04fd306cSNickeau            }
597*04fd306cSNickeau
598*04fd306cSNickeau            /**
599*04fd306cSNickeau             * Test Anchor on the value
600*04fd306cSNickeau             */
601*04fd306cSNickeau            if ($value != null) {
602*04fd306cSNickeau                if (($countHashTag = substr_count($value, "#")) >= 3) {
603*04fd306cSNickeau                    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);
604*04fd306cSNickeau                    continue;
605*04fd306cSNickeau                }
606*04fd306cSNickeau            } else {
607*04fd306cSNickeau                /**
608*04fd306cSNickeau                 * Boolean attribute
609*04fd306cSNickeau                 * (null does not make it)
610*04fd306cSNickeau                 */
611*04fd306cSNickeau                $value = null;
612*04fd306cSNickeau            }
613*04fd306cSNickeau
614*04fd306cSNickeau            $anchorPosition = false;
615*04fd306cSNickeau            $lowerCaseKey = strtolower($key);
616*04fd306cSNickeau            if ($lowerCaseKey === TextColor::CSS_ATTRIBUTE) {
617*04fd306cSNickeau                /**
618*04fd306cSNickeau                 * Special case when color has one color value as hexadecimal #
619*04fd306cSNickeau                 * and the hashtag
620*04fd306cSNickeau                 */
621*04fd306cSNickeau                if (strpos($value, '#') == 0) {
622*04fd306cSNickeau                    if (substr_count($value, "#") >= 2) {
623*04fd306cSNickeau
624*04fd306cSNickeau                        /**
625*04fd306cSNickeau                         * The last one
626*04fd306cSNickeau                         */
627*04fd306cSNickeau                        $anchorPosition = strrpos($value, '#');
628*04fd306cSNickeau                    }
629*04fd306cSNickeau                    // no anchor then
630*04fd306cSNickeau                } else {
631*04fd306cSNickeau                    // a color that is not hexadecimal can have an anchor
632*04fd306cSNickeau                    $anchorPosition = strpos($value, "#");
633*04fd306cSNickeau                }
634*04fd306cSNickeau            } else {
635*04fd306cSNickeau                // general case
636*04fd306cSNickeau                $anchorPosition = strpos($value, "#");
637*04fd306cSNickeau            }
638*04fd306cSNickeau            if ($anchorPosition !== false) {
639*04fd306cSNickeau                $this->url->setFragment(substr($value, $anchorPosition + 1));
640*04fd306cSNickeau                $value = substr($value, 0, $anchorPosition);
641*04fd306cSNickeau            }
642*04fd306cSNickeau
643*04fd306cSNickeau            switch ($lowerCaseKey) {
644*04fd306cSNickeau                case Dimension::WIDTH_KEY_SHORT: // used in a link w=xxx
645*04fd306cSNickeau                    $this->url->addQueryParameter(Dimension::WIDTH_KEY, $value);
646*04fd306cSNickeau                    break;
647*04fd306cSNickeau                case Dimension::HEIGHT_KEY_SHORT: // used in a link h=xxxx
648*04fd306cSNickeau                    $this->url->addQueryParameter(Dimension::HEIGHT_KEY, $value);
649*04fd306cSNickeau                    break;
650*04fd306cSNickeau                default:
651*04fd306cSNickeau                    $this->url->addQueryParameter($key, $value);
652*04fd306cSNickeau                    break;
653*04fd306cSNickeau            }
654*04fd306cSNickeau
655*04fd306cSNickeau        }
656*04fd306cSNickeau
657*04fd306cSNickeau    }
658*04fd306cSNickeau
659*04fd306cSNickeau    public function __toString()
660*04fd306cSNickeau    {
661*04fd306cSNickeau        return $this->getRef();
6624cadd4f8SNickeau    }
6634cadd4f8SNickeau
6644cadd4f8SNickeau
6654cadd4f8SNickeau}
666