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