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