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