OSDN Git Service

BugTrack2/230 Do nothing on updating page with unchanged content
[pukiwiki/pukiwiki.git] / lib / make_link.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // make_link.php
4 // Copyright
5 //   2003-2016 PukiWiki Development Team
6 //   2001-2002 Originally written by yu-ji
7 // License: GPL v2 or (at your option) any later version
8 //
9 // Hyperlink-related functions
10
11 // Hyperlink decoration
12 function make_link($string, $page = '')
13 {
14         global $vars;
15         static $converter;
16
17         if (! isset($converter)) $converter = new InlineConverter();
18
19         $clone = $converter->get_clone($converter);
20
21         return $clone->convert($string, ($page != '') ? $page : $vars['page']);
22 }
23
24 // Converters of inline element
25 class InlineConverter
26 {
27         var $converters; // as array()
28         var $pattern;
29         var $pos;
30         var $result;
31
32         function get_clone($obj) {
33                 static $clone_func;
34
35                 if (! isset($clone_func)) {
36                         if (version_compare(PHP_VERSION, '5.0.0', '<')) {
37                                 $clone_func = create_function('$a', 'return $a;');
38                         } else {
39                                 $clone_func = create_function('$a', 'return clone $a;');
40                         }
41                 }
42                 return $clone_func($obj);
43         }
44
45         function __clone() {
46                 $converters = array();
47                 foreach ($this->converters as $key=>$converter) {
48                         $converters[$key] = $this->get_clone($converter);
49                 }
50                 $this->converters = $converters;
51         }
52
53         function InlineConverter($converters = NULL, $excludes = NULL)
54         {
55                 if ($converters === NULL) {
56                         $converters = array(
57                                 'plugin',        // Inline plugins
58                                 'note',          // Footnotes
59                                 'url',           // URLs
60                                 'url_interwiki', // URLs (interwiki definition)
61                                 'mailto',        // mailto: URL schemes
62                                 'interwikiname', // InterWikiNames
63                                 'autolink',      // AutoLinks
64                                 'bracketname',   // BracketNames
65                                 'wikiname',      // WikiNames
66                                 'autolink_a',    // AutoLinks(alphabet)
67                         );
68                 }
69
70                 if ($excludes !== NULL)
71                         $converters = array_diff($converters, $excludes);
72
73                 $this->converters = $patterns = array();
74                 $start = 1;
75
76                 foreach ($converters as $name) {
77                         $classname = 'Link_' . $name;
78                         $converter = new $classname($start);
79                         $pattern   = $converter->get_pattern();
80                         if ($pattern === FALSE) continue;
81
82                         $patterns[] = '(' . "\n" . $pattern . "\n" . ')';
83                         $this->converters[$start] = $converter;
84                         $start += $converter->get_count();
85                         ++$start;
86                 }
87                 $this->pattern = join('|', $patterns);
88         }
89
90         function convert($string, $page)
91         {
92                 $this->page   = $page;
93                 $this->result = array();
94
95                 $string = preg_replace_callback('/' . $this->pattern . '/x',
96                         array(& $this, 'replace'), $string);
97
98                 $arr = explode("\x08", make_line_rules(htmlsc($string)));
99                 $retval = '';
100                 while (! empty($arr)) {
101                         $retval .= array_shift($arr) . array_shift($this->result);
102                 }
103                 return $retval;
104         }
105
106         function replace($arr)
107         {
108                 $obj = $this->get_converter($arr);
109
110                 $this->result[] = ($obj !== NULL && $obj->set($arr, $this->page) !== FALSE) ?
111                         $obj->toString() : make_line_rules(htmlsc($arr[0]));
112
113                 return "\x08"; // Add a mark into latest processed part
114         }
115
116         function get_objects($string, $page)
117         {
118                 $matches = $arr = array();
119                 preg_match_all('/' . $this->pattern . '/x', $string, $matches, PREG_SET_ORDER);
120                 foreach ($matches as $match) {
121                         $obj = $this->get_converter($match);
122                         if ($obj->set($match, $page) !== FALSE) {
123                                 $arr[] = $this->get_clone($obj);
124                                 if ($obj->body != '')
125                                         $arr = array_merge($arr, $this->get_objects($obj->body, $page));
126                         }
127                 }
128                 return $arr;
129         }
130
131         function & get_converter(& $arr)
132         {
133                 foreach (array_keys($this->converters) as $start) {
134                         if ($arr[$start] == $arr[0])
135                                 return $this->converters[$start];
136                 }
137                 return NULL;
138         }
139 }
140
141 // Base class of inline elements
142 class Link
143 {
144         var $start;   // Origin number of parentheses (0 origin)
145         var $text;    // Matched string
146
147         var $type;
148         var $page;
149         var $name;
150         var $body;
151         var $alias;
152
153         // Constructor
154         function Link($start)
155         {
156                 $this->start = $start;
157         }
158
159         // Return a regex pattern to match
160         function get_pattern() {}
161
162         // Return number of parentheses (except (?:...) )
163         function get_count() {}
164
165         // Set pattern that matches
166         function set($arr, $page) {}
167
168         function toString() {}
169
170         // Private: Get needed parts from a matched array()
171         function splice($arr)
172         {
173                 $count = $this->get_count() + 1;
174                 $arr   = array_pad(array_splice($arr, $this->start, $count), $count, '');
175                 $this->text = $arr[0];
176                 return $arr;
177         }
178
179         // Set basic parameters
180         function setParam($page, $name, $body, $type = '', $alias = '')
181         {
182                 static $converter = NULL;
183
184                 $this->page = $page;
185                 $this->name = $name;
186                 $this->body = $body;
187                 $this->type = $type;
188                 if (! PKWK_DISABLE_INLINE_IMAGE_FROM_URI &&
189                         is_url($alias) && preg_match('/\.(gif|png|jpe?g)$/i', $alias)) {
190                         $alias = '<img src="' . htmlsc($alias) . '" alt="' . $name . '" />';
191                 } else if ($alias != '') {
192                         if ($converter === NULL)
193                                 $converter = new InlineConverter(array('plugin'));
194
195                         $alias = make_line_rules($converter->convert($alias, $page));
196
197                         // BugTrack/669: A hack removing anchor tags added by AutoLink
198                         $alias = preg_replace('#</?a[^>]*>#i', '', $alias);
199                 }
200                 $this->alias = $alias;
201
202                 return TRUE;
203         }
204 }
205
206 // Inline plugins
207 class Link_plugin extends Link
208 {
209         var $pattern;
210         var $plain,$param;
211
212         function Link_plugin($start)
213         {
214                 parent::Link($start);
215         }
216
217         function get_pattern()
218         {
219                 $this->pattern = <<<EOD
220 &
221 (      # (1) plain
222  (\w+) # (2) plugin name
223  (?:
224   \(
225    ((?:(?!\)[;{]).)*) # (3) parameter
226   \)
227  )?
228 )
229 EOD;
230                 return <<<EOD
231 {$this->pattern}
232 (?:
233  \{
234   ((?:(?R)|(?!};).)*) # (4) body
235  \}
236 )?
237 ;
238 EOD;
239         }
240
241         function get_count()
242         {
243                 return 4;
244         }
245
246         function set($arr, $page)
247         {
248                 list($all, $this->plain, $name, $this->param, $body) = $this->splice($arr);
249
250                 // Re-get true plugin name and patameters (for PHP 4.1.2)
251                 $matches = array();
252                 if (preg_match('/^' . $this->pattern . '/x', $all, $matches)
253                         && $matches[1] != $this->plain) 
254                         list(, $this->plain, $name, $this->param) = $matches;
255
256                 return parent::setParam($page, $name, $body, 'plugin');
257         }
258
259         function toString()
260         {
261                 $body = ($this->body == '') ? '' : make_link($this->body);
262                 $str = FALSE;
263
264                 // Try to call the plugin
265                 if (exist_plugin_inline($this->name))
266                         $str = do_plugin_inline($this->name, $this->param, $body);
267
268                 if ($str !== FALSE) {
269                         return $str; // Succeed
270                 } else {
271                         // No such plugin, or Failed
272                         $body = (($body == '') ? '' : '{' . $body . '}') . ';';
273                         return make_line_rules(htmlsc('&' . $this->plain) . $body);
274                 }
275         }
276 }
277
278 // Footnotes
279 class Link_note extends Link
280 {
281         function Link_note($start)
282         {
283                 parent::Link($start);
284         }
285
286         function get_pattern()
287         {
288                 return <<<EOD
289 \(\(
290  ((?>(?=\(\()(?R)|(?!\)\)).)*) # (1) note body
291 \)\)
292 EOD;
293         }
294
295         function get_count()
296         {
297                 return 1;
298         }
299
300         function set($arr, $page)
301         {
302                 global $foot_explain, $vars;
303                 static $note_id = 0;
304
305                 list(, $body) = $this->splice($arr);
306
307                 if (PKWK_ALLOW_RELATIVE_FOOTNOTE_ANCHOR) {
308                         $script = '';
309                 } else {
310                         $script = get_script_uri() . '?' . pagename_urlencode($page);
311                 }
312
313                 $id   = ++$note_id;
314                 $note = make_link($body);
315                 $page = isset($vars['page']) ? rawurlencode($vars['page']) : '';
316
317                 // Footnote
318                 $foot_explain[$id] = '<a id="notefoot_' . $id . '" href="' .
319                         $script . '#notetext_' . $id . '" class="note_super">*' .
320                         $id . '</a>' . "\n" .
321                         '<span class="small">' . $note . '</span><br />';
322
323                 // A hyperlink, content-body to footnote
324                 if (! is_numeric(PKWK_FOOTNOTE_TITLE_MAX) || PKWK_FOOTNOTE_TITLE_MAX <= 0) {
325                         $title = '';
326                 } else {
327                         $title = strip_tags($note);
328                         $count = mb_strlen($title, SOURCE_ENCODING);
329                         $title = mb_substr($title, 0, PKWK_FOOTNOTE_TITLE_MAX, SOURCE_ENCODING);
330                         $abbr  = (mb_strlen($title) < $count) ? '...' : '';
331                         $title = ' title="' . $title . $abbr . '"';
332                 }
333                 $name = '<a id="notetext_' . $id . '" href="' . $script .
334                         '#notefoot_' . $id . '" class="note_super"' . $title .
335                         '>*' . $id . '</a>';
336
337                 return parent::setParam($page, $name, $body);
338         }
339
340         function toString()
341         {
342                 return $this->name;
343         }
344 }
345
346 // URLs
347 class Link_url extends Link
348 {
349         function Link_url($start)
350         {
351                 parent::Link($start);
352         }
353
354         function get_pattern()
355         {
356                 $s1 = $this->start + 1;
357                 return <<<EOD
358 ((?:\[\[))?       # (1) open bracket
359 ((?($s1)          # (2) alias
360 ((?:(?!\]\]).)+)  # (3) alias name
361  (?:>|:)
362 ))?
363 (                 # (4) url
364  (?:(?:https?|ftp|news):\/\/|mailto:)[\w\/\@\$()!?&%#:;.,~'=*+-]+
365 )
366 (?($s1)\]\])      # close bracket
367 EOD;
368         }
369
370         function get_count()
371         {
372                 return 4;
373         }
374
375         function set($arr, $page)
376         {
377                 list(, , , $alias, $name) = $this->splice($arr);
378                 return parent::setParam($page, htmlsc($name),
379                         '', 'url', $alias == '' ? $name : $alias);
380         }
381
382         function toString()
383         {
384                 if (FALSE) {
385                         $rel = '';
386                 } else {
387                         $rel = ' rel="nofollow"';
388                 }
389                 return '<a href="' . $this->name . '"' . $rel . '>' . $this->alias . '</a>';
390         }
391 }
392
393 // URLs (InterWiki definition on "InterWikiName")
394 class Link_url_interwiki extends Link
395 {
396         function Link_url_interwiki($start)
397         {
398                 parent::Link($start);
399         }
400
401         function get_pattern()
402         {
403                 return <<<EOD
404 \[       # open bracket
405 (        # (1) url
406  (?:(?:https?|ftp|news):\/\/|\.\.?\/)[!~*'();\/?:\@&=+\$,%#\w.-]*
407 )
408 \s
409 ([^\]]+) # (2) alias
410 \]       # close bracket
411 EOD;
412         }
413
414         function get_count()
415         {
416                 return 2;
417         }
418
419         function set($arr, $page)
420         {
421                 list(, $name, $alias) = $this->splice($arr);
422                 return parent::setParam($page, htmlsc($name), '', 'url', $alias);
423         }
424
425         function toString()
426         {
427                 return '<a href="' . $this->name . '" rel="nofollow">' . $this->alias . '</a>';
428         }
429 }
430
431 // mailto: URL schemes
432 class Link_mailto extends Link
433 {
434         var $is_image, $image;
435
436         function Link_mailto($start)
437         {
438                 parent::Link($start);
439         }
440
441         function get_pattern()
442         {
443                 $s1 = $this->start + 1;
444                 return <<<EOD
445 (?:
446  \[\[
447  ((?:(?!\]\]).)+)(?:>|:)  # (1) alias
448 )?
449 ([\w.-]+@[\w-]+\.[\w.-]+) # (2) mailto
450 (?($s1)\]\])              # close bracket if (1)
451 EOD;
452         }
453
454         function get_count()
455         {
456                 return 2;
457         }
458
459         function set($arr, $page)
460         {
461                 list(, $alias, $name) = $this->splice($arr);
462                 return parent::setParam($page, $name, '', 'mailto', $alias == '' ? $name : $alias);
463         }
464         
465         function toString()
466         {
467                 return '<a href="mailto:' . $this->name . '" rel="nofollow">' . $this->alias . '</a>';
468         }
469 }
470
471 // InterWikiName-rendered URLs
472 class Link_interwikiname extends Link
473 {
474         var $url    = '';
475         var $param  = '';
476         var $anchor = '';
477
478         function Link_interwikiname($start)
479         {
480                 parent::Link($start);
481         }
482
483         function get_pattern()
484         {
485                 $s2 = $this->start + 2;
486                 $s5 = $this->start + 5;
487                 return <<<EOD
488 \[\[                  # open bracket
489 (?:
490  ((?:(?!\]\]).)+)>    # (1) alias
491 )?
492 (\[\[)?               # (2) open bracket
493 ((?:(?!\s|:|\]\]).)+) # (3) InterWiki
494 (?<! > | >\[\[ )      # not '>' or '>[['
495 :                     # separator
496 (                     # (4) param
497  (\[\[)?              # (5) open bracket
498  (?:(?!>|\]\]).)+
499  (?($s5)\]\])         # close bracket if (5)
500 )
501 (?($s2)\]\])          # close bracket if (2)
502 \]\]                  # close bracket
503 EOD;
504         }
505
506         function get_count()
507         {
508                 return 5;
509         }
510
511         function set($arr, $page)
512         {
513                 global $script;
514
515                 list(, $alias, , $name, $this->param) = $this->splice($arr);
516
517                 $matches = array();
518                 if (preg_match('/^([^#]+)(#[A-Za-z][\w-]*)$/', $this->param, $matches))
519                         list(, $this->param, $this->anchor) = $matches;
520
521                 $url = get_interwiki_url($name, $this->param);
522                 $this->url = ($url === FALSE) ?
523                         $script . '?' . pagename_urlencode('[[' . $name . ':' . $this->param . ']]') :
524                         htmlsc($url);
525
526                 return parent::setParam(
527                         $page,
528                         htmlsc($name . ':' . $this->param),
529                         '',
530                         'InterWikiName',
531                         $alias == '' ? $name . ':' . $this->param : $alias
532                 );
533         }
534
535         function toString()
536         {
537                 return '<a href="' . $this->url . $this->anchor . '" title="' .
538                         $this->name . '" rel="nofollow">' . $this->alias . '</a>';
539         }
540 }
541
542 // BracketNames
543 class Link_bracketname extends Link
544 {
545         var $anchor, $refer;
546
547         function Link_bracketname($start)
548         {
549                 parent::Link($start);
550         }
551
552         function get_pattern()
553         {
554                 global $WikiName, $BracketName;
555
556                 $s2 = $this->start + 2;
557                 return <<<EOD
558 \[\[                     # Open bracket
559 (?:((?:(?!\]\]).)+)>)?   # (1) Alias
560 (\[\[)?                  # (2) Open bracket
561 (                        # (3) PageName
562  (?:$WikiName)
563  |
564  (?:$BracketName)
565 )?
566 (\#(?:[a-zA-Z][\w-]*)?)? # (4) Anchor
567 (?($s2)\]\])             # Close bracket if (2)
568 \]\]                     # Close bracket
569 EOD;
570         }
571
572         function get_count()
573         {
574                 return 4;
575         }
576
577         function set($arr, $page)
578         {
579                 global $WikiName;
580
581                 list(, $alias, , $name, $this->anchor) = $this->splice($arr);
582                 if ($name == '' && $this->anchor == '') return FALSE;
583
584                 if ($name == '' || ! preg_match('/^' . $WikiName . '$/', $name)) {
585                         if ($alias == '') $alias = $name . $this->anchor;
586                         if ($name != '') {
587                                 $name = get_fullname($name, $page);
588                                 if (! is_pagename($name)) return FALSE;
589                         }
590                 }
591
592                 return parent::setParam($page, $name, '', 'pagename', $alias);
593         }
594
595         function toString()
596         {
597                 return make_pagelink(
598                         $this->name,
599                         $this->alias,
600                         $this->anchor,
601                         $this->page
602                 );
603         }
604 }
605
606 // WikiNames
607 class Link_wikiname extends Link
608 {
609         function Link_wikiname($start)
610         {
611                 parent::Link($start);
612         }
613
614         function get_pattern()
615         {
616                 global $WikiName, $nowikiname;
617
618                 return $nowikiname ? FALSE : '(' . $WikiName . ')';
619         }
620
621         function get_count()
622         {
623                 return 1;
624         }
625
626         function set($arr, $page)
627         {
628                 list($name) = $this->splice($arr);
629                 return parent::setParam($page, $name, '', 'pagename', $name);
630         }
631
632         function toString()
633         {
634                 return make_pagelink(
635                         $this->name,
636                         $this->alias,
637                         '',
638                         $this->page
639                 );
640         }
641 }
642
643 // AutoLinks
644 class Link_autolink extends Link
645 {
646         var $forceignorepages = array();
647         var $auto;
648         var $auto_a; // alphabet only
649
650         function Link_autolink($start)
651         {
652                 global $autolink;
653
654                 parent::Link($start);
655
656                 if (! $autolink || ! file_exists(CACHE_DIR . 'autolink.dat'))
657                         return;
658
659                 @list($auto, $auto_a, $forceignorepages) = file(CACHE_DIR . 'autolink.dat');
660                 $this->auto   = $auto;
661                 $this->auto_a = $auto_a;
662                 $this->forceignorepages = explode("\t", trim($forceignorepages));
663         }
664
665         function get_pattern()
666         {
667                 return isset($this->auto) ? '(' . $this->auto . ')' : FALSE;
668         }
669
670         function get_count()
671         {
672                 return 1;
673         }
674
675         function set($arr, $page)
676         {
677                 global $WikiName;
678
679                 list($name) = $this->splice($arr);
680
681                 // Ignore pages listed, or Expire ones not found
682                 if (in_array($name, $this->forceignorepages) || ! is_page($name))
683                         return FALSE;
684
685                 return parent::setParam($page, $name, '', 'pagename', $name);
686         }
687
688         function toString()
689         {
690                 return make_pagelink($this->name, $this->alias, '', $this->page, TRUE);
691         }
692 }
693
694 class Link_autolink_a extends Link_autolink
695 {
696         function Link_autolink_a($start)
697         {
698                 parent::Link_autolink($start);
699         }
700
701         function get_pattern()
702         {
703                 return isset($this->auto_a) ? '(' . $this->auto_a . ')' : FALSE;
704         }
705 }
706
707 // Make hyperlink for the page
708 function make_pagelink($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE)
709 {
710         global $script, $vars, $link_compact, $related, $_symbol_noexists;
711
712         $s_page = htmlsc(strip_bracket($page));
713         $s_alias = ($alias == '') ? $s_page : $alias;
714
715         if ($page == '') return '<a href="' . $anchor . '">' . $s_alias . '</a>';
716
717         $r_page  = pagename_urlencode($page);
718         $r_refer = ($refer == '') ? '' : '&amp;refer=' . rawurlencode($refer);
719
720         if (! isset($related[$page]) && $page !== $vars['page'] && is_page($page))
721                 $related[$page] = get_filetime($page);
722
723         if ($isautolink || is_page($page)) {
724                 // Hyperlink to the page
725                 if ($link_compact) {
726                         $title   = '';
727                 } else {
728                         $title   = ' title="' . $s_page . get_pg_passage($page, FALSE) . '"';
729                 }
730
731                 // AutoLink marker
732                 if ($isautolink) {
733                         $al_left  = '<!--autolink-->';
734                         $al_right = '<!--/autolink-->';
735                 } else {
736                         $al_left = $al_right = '';
737                 }
738
739                 return $al_left . '<a ' . 'href="' . $script . '?' . $r_page . $anchor .
740                         '"' . $title . '>' . $s_alias . '</a>' . $al_right;
741         } else {
742                 // Dangling link
743                 if (PKWK_READONLY) return $s_alias; // No dacorations
744
745                 $retval = $s_alias . '<a href="' .
746                         $script . '?cmd=edit&amp;page=' . $r_page . $r_refer . '">' .
747                         $_symbol_noexists . '</a>';
748
749                 if ($link_compact) {
750                         return $retval;
751                 } else {
752                         return '<span class="noexists">' . $retval . '</span>';
753                 }
754         }
755 }
756
757 // Resolve relative / (Unix-like)absolute path of the page
758 function get_fullname($name, $refer)
759 {
760         global $defaultpage;
761
762         // 'Here'
763         if ($name == '' || $name == './') return $refer;
764
765         // Absolute path
766         if ($name{0} == '/') {
767                 $name = substr($name, 1);
768                 return ($name == '') ? $defaultpage : $name;
769         }
770
771         // Relative path from 'Here'
772         if (substr($name, 0, 2) == './') {
773                 $arrn    = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
774                 $arrn[0] = $refer;
775                 return join('/', $arrn);
776         }
777
778         // Relative path from dirname()
779         if (substr($name, 0, 3) == '../') {
780                 $arrn = preg_split('#/#', $name,  -1, PREG_SPLIT_NO_EMPTY);
781                 $arrp = preg_split('#/#', $refer, -1, PREG_SPLIT_NO_EMPTY);
782
783                 while (! empty($arrn) && $arrn[0] == '..') {
784                         array_shift($arrn);
785                         array_pop($arrp);
786                 }
787                 $name = ! empty($arrp) ? join('/', array_merge($arrp, $arrn)) :
788                         (! empty($arrn) ? $defaultpage . '/' . join('/', $arrn) : $defaultpage);
789         }
790
791         return $name;
792 }
793
794 // Render an InterWiki into a URL
795 function get_interwiki_url($name, $param)
796 {
797         global $WikiName, $interwiki;
798         static $interwikinames;
799         static $encode_aliases = array('sjis'=>'SJIS', 'euc'=>'EUC-JP', 'utf8'=>'UTF-8');
800
801         if (! isset($interwikinames)) {
802                 $interwikinames = $matches = array();
803                 foreach (get_source($interwiki) as $line)
804                         if (preg_match('/\[(' . '(?:(?:https?|ftp|news):\/\/|\.\.?\/)' .
805                             '[!~*\'();\/?:\@&=+\$,%#\w.-]*)\s([^\]]+)\]\s?([^\s]*)/',
806                             $line, $matches))
807                                 $interwikinames[$matches[2]] = array($matches[1], $matches[3]);
808         }
809
810         if (! isset($interwikinames[$name])) return FALSE;
811
812         list($url, $opt) = $interwikinames[$name];
813
814         // Encoding
815         switch ($opt) {
816
817         case '':    /* FALLTHROUGH */
818         case 'std': // Simply URL-encode the string, whose base encoding is the internal-encoding
819                 $param = rawurlencode($param);
820                 break;
821
822         case 'asis': /* FALLTHROUGH */
823         case 'raw' : // Truly as-is
824                 break;
825
826         case 'yw': // YukiWiki
827                 if (! preg_match('/' . $WikiName . '/', $param))
828                         $param = '[[' . mb_convert_encoding($param, 'SJIS', SOURCE_ENCODING) . ']]';
829                 break;
830
831         case 'moin': // MoinMoin
832                 $param = str_replace('%', '_', rawurlencode($param));
833                 break;
834
835         default:
836                 // Alias conversion of $opt
837                 if (isset($encode_aliases[$opt])) $opt = & $encode_aliases[$opt];
838
839                 // Encoding conversion into specified encode, and URLencode
840                 $param = rawurlencode(mb_convert_encoding($param, $opt, SOURCE_ENCODING));
841         }
842
843         // Replace or Add the parameter
844         if (strpos($url, '$1') !== FALSE) {
845                 $url = str_replace('$1', $param, $url);
846         } else {
847                 $url .= $param;
848         }
849
850         $len = strlen($url);
851         if ($len > 512) die_message('InterWiki URL too long: ' . $len . ' characters');
852
853         return $url;
854 }