OSDN Git Service

BugTrack/2514 Support PHP 8
[pukiwiki/pukiwiki.git] / lib / make_link.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // make_link.php
4 // Copyright
5 //   2003-2021 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                 global $autoalias, $aliaspage;
783
784                 parent::Link($start);
785
786                 if (! $autoalias || ! file_exists(CACHE_DIR . PKWK_AUTOALIAS_REGEX_CACHE) || $this->page == $aliaspage)
787                 {
788                         return;
789                 }
790
791                 @list($auto, $auto_a, $forceignorepages) = file(CACHE_DIR . PKWK_AUTOALIAS_REGEX_CACHE);
792                 $this->auto = $auto;
793                 $this->auto_a = $auto_a;
794                 $this->forceignorepages = explode("\t", trim($forceignorepages));
795                 $this->alias = '';
796         }
797         function get_pattern()
798         {
799                 return isset($this->auto) ? '(' . $this->auto . ')' : FALSE;
800         }
801         function get_count()
802         {
803                 return 1;
804         }
805         function set($arr,$page)
806         {
807                 list($name) = $this->splice($arr);
808                 // Ignore pages listed
809                 if (in_array($name, $this->forceignorepages) || get_autoalias_right_link($name) == '') {
810                         return FALSE;
811                 }
812                 return parent::setParam($page,$name,'','pagename',$name);
813         }
814
815         function toString()
816         {
817                 $this->alias = get_autoalias_right_link($this->name);
818                 if ($this->alias != '') {
819                         $link = '[[' . $this->name . '>' . $this->alias . ']]';
820                         return make_link($link);
821                 }
822                 return '';
823         }
824 }
825
826 class Link_autoalias_a extends Link_autoalias
827 {
828         function Link_autoalias_a($start)
829         {
830                 parent::Link_autoalias($start);
831         }
832         function get_pattern()
833         {
834                 return isset($this->auto_a) ? '(' . $this->auto_a . ')' : FALSE;
835         }
836 }
837
838 // Make hyperlink for the page
839 function make_pagelink($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE)
840 {
841         global $vars, $link_compact, $related, $_symbol_noexists;
842
843         $script = get_base_uri();
844         $s_page = htmlsc(strip_bracket($page));
845         $s_alias = ($alias == '') ? $s_page : $alias;
846
847         if ($page == '') return '<a href="' . $anchor . '">' . $s_alias . '</a>';
848
849         $r_page  = pagename_urlencode($page);
850         $r_refer = ($refer == '') ? '' : '&amp;refer=' . rawurlencode($refer);
851
852         $page_filetime = fast_get_filetime($page);
853         $is_page = $page_filetime !== 0;
854         if (! isset($related[$page]) && $page !== $vars['page'] && $is_page) {
855                 $related[$page] = $page_filetime;
856         }
857
858         if ($isautolink || $is_page) {
859                 // Hyperlink to the page
860                 $attrs = get_filetime_a_attrs($page_filetime);
861                 // AutoLink marker
862                 if ($isautolink) {
863                         $al_left  = '<!--autolink-->';
864                         $al_right = '<!--/autolink-->';
865                 } else {
866                         $al_left = $al_right = '';
867                 }
868                 $title_attr_html = '';
869                 if ($s_page !== $s_alias) {
870                         $title_attr_html = ' title="' . $s_page . '"';
871                 }
872                 return $al_left . '<a ' . 'href="' . $script . '?' . $r_page . $anchor .
873                         '"' . $title_attr_html . ' class="' .
874                         $attrs['class'] . '" data-mtime="' . $attrs['data_mtime'] .
875                         '">' . $s_alias . '</a>' . $al_right;
876         } else {
877                 // Support Page redirection
878                 $redirect_page = get_pagename_on_redirect($page);
879                 if ($redirect_page !== false) {
880                         return make_pagelink($redirect_page, $s_alias);
881                 }
882                 // Dangling link
883                 if (PKWK_READONLY) return $s_alias; // No dacorations
884                 $symbol_html = '';
885                 if ($_symbol_noexists !== '') {
886                         $symbol_html = '<span style="user-select:none;">' .
887                                 htmlsc($_symbol_noexists) . '</span>';
888                 }
889                 $href = $script . '?cmd=edit&amp;page=' . $r_page . $r_refer;
890                 if ($link_compact && $_symbol_noexists != '') {
891                         $retval = '<a href="' . $href . '">' . $_symbol_noexists . '</a>';
892                         return $retval;
893                 } else {
894                         $retval = '<a href="' . $href . '">' . $s_alias . '</a>';
895                         return '<span class="noexists">' . $retval . $symbol_html . '</span>';
896                 }
897         }
898 }
899
900 // Resolve relative / (Unix-like)absolute path of the page
901 function get_fullname($name, $refer)
902 {
903         global $defaultpage;
904
905         // 'Here'
906         if ($name == '' || $name == './') return $refer;
907
908         // Absolute path
909         if ($name[0] == '/') {
910                 $name = substr($name, 1);
911                 return ($name == '') ? $defaultpage : $name;
912         }
913
914         // Relative path from 'Here'
915         if (substr($name, 0, 2) == './') {
916                 $arrn    = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
917                 $arrn[0] = $refer;
918                 return join('/', $arrn);
919         }
920
921         // Relative path from dirname()
922         if (substr($name, 0, 3) == '../') {
923                 $arrn = preg_split('#/#', $name,  -1, PREG_SPLIT_NO_EMPTY);
924                 $arrp = preg_split('#/#', $refer, -1, PREG_SPLIT_NO_EMPTY);
925
926                 while (! empty($arrn) && $arrn[0] == '..') {
927                         array_shift($arrn);
928                         array_pop($arrp);
929                 }
930                 $name = ! empty($arrp) ? join('/', array_merge($arrp, $arrn)) :
931                         (! empty($arrn) ? $defaultpage . '/' . join('/', $arrn) : $defaultpage);
932         }
933
934         return $name;
935 }
936
937 // Render an InterWiki into a URL
938 function get_interwiki_url($name, $param)
939 {
940         global $WikiName, $interwiki;
941         static $interwikinames;
942         static $encode_aliases = array('sjis'=>'SJIS', 'euc'=>'EUC-JP', 'utf8'=>'UTF-8');
943
944         if (! isset($interwikinames)) {
945                 $interwikinames = $matches = array();
946                 foreach (get_source($interwiki) as $line)
947                         if (preg_match('/\[(' . '(?:(?:https?|ftp|news):\/\/|\.\.?\/)' .
948                             '[!~*\'();\/?:\@&=+\$,%#\w.-]*)\s([^\]]+)\]\s?([^\s]*)/',
949                             $line, $matches))
950                                 $interwikinames[$matches[2]] = array($matches[1], $matches[3]);
951         }
952
953         if (! isset($interwikinames[$name])) return FALSE;
954
955         list($url, $opt) = $interwikinames[$name];
956
957         // Encoding
958         switch ($opt) {
959
960         case '':    /* FALLTHROUGH */
961         case 'std': // Simply URL-encode the string, whose base encoding is the internal-encoding
962                 $param = rawurlencode($param);
963                 break;
964
965         case 'asis': /* FALLTHROUGH */
966         case 'raw' : // Truly as-is
967                 break;
968
969         case 'yw': // YukiWiki
970                 if (! preg_match('/' . $WikiName . '/', $param))
971                         $param = '[[' . mb_convert_encoding($param, 'SJIS', SOURCE_ENCODING) . ']]';
972                 break;
973
974         case 'moin': // MoinMoin
975                 $param = str_replace('%', '_', rawurlencode($param));
976                 break;
977
978         default:
979                 // Alias conversion of $opt
980                 if (isset($encode_aliases[$opt])) $opt = & $encode_aliases[$opt];
981
982                 // Encoding conversion into specified encode, and URLencode
983                 if (strpos($url, '$1') === FALSE && substr($url, -1) === '?') {
984                         // PukiWiki site
985                         $param = pagename_urlencode(mb_convert_encoding($param, $opt, SOURCE_ENCODING));
986                 } else {
987                         $param = rawurlencode(mb_convert_encoding($param, $opt, SOURCE_ENCODING));
988                 }
989         }
990
991         // Replace or Add the parameter
992         if (strpos($url, '$1') !== FALSE) {
993                 $url = str_replace('$1', $param, $url);
994         } else {
995                 $url .= $param;
996         }
997
998         $len = strlen($url);
999         if ($len > 512) die_message('InterWiki URL too long: ' . $len . ' characters');
1000
1001         return $url;
1002 }
1003
1004 function get_autoticketlink_def_page()
1005 {
1006         return 'AutoTicketLinkName';
1007 }
1008
1009 /**
1010  * Get AutoTicketLink - JIRA projects from AutoTiketLinkName page
1011  */
1012 function get_ticketlink_jira_projects()
1013 {
1014         $autoticketlink_def_page = get_autoticketlink_def_page();
1015         $active_jira_base_url = null;
1016         $jira_projects = array();
1017         foreach (get_source($autoticketlink_def_page) as $line) {
1018                 if (substr($line, 0, 1) !== '-') {
1019                         $active_jira_base_url = null;
1020                         continue;
1021                 }
1022                 $m = null;
1023                 if (preg_match('/^-\s*(jira)\s+(https?:\/\/[!~*\'();\/?:\@&=+\$,%#\w.-]+)\s*$/', $line, $m)) {
1024                         $active_jira_base_url = $m[2];
1025                 } else if (preg_match('/^--\s*([A-Z][A-Z0-9]{1,10}(?:_[A-Z0-9]{1,10}){0,2})(\s+(.+?))?\s*$/', $line, $m)) {
1026                         if ($active_jira_base_url) {
1027                                 $project_key = $m[1];
1028                                 $title = isset($m[2]) ? $m[2] : '';
1029                                 array_push($jira_projects, array(
1030                                         'key' => $m[1],
1031                                         'title' => $title,
1032                                         'base_url' => $active_jira_base_url,
1033                                 ));
1034                         }
1035                 } else {
1036                         $active_jira_base_url = null;
1037                 }
1038         }
1039         return $jira_projects;
1040 }
1041
1042 function init_autoticketlink_def_page()
1043 {
1044         $autoticketlink_def_page = get_autoticketlink_def_page();
1045         if (is_page($autoticketlink_def_page)) {
1046                 return;
1047         }
1048         $body = <<<EOS
1049 #freeze
1050 * AutoTicketLink definition [#def]
1051
1052 Reference: https://pukiwiki.osdn.jp/?AutoTicketLink
1053
1054  - jira https://site1.example.com/jira/browse/
1055  -- AAA Project title \$1
1056  -- BBB Project title \$1
1057  - jira https://site2.example.com/jira/browse/
1058  -- PROJECTA Site2 \$1
1059
1060  (Default definition) pukiwiki.ini.php
1061  $ticket_jira_default_site = array(
1062    'title' => 'My JIRA - \$1',
1063    'base_url' => 'https://issues.example.com/jira/browse/',
1064  );
1065 EOS;
1066         page_write($autoticketlink_def_page, $body);
1067 }
1068
1069 function init_autoalias_def_page()
1070 {
1071         global $aliaspage; // 'AutoAliasName'
1072         $autoticketlink_def_page = get_autoticketlink_def_page();
1073         if (is_page($aliaspage)) {
1074                 return;
1075         }
1076         $body = <<<EOS
1077 #freeze
1078 *AutoAliasName [#qf9311bb]
1079 AutoAlias definition
1080
1081 Reference: https://pukiwiki.osdn.jp/?AutoAlias
1082
1083 * PukiWiki [#ee87d39e]
1084 -[[pukiwiki.official>https://pukiwiki.osdn.jp/]]
1085 -[[pukiwiki.dev>https://pukiwiki.osdn.jp/dev/]]
1086 EOS;
1087         page_write($aliaspage, $body);
1088         update_autoalias_cache_file();
1089 }