3 /* Copyright (C) 2009 Winch Gate Property Limited
5 * This file is part of ryzom_api.
6 * ryzom_api is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * ryzom_api is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with ryzom_api. If not, see <http://www.gnu.org/licenses/>.
20 // setup bbCode formatter
22 bbCode
::$ig = RYZOM_IG
;
27 if(!defined('IMG_PROXY')){
28 $url = 'http://'.$_SERVER['HTTP_HOST'].'/app_forum/tools/imageproxy.php';
29 define('IMG_PROXY', $url);
31 if (!function_exists('proxy_image_url')) {
32 function proxy_image_url($href, $attrs=''){
33 return IMG_PROXY
.'?'.($attrs != '' ?
$attrs.'&' : '').'url='.urlencode($href);
38 abstract class bbCodeParser
{
49 private $tags_block_open;
50 private $tags_block_close;
51 private $tags_ignore_depth;
61 private $last_closed_tag;
74 * @param bool $ig if true, use ingame markup
76 function __construct($ig) {
79 // ignore bbcode between these tags
80 $this->tags_ignore
= array(
82 'url', 'img', 'mail', 'page', 'forum', 'topic', 'post', 'wiki', 'time', 'date'
85 // these create block level html code, so '\n' or ' ' or '\t' around them needs to be cleared
86 $this->tags_block_open
= array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'quote', 'list', 'p');
87 $this->tags_block_close
= $this->tags_block_open
;
89 // ingame <p> is not block level when closing, so dont strip there
90 $key = array_search('p', $this->tags_block_close
, true);
91 unset($this->tags_block_close
[$key]);
94 $this->state
= array();
103 * @param string $tag tag name
104 * @param string $open open markup
105 * @param string $close close markup
106 * @param string $attr tag attributes
107 * @param string $text text between tags
109 abstract function format($tag, $open, $close, $attr, $text);
112 * Wrapper to call Child->format(...)
114 * @param array $tag assoc array with tag info
117 function handle_tag($tag) {
118 return $this->format($tag['tag'], $tag['open'], $tag['close'], $tag['attr'], $tag['text']);
125 $this->current_tag
= 0;
126 $this->tags_ignore_depth
= 0;
128 // 0'th position is used as result
129 $this->open_tags
= array(
130 0 => array('tag' => '', 'open' => '', 'close' => '', 'attr' => '', 'text' => '')
133 $this->last_closed_tag
= false;
139 private function _state_save() {
140 $this->state
[] = array($this->current_tag
, $this->tags_ignore_depth
, $this->open_tags
, $this->last_closed_tag
);
145 * Restore working state
147 private function _state_restore() {
148 if (!empty($this->state
)) {
149 list($this->current_tag
, $this->tags_ignore_depth
, $this->open_tags
, $this->last_closed_tag
) = array_pop($this->state
);
154 * Main worker. Parse $text for bbCode tags
156 * NOTE: Text must already be safe for HTML, ie. treated with htmlspecialchars()
158 * @param string $text
159 * @return string formatted string
161 function bbcode($text) {
162 $text = str_replace("\r\n", "\n", $text);
164 $split = preg_split('/(\[[a-zA-Z0-9_\/]*?(?:[= ].*?)?\])/', $text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
166 foreach ($split as $chunk) {
167 if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') {
168 if (substr($chunk, 0, 2) == '[/') {
169 $this->close($chunk);
178 return $this->result();
182 * Push tag with args to stack
183 * Do not strip whitespace because tag might be invalid
185 * @param string $chunk full tag string, eg. [tag=attr]
187 function open($chunk) {
188 list($tag, $attr) = $this->split_params($chunk);
190 // test for [noparse]
191 if ($this->tags_ignore_depth
> 0) {
194 $this->current_tag++
;
195 // remember tag, attributes and complete string that was used in markup
196 $this->open_tags
[$this->current_tag
] = array('tag' => $tag, 'attr' => $attr, 'open' => $chunk, 'close' => '', 'text' => '');
199 if (in_array($tag, $this->tags_ignore
)) {
200 $this->tags_ignore_depth++
;
205 * Close tag and call tag handler to format output
207 * @param $chunk full tag string, eg. [/tag]
209 function close($chunk) {
210 // extract tag name from [/name]
211 $tag = strtolower(substr($chunk, 2, -1));
213 if ($this->tags_ignore_depth
> 0 && in_array($tag, $this->tags_ignore
)) {
214 $this->tags_ignore_depth
--;
218 if ($this->current_tag
< 0) {
224 if ($this->tags_ignore_depth
> 0) {
230 if ($this->open_tags
[$this->current_tag
]['tag'] !== $tag) {
231 // try to find first open tag for this
233 for ($i = $this->current_tag
- 1; $i > 0; $i--) {
234 if (isset($this->open_tags
[$i]['tag']) && $this->open_tags
[$i]['tag'] === $tag) {
239 if ($key === false) {
244 // tag is open so we need to 'rewind' a bit
245 for ($i = $this->current_tag
; $i > $key; $i--) {
246 $tmp_tag = $this->pop_stack();
247 $this->text($tmp_tag['open'] . $tmp_tag['text']);
252 $open = $this->pop_stack();
255 $open['close'] = $chunk;
257 $block_level = false;
258 if (in_array($tag, $this->tags_block_open
)) {
260 // for block level element, trim whitespace from inside tag
261 // [tag]<ws>...text...<ws>[/tag]
262 $open['text'] = $this->trim_ws($open['text']);
264 $result = $this->handle_tag($open);
266 // strip whitespace from text before tag 'text...<ws>[tag]'
268 $ts = $this->rtrim_ws($this->open_tags
[$this->current_tag
]['text']);
269 $this->open_tags
[$this->current_tag
]['text'] = $ts;
272 $this->text($result);
274 $this->last_closed_tag
= $open['tag'];
277 function text($text) {
278 // strip whitespace after closing '[/tag]<ws>...text'
279 if (in_array($this->last_closed_tag
, $this->tags_block_close
)) {
280 $text = $this->ltrim_ws($text);
282 $this->open_tags
[$this->current_tag
]['text'] .= $text;
284 $this->last_closed_tag
= false;
288 // close tags that are still open
289 while ($this->current_tag
> 0) {
290 $open = $this->pop_stack();
292 if ($this->tags_ignore_depth
> 0) {
293 $this->tags_ignore_depth
--;
294 // need to reparse text that's after ignore tag
295 $this->_state_save();
296 $text = $open['open'] . $this->bbcode($open['text']);
297 $this->_state_restore();
299 // tag was not closed proprely, include start tag with result
300 $text = $open['open'] . $open['text'];
306 return $this->open_tags
[0]['text'];
310 * Pop tag and text from stack and return them
312 * @return array [0] = tag, [1] = text
314 function pop_stack() {
316 $open = $this->open_tags
[$this->current_tag
];
317 unset($this->open_tags
[$this->current_tag
]);
318 $this->current_tag
--;
324 * Trim from end of string
325 * 'text...\s{0,}\n{1}\s{0,}'
330 function rtrim_ws($ts){
331 // we want to get rid of all spaces/tabs, but only single \n, so rtrim($ts, " \t\n\r") would not work
332 $ts = rtrim($ts, " \t");
333 if (substr($ts, -1, 1) === "\n") {
334 $ts = substr($ts, 0, -1);
335 $ts = rtrim($ts, " \t");
341 * Trim from start of string
342 * '\s{0,}\n{1}...text'
347 function ltrim_ws($ts){
348 // we want to get rid of all spaces/tabs, but only single \n, so ltrim($ts, " \t\n\r") would not work
349 $ts = ltrim($ts, " \t");
350 if (substr($ts, 0, 1) === "\n") {
351 $ts = substr($ts, 1);
357 * Trim from both sides
358 * '\s{0,}\n{1}...text...\s{0,}\n{1}\s{0,}
363 function trim_ws($ts){
364 $ts = $this->ltrim_ws($ts);
365 $ts = $this->rtrim_ws($ts);
370 * Extract tag parameters from [tag=params] or [tag key1=val1 key2=val2]
375 function split_params($chunk) {
376 if (substr($chunk, 0, 1) == '[') {
384 if (preg_match('/^' . $b . '([\*a-zA-Z0-9]*?)' . '(=| )' . '(.*?)' . $e . '$/', $chunk, $match)) {
385 $tagName = strtolower($match[1]);
386 if ($match[2] == '=') {
387 // = means single parameter
388 $tagParam = $match[3];
390 // <space> means multiple parameters
392 $args = preg_split('/[ ]/', $match[3], null, PREG_SPLIT_NO_EMPTY
);
393 foreach ($args as $arg) {
394 $pairs = explode('=', $arg);
395 // preg_replace will remove possible quotes around value
396 if (isset($pairs[1])) {
397 $tagParam[strtolower($pairs[0])] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[1]);
399 $tagParam[] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[0]);
404 if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') {
405 $chunk = substr($chunk, 1, -1);
407 $tagName = strtolower($chunk);
410 return array($tagName, $tagParam);
415 class bbCode
extends bbCodeParser
{
416 static $legacy_sync = 1348956841;
417 static $legacy_shard = array(
424 static $timezone = 'UTC';
425 static $clock12h = false;
426 static $shardid = false;
428 static $disabledTags = array();
430 const COLOR_P
= '#d0d0d0'; // normal text
432 const COLOR_BBCODE_TAG
= '#444444';
434 static function bbDisabled($tag) {
435 return in_array(strtolower($tag), self
::$disabledTags);
438 static function getFontSize($value) {
440 switch (strtolower($value)) {
441 case '1': case 'xx-small': $size = 9; break;
442 case '2': case 'x-small' : $size = 10; break;
443 case '3': case 'small' : $size = 13; break;
444 case '4': case 'medium' : $size = 16; break;
445 case '5': case 'large' : $size = 18; break;
446 case '6': case 'x-large' : $size = 24; break;
447 case '7': case 'xx-large': $size = 32; break;
448 //case '8': case 'smaller' : break;
449 //case '9': case 'larger' : break;
454 static function bb_noparse($code) {
455 return preg_replace(array('/\[/', '/\]/'), array('[', ']'), $code);
458 static function bb_code($code) {
459 return '<pre>' . self
::bb_noparse($code) . '</pre>';
462 static function bb_list($list) {
464 $list = str_replace("\n[", '[', $list);
465 $result = '<ul>' . preg_replace('/\s*\[\*\]\s*/is', "</li><li>", $list) . '</li></ul>';
466 return preg_replace('#<ul>\s*</li>#is', '<ul>', $result);
469 static function bb_quote($author, $text) {
471 // prevents [color] tag to take over color
472 $author = self
::bb_color(self
::COLOR_P
, $author);
473 $text = self
::bb_color(self
::COLOR_P
, $text);
474 // left/right border, top/bottom border height
475 $l = '<td width="1" bgcolor="#888888" height="1"></td>';
476 $r = '<td width="1" bgcolor="#888888" height="1"></td>';
477 return // 98% gives bit padding on the right
478 '<table width="98%" cellpadding="0" cellspacing="0" border="0">' .
479 '<tr><td width="1" height="5"></td><td></td><td></td></tr>' . // top padding - no border
480 '<tr>' . $l . '<td bgcolor="#888888"></td>' . $r . '</tr>' . // top border
481 '<tr>' . $l . '<td bgcolor="#000000" l_margin="5" height="3"></td>' . $r . '</tr>' . // author top padding
482 '<tr>' . $l . '<td bgcolor="#000000" l_margin="5">' . $author . '</td>' . $r . '</tr>' . // author
483 '<tr>' . $l . '<td bgcolor="#000000" l_margin="5" height="2"></td>' . $r . '</tr>' . // author bottom padding
484 '<tr>' . $l . '<td bgcolor="#555555" l_margin="10" height="3"></td>' . $r . '</tr>' . // quote top padding
485 '<tr>' . $l . '<td bgcolor="#555555" l_margin="10">' . $text . '</td>' . $r . '</tr>' . // quote
486 '<tr>' . $l . '<td bgcolor="#555555" l_margin="10" height="2"></td>' . $r . '</tr>' . // quote bottom padding
487 '<tr>' . $l . '<td bgcolor="#888888"></td>' . $r . '</tr>' . // bottom border
488 '<tr><td width="1" height="8"></td><td></td><td></td></tr>' . // bottom padding - no border
492 '<div class="post-quote">' .
493 '<cite>' . $author . '</cite>' .
494 '<blockquote>' . $text . '</blockquote>' .
499 static function bb_h($nr, $color, $text) {
504 $text = '<font color="' . $color . '">' . $text . '</font>';
506 return '<' . $tag . '>' . $text . '</' . $tag . '>';
509 $style = ' style="color: ' . $color . ';"';
513 return '<' . $tag . $style . '>' . $text . '</' . $tag . '>';
517 static function bb_url($href, $text) {
518 // "http://..../" remove " if present
519 if (substr($href, 0, 6) == '"') {
520 if (substr($href, -6) == '"') {
521 $href = substr($href, 6, -6);
523 $href = substr($href, 6);
531 $text = wordwrap($text, 65, ' ', true);
534 $disable = self
::bbDisabled('url');
535 // if not disabled and in ryzom and is proper url (<scheme>://<host>/)
536 if (!$disable && self
::$ig) {
537 $url = @parse_url
(strtolower($href));
539 if (!empty($url['scheme']) && !empty($url['host'])) {
540 if (in_array($url['scheme'], array('http', 'https'))) {
541 if (in_array($url['host'], array('app.ryzom.com'))) {
542 if (empty($url['query']) ||
stripos($url['query'], 'redirect') === false) {
543 // http://atys.ryzom.com/
544 // and does not contain redirect
545 // - allow url in game browser
554 // empty href will give proper link color without 'underline' - perfect for 'disabled' links
556 $text = '<a href="">' . $text . '</a>';
558 $href = wordwrap($href, 65, ' ', true);
559 $text = wordwrap($text, 65, ' ', true);
560 $text = '<a href="">' . $text . '</a> ' . self
::bb_color(self
::COLOR_BBCODE_TAG
, '(' . $href . ')');
565 // make sure http:// (or ftp:// or mailto:// etc is present), if not, add it
566 if (!preg_match('#://#', $href)) {
567 $href = 'http://' . $href;
570 return sprintf('<a href="%s"' . (self
::$ig ?
'' : ' target="_blank"') . '>%s</a>', $href, $text);
573 static function bb_img($attr, $href) {
574 if (self
::bbDisabled('img')) {
575 return self
::bb_noparse('[img]' . $href . '[/img]');
577 // $href is treated with htmlspecialchars() so any & in url is &
578 $href = str_replace('&', '&', $href);
580 // images from current server directly
581 if ($attr=='src' ||
strstr($href, $_SERVER['HTTP_HOST']) !== false){
582 return '<img src="' . $href . '" />';
584 $url = proxy_image_url($href);
585 return '<a href="' . $url . '&no_proxy=1"><img src="' . $url . '" /></a>';
588 static function bb_banner($lang, $ckey) {
589 // $lang and $ckey should already be escaped for HTML, so urlencode() in here would double escape them
590 // - channel it thru image proxy. proxy does caching better and uses '304 Not Modified' status
591 $src = 'http://atys.ryzom.com/api/banner.php?ckey=' . $ckey . '&langid=' . $lang . '&size=500';
592 return self
::bb_img('', $src);
595 static function bb_mail($user) {
596 $url = 'http://' . $_SERVER['HTTP_HOST'] . '/app_mail/?page=compose/to/' . urlencode($user);
597 return '<a href="' . $url . '">' . $user . '</a>';
600 static function bb_profile($ptype, $pname) {
601 // types from app_profile
602 $types = array('user', 'player', 'npc', 'fauna', 'entity', 'source');
603 $ptype = array_search($ptype, $types, true);
604 // if type not found, then fall back to player
605 if ($ptype === false)
608 $url = 'http://' . $_SERVER['HTTP_HOST'] . '/app_profile/?ptype=' . intval($ptype) . '&pname=' . urlencode($pname);
609 return '<a href="' . $url . '">' . $pname . '</a>';
612 static function bb_color($color, $str) {
618 return '<font color="' . $color . '">' . $str . '</font>';
620 return '<span style="color: ' . $color . ';">' . $str . '</span>';
624 static function bb_size($size, $str) {
625 $size = self
::getFontSize($size);
628 return '<font size="' . $size . 'px">' . $str . '</font>';
630 return '<span style="font-size: ' . $size . 'px;">' . $str . '</span>';
634 static function bb_pre($str) {
635 return '<pre>' . $str . '</pre>';
638 static function bb_p($str) {
639 return '<p>' . $str . '</p>';
642 // Added by ulukyn. WebIg compatibility.
643 static function bb_center($str) {
645 return '<table width="100%" cellpadding="0" cellspacing="0"><tr><td align="center" valign="middle">' . $str . '</td></tr></table>';
647 return '<div style="text-align: center;">' . $str . '</div>';
651 /** Table format : (added by ulukyn)
656 static function bb_table($attr, $content) {
657 $width = isset($attr['width'])?
$attr['width']:'100%';
658 $border = isset($attr['border'])?
$attr['border']:'0';
659 $bgcolor = isset($attr['bgcolor'])?
' bgcolor="'.$attr['bgcolor'].'" ':'';
660 $ret = '<table width="'.$width.'" border="'.$border.'" cellpadding="0" cellspacing="0" '.$bgcolor.' >';
661 $lines = explode("\n", $content);
662 foreach ($lines as $line) {
665 $cols = explode('|', $line);
666 foreach ($cols as $text) {
669 $params = array('valign' => 'middle');
670 if ($text[0] == '#') {
671 $paramsdef = explode(' ', $text);
672 $paramlist = substr(array_shift($paramsdef), 1);
673 $paramlist = explode(',', $paramlist);
674 foreach ($paramlist as $p) {
675 list($name, $value) = explode('=', $p);
676 $params[ _h(str_replace('"', '', $name))] = _h(str_replace('"', '', $value));
679 $text = implode(' ', $paramsdef);
682 foreach ($params as $name => $value)
683 $param_html .= $name.'="'.$value.'" ';
685 if ($text && $text[0] == ' ' && $text[strlen($text)-1] == ' ')
687 else if ($text && $text[0] == ' ')
692 $ret .= '<td '.$param_html.' align="'.$align.'">'.$text.'</td>';
703 static function bb_page_link($page, $txt) {
707 $tmp = explode('/', $page);
708 foreach ($tmp as $k => $v) {
709 $tmp[$k] = urlencode($v);
711 $url = 'http://' . $_SERVER['HTTP_HOST'] . '/app_forum/?page=' . join('/', $tmp);
712 return '<a href="' . $url . '">' . $txt . '</a>';
715 static function bb_forum_link($page, $id, $txt) {
716 $page = $page . '/view/' . $id;
720 return self
::bb_page_link($page, $txt);
724 static function bb_wiki_link($page, $txt) {
725 $need_new_txt = false;
728 $need_new_txt = true;
731 if (substr($page, 0, 22) == 'http://atys.ryzom.com/')
732 $url = 'http://atys.ryzom.com/start/app_wiki.php?page=' . substr($page, 21);
734 $tmp = explode('/', $page);
735 if (count($tmp) != 2) {
736 return 'Syntax: [wiki]/[page], ex: en/Chronicles';
742 $url = 'http://atys.ryzom.com/start/app_wiki.php?page=/projects/pub' . $wiki . '/wiki/' . $page;
745 $url = 'http://atys.ryzom.com/projects/pub' . $wiki . '/wiki/' . $page;
747 $txt = 'WIKI [' . $page . ']';
749 return '<a href="' . $url . '"' . (self
::$ig ?
'' : ' target="_blank"') . '>' . $txt . '</a>';
752 static function bb_biu($tag, $txt) {
753 $tag = strtolower($tag);
756 // FIXME: darken/lighter or tint current color
757 case 'b': $txt = self
::bb_color('white', $txt);
759 case 'i': $txt = self
::bb_color('#ffffd0', $txt);
761 case 'u': $txt = '<a href="ah:">' . self
::bb_color(self
::COLOR_P
, $txt) . '</a>';
763 default : $txt = self
::bb_color(self
::COLOR_BBCODE_TAG
, $txt);
770 case 'b': $tag = 'strong';
772 case 'i': $tag = 'em';
774 case 'u': $tag = 'u';
776 default: $tag = 'span'; // fallback
778 return '<' . $tag . '>' . $txt . '</' . $tag . '>';
781 static function bb_date($attr, $txt) {
782 $time = strtotime($txt);
784 $shardid = isset($attr['shard']) ?
$attr['shard'] : self
::$shardid;
785 if ($time === false ||
$shardid === false)
786 return 'ERR:[' . $txt . ']';
788 if (isset(self
::$legacy_shard[$shardid])) {
789 $tick = self
::$legacy_shard[$shardid];
790 if (self
::$legacy_sync > $time) {
791 // only modify game cycle when asked time is before sync
792 $tick = ($time - self
::$legacy_sync) * 10 +
$tick;
795 $tick = ryzom_time_tick($shardid);
796 // tick is for NOW, adjust it to match time given
798 $tick = ($time - $now) * 10 +
$tick;
801 $rytime = ryzom_time_array($tick, $shardid);
802 $txt = ryzom_time_txt($rytime, self
::$lang);
807 static function bb_lang($attr, $txt) {
808 if (_user()->lang
== $attr)
814 static function bb_time($options, $txt) {
815 $time = strtotime($txt);
821 $timezone = self
::$timezone;
827 if (is_array($options)) {
828 foreach ($options as $key => $val) {
831 // fix some timezones for php
833 case 'pst': // fall thru
834 case 'pdt': $val = 'US/Pacific';
840 $show_date = $val == 'off' ?
false : $val;
843 $show_time = $val == 'off' ?
false : $val;
846 $show_timer = $val == 'off' ?
false : $val;
854 $old_timezone = date_default_timezone_get();
855 @date_default_timezone_set
($timezone);
856 if ($show_date !== false) {
857 $date = ryzom_absolute_time($time);
858 //ryzom_absolute_time does not have year, so we need to add it
859 $current_y = date('Y', time());
860 $y = date('Y', $time);
861 if ($y != $current_y) {
864 $ret[] = self
::bb_color($show_date, $date);
866 if ($show_time !== false) {
867 $fmtTime = self
::$clock12h ?
'g:i:s a T' : 'H:i:s T';
868 $ret[] = self
::bb_color($show_time, date($fmtTime, $time));
870 date_default_timezone_set($old_timezone);
872 if ($show_timer !== false) {
873 if ($show_time === false && $show_date === false) {
878 $ret[] = self
::bb_color($show_timer, sprintf($f, ryzom_relative_time($time)));
881 return join(' ', $ret);
885 * This function is called by bbCodeParser class
887 * @see bbCodeParser::format
889 public function format($tag, $open, $close, $attr, $text) {
890 // silly functions all have different parameters
893 $result = self
::bb_noparse($text);
896 $result = self
::bb_code($text);
899 $result = self
::bb_quote($attr, $text);
901 case 'h1' : // fall thru
902 case 'h2' : // fall thru
903 case 'h3' : // fall thru
904 case 'h4' : // fall thru
905 case 'h5' : // fall thru
907 $nr = (int) substr($tag, -1);
908 $color = isset($attr['color']) ?
$attr['color'] : '';
909 $result = self
::bb_h($nr, $color, $text);
912 $result = self
::bb_color($attr, $text);
915 $result = self
::bb_size($attr, $text);
918 $result = self
::bb_list($text);
921 $result = self
::bb_img($attr, $text);
924 $result = self
::bb_banner($attr, $text);
927 $result = self
::bb_pre($text);
930 $result = self
::bb_p($text);
933 $result = self
::bb_table($attr, $text);
936 $result = self
::bb_center($text);
939 $result = self
::bb_url($attr, $text);
942 $result = self
::bb_mail($text);
945 $result = self
::bb_profile($attr, $text);
948 $result = self
::bb_page_link($attr, $text);
950 case 'forum' : // fall thru
951 case 'topic' : // fall thru
953 $result = self
::bb_forum_link($tag, $attr, $text);
956 $result = self
::bb_wiki_link($attr, $text);
958 case 'b' : // fall thru
959 case 'i' : // fall thru
961 $result = self
::bb_biu($tag, $text);
964 $result = self
::bb_time($attr, $text);
967 $result = self
::bb_date($attr, $text);
970 $result = self
::bb_lang($attr, $text);
973 $result = $open . $text . $close;
980 * Replaces some BBcode with HTML code
982 * NOTE: $text should be already escaped for HTML
984 * @param string $text html escaped input text
985 * @param array $disabledTags
987 static function parse($text, $disabledTags = array()) {
988 static $parser = null;
989 if ($parser === null) {
990 $parser = new self(self
::$ig);
994 self
::$disabledTags = $disabledTags;
995 return $parser->bbcode($text);