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