Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / web / public_php / api / common / bbCode.php
blobb1597d5b5039528085c211b3b77af6fa07e69fe3
1 <?php
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;
24 /**
25 * Image proxy
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 {
40 /**
41 * @var bool
43 private $_ig;
45 /**
46 * @var array
48 private $tags_ignore;
49 private $tags_block_open;
50 private $tags_block_close;
51 private $tags_ignore_depth;
53 /**
54 * @var array
56 private $open_tags;
58 /**
59 * @var string
61 private $last_closed_tag;
63 /**
64 * @var int
66 private $current_tag;
68 /**
69 * @var array
71 private $state;
73 /**
74 * @param bool $ig if true, use ingame markup
76 function __construct($ig) {
77 $this->_ig = $ig;
79 // ignore bbcode between these tags
80 $this->tags_ignore = array(
81 'noparse', 'code',
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;
88 if ($this->_ig) {
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();
96 // reset internals
97 $this->reset();
101 * Format bbcode tag
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
115 * @return string
117 function handle_tag($tag) {
118 return $this->format($tag['tag'], $tag['open'], $tag['close'], $tag['attr'], $tag['text']);
122 * Reset internals
124 function reset() {
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;
137 * Save working state
139 private function _state_save() {
140 $this->state[] = array($this->current_tag, $this->tags_ignore_depth, $this->open_tags, $this->last_closed_tag);
141 $this->reset();
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);
170 } else {
171 $this->open($chunk);
173 } else {
174 $this->text($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) {
192 $this->text($chunk);
193 } else {
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--;
217 // stack underrun
218 if ($this->current_tag < 0) {
219 $this->text($chunk);
220 return;
223 // ignore block
224 if ($this->tags_ignore_depth > 0) {
225 $this->text($chunk);
226 return;
229 // tag mismatch
230 if ($this->open_tags[$this->current_tag]['tag'] !== $tag) {
231 // try to find first open tag for this
232 $key = false;
233 for ($i = $this->current_tag - 1; $i > 0; $i--) {
234 if (isset($this->open_tags[$i]['tag']) && $this->open_tags[$i]['tag'] === $tag) {
235 $key = $i;
236 break;
239 if ($key === false) {
240 $this->text($chunk);
241 return;
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']);
251 // close tag
252 $open = $this->pop_stack();
254 // handle bbcode
255 $open['close'] = $chunk;
257 $block_level = false;
258 if (in_array($tag, $this->tags_block_open)) {
259 $block_level = true;
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]'
267 if ($block_level) {
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;
287 function result() {
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();
298 } else {
299 // tag was not closed proprely, include start tag with result
300 $text = $open['open'] . $open['text'];
303 $this->text($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() {
315 // remove from stack
316 $open = $this->open_tags[$this->current_tag];
317 unset($this->open_tags[$this->current_tag]);
318 $this->current_tag--;
320 return $open;
324 * Trim from end of string
325 * 'text...\s{0,}\n{1}\s{0,}'
327 * @param string $ts
328 * @return string
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");
337 return $ts;
341 * Trim from start of string
342 * '\s{0,}\n{1}...text'
344 * @param string $ts
345 * @return string
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);
353 return $ts;
357 * Trim from both sides
358 * '\s{0,}\n{1}...text...\s{0,}\n{1}\s{0,}
360 * @param string $ts
361 * @return string
363 function trim_ws($ts){
364 $ts = $this->ltrim_ws($ts);
365 $ts = $this->rtrim_ws($ts);
366 return $ts;
370 * Extract tag parameters from [tag=params] or [tag key1=val1 key2=val2]
372 * @param type $tag
373 * @return type
375 function split_params($chunk) {
376 if (substr($chunk, 0, 1) == '[') {
377 $b = '\[';
378 $e = '\]';
379 } else {
380 $b = '';
381 $e = '';
383 // [1] [2] [3]
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];
389 } else {
390 // <space> means multiple parameters
391 $tagParam = array();
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]);
398 } else {
399 $tagParam[] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[0]);
403 } else {
404 if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') {
405 $chunk = substr($chunk, 1, -1);
407 $tagName = strtolower($chunk);
408 $tagParam = '';
410 return array($tagName, $tagParam);
415 class bbCode extends bbCodeParser {
416 static $legacy_sync = 1348956841;
417 static $legacy_shard = array(
418 'ani' => 2363920179,
419 'lea' => 2437578274,
420 'ari' => 2358620001,
423 static $ig = false;
424 static $timezone = 'UTC';
425 static $clock12h = false;
426 static $shardid = false;
427 static $lang = 'en';
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) {
439 $size = 16;
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;
451 return $size;
454 static function bb_noparse($code) {
455 return preg_replace(array('/\[/', '/\]/'), array('&#91;', '&#93;'), $code);
458 static function bb_code($code) {
459 return '<pre>' . self::bb_noparse($code) . '</pre>';
462 static function bb_list($list) {
463 $result = '';
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) {
470 if (self::$ig) {
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
489 '</table>';
490 } else {
491 return '' .
492 '<div class="post-quote">' .
493 '<cite>' . $author . '</cite>' .
494 '<blockquote>' . $text . '</blockquote>' .
495 '</div>';
499 static function bb_h($nr, $color, $text) {
500 $tag = 'h' . $nr;
502 if (self::$ig) {
503 if ($color != '') {
504 $text = '<font color="' . $color . '">' . $text . '</font>';
506 return '<' . $tag . '>' . $text . '</' . $tag . '>';
507 } else {
508 if ($color != '') {
509 $style = ' style="color: ' . $color . ';"';
510 } else {
511 $style = '';
513 return '<' . $tag . $style . '>' . $text . '</' . $tag . '>';
517 static function bb_url($href, $text) {
518 // &quot;http://..../&quot; remove &quot; if present
519 if (substr($href, 0, 6) == '&quot;') {
520 if (substr($href, -6) == '&quot;') {
521 $href = substr($href, 6, -6);
522 } else {
523 $href = substr($href, 6);
527 if ($href == '')
528 $href = $text;
529 if ($text == '') {
530 $text = $href;
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));
538 $disable = true;
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
546 $disable = false;
550 } // !empty
551 }// isRYZOM
553 if ($disable) {
554 // empty href will give proper link color without 'underline' - perfect for 'disabled' links
555 if ($href == '') {
556 $text = '<a href="">' . $text . '</a>';
557 } else {
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 . ')');
562 return $text;
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 &amp;
578 $href = str_replace('&amp;', '&', $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)
606 $ptype = 1;
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) {
613 if ($color == '') {
614 return $str;
617 if (self::$ig) {
618 return '<font color="' . $color . '">' . $str . '</font>';
619 } else {
620 return '<span style="color: ' . $color . ';">' . $str . '</span>';
624 static function bb_size($size, $str) {
625 $size = self::getFontSize($size);
627 if (self::$ig) {
628 return '<font size="' . $size . 'px">' . $str . '</font>';
629 } else {
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) {
644 if (self::$ig) {
645 return '<table width="100%" cellpadding="0" cellspacing="0"><tr><td align="center" valign="middle">' . $str . '</td></tr></table>';
646 } else {
647 return '<div style="text-align: center;">' . $str . '</div>';
651 /** Table format : (added by ulukyn)
652 * A1| A2|A3
653 * B1| B2 |B3
654 * C1|C2 |C3
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) {
663 if ($line) {
664 $ret .= '<tr>';
665 $cols = explode('|', $line);
666 foreach ($cols as $text) {
667 if (!$text)
668 continue;
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));
678 if ($paramsdef)
679 $text = implode(' ', $paramsdef);
681 $param_html = '';
682 foreach ($params as $name => $value)
683 $param_html .= $name.'="'.$value.'" ';
685 if ($text && $text[0] == ' ' && $text[strlen($text)-1] == ' ')
686 $align = 'center';
687 else if ($text && $text[0] == ' ')
688 $align = 'right';
689 else
690 $align = 'left';
692 $ret .= '<td '.$param_html.' align="'.$align.'">'.$text.'</td>';
694 $ret .= '</tr>';
698 $ret .= '</table>';
699 return $ret;
703 static function bb_page_link($page, $txt) {
704 if ($page == '') {
705 $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;
717 if ($id == '') {
718 $page.= $txt;
720 return self::bb_page_link($page, $txt);
723 // Added by Ulukyn
724 static function bb_wiki_link($page, $txt) {
725 $need_new_txt = false;
726 if ($page == '') {
727 $page = $txt;
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);
733 else {
734 $tmp = explode('/', $page);
735 if (count($tmp) != 2) {
736 return 'Syntax: [wiki]/[page], ex: en/Chronicles';
737 } else {
738 $wiki = $tmp[0];
739 $page = $tmp[1];
741 if (self::$ig) {
742 $url = 'http://atys.ryzom.com/start/app_wiki.php?page=/projects/pub' . $wiki . '/wiki/' . $page;
744 else
745 $url = 'http://atys.ryzom.com/projects/pub' . $wiki . '/wiki/' . $page;
746 if ($need_new_txt)
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);
754 if (self::$ig) {
755 switch ($tag) {
756 // FIXME: darken/lighter or tint current color
757 case 'b': $txt = self::bb_color('white', $txt);
758 break;
759 case 'i': $txt = self::bb_color('#ffffd0', $txt);
760 break;
761 case 'u': $txt = '<a href="ah:">' . self::bb_color(self::COLOR_P, $txt) . '</a>';
762 break;
763 default : $txt = self::bb_color(self::COLOR_BBCODE_TAG, $txt);
764 break; // fallback
766 return $txt;
769 switch ($tag) {
770 case 'b': $tag = 'strong';
771 break;
772 case 'i': $tag = 'em';
773 break;
774 case 'u': $tag = 'u';
775 break;
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;
794 } else {
795 $tick = ryzom_time_tick($shardid);
796 // tick is for NOW, adjust it to match time given
797 $now = time();
798 $tick = ($time - $now) * 10 + $tick;
801 $rytime = ryzom_time_array($tick, $shardid);
802 $txt = ryzom_time_txt($rytime, self::$lang);
804 return $txt;
807 static function bb_lang($attr, $txt) {
808 if (_user()->lang == $attr)
809 return $txt;
810 else
811 return '';
814 static function bb_time($options, $txt) {
815 $time = strtotime($txt);
817 if ($time == 0) {
818 return $txt;
821 $timezone = self::$timezone;
823 $show_time = '';
824 $show_date = '';
825 $show_timer = '';
827 if (is_array($options)) {
828 foreach ($options as $key => $val) {
829 switch ($key) {
830 case 'timezone':
831 // fix some timezones for php
832 switch ($val) {
833 case 'pst': // fall thru
834 case 'pdt': $val = 'US/Pacific';
835 break;
837 $timezone = $val;
838 break;
839 case 'date' :
840 $show_date = $val == 'off' ? false : $val;
841 break;
842 case 'time' :
843 $show_time = $val == 'off' ? false : $val;
844 break;
845 case 'timer':
846 $show_timer = $val == 'off' ? false : $val;
847 break;
848 }//switch
849 }//foreach
852 $ret = array();
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) {
862 $date.= ' ' . $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) {
874 $f = '%s';
875 } else {
876 $f = '(%s)';
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
891 switch ($tag) {
892 case 'noparse' :
893 $result = self::bb_noparse($text);
894 break;
895 case 'code' :
896 $result = self::bb_code($text);
897 break;
898 case 'quote' :
899 $result = self::bb_quote($attr, $text);
900 break;
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
906 case 'h6' :
907 $nr = (int) substr($tag, -1);
908 $color = isset($attr['color']) ? $attr['color'] : '';
909 $result = self::bb_h($nr, $color, $text);
910 break;
911 case 'color' :
912 $result = self::bb_color($attr, $text);
913 break;
914 case 'size' :
915 $result = self::bb_size($attr, $text);
916 break;
917 case 'list' :
918 $result = self::bb_list($text);
919 break;
920 case 'img' :
921 $result = self::bb_img($attr, $text);
922 break;
923 case 'banner' :
924 $result = self::bb_banner($attr, $text);
925 break;
926 case 'pre' :
927 $result = self::bb_pre($text);
928 break;
929 case 'p' :
930 $result = self::bb_p($text);
931 break;
932 case 'table' :
933 $result = self::bb_table($attr, $text);
934 break;
935 case 'center' :
936 $result = self::bb_center($text);
937 break;
938 case 'url' :
939 $result = self::bb_url($attr, $text);
940 break;
941 case 'mail' :
942 $result = self::bb_mail($text);
943 break;
944 case 'profile' :
945 $result = self::bb_profile($attr, $text);
946 break;
947 case 'page' :
948 $result = self::bb_page_link($attr, $text);
949 break;
950 case 'forum' : // fall thru
951 case 'topic' : // fall thru
952 case 'post' :
953 $result = self::bb_forum_link($tag, $attr, $text);
954 break;
955 case 'wiki' :
956 $result = self::bb_wiki_link($attr, $text);
957 break;
958 case 'b' : // fall thru
959 case 'i' : // fall thru
960 case 'u' :
961 $result = self::bb_biu($tag, $text);
962 break;
963 case 'time' :
964 $result = self::bb_time($attr, $text);
965 break;
966 case 'date' :
967 $result = self::bb_date($attr, $text);
968 break;
969 case 'lang' :
970 $result = self::bb_lang($attr, $text);
971 break;
972 default :
973 $result = $open . $text . $close;
974 break;
976 return $result;
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);
992 $parser->reset();
994 self::$disabledTags = $disabledTags;
995 return $parser->bbcode($text);