OSDN Git Service

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