OSDN Git Service

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