OSDN Git Service

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