OSDN Git Service

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