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