BBCode
Description
Source
File: bp-forums/admin/parser.php
class BBCode { var $tag_rules; var $defaults; var $current_class; var $root_class; var $lost_start_tags; var $start_tags; var $allow_ampersand; var $tag_marker; var $ignore_newlines; var $plain_mode; var $detect_urls; var $url_pattern; var $output_limit; var $text_length; var $was_limited; var $limit_tail; var $limit_precision; var $smiley_dir; var $smiley_url; var $smileys; var $smiley_regex; var $enable_smileys; var $wiki_url; var $local_img_dir; var $local_img_url; var $url_targetable; var $url_target; var $rule_html; var $pre_trim; var $post_trim; var $debug; /* ADDED */ // singleton instance private static $instance; // private constructor function // to prevent external instantiation private function __construct() { $this->defaults = new BBCodeLibrary; $this->tag_rules = $this->defaults->default_tag_rules; $this->smileys = $this->defaults->default_smileys; $this->enable_smileys = true; $this->smiley_regex = false; $this->smiley_dir = $this->GetDefaultSmileyDir(); $this->smiley_url = $this->GetDefaultSmileyURL(); $this->wiki_url = $this->GetDefaultWikiURL(); $this->local_img_dir = $this->GetDefaultLocalImgDir(); $this->local_img_url = $this->GetDefaultLocalImgURL(); $this->rule_html = $this->GetDefaultRuleHTML(); $this->pre_trim = ""; $this->post_trim = ""; $this->root_class = 'block'; $this->lost_start_tags = Array(); $this->start_tags = Array(); $this->tag_marker = '['; $this->allow_ampsersand = false; $this->current_class = $this->root_class; $this->debug = false; $this->ignore_newlines = false; $this->output_limit = 0; $this->plain_mode = false; $this->was_limited = false; $this->limit_tail = "..."; $this->limit_precision = 0.15; $this->detect_urls = false; $this->url_pattern = '<a href="{$url/h}">{$text/h}</a>'; $this->url_targetable = false; $this->url_target = false; } // getInstance method public static function getInstance() { if(!self::$instance) { self::$instance = new self(); } return self::$instance; } /* ADDED */ function SetPreTrim($trim = "a") { $this->pre_trim = $trim; } function GetPreTrim() { return $this->pre_trim; } function SetPostTrim($trim = "a") { $this->post_trim = $trim; } function GetPostTrim() { return $this->post_trim; } function SetRoot($class = 'block') { $this->root_class = $class; } function SetRootInline() { $this->root_class = 'inline'; } function SetRootBlock() { $this->root_class = 'block'; } function GetRoot() { return $this->root_class; } function SetDebug($enable = true) { $this->debug = $enable; } function GetDebug() { return $this->debug; } function SetAllowAmpersand($enable = true) { $this->allow_ampersand = $enable; } function GetAllowAmpersand() { return $this->allow_ampersand; } function SetTagMarker($marker = '[') { $this->tag_marker = $marker; } function GetTagMarker() { return $this->tag_marker; } function SetIgnoreNewlines($ignore = true) { $this->ignore_newlines = $ignore; } function GetIgnoreNewlines() { return $this->ignore_newlines; } function SetLimit($limit = 0) { $this->output_limit = $limit; } function GetLimit() { return $this->output_limit; } function SetLimitTail($tail = "...") { $this->limit_tail = $tail; } function GetLimitTail() { return $this->limit_tail; } function SetLimitPrecision($prec = 0.15) { $this->limit_precision = $prec; } function GetLimitPrecision() { return $this->limit_precision; } function WasLimited() { return $this->was_limited; } function SetPlainMode($enable = true) { $this->plain_mode = $enable; } function GetPlainMode() { return $this->plain_mode; } function SetDetectURLs($enable = true) { $this->detect_urls = $enable; } function GetDetectURLs() { return $this->detect_urls; } function SetURLPattern($pattern) { $this->url_pattern = $pattern; } function GetURLPattern() { return $this->url_pattern; } function SetURLTargetable($enable) { $this->url_targetable = $enable; } function GetURLTargetable() { return $this->url_targetable; } function SetURLTarget($target) { $this->url_target = $target; } function GetURLTarget() { return $this->url_target; } function AddRule($name, $rule) { $this->tag_rules[$name] = $rule; } function RemoveRule($name) { unset($this->tag_rules[$name]); } function GetRule($name) { return isset($this->tag_rules[$name]) ? $this->tag_rules[$name] : false; } function ClearRules() { $this->tag_rules = Array(); } function GetDefaultRule($name) { return isset($this->defaults->default_tag_rules[$name]) ? $this->defaults->default_tag_rules[$name] : false; } function SetDefaultRule($name) { if (isset($this->defaults->default_tag_rules[$name])) $this->AddRule($name, $this->defaults->default_tag_rules[$name]); else $this->RemoveRule($name); } function GetDefaultRules() { return $this->defaults->default_tag_rules; } function SetDefaultRules() { $this->tag_rules = $this->defaults->default_tag_rules; } function SetWikiURL($url) { $this->wiki_url = $url; } function GetWikiURL($url) { return $this->wiki_url; } function GetDefaultWikiURL() { return '/?page='; } function SetLocalImgDir($path) { $this->local_img_dir = $path; } function GetLocalImgDir() { return $this->local_img_dir; } function GetDefaultLocalImgDir() { return "img"; } function SetLocalImgURL($path) { $this->local_img_url = $path; } function GetLocalImgURL() { return $this->local_img_url; } function GetDefaultLocalImgURL() { return "img"; } function SetRuleHTML($html) { $this->rule_html = $html; } function GetRuleHTML() { return $this->rule_html; } function GetDefaultRuleHTML() { return "\n<hr class=\"bbcode_rule\" />\n"; } function AddSmiley($code, $image) { $this->smileys[$code] = $image; $this->smiley_regex = false; } function RemoveSmiley($code) { unset($this->smileys[$code]); $this->smiley_regex = false; } function GetSmiley($code) { return isset($this->smileys[$code]) ? $this->smileys[$code] : false; } function ClearSmileys() { $this->smileys = Array(); $this->smiley_regex = false; } function GetDefaultSmiley($code) { return isset($this->defaults->default_smileys[$code]) ? $this->defaults->default_smileys[$code] : false; } function SetDefaultSmiley($code) { $this->smileys[$code] = @$this->defaults->default_smileys[$code]; $this->smiley_regex = false; } function GetDefaultSmileys() { return $this->defaults->default_smileys; } function SetDefaultSmileys() { $this->smileys = $this->defaults->default_smileys; $this->smiley_regex = false; } function SetSmileyDir($path) { $this->smiley_dir = $path; } function GetSmileyDir() { return $this->smiley_dir; } function GetDefaultSmileyDir() { return "smileys"; } function SetSmileyURL($path) { $this->smiley_url = $path; } function GetSmileyURL() { return $this->smiley_url; } function GetDefaultSmileyURL() { return "smileys"; } function SetEnableSmileys($enable = true) { $this->enable_smileys = $enable; } function GetEnableSmileys() { return $this->enable_smileys; } function nl2br($string) { return preg_replace("/\\x0A|\\x0D|\\x0A\\x0D|\\x0D\\x0A/", "<br />\n", $string); } function UnHTMLEncode($string) { if (function_exists("html_entity_decode")) return html_entity_decode($string); $string = preg_replace_callback('~&#x([0-9a-f]+);~i', array( $this, '_UnHTMLEncode_chr_callback'), $string); $string = preg_replace_callback('~&#([0-9]+);~', array($this, '_UnHTMLEncode_chr_hexdec_callback'), $string); $trans_tbl = get_html_translation_table(HTML_ENTITIES); $trans_tbl = array_flip($trans_tbl); return strtr($string, $trans_tbl); } function _UnHTMLEncode_chr_callback($match) { return chr(hexdec($match[1])); } function _UnHTMLEncode_chr_hexdec_callback($match) { return chr(hexdec($match[1])); } function Wikify($string) { return rawurlencode(str_replace(" ", "_", trim(preg_replace("/[!?;@#\$%\\^&*<>=+`~\\x00-\\x20_-]+/", " ", $string)))); } function IsValidURL($string, $email_too = true) { if (preg_match("/^ (?:https?|ftp):\\/\\/ (?: (?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+ [a-zA-Z0-9] (?:[a-zA-Z0-9-]*[a-zA-Z0-9])? | \\[ (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3} (?: 25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]: (?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21-\\x5A\\x53-\\x7F] |\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])+ ) \\] ) (?::[0-9]{1,5})? (?:[\\/\\?\\#][^\\n\\r]*)? $/Dx", $string)) return true; if (preg_match("/^[^:]+([\\/\\\\?#][^\\r\\n]*)?$/D", $string)) return true; if ($email_too) if (substr($string, 0, 7) == "mailto:") return $this->IsValidEmail(substr($string, 7)); return false; } function IsValidEmail($string) { $validator = new BBCodeEmailAddressValidator; return $validator->check_email_address($string); /* return preg_match("/^ (?: [a-z0-9\\!\\#\\\$\\%\\&\\'\\*\\+\\/=\\?\\^_`\\{\\|\\}~-]+ (?:\.[a-z0-9\\!\\#\\\$\\%\\&\\'\\*\\+\\/=\\?\\^_`\\{\\|\\}~-]+)* | \"(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F] |\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])*\" ) @ (?: (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+ [a-z0-9] (?:[a-z0-9-]*[a-z0-9])? | \\[ (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3} (?: 25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]: (?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21-\\x5A\\x53-\\x7F] |\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])+ ) \\] ) $/Dx", $string); */ } function HTMLEncode($string) { if (!$this->allow_ampersand) return htmlspecialchars($string); else return str_replace(Array('<', '>', '"'), Array('<', '>', '"'), $string); } function FixupOutput($string) { if (!$this->detect_urls) { $output = $this->Internal_ProcessSmileys($string); } else { $chunks = $this->Internal_AutoDetectURLs($string); $output = Array(); if (count($chunks)) { $is_a_url = false; foreach ($chunks as $index => $chunk) { if (!$is_a_url) { $chunk = $this->Internal_ProcessSmileys($chunk); } $output[] = $chunk; $is_a_url = !$is_a_url; } } $output = implode("", $output); } return $output; } function Internal_ProcessSmileys($string) { if (!$this->enable_smileys || $this->plain_mode) { $output = $this->HTMLEncode($string); } else { if ($this->smiley_regex === false) { $this->Internal_RebuildSmileys(); } $tokens = preg_split($this->smiley_regex, $string, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($tokens) <= 1) { $output = $this->HTMLEncode($string); } else { $output = ""; $is_a_smiley = false; foreach ($tokens as $token) { if (!$is_a_smiley) { $output .= $this->HTMLEncode($token); } else { if (isset($this->smiley_info[$token])) { $info = $this->smiley_info[$token]; } else { $info = @getimagesize($this->smiley_dir . '/' . $this->smileys[$token]); $this->smiley_info[$token] = $info; } $alt = htmlspecialchars($token); $output .= "<img src=\"" . htmlspecialchars($this->smiley_url . '/' . $this->smileys[$token]) . "\" width=\"{$info[0]}\" height=\"{$info[1]}\"" . " alt=\"$alt\" title=\"$alt\" class=\"bbcode_smiley\" />"; } $is_a_smiley = !$is_a_smiley; } } } return $output; } function Internal_RebuildSmileys() { $regex = Array("/(?<![\\w])("); $first = true; foreach ($this->smileys as $code => $filename) { if (!$first) $regex[] = "|"; $regex[] = preg_quote("$code", '/'); $first = false; } $regex[] = ")(?![\\w])/"; $this->smiley_regex = implode("", $regex); } function Internal_AutoDetectURLs($string) { $output = preg_split("/( (?: (?:https?|ftp) : \\/* (?: (?: (?: [a-zA-Z0-9-]{2,} \\. )+ (?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2} | aero | biz | coop | info | museum | name | pro | example | invalid | localhost | test | local | onion | swift ) ) | (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} ) | (?: [0-9A-Fa-f:]+ : [0-9A-Fa-f]{1,4} ) ) (?: : [0-9]+ )? (?! [a-zA-Z0-9.:-] ) (?: \\/ [^&?#\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]* )? (?: [?#] [^\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]+ )? ) | (?: (?: (?: (?: [a-zA-Z0-9-]{2,} \\. )+ (?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2} | aero | biz | coop | info | museum | name | pro | example | invalid | localhost | test | local | onion | swift ) ) | (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} ) ) (?: : [0-9]+ )? (?! [a-zA-Z0-9.:-] ) (?: \\/ [^&?#\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]* )? (?: [?#] [^\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]+ )? ) | (?: [a-zA-Z0-9._-]{2,} @ (?: (?: (?: [a-zA-Z0-9-]{2,} \\. )+ (?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2} | aero | biz | coop | info | museum | name | pro | example | invalid | localhost | test | local | onion | swift ) ) | (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} ) ) ) )/Dx", $string, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($output) > 1) { $is_a_url = false; foreach ($output as $index => $token) { if ($is_a_url) { if (preg_match("/^[a-zA-Z0-9._-]{2,}@/", $token)) { $url = "mailto:" . $token; } else if (preg_match("/^(https?:|ftp:)\\/*([^\\/&?#]+)\\/*(.*)\$/", $token, $matches)) { $url = $matches[1] . '/' . '/' . $matches[2] . "/" . $matches[3]; } else { preg_match("/^([^\\/&?#]+)\\/*(.*)\$/", $token, $matches); $url = "http:/" . "/" . $matches[1] . "/" . $matches[2]; } $params = @parse_url($url); if (!is_array($params)) $params = Array(); $params['url'] = $url; $params['link'] = $url; $params['text'] = $token; $output[$index] = $this->FillTemplate($this->url_pattern, $params); } $is_a_url = !$is_a_url; } } return $output; } function FillTemplate($template, $insert_array, $default_array = Array()) { $pieces = preg_split('/(\{\$[a-zA-Z0-9_.:\/-]+\})/', $template, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($pieces) <= 1) return $template; $result = Array(); $is_an_insert = false; foreach ($pieces as $piece) { if (!$is_an_insert) { $result[] = $piece; } else if (!preg_match('/\{\$([a-zA-Z0-9_:-]+)((?:\\.[a-zA-Z0-9_:-]+)*)(?:\/([a-zA-Z0-9_:-]+))?\}/', $piece, $matches)) { $result[] = $piece; } else { if (isset($insert_array[$matches[1]])) $value = @$insert_array[$matches[1]]; else $value = @$default_array[$matches[1]]; if (strlen(@$matches[2])) { foreach (explode(".", substr($matches[2], 1)) as $index) { if (is_array($value)) $value = @$value[$index]; else if (is_object($value)) { $value = (array)$value; $value = @$value[$index]; } else $value = ""; } } switch (gettype($value)) { case 'boolean': $value = $value ? "true" : "false"; break; case 'integer': $value = (string)$value; break; case 'double': $value = (string)$value; break; case 'string': break; default: $value = ""; break; } if (strlen(@$matches[3])) $flags = array_flip(str_split($matches[3])); else $flags = Array(); if (!isset($flags['v'])) { if (isset($flags['w'])) $value = preg_replace("/[\\x00-\\x09\\x0B-\x0C\x0E-\\x20]+/", " ", $value); if (isset($flags['t'])) $value = trim($value); if (isset($flags['b'])) $value = basename($value); if (isset($flags['e'])) $value = $this->HTMLEncode($value); else if (isset($flags['k'])) $value = $this->Wikify($value); else if (isset($flags['h'])) $value = htmlspecialchars($value); else if (isset($flags['u'])) $value = urlencode($value); if (isset($flags['n'])) $value = $this->nl2br($value); } $result[] = $value; } $is_an_insert = !$is_an_insert; } return implode("", $result); } function Internal_CollectText($array, $start = 0) { ob_start(); for ($start = intval($start), $end = count($array); $start < $end; $start++) print $array[$start][BBCODE_STACK_TEXT]; $output = ob_get_contents(); ob_end_clean(); return $output; } function Internal_CollectTextReverse($array, $start = 0, $end = 0) { ob_start(); for ($start = intval($start); $start >= $end; $start--) print $array[$start][BBCODE_STACK_TEXT]; $output = ob_get_contents(); ob_end_clean(); return $output; } function Internal_GenerateOutput($pos) { $output = Array(); while (count($this->stack) > $pos) { $token = array_pop($this->stack); if ($token[BBCODE_STACK_TOKEN] != BBCODE_TAG) { $output[] = $token; } else { $name = @$token[BBCODE_STACK_TAG]['_name']; $rule = @$this->tag_rules[$name]; $end_tag = @$rule['end_tag']; if (!isset($rule['end_tag'])) $end_tag = BBCODE_REQUIRED; else $end_tag = $rule['end_tag']; array_pop($this->start_tags[$name]); if ($end_tag == BBCODE_PROHIBIT) { $output[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TAG => false, BBCODE_STACK_TEXT => $token[BBCODE_STACK_TEXT], BBCODE_STACK_CLASS => $this->current_class, ); } else { if ($end_tag == BBCODE_REQUIRED) @$this->lost_start_tags[$name] += 1; $end = $this->Internal_CleanupWSByIteratingPointer(@$rule['before_endtag'], 0, $output); $this->Internal_CleanupWSByPoppingStack(@$rule['after_tag'], $output); $tag_body = $this->Internal_CollectTextReverse($output, count($output)-1, $end); $this->Internal_CleanupWSByPoppingStack(@$rule['before_tag'], $this->stack); $this->Internal_UpdateParamsForMissingEndTag(@$token[BBCODE_STACK_TAG]); $tag_output = $this->DoTag(BBCODE_OUTPUT, $name, @$token[BBCODE_STACK_TAG]['_default'], @$token[BBCODE_STACK_TAG], $tag_body); $output = Array(Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TAG => false, BBCODE_STACK_TEXT => $tag_output, BBCODE_STACK_CLASS => $this->current_class )); } } } $this->Internal_ComputeCurrentClass(); return $output; } function Internal_RewindToClass($class_list) { $pos = count($this->stack) - 1; while ($pos >= 0 && !in_array($this->stack[$pos][BBCODE_STACK_CLASS], $class_list)) $pos--; if ($pos < 0) { if (!in_array($this->root_class, $class_list)) return false; } $output = $this->Internal_GenerateOutput($pos+1); while (count($output)) { $token = array_pop($output); $token[BBCODE_STACK_CLASS] = $this->current_class; $this->stack[] = $token; } return true; } function Internal_FinishTag($tag_name) { if (strlen($tag_name) <= 0) return false; if (isset($this->start_tags[$tag_name]) && count($this->start_tags[$tag_name])) $pos = array_pop($this->start_tags[$tag_name]); else $pos = -1; if ($pos < 0) return false; $newpos = $this->Internal_CleanupWSByIteratingPointer(@$this->tag_rules[$tag_name]['after_tag'], $pos+1, $this->stack); $delta = $newpos - ($pos+1); $output = $this->Internal_GenerateOutput($newpos); $newend = $this->Internal_CleanupWSByIteratingPointer(@$this->tag_rules[$tag_name]['before_endtag'], 0, $output); $output = $this->Internal_CollectTextReverse($output, count($output) - 1, $newend); while ($delta-- > 0) array_pop($this->stack); $this->Internal_ComputeCurrentClass(); return $output; } function Internal_ComputeCurrentClass() { if (count($this->stack) > 0) $this->current_class = $this->stack[count($this->stack)-1][BBCODE_STACK_CLASS]; else $this->current_class = $this->root_class; } function Internal_DumpStack($array = false, $raw = false) { if (!$raw) $string = "<span style='color: #00C;'>"; else $string = ""; if ($array === false) $array = $this->stack; foreach ($array as $item) { switch (@$item[BBCODE_STACK_TOKEN]) { case BBCODE_TEXT: $string .= "\"" . htmlspecialchars(@$item[BBCODE_STACK_TEXT]) . "\" "; break; case BBCODE_WS: $string .= "WS "; break; case BBCODE_NL: $string .= "NL "; break; case BBCODE_TAG: $string .= "[" . htmlspecialchars(@$item[BBCODE_STACK_TAG]['_name']) . "] "; break; default: $string .= "unknown "; break; } } if (!$raw) $string .= "</span>"; return $string; } function Internal_CleanupWSByPoppingStack($pattern, &$array) { if (strlen($pattern) <= 0) return; $oldlen = count($array); foreach (str_split($pattern) as $char) { switch ($char) { case 's': while (count($array) > 0 && $array[count($array)-1][BBCODE_STACK_TOKEN] == BBCODE_WS) array_pop($array); break; case 'n': if (count($array) > 0 && $array[count($array)-1][BBCODE_STACK_TOKEN] == BBCODE_NL) array_pop($array); break; case 'a': while (count($array) > 0 && (($token = $array[count($array)-1][BBCODE_STACK_TOKEN]) == BBCODE_WS || $token == BBCODE_NL)) array_pop($array); break; } } if (count($array) != $oldlen) { $this->Internal_ComputeCurrentClass(); } } function Internal_CleanupWSByEatingInput($pattern) { if (strlen($pattern) <= 0) return; foreach (str_split($pattern) as $char) { switch ($char) { case 's': $token_type = $this->lexer->NextToken(); while ($token_type == BBCODE_WS) { $token_type = $this->lexer->NextToken(); } $this->lexer->UngetToken(); break; case 'n': $token_type = $this->lexer->NextToken(); if ($token_type != BBCODE_NL) $this->lexer->UngetToken(); break; case 'a': $token_type = $this->lexer->NextToken(); while ($token_type == BBCODE_WS || $token_type == BBCODE_NL) { $token_type = $this->lexer->NextToken(); } $this->lexer->UngetToken(); break; } } } function Internal_CleanupWSByIteratingPointer($pattern, $pos, $array) { if (strlen($pattern) <= 0) return $pos; foreach (str_split($pattern) as $char) { switch ($char) { case 's': while ($pos < count($array) && $array[$pos][BBCODE_STACK_TOKEN] == BBCODE_WS) $pos++; break; case 'n': if ($pos < count($array) && $array[$pos][BBCODE_STACK_TOKEN] == BBCODE_NL) $pos++; break; case 'a': while ($pos < count($array) && (($token = $array[$pos][BBCODE_STACK_TOKEN]) == BBCODE_WS || $token == BBCODE_NL)) $pos++; break; } } return $pos; } function Internal_LimitText($string, $limit) { $chunks = preg_split("/([\\x00-\\x20]+)/", $string, -1, PREG_SPLIT_DELIM_CAPTURE); $output = ""; foreach ($chunks as $chunk) { if (strlen($output) + strlen($chunk) > $limit) break; $output .= $chunk; } $output = rtrim($output); return $output; } function Internal_DoLimit() { $this->Internal_CleanupWSByPoppingStack("a", $this->stack); if (strlen($this->limit_tail) > 0) { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->limit_tail, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } $this->was_limited = true; } function DoTag($action, $tag_name, $default_value, $params, $contents) { $tag_rule = @$this->tag_rules[$tag_name]; switch ($action) { case BBCODE_CHECK: if (isset($tag_rule['allow'])) { foreach ($tag_rule['allow'] as $param => $pattern) { if ($param == '_content') $value = $contents; else if ($param == '_defaultcontent') { if (strlen($default_value)) $value = $default_value; else $value = $contents; } else { if (isset($params[$param])) $value = $params[$param]; else $value = @$tag_rule['default'][$param]; } if (!preg_match($pattern, $value)) { return false; } } return true; } switch (@$tag_rule['mode']) { default: case BBCODE_MODE_SIMPLE: $result = true; break; case BBCODE_MODE_ENHANCED: $result = true; break; case BBCODE_MODE_INTERNAL: $result = @call_user_func(Array($this, @$tag_rule['method']), BBCODE_CHECK, $tag_name, $default_value, $params, $contents); break; case BBCODE_MODE_LIBRARY: $result = @call_user_func(Array($this->defaults, @$tag_rule['method']), $this, BBCODE_CHECK, $tag_name, $default_value, $params, $contents); break; case BBCODE_MODE_CALLBACK: $result = @call_user_func(@$tag_rule['method'], $this, BBCODE_CHECK, $tag_name, $default_value, $params, $contents); break; } return $result; case BBCODE_OUTPUT: if ($this->plain_mode) { if (!isset($tag_rule['plain_content'])) $plain_content = Array('_content'); else $plain_content = $tag_rule['plain_content']; $result = $possible_content = ""; foreach ($plain_content as $possible_content) { if ($possible_content == '_content' && strlen($contents) > 0) { $result = $contents; break; } if (isset($params[$possible_content]) && strlen($params[$possible_content]) > 0) { $result = htmlspecialchars($params[$possible_content]); break; } } $start = @$tag_rule['plain_start']; $end = @$tag_rule['plain_end']; if (isset($tag_rule['plain_link'])) { $link = $possible_content = ""; foreach ($tag_rule['plain_link'] as $possible_content) { if ($possible_content == '_content' && strlen($contents) > 0) { $link = $this->UnHTMLEncode(strip_tags($contents)); break; } if (isset($params[$possible_content]) && strlen($params[$possible_content]) > 0) { $link = $params[$possible_content]; break; } } $params = @parse_url($link); if (!is_array($params)) $params = Array(); $params['link'] = $link; $params['url'] = $link; $start = $this->FillTemplate($start, $params); $end = $this->FillTemplate($end, $params); } return $start . $result . $end; } switch (@$tag_rule['mode']) { default: case BBCODE_MODE_SIMPLE: $result = @$tag_rule['simple_start'] . $contents . @$tag_rule['simple_end']; break; case BBCODE_MODE_ENHANCED: $result = $this->Internal_DoEnhancedTag($tag_rule, $params, $contents); break; case BBCODE_MODE_INTERNAL: $result = @call_user_func(Array($this, @$tag_rule['method']), BBCODE_OUTPUT, $tag_name, $default_value, $params, $contents); break; case BBCODE_MODE_LIBRARY: $result = @call_user_func(Array($this->defaults, @$tag_rule['method']), $this, BBCODE_OUTPUT, $tag_name, $default_value, $params, $contents); break; case BBCODE_MODE_CALLBACK: $result = @call_user_func(@$tag_rule['method'], $this, BBCODE_OUTPUT, $tag_name, $default_value, $params, $contents); break; } return $result; default: return false; } } function Internal_DoEnhancedTag($tag_rule, $params, $contents) { $params['_content'] = $contents; $params['_defaultcontent'] = strlen(@$params['_default']) ? $params['_default'] : $contents; return $this->FillTemplate(@$tag_rule['template'], $params, @$tag_rule['default']); } function Internal_UpdateParamsForMissingEndTag(&$params) { switch ($this->tag_marker) { case '[': $tail_marker = ']'; break; case '<': $tail_marker = '>'; break; case '{': $tail_marker = '}'; break; case '(': $tail_marker = ')'; break; default: $tail_marker = $this->tag_marker; break; } $params['_endtag'] = $this->tag_marker . '/' . $params['_name'] . $tail_marker; } function Internal_ProcessIsolatedTag($tag_name, $tag_params, $tag_rule) { if (!$this->DoTag(BBCODE_CHECK, $tag_name, @$tag_params['_default'], $tag_params, "")) { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); return; } $this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_tag'], $this->stack); $output = $this->DoTag(BBCODE_OUTPUT, $tag_name, @$tag_params['_default'], $tag_params, ""); $this->Internal_CleanupWSByEatingInput(@$tag_rule['after_tag']); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $output, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } function Internal_ProcessVerbatimTag($tag_name, $tag_params, $tag_rule) { $state = $this->lexer->SaveState(); $end_tag = $this->lexer->tagmarker . "/" . $tag_name . $this->lexer->end_tagmarker; $start = count($this->stack); $this->lexer->verbatim = true; while (($token_type = $this->lexer->NextToken()) != BBCODE_EOI) { if ($this->lexer->text == $end_tag) { $end_tag_params = $this->lexer->tag; break; } if ($this->output_limit > 0 && $this->text_length + strlen($this->lexer->text) >= $this->output_limit) { $text = $this->Internal_LimitText($this->lexer->text, $this->output_limit - $this->text_length); if (strlen($text) > 0) { $this->text_length += strlen($text); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } $this->Internal_DoLimit(); break; } $this->text_length += strlen($this->lexer->text); $this->stack[] = Array( BBCODE_STACK_TOKEN => $token_type, BBCODE_STACK_TEXT => htmlspecialchars($this->lexer->text), BBCODE_STACK_TAG => $this->lexer->tag, BBCODE_STACK_CLASS => $this->current_class, ); } $this->lexer->verbatim = false; if ($token_type == BBCODE_EOI) { $this->lexer->RestoreState($state); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); return; } $newstart = $this->Internal_CleanupWSByIteratingPointer(@$tag_rule['after_tag'], $start, $this->stack); $this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_endtag'], $this->stack); $this->Internal_CleanupWSByEatingInput(@$tag_rule['after_endtag']); $content = $this->Internal_CollectText($this->stack, $newstart); array_splice($this->stack, $start); $this->Internal_ComputeCurrentClass(); $this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_tag'], $this->stack); $tag_params['_endtag'] = $end_tag_params['_tag']; $tag_params['_hasend'] = true; $output = $this->DoTag(BBCODE_OUTPUT, $tag_name, @$tag_params['_default'], $tag_params, $content); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $output, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } function Internal_ParseStartTagToken() { $tag_params = $this->lexer->tag; $tag_name = @$tag_params['_name']; if (!isset($this->tag_rules[$tag_name])) { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); return; } $tag_rule = $this->tag_rules[$tag_name]; $allow_in = is_array($tag_rule['allow_in']) ? $tag_rule['allow_in'] : Array($this->root_class); if (!in_array($this->current_class, $allow_in)) { if (!$this->Internal_RewindToClass($allow_in)) { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); return; } } $end_tag = isset($tag_rule['end_tag']) ? $tag_rule['end_tag'] : BBCODE_REQUIRED; if ($end_tag == BBCODE_PROHIBIT) { $this->Internal_ProcessIsolatedTag($tag_name, $tag_params, $tag_rule); return; } if (!$this->DoTag(BBCODE_CHECK, $tag_name, @$tag_params['_default'], $tag_params, "")) { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); return; } if (@$tag_rule['content'] == BBCODE_VERBATIM) { $this->Internal_ProcessVerbatimTag($tag_name, $tag_params, $tag_rule); return; } if (isset($tag_rule['class'])) $newclass = $tag_rule['class']; else $newclass = $this->root_class; $this->stack[] = Array( BBCODE_STACK_TOKEN => $this->lexer->token, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => $this->lexer->tag, BBCODE_STACK_CLASS => ($this->current_class = $newclass), ); if (!isset($this->start_tags[$tag_name])) $this->start_tags[$tag_name] = Array(count($this->stack)-1); else $this->start_tags[$tag_name][] = count($this->stack)-1; } function Internal_ParseEndTagToken() { $tag_params = $this->lexer->tag; $tag_name = @$tag_params['_name']; $contents = $this->Internal_FinishTag($tag_name); if ($contents === false) { if (@$this->lost_start_tags[$tag_name] > 0) { $this->lost_start_tags[$tag_name]--; } else { $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } return; } $start_tag_node = array_pop($this->stack); $start_tag_params = $start_tag_node[BBCODE_STACK_TAG]; $this->Internal_ComputeCurrentClass(); $this->Internal_CleanupWSByPoppingStack(@$this->tag_rules[$tag_name]['before_tag'], $this->stack); $start_tag_params['_endtag'] = $tag_params['_tag']; $start_tag_params['_hasend'] = true; $output = $this->DoTag(BBCODE_OUTPUT, $tag_name, @$start_tag_params['_default'], $start_tag_params, $contents); $this->Internal_CleanupWSByEatingInput(@$this->tag_rules[$tag_name]['after_endtag']); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $output, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } function Parse($string) { $this->lexer = new BBCodeLexer($string, $this->tag_marker); $this->lexer->debug = $this->debug; $old_output_limit = $this->output_limit; if ($this->output_limit > 0) { if (strlen($string) < $this->output_limit) { $this->output_limit = 0; } else if ($this->limit_precision > 0) { $guess_length = $this->lexer->GuessTextLength(); if ($guess_length < $this->output_limit * ($this->limit_precision + 1.0)) { $this->output_limit = 0; } else { } } } $this->stack = Array(); $this->start_tags = Array(); $this->lost_start_tags = Array(); $this->text_length = 0; $this->was_limited = false; if (strlen($this->pre_trim) > 0) $this->Internal_CleanupWSByEatingInput($this->pre_trim); $newline = $this->plain_mode ? "\n" : "<br />\n"; while (true) { if (($token_type = $this->lexer->NextToken()) == BBCODE_EOI) { break; } switch ($token_type) { case BBCODE_TEXT: if ($this->output_limit > 0 && $this->text_length + strlen($this->lexer->text) >= $this->output_limit) { $text = $this->Internal_LimitText($this->lexer->text, $this->output_limit - $this->text_length); if (strlen($text) > 0) { $this->text_length += strlen($text); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } $this->Internal_DoLimit(); break 2; } $this->text_length += strlen($this->lexer->text); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_TEXT, BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text), BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); break; case BBCODE_WS: if ($this->output_limit > 0 && $this->text_length + strlen($this->lexer->text) >= $this->output_limit) { $this->Internal_DoLimit(); break 2; } $this->text_length += strlen($this->lexer->text); $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_WS, BBCODE_STACK_TEXT => $this->lexer->text, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); break; case BBCODE_NL: if ($this->ignore_newlines) { if ($this->output_limit > 0 && $this->text_length + 1 >= $this->output_limit) { $this->Internal_DoLimit(); break 2; } $this->text_length += 1; $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_WS, BBCODE_STACK_TEXT => "\n", BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); } else { $this->Internal_CleanupWSByPoppingStack("s", $this->stack); if ($this->output_limit > 0 && $this->text_length + 1 >= $this->output_limit) { $this->Internal_DoLimit(); break 2; } $this->text_length += 1; $this->stack[] = Array( BBCODE_STACK_TOKEN => BBCODE_NL, BBCODE_STACK_TEXT => $newline, BBCODE_STACK_TAG => false, BBCODE_STACK_CLASS => $this->current_class, ); $this->Internal_CleanupWSByEatingInput("s"); } break; case BBCODE_TAG: $this->Internal_ParseStartTagToken(); break; case BBCODE_ENDTAG: $this->Internal_ParseEndTagToken(); break; default: break; } } if (strlen($this->post_trim) > 0) $this->Internal_CleanupWSByPoppingStack($this->post_trim, $this->stack); $result = $this->Internal_GenerateOutput(0); $result = $this->Internal_CollectTextReverse($result, count($result) - 1); $this->output_limit = $old_output_limit; if ($this->plain_mode) { $result = preg_replace("/[\\x00-\\x09\\x0B-\\x20]+/", " ", $result); $result = preg_replace("/(?:[\\x20]*\\n){2,}[\\x20]*/", "\n\n", $result); $result = trim($result); } return $result; } }
Methods
- __construct
- _UnHTMLEncode_chr_callback
- _UnHTMLEncode_chr_hexdec_callback
- AddRule
- AddSmiley
- ClearRules
- ClearSmileys
- DoTag
- FillTemplate
- FixupOutput
- GetAllowAmpersand
- GetDebug
- GetDefaultLocalImgDir
- GetDefaultLocalImgURL
- GetDefaultRule
- GetDefaultRuleHTML
- GetDefaultRules
- GetDefaultSmiley
- GetDefaultSmileyDir
- GetDefaultSmileys
- GetDefaultSmileyURL
- GetDefaultWikiURL
- GetDetectURLs
- GetEnableSmileys
- GetIgnoreNewlines
- getInstance
- GetLimit
- GetLimitPrecision
- GetLimitTail
- GetLocalImgDir
- GetLocalImgURL
- GetPlainMode
- GetPostTrim
- GetPreTrim
- GetRoot
- GetRule
- GetRuleHTML
- GetSmiley
- GetSmileyDir
- GetSmileyURL
- GetTagMarker
- GetURLPattern
- GetURLTarget
- GetURLTargetable
- GetWikiURL
- HTMLEncode
- Internal_AutoDetectURLs
- Internal_CleanupWSByEatingInput
- Internal_CleanupWSByIteratingPointer
- Internal_CleanupWSByPoppingStack
- Internal_CollectText
- Internal_CollectTextReverse
- Internal_ComputeCurrentClass
- Internal_DoEnhancedTag
- Internal_DoLimit
- Internal_DumpStack
- Internal_FinishTag
- Internal_GenerateOutput
- Internal_LimitText
- Internal_ParseEndTagToken
- Internal_ParseStartTagToken
- Internal_ProcessIsolatedTag
- Internal_ProcessSmileys
- Internal_ProcessVerbatimTag
- Internal_RebuildSmileys
- Internal_RewindToClass
- Internal_UpdateParamsForMissingEndTag
- IsValidEmail
- IsValidURL
- nl2br
- Parse
- RemoveRule
- RemoveSmiley
- SetAllowAmpersand
- SetDebug
- SetDefaultRule
- SetDefaultRules
- SetDefaultSmiley
- SetDefaultSmileys
- SetDetectURLs
- SetEnableSmileys
- SetIgnoreNewlines
- SetLimit
- SetLimitPrecision
- SetLimitTail
- SetLocalImgDir
- SetLocalImgURL
- SetPlainMode
- SetPostTrim
- SetPreTrim
- SetRoot
- SetRootBlock
- SetRootInline
- SetRuleHTML
- SetSmileyDir
- SetSmileyURL
- SetTagMarker
- SetURLPattern
- SetURLTarget
- SetURLTargetable
- SetWikiURL
- UnHTMLEncode
- WasLimited
- Wikify
Questions?
We're always happy to help with code or other questions you might have! Search our developer docs, contact support, or connect with our sales team.