. */ // setup bbCode formatter bbCode::$ig = RYZOM_IG; /** * Image proxy */ if(!defined('IMG_PROXY')){ $url = 'http://'.$_SERVER['HTTP_HOST'].'/app_forum/tools/imageproxy.php'; define('IMG_PROXY', $url); } if (!function_exists('proxy_image_url')) { function proxy_image_url($href, $attrs=''){ return IMG_PROXY.'?'.($attrs != '' ? $attrs.'&' : '').'url='.urlencode($href); } } abstract class bbCodeParser { /** * @var bool */ private $_ig; /** * @var array */ private $tags_ignore; private $tags_block_open; private $tags_block_close; private $tags_ignore_depth; /** * @var array */ private $open_tags; /** * @var string */ private $last_closed_tag; /** * @var int */ private $current_tag; /** * @var array */ private $state; /** * @param bool $ig if true, use ingame markup */ function __construct($ig) { $this->_ig = $ig; // ignore bbcode between these tags $this->tags_ignore = array( 'noparse', 'code', 'url', 'img', 'mail', 'page', 'forum', 'topic', 'post', 'wiki', 'time', 'date' ); // these create block level html code, so '\n' or ' ' or '\t' around them needs to be cleared $this->tags_block_open = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'quote', 'list', 'p'); $this->tags_block_close = $this->tags_block_open; if ($this->_ig) { // ingame

