2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // $Id: make_link.php,v 1.10 2005/01/15 03:16:22 henoheno Exp $
5 // Hyperlink-related functions
8 function make_link($string, $page = '')
13 if (! isset($converter)) $converter = new InlineConverter();
15 $clone = $converter->get_clone($converter);
17 return $clone->convert($string, ($page != '') ? $page : $vars['page']);
20 //¥¤¥ó¥é¥¤¥óÍ×ÁǤòÃÖ´¹¤¹¤ë
23 var $converters; // as array()
28 function get_clone($obj) {
31 if (! isset($clone_func)) {
32 if (version_compare(PHP_VERSION, '5.0.0', '<')) {
33 $clone_func = create_function('$a', 'return $a;');
35 $clone_func = create_function('$a', 'return clone $a;');
38 return $clone_func($obj);
42 $converters = array();
43 foreach ($this->converters as $key=>$converter) {
44 $converters[$key] = $this->get_clone($converter);
46 $this->converters = $converters;
49 function InlineConverter($converters = NULL, $excludes = NULL)
51 if ($converters === NULL) {
53 'plugin', // ¥¤¥ó¥é¥¤¥ó¥×¥é¥°¥¤¥ó
56 'url_interwiki', // URL (interwiki definition)
58 'interwikiname', // InterWikiName
59 'autolink', // AutoLink
60 'bracketname', // BracketName
61 'wikiname', // WikiName
62 'autolink_a', // AutoLink(¥¢¥ë¥Õ¥¡¥Ù¥Ã¥È)
66 if ($excludes !== NULL)
67 $converters = array_diff($converters, $excludes);
69 $this->converters = $patterns = array();
72 foreach ($converters as $name) {
73 $classname = 'Link_' . $name;
74 $converter = new $classname($start);
75 $pattern = $converter->get_pattern();
76 if ($pattern === FALSE) continue;
78 $patterns[] = '(' . "\n" . $pattern . "\n" . ')';
79 $this->converters[$start] = $converter;
80 $start += $converter->get_count();
83 $this->pattern = join('|', $patterns);
86 function convert($string, $page)
89 $this->result = array();
91 $string = preg_replace_callback('/' . $this->pattern . '/x',
92 array(& $this, 'replace'), $string);
94 $arr = explode("\x08", make_line_rules(htmlspecialchars($string)));
96 while (! empty($arr)) {
97 $retval .= array_shift($arr) . array_shift($this->result);
102 function replace($arr)
104 $obj = $this->get_converter($arr);
106 $this->result[] = ($obj !== NULL && $obj->set($arr, $this->page) !== FALSE) ?
107 $obj->toString() : make_line_rules(htmlspecialchars($arr[0]));
109 return "\x08"; //½èÍýºÑ¤ß¤ÎÉôʬ¤Ë¥Þ¡¼¥¯¤òÆþ¤ì¤ë
112 function get_objects($string, $page)
114 $matches = $arr = array();
115 preg_match_all('/' . $this->pattern . '/x', $string, $matches, PREG_SET_ORDER);
116 foreach ($matches as $match) {
117 $obj = $this->get_converter($match);
118 if ($obj->set($match, $page) !== FALSE) {
119 $arr[] = $this->get_clone($obj);
120 if ($obj->body != '')
121 $arr = array_merge($arr, $this->get_objects($obj->body, $page));
127 function & get_converter(& $arr)
129 foreach (array_keys($this->converters) as $start) {
130 if ($arr[$start] == $arr[0])
131 return $this->converters[$start];
137 //¥¤¥ó¥é¥¤¥óÍ×Áǽ¸¹ç¤Î¥Ù¡¼¥¹¥¯¥é¥¹
140 var $start; // ³ç¸Ì¤ÎÀèƬÈÖ¹æ(0¥ª¥ê¥¸¥ó)
141 var $text; // ¥Þ¥Ã¥Á¤·¤¿Ê¸»úÎóÁ´ÂÎ
150 function Link($start)
152 $this->start = $start;
155 // ¥Þ¥Ã¥Á¤Ë»ÈÍѤ¹¤ë¥Ñ¥¿¡¼¥ó¤òÊÖ¤¹
156 function get_pattern() {}
158 // »ÈÍѤ·¤Æ¤¤¤ë³ç¸Ì¤Î¿ô¤òÊÖ¤¹ ((?:...)¤ò½ü¤¯)
159 function get_count() {}
161 // ¥Þ¥Ã¥Á¤·¤¿¥Ñ¥¿¡¼¥ó¤òÀßÄꤹ¤ë
162 function set($arr,$page) {}
165 function toString() {}
168 // ¥Þ¥Ã¥Á¤·¤¿ÇÛÎ󤫤顢¼«Ê¬¤ËɬÍפÊÉôʬ¤À¤±¤ò¼è¤ê½Ð¤¹
169 function splice($arr) {
170 $count = $this->get_count() + 1;
171 $arr = array_pad(array_splice($arr, $this->start, $count), $count, '');
172 $this->text = $arr[0];
176 // ´ðËܥѥé¥á¡¼¥¿¤òÀßÄꤹ¤ë
177 function setParam($page, $name, $body, $type = '', $alias = '')
179 static $converter = NULL;
185 if (is_url($alias) && preg_match('/\.(gif|png|jpe?g)$/i', $alias)) {
186 $alias = htmlspecialchars($alias);
187 $alias = '<img src="' . $alias . '" alt="' . $name . '" />';
188 } else if ($alias != '') {
189 if ($converter === NULL)
190 $converter = new InlineConverter(array('plugin'));
192 $alias = make_line_rules($converter->convert($alias, $page));
194 // BugTrack/669: A hack removing anchor tags added by AutoLink
195 $alias = preg_replace('#</?a[^>]*>#i', '', $alias);
197 $this->alias = $alias;
203 // ¥¤¥ó¥é¥¤¥ó¥×¥é¥°¥¤¥ó
204 class Link_plugin extends Link
209 function Link_plugin($start)
211 parent::Link($start);
214 function get_pattern()
216 $this->pattern = <<<EOD
219 (\w+) # (2) plugin name
222 ((?:(?!\)[;{]).)*) # (3) parameter
231 ((?:(?R)|(?!};).)*) # (4) body
243 function set($arr, $page)
245 list($all, $this->plain, $name, $this->param, $body) = $this->splice($arr);
247 // ËÜÍè¤Î¥×¥é¥°¥¤¥ó̾¤ª¤è¤Ó¥Ñ¥é¥á¡¼¥¿¤ò¼èÆÀ¤·¤Ê¤ª¤¹ PHP4.1.2 (?R)Âкö
249 if (preg_match('/^' . $this->pattern . '/x', $all, $matches)
250 && $matches[1] != $this->plain)
251 list(, $this->plain, $name, $this->param) = $matches;
253 return parent::setParam($page, $name, $body, 'plugin');
258 $body = ($this->body == '') ? '' : make_link($this->body);
260 // ¥×¥é¥°¥¤¥ó¸Æ¤Ó½Ð¤·
261 if (exist_plugin_inline($this->name)) {
262 $str = do_plugin_inline($this->name, $this->param, $body);
263 if ($str !== FALSE) //À®¸ù
267 // ¥×¥é¥°¥¤¥ó¤¬Â¸ºß¤·¤Ê¤¤¤«¡¢ÊÑ´¹¤Ë¼ºÇÔ
268 $body = (($body == '') ? '' : '{' . $body . '}') . ';';
269 return make_line_rules(htmlspecialchars('&' . $this->plain) . $body);
274 class Link_note extends Link
276 function Link_note($start)
278 parent::Link($start);
281 function get_pattern()
285 ((?:(?R)|(?!\)\)).)*) # (1) note body
295 function set($arr, $page)
297 global $foot_explain, $script, $vars;
300 list(, $body) = $this->splice($arr);
303 $note = make_link($body);
304 $page = isset($vars['page']) ? htmlspecialchars($vars['page']) : '';
307 $foot_explain[$id] = <<<EOD
308 <a id="notefoot_$id" href="$script?$page#notetext_$id" class="note_super">*$id</a>
309 <span class="small">$note</span>
312 // A hyperlink, content-body to footnote
313 $name = '<a id="notetext_' . $id . '" href="' .
314 $script . '?' . $page . '#notefoot_' . $id .
315 '" class="note_super">*' . $id . '</a>';
317 return parent::setParam($page, $name, $body);
327 class Link_url extends Link
329 function Link_url($start)
331 parent::Link($start);
334 function get_pattern()
336 $s1 = $this->start + 1;
338 (\[\[ # (1) open bracket
339 ((?:(?!\]\]).)+) # (2) alias
343 (?:(?:https?|ftp|news):\/\/|mailto:)[\w\/\@\$()!?&%#:;.,~'=*+-]+
345 (?($s1)\]\]) # close bracket
354 function set($arr, $page)
356 list(, , $alias, $name) = $this->splice($arr);
357 return parent::setParam($page, htmlspecialchars($name),
358 '', 'url', $alias == '' ? $name : $alias);
363 return '<a href="' . $this->name . '">' . $this->alias . '</a>';
367 // url (InterWiki definition type)
368 class Link_url_interwiki extends Link
370 function Link_url_interwiki($start)
372 parent::Link($start);
375 function get_pattern()
380 (?:(?:https?|ftp|news):\/\/|\.\.?\/)[!~*'();\/?:\@&=+\$,%#\w.-]*
393 function set($arr, $page)
395 list(, $name, $alias) = $this->splice($arr);
396 return parent::setParam($page, htmlspecialchars($name), '', 'url', $alias);
401 return '<a href="' . $this->name . '">' . $this->alias . '</a>';
406 class Link_mailto extends Link
408 var $is_image, $image;
410 function Link_mailto($start)
412 parent::Link($start);
415 function get_pattern()
417 $s1 = $this->start + 1;
421 ((?:(?!\]\]).)+)(?:>|:) # (1) alias
423 ([\w.-]+@[\w-]+\.[\w.-]+) # (2) mailto
424 (?($s1)\]\]) # close bracket if (1)
433 function set($arr, $page)
435 list(, $alias, $name) = $this->splice($arr);
436 return parent::setParam($page, $name, '', 'mailto', $alias == '' ? $name : $alias);
441 return '<a href="mailto:' . $this->name . '">' . $this->alias . '</a>';
446 class Link_interwikiname extends Link
452 function Link_interwikiname($start)
454 parent::Link($start);
457 function get_pattern()
459 $s2 = $this->start + 2;
460 $s5 = $this->start + 5;
464 ((?:(?!\]\]).)+)> # (1) alias
466 (\[\[)? # (2) open bracket
467 ((?:(?!\s|:|\]\]).)+) # (3) InterWiki
468 (?<! > | >\[\[ ) # not '>' or '>[['
471 (\[\[)? # (5) open bracket
473 (?($s5)\]\]) # close bracket if (5)
475 (?($s2)\]\]) # close bracket if (2)
485 function set($arr, $page)
489 list(, $alias, , $name, $this->param) = $this->splice($arr);
492 if (preg_match('/^([^#]+)(#[A-Za-z][\w-]*)$/', $this->param, $matches))
493 list(, $this->param, $this->anchor) = $matches;
495 $url = get_interwiki_url($name, $this->param);
496 $this->url = ($url === FALSE) ?
497 $script . '?' . rawurlencode('[[' . $name . ':' . $this->param . ']]') :
498 htmlspecialchars($url);
500 return parent::setParam(
502 htmlspecialchars($name . ':' . $this->param),
505 $alias == '' ? $name . ':' . $this->param : $alias
511 return '<a href="' . $this->url . $this->anchor .
512 '" title="' . $this->name . '">' . $this->alias . '</a>';
517 class Link_bracketname extends Link
521 function Link_bracketname($start)
523 parent::Link($start);
526 function get_pattern()
528 global $WikiName, $BracketName;
530 $s2 = $this->start + 2;
533 (?:((?:(?!\]\]).)+)>)? # (1) alias
534 (\[\[)? # (2) open bracket
540 (\#(?:[a-zA-Z][\w-]*)?)? # (4) anchor
541 (?($s2)\]\]) # close bracket if (2)
551 function set($arr, $page)
555 list(, $alias, , $name, $this->anchor) = $this->splice($arr);
557 if ($name == '' && $this->anchor == '') return FALSE;
559 if ($name == '' || ! preg_match('/^' . $WikiName . '$/', $name)) {
561 if ($alias == '') $alias = $name . $this->anchor;
564 $name = get_fullname($name, $page);
565 if (! is_pagename($name)) return FALSE;
569 return parent::setParam($page, $name, '', 'pagename', $alias);
574 return make_pagelink(
584 class Link_wikiname extends Link
586 function Link_wikiname($start)
588 parent::Link($start);
591 function get_pattern()
593 global $WikiName, $nowikiname;
595 return $nowikiname ? FALSE : '(' . $WikiName . ')';
603 function set($arr, $page)
605 list($name) = $this->splice($arr);
606 return parent::setParam($page, $name, '', 'pagename', $name);
611 return make_pagelink(
621 class Link_autolink extends Link
623 var $forceignorepages = array();
625 var $auto_a; // alphabet only
627 function Link_autolink($start)
631 parent::Link($start);
633 if (! $autolink || ! file_exists(CACHE_DIR . 'autolink.dat'))
636 @list($auto, $auto_a, $forceignorepages) = file(CACHE_DIR . 'autolink.dat');
638 $this->auto_a = $auto_a;
639 $this->forceignorepages = explode("\t", trim($forceignorepages));
642 function get_pattern()
644 return isset($this->auto) ? '(' . $this->auto . ')' : FALSE;
652 function set($arr, $page)
656 list($name) = $this->splice($arr);
658 // ̵»ë¥ê¥¹¥È¤Ë´Þ¤Þ¤ì¤Æ¤¤¤ë¡¢¤¢¤ë¤¤¤Ï¸ºß¤·¤Ê¤¤¥Ú¡¼¥¸¤ò¼Î¤Æ¤ë
659 if (in_array($name, $this->forceignorepages) || ! is_page($name))
662 return parent::setParam($page, $name, '', 'pagename', $name);
667 return make_pagelink(
676 class Link_autolink_a extends Link_autolink
678 function Link_autolink_a($start)
680 parent::Link_autolink($start);
683 function get_pattern()
685 return isset($this->auto_a) ? '(' . $this->auto_a . ')' : FALSE;
689 // ¥Ú¡¼¥¸Ì¾¤Î¥ê¥ó¥¯¤òºîÀ®
690 function make_pagelink($page, $alias = '', $anchor = '', $refer = '')
692 global $script, $vars, $link_compact, $related, $_symbol_noexists;
694 $s_page = htmlspecialchars(strip_bracket($page));
695 $s_alias = ($alias == '') ? $s_page : $alias;
697 if ($page == '') return '<a href="' . $anchor . '">' . $s_alias . '</a>';
699 $r_page = rawurlencode($page);
700 $r_refer = ($refer == '') ? '' : '&refer=' . rawurlencode($refer);
702 if (! isset($related[$page]) && $page != $vars['page'] && is_page($page))
703 $related[$page] = get_filetime($page);
705 if (is_page($page)) {
706 $passage = get_pg_passage($page, FALSE);
707 $title = $link_compact ? '' : ' title="' . $s_page . $passage . '"';
708 return '<a href="' . $script . '?' . $r_page . $anchor . '"' . $title . '>' .
711 $retval = $s_alias . '<a href="' .
712 $script . '?cmd=edit&page=' . $r_page . $r_refer . '">' .
713 $_symbol_noexists . '</a>';
715 $retval = '<span class="noexists">' . $retval . '</span>';
720 // Resolve relative/(Unix-like)absolute path of the page
721 function get_fullname($name, $refer)
726 if ($name == '' || $name == './') return $refer;
729 if ($name{0} == '/') {
730 $name = substr($name, 1);
731 return ($name == '') ? $defaultpage : $name;
734 // Relative path from 'Here'
735 if (substr($name, 0, 2) == './') {
736 $arrn = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
738 return join('/', $arrn);
741 // Relative path from dirname()
742 if (substr($name, 0, 3) == '../') {
743 $arrn = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
744 $arrp = preg_split('#/#', $refer, -1, PREG_SPLIT_NO_EMPTY);
746 while (! empty($arrn) && $arrn[0] == '..') {
750 $name = ! empty($arrp) ? join('/', array_merge($arrp, $arrn)) :
751 (! empty($arrn) ? $defaultpage . '/' . join('/', $arrn) : $defaultpage);
757 // InterWikiName¤òŸ³«
758 function get_interwiki_url($name, $param)
760 global $WikiName, $interwiki;
761 static $interwikinames;
762 static $encode_aliases = array('sjis'=>'SJIS', 'euc'=>'EUC-JP', 'utf8'=>'UTF-8');
764 if (! isset($interwikinames)) {
765 $interwikinames = $matches = array();
766 foreach (get_source($interwiki) as $line)
767 if (preg_match('/\[(' . '(?:(?:https?|ftp|news):\/\/|\.\.?\/)' .
768 '[!~*\'();\/?:\@&=+\$,%#\w.-]*)\s([^\]]+)\]\s?([^\s]*)/',
770 $interwikinames[$matches[2]] = array($matches[1], $matches[3]);
773 if (! isset($interwikinames[$name])) return FALSE;
775 list($url, $opt) = $interwikinames[$name];
777 // ʸ»ú¥¨¥ó¥³¡¼¥Ç¥£¥ó¥°
781 case 'std': // ÆâÉôʸ»ú¥¨¥ó¥³¡¼¥Ç¥£¥ó¥°¤Î¤Þ¤ÞURL¥¨¥ó¥³¡¼¥É
782 $param = rawurlencode($param);
787 // $param = htmlspecialchars($param);
790 case 'yw': // YukiWiki
791 if (! preg_match('/' . $WikiName . '/', $param))
792 $param = '[[' . mb_convert_encoding($param, 'SJIS', SOURCE_ENCODING) . ']]';
793 // $param = htmlspecialchars($param);
796 case 'moin': // MoinMoin
797 $param = str_replace('%', '_', rawurlencode($param));
802 if (isset($encode_aliases[$opt])) $opt = $encode_aliases[$opt];
803 // »ØÄꤵ¤ì¤¿Ê¸»ú¥³¡¼¥É¤Ø¥¨¥ó¥³¡¼¥É¤·¤ÆURL¥¨¥ó¥³¡¼¥É
804 $param = rawurlencode(mb_convert_encoding($param, $opt, 'auto'));
808 if (strpos($url, '$1') !== FALSE) {
809 $url = str_replace('$1', $param, $url);
815 if ($len > 512) die_message('InterWiki URL too long: ' . $len . ' characters');