get_clone($converter);
return $clone->convert($string, ($page != '') ? $page : $vars['page']);
}
// Converters of inline element
class InlineConverter
{
var $converters; // as array()
var $pattern;
var $pos;
var $result;
function get_clone($obj) {
static $clone_func;
if (! isset($clone_func)) {
if (version_compare(PHP_VERSION, '5.0.0', '<')) {
$clone_func = create_function('$a', 'return $a;');
} else {
$clone_func = create_function('$a', 'return clone $a;');
}
}
return $clone_func($obj);
}
function __clone() {
$converters = array();
foreach ($this->converters as $key=>$converter) {
$converters[$key] = $this->get_clone($converter);
}
$this->converters = $converters;
}
function InlineConverter($converters = NULL, $excludes = NULL)
{
if ($converters === NULL) {
$converters = array(
'plugin', // Inline plugins
'note', // Footnotes
'url', // URLs
'url_interwiki', // URLs (interwiki definition)
'mailto', // mailto: URL schemes
'interwikiname', // InterWikiNames
'autolink', // AutoLinks
'bracketname', // BracketNames
'wikiname', // WikiNames
'autolink_a', // AutoLinks(alphabet)
);
}
if ($excludes !== NULL)
$converters = array_diff($converters, $excludes);
$this->converters = $patterns = array();
$start = 1;
foreach ($converters as $name) {
$classname = 'Link_' . $name;
$converter = new $classname($start);
$pattern = $converter->get_pattern();
if ($pattern === FALSE) continue;
$patterns[] = '(' . "\n" . $pattern . "\n" . ')';
$this->converters[$start] = $converter;
$start += $converter->get_count();
++$start;
}
$this->pattern = join('|', $patterns);
}
function convert($string, $page)
{
$this->page = $page;
$this->result = array();
$string = preg_replace_callback('/' . $this->pattern . '/x',
array(& $this, 'replace'), $string);
$arr = explode("\x08", make_line_rules(htmlsc($string)));
$retval = '';
while (! empty($arr)) {
$retval .= array_shift($arr) . array_shift($this->result);
}
return $retval;
}
function replace($arr)
{
$obj = $this->get_converter($arr);
$this->result[] = ($obj !== NULL && $obj->set($arr, $this->page) !== FALSE) ?
$obj->toString() : make_line_rules(htmlsc($arr[0]));
return "\x08"; // Add a mark into latest processed part
}
function get_objects($string, $page)
{
$matches = $arr = array();
preg_match_all('/' . $this->pattern . '/x', $string, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$obj = $this->get_converter($match);
if ($obj->set($match, $page) !== FALSE) {
$arr[] = $this->get_clone($obj);
if ($obj->body != '')
$arr = array_merge($arr, $this->get_objects($obj->body, $page));
}
}
return $arr;
}
function & get_converter(& $arr)
{
foreach (array_keys($this->converters) as $start) {
if ($arr[$start] == $arr[0])
return $this->converters[$start];
}
return NULL;
}
}
// Base class of inline elements
class Link
{
var $start; // Origin number of parentheses (0 origin)
var $text; // Matched string
var $type;
var $page;
var $name;
var $body;
var $alias;
// Constructor
function Link($start)
{
$this->start = $start;
}
// Return a regex pattern to match
function get_pattern() {}
// Return number of parentheses (except (?:...) )
function get_count() {}
// Set pattern that matches
function set($arr, $page) {}
function toString() {}
// Private: Get needed parts from a matched array()
function splice($arr)
{
$count = $this->get_count() + 1;
$arr = array_pad(array_splice($arr, $this->start, $count), $count, '');
$this->text = $arr[0];
return $arr;
}
// Set basic parameters
function setParam($page, $name, $body, $type = '', $alias = '')
{
static $converter = NULL;
$this->page = $page;
$this->name = $name;
$this->body = $body;
$this->type = $type;
if (! PKWK_DISABLE_INLINE_IMAGE_FROM_URI &&
is_url($alias) && preg_match('/\.(gif|png|jpe?g)$/i', $alias)) {
$alias = '';
} else if ($alias != '') {
if ($converter === NULL)
$converter = new InlineConverter(array('plugin'));
$alias = make_line_rules($converter->convert($alias, $page));
// BugTrack/669: A hack removing anchor tags added by AutoLink
$alias = preg_replace('#?a[^>]*>#i', '', $alias);
}
$this->alias = $alias;
return TRUE;
}
}
// Inline plugins
class Link_plugin extends Link
{
var $pattern;
var $plain,$param;
function Link_plugin($start)
{
parent::Link($start);
}
function get_pattern()
{
$this->pattern = <<pattern}
(?:
\{
((?:(?R)|(?!};).)*) # (4) body
\}
)?
;
EOD;
}
function get_count()
{
return 4;
}
function set($arr, $page)
{
list($all, $this->plain, $name, $this->param, $body) = $this->splice($arr);
// Re-get true plugin name and patameters (for PHP 4.1.2)
$matches = array();
if (preg_match('/^' . $this->pattern . '/x', $all, $matches)
&& $matches[1] != $this->plain)
list(, $this->plain, $name, $this->param) = $matches;
return parent::setParam($page, $name, $body, 'plugin');
}
function toString()
{
$body = ($this->body == '') ? '' : make_link($this->body);
$str = FALSE;
// Try to call the plugin
if (exist_plugin_inline($this->name))
$str = do_plugin_inline($this->name, $this->param, $body);
if ($str !== FALSE) {
return $str; // Succeed
} else {
// No such plugin, or Failed
$body = (($body == '') ? '' : '{' . $body . '}') . ';';
return make_line_rules(htmlsc('&' . $this->plain) . $body);
}
}
}
// Footnotes
class Link_note extends Link
{
function Link_note($start)
{
parent::Link($start);
}
function get_pattern()
{
return <<(?=\(\()(?R)|(?!\)\)).)*) # (1) note body
\)\)
EOD;
}
function get_count()
{
return 1;
}
function set($arr, $page)
{
global $foot_explain, $vars;
static $note_id = 0;
list(, $body) = $this->splice($arr);
if (PKWK_ALLOW_RELATIVE_FOOTNOTE_ANCHOR) {
$script = '';
} else {
$script = get_script_uri() . '?' . pagename_urlencode($page);
}
$id = ++$note_id;
$note = make_link($body);
$page = isset($vars['page']) ? rawurlencode($vars['page']) : '';
// Footnote
$foot_explain[$id] = '' . "\n" .
'' . $note . '
';
// A hyperlink, content-body to footnote
if (! is_numeric(PKWK_FOOTNOTE_TITLE_MAX) || PKWK_FOOTNOTE_TITLE_MAX <= 0) {
$title = '';
} else {
$title = strip_tags($note);
$count = mb_strlen($title, SOURCE_ENCODING);
$title = mb_substr($title, 0, PKWK_FOOTNOTE_TITLE_MAX, SOURCE_ENCODING);
$abbr = (mb_strlen($title) < $count) ? '...' : '';
$title = ' title="' . $title . $abbr . '"';
}
$name = '*' . $id . '';
return parent::setParam($page, $name, $body);
}
function toString()
{
return $this->name;
}
}
// URLs
class Link_url extends Link
{
function Link_url($start)
{
parent::Link($start);
}
function get_pattern()
{
$s1 = $this->start + 1;
return <<|:)
))?
( # (4) url
(?:(?:https?|ftp|news):\/\/|mailto:)[\w\/\@\$()!?&%#:;.,~'=*+-]+
)
(?($s1)\]\]) # close bracket
EOD;
}
function get_count()
{
return 4;
}
function set($arr, $page)
{
list(, , , $alias, $name) = $this->splice($arr);
return parent::setParam($page, htmlsc($name),
'', 'url', $alias == '' ? $name : $alias);
}
function toString()
{
if (FALSE) {
$rel = '';
} else {
$rel = ' rel="nofollow"';
}
return '' . $this->alias . '';
}
}
// URLs (InterWiki definition on "InterWikiName")
class Link_url_interwiki extends Link
{
function Link_url_interwiki($start)
{
parent::Link($start);
}
function get_pattern()
{
return <<splice($arr);
return parent::setParam($page, htmlsc($name), '', 'url', $alias);
}
function toString()
{
return '' . $this->alias . '';
}
}
// mailto: URL schemes
class Link_mailto extends Link
{
var $is_image, $image;
function Link_mailto($start)
{
parent::Link($start);
}
function get_pattern()
{
$s1 = $this->start + 1;
return <<|:) # (1) alias
)?
([\w.-]+@[\w-]+\.[\w.-]+) # (2) mailto
(?($s1)\]\]) # close bracket if (1)
EOD;
}
function get_count()
{
return 2;
}
function set($arr, $page)
{
list(, $alias, $name) = $this->splice($arr);
return parent::setParam($page, $name, '', 'mailto', $alias == '' ? $name : $alias);
}
function toString()
{
return '' . $this->alias . '';
}
}
// InterWikiName-rendered URLs
class Link_interwikiname extends Link
{
var $url = '';
var $param = '';
var $anchor = '';
function Link_interwikiname($start)
{
parent::Link($start);
}
function get_pattern()
{
$s2 = $this->start + 2;
$s5 = $this->start + 5;
return << # (1) alias
)?
(\[\[)? # (2) open bracket
((?:(?!\s|:|\]\]).)+) # (3) InterWiki
(? | >\[\[ ) # not '>' or '>[['
: # separator
( # (4) param
(\[\[)? # (5) open bracket
(?:(?!>|\]\]).)+
(?($s5)\]\]) # close bracket if (5)
)
(?($s2)\]\]) # close bracket if (2)
\]\] # close bracket
EOD;
}
function get_count()
{
return 5;
}
function set($arr, $page)
{
global $script;
list(, $alias, , $name, $this->param) = $this->splice($arr);
$matches = array();
if (preg_match('/^([^#]+)(#[A-Za-z][\w-]*)$/', $this->param, $matches))
list(, $this->param, $this->anchor) = $matches;
$url = get_interwiki_url($name, $this->param);
$this->url = ($url === FALSE) ?
$script . '?' . pagename_urlencode('[[' . $name . ':' . $this->param . ']]') :
htmlsc($url);
return parent::setParam(
$page,
htmlsc($name . ':' . $this->param),
'',
'InterWikiName',
$alias == '' ? $name . ':' . $this->param : $alias
);
}
function toString()
{
return '' . $this->alias . '';
}
}
// BracketNames
class Link_bracketname extends Link
{
var $anchor, $refer;
function Link_bracketname($start)
{
parent::Link($start);
}
function get_pattern()
{
global $WikiName, $BracketName;
$s2 = $this->start + 2;
return <<)? # (1) Alias
(\[\[)? # (2) Open bracket
( # (3) PageName
(?:$WikiName)
|
(?:$BracketName)
)?
(\#(?:[a-zA-Z][\w-]*)?)? # (4) Anchor
(?($s2)\]\]) # Close bracket if (2)
\]\] # Close bracket
EOD;
}
function get_count()
{
return 4;
}
function set($arr, $page)
{
global $WikiName;
list(, $alias, , $name, $this->anchor) = $this->splice($arr);
if ($name == '' && $this->anchor == '') return FALSE;
if ($name == '' || ! preg_match('/^' . $WikiName . '$/', $name)) {
if ($alias == '') $alias = $name . $this->anchor;
if ($name != '') {
$name = get_fullname($name, $page);
if (! is_pagename($name)) return FALSE;
}
}
return parent::setParam($page, $name, '', 'pagename', $alias);
}
function toString()
{
return make_pagelink(
$this->name,
$this->alias,
$this->anchor,
$this->page
);
}
}
// WikiNames
class Link_wikiname extends Link
{
function Link_wikiname($start)
{
parent::Link($start);
}
function get_pattern()
{
global $WikiName, $nowikiname;
return $nowikiname ? FALSE : '(' . $WikiName . ')';
}
function get_count()
{
return 1;
}
function set($arr, $page)
{
list($name) = $this->splice($arr);
return parent::setParam($page, $name, '', 'pagename', $name);
}
function toString()
{
return make_pagelink(
$this->name,
$this->alias,
'',
$this->page
);
}
}
// AutoLinks
class Link_autolink extends Link
{
var $forceignorepages = array();
var $auto;
var $auto_a; // alphabet only
function Link_autolink($start)
{
global $autolink;
parent::Link($start);
if (! $autolink || ! file_exists(CACHE_DIR . 'autolink.dat'))
return;
@list($auto, $auto_a, $forceignorepages) = file(CACHE_DIR . 'autolink.dat');
$this->auto = $auto;
$this->auto_a = $auto_a;
$this->forceignorepages = explode("\t", trim($forceignorepages));
}
function get_pattern()
{
return isset($this->auto) ? '(' . $this->auto . ')' : FALSE;
}
function get_count()
{
return 1;
}
function set($arr, $page)
{
global $WikiName;
list($name) = $this->splice($arr);
// Ignore pages listed, or Expire ones not found
if (in_array($name, $this->forceignorepages) || ! is_page($name))
return FALSE;
return parent::setParam($page, $name, '', 'pagename', $name);
}
function toString()
{
return make_pagelink($this->name, $this->alias, '', $this->page, TRUE);
}
}
class Link_autolink_a extends Link_autolink
{
function Link_autolink_a($start)
{
parent::Link_autolink($start);
}
function get_pattern()
{
return isset($this->auto_a) ? '(' . $this->auto_a . ')' : FALSE;
}
}
// Make hyperlink for the page
function make_pagelink($page, $alias = '', $anchor = '', $refer = '', $isautolink = FALSE)
{
global $script, $vars, $link_compact, $related, $_symbol_noexists;
$s_page = htmlsc(strip_bracket($page));
$s_alias = ($alias == '') ? $s_page : $alias;
if ($page == '') return '' . $s_alias . '';
$r_page = pagename_urlencode($page);
$r_refer = ($refer == '') ? '' : '&refer=' . rawurlencode($refer);
if (! isset($related[$page]) && $page !== $vars['page'] && is_page($page))
$related[$page] = get_filetime($page);
if ($isautolink || is_page($page)) {
// Hyperlink to the page
if ($link_compact) {
$title = '';
} else {
$title = ' title="' . $s_page . get_pg_passage($page, FALSE) . '"';
}
// AutoLink marker
if ($isautolink) {
$al_left = '';
$al_right = '';
} else {
$al_left = $al_right = '';
}
return $al_left . '' . $s_alias . '' . $al_right;
} else {
// Dangling link
if (PKWK_READONLY) return $s_alias; // No dacorations
$retval = $s_alias . '' .
$_symbol_noexists . '';
if ($link_compact) {
return $retval;
} else {
return '' . $retval . '';
}
}
}
// Resolve relative / (Unix-like)absolute path of the page
function get_fullname($name, $refer)
{
global $defaultpage;
// 'Here'
if ($name == '' || $name == './') return $refer;
// Absolute path
if ($name{0} == '/') {
$name = substr($name, 1);
return ($name == '') ? $defaultpage : $name;
}
// Relative path from 'Here'
if (substr($name, 0, 2) == './') {
$arrn = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
$arrn[0] = $refer;
return join('/', $arrn);
}
// Relative path from dirname()
if (substr($name, 0, 3) == '../') {
$arrn = preg_split('#/#', $name, -1, PREG_SPLIT_NO_EMPTY);
$arrp = preg_split('#/#', $refer, -1, PREG_SPLIT_NO_EMPTY);
while (! empty($arrn) && $arrn[0] == '..') {
array_shift($arrn);
array_pop($arrp);
}
$name = ! empty($arrp) ? join('/', array_merge($arrp, $arrn)) :
(! empty($arrn) ? $defaultpage . '/' . join('/', $arrn) : $defaultpage);
}
return $name;
}
// Render an InterWiki into a URL
function get_interwiki_url($name, $param)
{
global $WikiName, $interwiki;
static $interwikinames;
static $encode_aliases = array('sjis'=>'SJIS', 'euc'=>'EUC-JP', 'utf8'=>'UTF-8');
if (! isset($interwikinames)) {
$interwikinames = $matches = array();
foreach (get_source($interwiki) as $line)
if (preg_match('/\[(' . '(?:(?:https?|ftp|news):\/\/|\.\.?\/)' .
'[!~*\'();\/?:\@&=+\$,%#\w.-]*)\s([^\]]+)\]\s?([^\s]*)/',
$line, $matches))
$interwikinames[$matches[2]] = array($matches[1], $matches[3]);
}
if (! isset($interwikinames[$name])) return FALSE;
list($url, $opt) = $interwikinames[$name];
// Encoding
switch ($opt) {
case '': /* FALLTHROUGH */
case 'std': // Simply URL-encode the string, whose base encoding is the internal-encoding
$param = rawurlencode($param);
break;
case 'asis': /* FALLTHROUGH */
case 'raw' : // Truly as-is
break;
case 'yw': // YukiWiki
if (! preg_match('/' . $WikiName . '/', $param))
$param = '[[' . mb_convert_encoding($param, 'SJIS', SOURCE_ENCODING) . ']]';
break;
case 'moin': // MoinMoin
$param = str_replace('%', '_', rawurlencode($param));
break;
default:
// Alias conversion of $opt
if (isset($encode_aliases[$opt])) $opt = & $encode_aliases[$opt];
// Encoding conversion into specified encode, and URLencode
if (strpos($url, '$1') === FALSE && substr($url, -1) === '?') {
// PukiWiki site
$param = pagename_urlencode(mb_convert_encoding($param, $opt, SOURCE_ENCODING));
} else {
$param = rawurlencode(mb_convert_encoding($param, $opt, SOURCE_ENCODING));
}
}
// Replace or Add the parameter
if (strpos($url, '$1') !== FALSE) {
$url = str_replace('$1', $param, $url);
} else {
$url .= $param;
}
$len = strlen($url);
if ($len > 512) die_message('InterWiki URL too long: ' . $len . ' characters');
return $url;
}