is not block level when closing, so dont strip there $key = array_search('p', $this->tags_block_close, true); unset($this->tags_block_close[$key]); } $this->state = array(); // reset internals $this->reset(); } /** * Format bbcode tag * * @param string $tag tag name * @param string $open open markup * @param string $close close markup * @param string $attr tag attributes * @param string $text text between tags */ abstract function format($tag, $open, $close, $attr, $text); /** * Wrapper to call Child->format(...) * * @param array $tag assoc array with tag info * @return string */ function handle_tag($tag) { return $this->format($tag['tag'], $tag['open'], $tag['close'], $tag['attr'], $tag['text']); } /** * Reset internals */ function reset() { $this->current_tag = 0; $this->tags_ignore_depth = 0; // 0'th position is used as result $this->open_tags = array( 0 => array('tag' => '', 'open' => '', 'close' => '', 'attr' => '', 'text' => '') ); $this->last_closed_tag = false; } /** * Save working state */ private function _state_save() { $this->state[] = array($this->current_tag, $this->tags_ignore_depth, $this->open_tags, $this->last_closed_tag); $this->reset(); } /** * Restore working state */ private function _state_restore() { if (!empty($this->state)) { list($this->current_tag, $this->tags_ignore_depth, $this->open_tags, $this->last_closed_tag) = array_pop($this->state); } } /** * Main worker. Parse $text for bbCode tags * * NOTE: Text must already be safe for HTML, ie. treated with htmlspecialchars() * * @param string $text * @return string formatted string */ function bbcode($text) { $text = str_replace("\r\n", "\n", $text); $split = preg_split('/(\[[a-zA-Z0-9_\/]*?(?:[= ].*?)?\])/', $text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($split as $chunk) { if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') { if (substr($chunk, 0, 2) == '[/') { $this->close($chunk); } else { $this->open($chunk); } } else { $this->text($chunk); } } return $this->result(); } /** * Push tag with args to stack * Do not strip whitespace because tag might be invalid * * @param string $chunk full tag string, eg. [tag=attr] */ function open($chunk) { list($tag, $attr) = $this->split_params($chunk); // test for [noparse] if ($this->tags_ignore_depth > 0) { $this->text($chunk); } else { $this->current_tag++; // remember tag, attributes and complete string that was used in markup $this->open_tags[$this->current_tag] = array('tag' => $tag, 'attr' => $attr, 'open' => $chunk, 'close' => '', 'text' => ''); } if (in_array($tag, $this->tags_ignore)) { $this->tags_ignore_depth++; } } /** * Close tag and call tag handler to format output * * @param $chunk full tag string, eg. [/tag] */ function close($chunk) { // extract tag name from [/name] $tag = strtolower(substr($chunk, 2, -1)); if ($this->tags_ignore_depth > 0 && in_array($tag, $this->tags_ignore)) { $this->tags_ignore_depth--; } // stack underrun if ($this->current_tag < 0) { $this->text($chunk); return; } // ignore block if ($this->tags_ignore_depth > 0) { $this->text($chunk); return; } // tag mismatch if ($this->open_tags[$this->current_tag]['tag'] !== $tag) { // try to find first open tag for this $key = false; for ($i = $this->current_tag - 1; $i > 0; $i--) { if (isset($this->open_tags[$i]['tag']) && $this->open_tags[$i]['tag'] === $tag) { $key = $i; break; } } if ($key === false) { $this->text($chunk); return; } // tag is open so we need to 'rewind' a bit for ($i = $this->current_tag; $i > $key; $i--) { $tmp_tag = $this->pop_stack(); $this->text($tmp_tag['open'] . $tmp_tag['text']); } } // close tag $open = $this->pop_stack(); // handle bbcode $open['close'] = $chunk; $block_level = false; if (in_array($tag, $this->tags_block_open)) { $block_level = true; // for block level element, trim whitespace from inside tag // [tag]...text...[/tag] $open['text'] = $this->trim_ws($open['text']); } $result = $this->handle_tag($open); // strip whitespace from text before tag 'text...[tag]' if ($block_level) { $ts = $this->rtrim_ws($this->open_tags[$this->current_tag]['text']); $this->open_tags[$this->current_tag]['text'] = $ts; } $this->text($result); $this->last_closed_tag = $open['tag']; } function text($text) { // strip whitespace after closing '[/tag]...text' if (in_array($this->last_closed_tag, $this->tags_block_close)) { $text = $this->ltrim_ws($text); } $this->open_tags[$this->current_tag]['text'] .= $text; $this->last_closed_tag = false; } function result() { // close tags that are still open while ($this->current_tag > 0) { $open = $this->pop_stack(); if ($this->tags_ignore_depth > 0) { $this->tags_ignore_depth--; // need to reparse text that's after ignore tag $this->_state_save(); $text = $open['open'] . $this->bbcode($open['text']); $this->_state_restore(); } else { // tag was not closed proprely, include start tag with result $text = $open['open'] . $open['text']; } $this->text($text); }; return $this->open_tags[0]['text']; } /** * Pop tag and text from stack and return them * * @return array [0] = tag, [1] = text */ function pop_stack() { // remove from stack $open = $this->open_tags[$this->current_tag]; unset($this->open_tags[$this->current_tag]); $this->current_tag--; return $open; } /** * Trim from end of string * 'text...\s{0,}\n{1}\s{0,}' * * @param string $ts * @return string */ function rtrim_ws($ts){ // we want to get rid of all spaces/tabs, but only single \n, so rtrim($ts, " \t\n\r") would not work $ts = rtrim($ts, " \t"); if (substr($ts, -1, 1) === "\n") { $ts = substr($ts, 0, -1); $ts = rtrim($ts, " \t"); } return $ts; } /** * Trim from start of string * '\s{0,}\n{1}...text' * * @param string $ts * @return string */ function ltrim_ws($ts){ // we want to get rid of all spaces/tabs, but only single \n, so ltrim($ts, " \t\n\r") would not work $ts = ltrim($ts, " \t"); if (substr($ts, 0, 1) === "\n") { $ts = substr($ts, 1); } return $ts; } /** * Trim from both sides * '\s{0,}\n{1}...text...\s{0,}\n{1}\s{0,} * * @param string $ts * @return string */ function trim_ws($ts){ $ts = $this->ltrim_ws($ts); $ts = $this->rtrim_ws($ts); return $ts; } /** * Extract tag parameters from [tag=params] or [tag key1=val1 key2=val2] * * @param type $tag * @return type */ function split_params($chunk) { if (substr($chunk, 0, 1) == '[') { $b = '\['; $e = '\]'; } else { $b = ''; $e = ''; } // [1] [2] [3] if (preg_match('/^' . $b . '([\*a-zA-Z0-9]*?)' . '(=| )' . '(.*?)' . $e . '$/', $chunk, $match)) { $tagName = strtolower($match[1]); if ($match[2] == '=') { // = means single parameter $tagParam = $match[3]; } else { // means multiple parameters $tagParam = array(); $args = preg_split('/[ ]/', $match[3], null, PREG_SPLIT_NO_EMPTY); foreach ($args as $arg) { $pairs = explode('=', $arg); // preg_replace will remove possible quotes around value if (isset($pairs[1])) { $tagParam[strtolower($pairs[0])] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[1]); } else { $tagParam[] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[0]); } } } } else { if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') { $chunk = substr($chunk, 1, -1); } $tagName = strtolower($chunk); $tagParam = ''; } return array($tagName, $tagParam); } } class bbCode extends bbCodeParser { static $legacy_sync = 1348956841; static $legacy_shard = array( 'ani' => 2363920179, 'lea' => 2437578274, 'ari' => 2358620001, ); static $ig = false; static $timezone = 'UTC'; static $clock12h = false; static $shardid = false; static $lang = 'en'; static $disabledTags = array(); // const COLOR_P = '#d0d0d0'; // normal text // const COLOR_BBCODE_TAG = '#444444'; static function bbDisabled($tag) { return in_array(strtolower($tag), self::$disabledTags); } static function getFontSize($value) { $size = 16; switch (strtolower($value)) { case '1': case 'xx-small': $size = 9; break; case '2': case 'x-small' : $size = 10; break; case '3': case 'small' : $size = 13; break; case '4': case 'medium' : $size = 16; break; case '5': case 'large' : $size = 18; break; case '6': case 'x-large' : $size = 24; break; case '7': case 'xx-large': $size = 32; break; //case '8': case 'smaller' : break; //case '9': case 'larger' : break; } return $size; } static function bb_noparse($code) { return preg_replace(array('/\[/', '/\]/'), array('[', ']'), $code); } static function bb_code($code) { return '

' . self::bb_noparse($code) . '
'; } static function bb_list($list) { $result = ''; $list = str_replace("\n[", '[', $list); $result = ''; return preg_replace('#