1<?php 2 3namespace ComboStrap; 4 5use ComboStrap\Web\Url; 6 7class InterWiki 8{ 9 10 11 const DEFAULT_INTERWIKI_NAME = 'default'; 12 13 /** 14 * The pattern that select the characters to encode in URL 15 */ 16 const CHARACTERS_TO_ENCODE = '/[[\\\\\]^`{|}#%]/'; 17 const IW_PREFIX = "iw_"; 18 19 20 private static ?array $INTERWIKI_URL_TEMPLATES = null; 21 22 private string $name; 23 private string $ref; 24 25 private ?string $urlWithoutFragment = null; 26 27 private ?string $fragment = null; 28 29 private string $markupType; 30 31 /** 32 * @param string $interWikiRef - The interwiki 33 * @param string $markupType - The {@link MarkupRef::getSchemeType()} ie media or link 34 */ 35 public function __construct(string $interWikiRef, string $markupType) 36 { 37 38 $this->ref = $interWikiRef; 39 $this->markupType = $markupType; 40 [$this->name, $this->urlWithoutFragment] = explode(">", $interWikiRef, 2); 41 42 # The name is a key that should be lowercase 43 $this->name = strtolower($this->name); 44 45 $hash = strrchr($this->urlWithoutFragment, '#'); 46 if ($hash) { 47 $this->urlWithoutFragment = substr($this->urlWithoutFragment, 0, -strlen($hash)); 48 $this->fragment = substr($hash, 1); 49 } 50 51 } 52 53 public static function addInterWiki(string $name, string $value) 54 { 55 ExecutionContext::getActualOrCreateFromEnv() 56 ->getConfig() 57 ->addInterWiki($name, $value); 58 59 } 60 61 62 public static function createMediaInterWikiFromString(string $ref): InterWiki 63 { 64 return new InterWiki($ref, MarkupRef::MEDIA_TYPE); 65 } 66 67 /** 68 * @return string - the general component class 69 */ 70 public static function getComponentClass(): string 71 { 72 $oldClassName = SiteConfig::getConfValue(LinkMarkup::CONF_USE_DOKUWIKI_CLASS_NAME); 73 if ($oldClassName) { 74 return "interwiki"; 75 } else { 76 return "link-interwiki"; 77 } 78 } 79 80 /** 81 * @throws ExceptionNotFound 82 * @throws ExceptionBadSyntax 83 * @throws ExceptionBadArgument 84 * Adapted from {@link Doku_Renderer_xhtml::_resolveInterWiki()} 85 */ 86 public function toUrl(): Url 87 { 88 89 $originalInterWikiUrlTemplate = $this->getTemplateUrlStringOrDefault(); 90 $interWikiUrlTemplate = $originalInterWikiUrlTemplate; 91 92 /** 93 * Dokuwiki Id template 94 */ 95 if ($interWikiUrlTemplate[0] === ':') { 96 $interWikiUrlTemplate = str_replace( 97 '{NAME}', 98 $this->urlWithoutFragment, 99 $interWikiUrlTemplate 100 ); 101 if ($this->fragment !== null) { 102 $interWikiUrlTemplate = "$interWikiUrlTemplate#$this->fragment"; 103 } 104 switch ($this->markupType) { 105 case MarkupRef::MEDIA_TYPE: 106 return MarkupRef::createMediaFromRef($interWikiUrlTemplate)->getUrl(); 107 case MarkupRef::LINK_TYPE: 108 default: 109 return MarkupRef::createLinkFromRef($interWikiUrlTemplate)->getUrl(); 110 } 111 112 } 113 114 // Replace placeholder if any 115 if (preg_match('#{URL}#', $interWikiUrlTemplate)) { 116 117 // Replace the Url 118 $interWikiUrlTemplate = str_replace( 119 '{URL}', 120 rawurlencode($this->urlWithoutFragment), 121 $interWikiUrlTemplate 122 ); 123 124 } 125 126 // Name placeholder means replace with URL encoding 127 if (preg_match('#{NAME}#', $interWikiUrlTemplate)) { 128 129 $interWikiUrlTemplate = str_replace( 130 '{NAME}', 131 preg_replace_callback( 132 self::CHARACTERS_TO_ENCODE, 133 function ($match) { 134 return rawurlencode($match[0]); 135 }, 136 $this->urlWithoutFragment 137 ), 138 $interWikiUrlTemplate 139 ); 140 141 } 142 143 // Url replacement 144 if (preg_match('#{(SCHEME|HOST|PORT|PATH|QUERY)}#', $interWikiUrlTemplate)) { 145 146 $parsed = parse_url($this->urlWithoutFragment); 147 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 148 if (empty($parsed['host'])) $parsed['host'] = ''; 149 if (empty($parsed['port'])) $parsed['port'] = 80; 150 if (empty($parsed['path'])) $parsed['path'] = ''; 151 if (empty($parsed['query'])) $parsed['query'] = ''; 152 $interWikiUrlTemplate = strtr($interWikiUrlTemplate, [ 153 '{SCHEME}' => $parsed['scheme'], 154 '{HOST}' => $parsed['host'], 155 '{PORT}' => $parsed['port'], 156 '{PATH}' => $parsed['path'], 157 '{QUERY}' => $parsed['query'], 158 ]); 159 } 160 161 // If no replacement 162 if ($interWikiUrlTemplate === $originalInterWikiUrlTemplate) { 163 164 $interWikiUrlTemplate = $interWikiUrlTemplate . rawurlencode($this->urlWithoutFragment); 165 166 } 167 168 169 if ($this->fragment) $interWikiUrlTemplate .= '#' . rawurlencode($this->fragment); 170 171 return Url::createFromString($interWikiUrlTemplate); 172 } 173 174 /** 175 * @param string $interWikiRef 176 * @return InterWiki 177 */ 178 public static function createLinkInterWikiFromString(string $interWikiRef): InterWiki 179 { 180 return new InterWiki($interWikiRef, MarkupRef::LINK_TYPE); 181 } 182 183 /** 184 * @throws ExceptionNotFound 185 */ 186 private function getTemplateUrlString(): string 187 { 188 $interWikis = $this->getInterWikis(); 189 190 $urlTemplate = $interWikis[$this->name] ?? null; 191 if ($urlTemplate !== null) { 192 return $urlTemplate; 193 } 194 throw new ExceptionNotFound("No Wiki ($this->name) found"); 195 196 } 197 198 private static function getInterWikis(): array 199 { 200 return ExecutionContext::getActualOrCreateFromEnv() 201 ->getConfig() 202 ->getInterWikis(); 203 } 204 205 /** 206 * @throws ExceptionNotFound 207 */ 208 private function getTemplateUrlStringOrDefault() 209 { 210 try { 211 return $this->getTemplateUrlString(); 212 } catch (ExceptionNotFound $e) { 213 $interWikis = $this->getInterWikis(); 214 if (isset($interWikis[self::DEFAULT_INTERWIKI_NAME])) { 215 $this->name = self::DEFAULT_INTERWIKI_NAME; 216 return $interWikis[self::DEFAULT_INTERWIKI_NAME]; 217 } 218 } 219 throw new ExceptionNotFound("The inter-wiki ({$this->getWiki()}) does not exist and there is no default inter-wiki defined."); 220 221 } 222 223 public function getWiki() 224 { 225 return $this->name; 226 } 227 228 /** 229 * @return string 230 */ 231 public function getRef(): string 232 { 233 return $this->ref; 234 } 235 236 /** 237 * @return string - the class for this specific interwiki 238 */ 239 public function getSubComponentClass(): string 240 { 241 return self::IW_PREFIX . preg_replace('/[^_\-a-z0-9]+/i', '_', $this->getWiki()); 242 } 243 244 /** 245 * @throws ExceptionNotFound 246 */ 247 public function getSpecificCssRules(): string 248 { 249 250 /** 251 * Adapted from {@link css_interwiki()} 252 */ 253 foreach (['svg', 'png', 'gif'] as $ext) { 254 $file = 'lib/images/interwiki/' . $this->name . '.' . $ext; 255 $urlFile = DOKU_BASE . $file; 256 $class = $this->getSubComponentClass(); 257 if (file_exists(DOKU_INC . $file)) { 258 return <<<EOF 259a.$class { 260 background-image: url($urlFile) 261} 262EOF; 263 } 264 } 265 throw new ExceptionNotFound("No interwiki file found"); 266 } 267 268 public 269 function getDefaultCssRules(): string 270 { 271 $url = DOKU_BASE . 'lib/images/interwiki.svg'; 272 return <<<EOF 273a.interwiki { 274 background: transparent url($url) 0 0 no-repeat; 275 background-size: 1.2em; 276 padding: 0 0 0 1.4em; 277} 278EOF; 279 } 280 281 282} 283