static function error_check($text, &$errors) { global $futurebb_user, $futurebb_config; static $filter_data, $filter_domains; if (!$futurebb_user['g_post_links'] && preg_match('%\\[url.*?\\]%', $text)) { $errors[] = translate('nolinks'); } if (!$futurebb_user['g_post_images'] && preg_match('%\\[img.*?\\]%', $text)) { $errors[] = translate('noimgs'); } if (!isset($filter_data)) { $filter_data = explode('|', $futurebb_config['imghostrestriction']); } if (!$futurebb_user['g_mod_privs'] && !$futurebb_user['g_admin_privs'] && $filter_data[0] != 'none') { if (!isset($filter_domains)) { $filter_domains = explode("\n", $filter_data[1]); } preg_match_all('%\\[img\\](.*?)\\[/img\\]%', $text, $matches); foreach ($matches[1] as $url) { if (!preg_match('%^(ht|f)tps?://%', $url)) { $url = 'http://' . $url; } $parse = parse_url($url); $host = $parse['host']; if ($filter_data[0] == 'blacklist') { foreach ($filter_domains as $domain) { if (preg_match('%' . preg_quote($domain) . '$%', $host)) { $errors[] = translate('imgblacklisterror', $url, implode(', ', $filter_domains)); break; } } } else { if ($filter_data[0] == 'whitelist') { $ok = false; foreach ($filter_domains as $domain) { if (preg_match('%' . preg_quote($domain) . '$%', $host)) { $ok = true; } } if (!$ok) { $errors[] = translate('imgwhitelisterror', $url, implode(', ', $filter_domains)); } } } } } if (empty(self::$tags)) { self::$tags = array('b', 'i', 'u', 's', 'color', 'colour', 'url', 'img', 'quote', 'code', 'list', '\\*', 'table', 'tr', 'td', 'th'); } if (preg_match_all('%\\[(' . implode('|', self::$tags) . ')=(.*?)(\\[|\\])\\]%', $text, $matches)) { $errors[] = translate('bracketparam', $matches[1][0]); return; } //parsing rules $no_nest_tags = array('img'); $block_tags = array('quote', 'code', 'list', 'table', 'tr'); $inline_tags = array('b', 'i', 'u', 's', 'color', 'colour', 'url', 'img', '\\*', 'th', 'td'); $nest_only = array('table' => array('tr'), 'tr' => array('td', 'th'), 'list' => array('*')); //tags that can only have a specific set of subtags $nest_forbid = array('td' => array('td', 'th'), 'th' => array('td', 'th')); $no_body = array('table', 'tr', 'list'); //tags that can't have text inside them $bbcode_parts = preg_split('%(\\[[\\*a-zA-Z0-9-/]*?(?:=.*?)?\\])%', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); //this regular expression was copied from FluxBB. However, everything used to parse it is completely original //split the message into tags and check syntax $open_tags = array(); $last_key = 0; $quotes = 0; foreach ($bbcode_parts as $key => $val) { if (preg_match('%^\\[/(' . implode('|', self::$tags) . ')\\]$%', $val, $matches)) { //closing tag of some sort if ($last_key == 0) { $errors[] = translate('closenoopen', $matches[1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); return; } if ($open_tags[$last_key - 1] != 'code' && $matches[1] != $open_tags[$last_key - 1]) { //if it's not a [code] tag, ignore it $errors[] = translate('expectedfound', $open_tags[$last_key - 1], $matches[1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); return; } if (!($open_tags[$last_key - 1] == 'code' && $matches[1] != $open_tags[$last_key - 1])) { //close the tag in the tag stack if it's not a mismatch inside a [code] tag (like [code][/tr][/code]) if ($open_tags[$last_key - 1] == 'quote') { $quotes--; } unset($open_tags[$last_key - 1]); $last_key--; } } else { if (!($last_key > 0 && $open_tags[$last_key - 1] == 'code') && preg_match('%^\\[(' . implode('|', self::$tags) . ')(=.*?)?\\]$%', $val, $matches)) { //opening tag of some sort $open_tags[$last_key] = $matches[1]; //check if there are any block tags inside inline tags if ($last_key > 0 && in_array($open_tags[$last_key - 1], $inline_tags) && in_array($matches[1], $block_tags)) { $errors[] = translate('blockininline', $matches[1], $open_tags[$last_key - 1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); } //check for the tags that only allow specific tags directly inside them if ($last_key > 0 && array_key_exists($open_tags[$last_key - 1], $nest_only) && !in_array($open_tags[$last_key], $nest_only[$open_tags[$last_key - 1]])) { $errors[] = translate('specificnestingerror', $matches[1], $open_tags[$last_key - 1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); } if ($last_key > 0 && array_key_exists($open_tags[$last_key - 1], $nest_forbid) && in_array($open_tags[$last_key], $nest_forbid[$open_tags[$last_key - 1]])) { $errors[] = translate('specificnestingerror', $matches[1], $open_tags[$last_key - 1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); } //check if there is any bbcode inside a tag which can't nest if ($last_key > 0 && in_array($open_tags[$last_key - 1], $no_nest_tags)) { $errors[] = translate('nonesting', $open_tags[$last_key - 1]); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); } if ($open_tags[$last_key] == 'quote') { $quotes++; if ($quotes > $futurebb_config['max_quote_depth']) { $errors[] = translate('toomanynestedquotes', $futurebb_config['max_quote_depth']); $errors[] = self::highlight_error($text, $matches[0], $bbcode_parts, $key); } } $last_key++; } else { if ($last_key > 0) { //no tag, just text if (!preg_match('%^\\s+$%ms', $val) && in_array($open_tags[$last_key - 1], $no_body)) { $errors[] = translate('notextinsidetag', $open_tags[$last_key - 1]); $errors[] = self::highlight_error($text, $val, $bbcode_parts, $key); } } } } } if (sizeof($open_tags) > 0) { $location_notices = array(); foreach ($open_tags as &$val) { //find the last occurrence of this tag $reverse_parts = array_reverse($bbcode_parts); foreach ($reverse_parts as $partkey => $part) { if (strpos($part, '[' . $val) === 0) { $location_notices[] = self::highlight_error($text, $part, $bbcode_parts, sizeof($bbcode_parts) - $partkey - 1, 'tagwasopened', $val); break; } } //bold this so it goes on the error list $val = '<b>[' . $val . ']</b>'; } $errors[] = translate('tagsnotclosed', implode(', ', $open_tags)); $errors = array_merge($errors, $location_notices); } }