Example #1
0
function sort_array(&$array)
{
    for ($i = 0; $i < sizeof($array); $i++) {
        $min = return_indice_min($array, $i);
        permute($array[$i], $array[$min]);
    }
}
Example #2
0
function permute($prefix, $str, $array)
{
    $n = strlen($str);
    if ($n == 0) {
        $array[$prefix] = 1;
    } else {
        for ($i = 0; $i < $n; $i++) {
            permute($prefix . $str[$i], substr($str, 0, $i) . substr($str, $i + 1, $n), $array);
        }
    }
}
/**
 * Returns the Permutations of a String
 */
function permute($in, $startPos, $endPos)
{
    if (strlen($in) == 1 || $startPos == $endPos) {
        print $in . "\n";
        return $in;
    } else {
        for ($a = $startPos; $a < $endPos; $a++) {
            $in = swap($in, $startPos, $a);
            permute($in, $startPos + 1, $endPos);
        }
    }
}
Example #4
0
function generate($key)
{
    echo "    input key: ";
    dumpkey($key);
    echo " permuted key: ";
    $permuted = permute($key);
    dumpkey($permuted);
    echo "   CRC'ed key: ";
    $crc = crc($permuted);
    dumpkey($crc);
    return $crc;
}
function permute($str, $i, $n)
{
    if ($i == $n) {
        print "{$str}\n";
    } else {
        for ($j = $i; $j < $n; $j++) {
            swap($str, $i, $j);
            permute($str, $i + 1, $n);
            swap($str, $i, $j);
            // backtrack.
        }
    }
}
function permute($arg)
{
    $array = is_string($arg) ? str_split($arg) : $arg;
    if (1 === count($array)) {
        return $array;
    }
    $result = array();
    foreach ($array as $key => $item) {
        foreach (permute(array_diff_key($array, array($key => $item))) as $p) {
            $result[] = $item . $p;
        }
    }
    return $result;
}
Example #7
0
function permute($items, $perms = array())
{
    if (empty($items)) {
        var_dump($perms);
    } else {
        for ($i = count($items) - 1; $i >= 0; --$i) {
            $newitems = $items;
            $newperms = $perms;
            list($foo) = array_splice($newitems, $i, 1);
            array_unshift($newperms, $foo);
            permute($newitems, $newperms);
        }
    }
}
Example #8
0
 public function score($input)
 {
     $high_score = 0;
     $edges = $this->get_edges($input);
     $names = array_keys($edges);
     $permuted_names = permute($names);
     $high_score = 0;
     foreach ($permuted_names as $seating) {
         $seating_score = $this->score_seating($seating, $edges);
         if ($seating_score > $high_score) {
             $high_score = $seating_score;
         }
     }
     return $high_score;
 }
Example #9
0
function permute($list)
{
    if (count($list) == 1) {
        return array($list);
    }
    $permutations = array();
    for ($l = 0; $l < count($list); $l++) {
        $copy = $list;
        $head = array_splice($copy, $l, 1);
        $remaining = permute($copy);
        foreach ($remaining as $tail) {
            $permutations[] = array_merge($head, $tail);
        }
    }
    return $permutations;
}
Example #10
0
function permute($str, $i, $n, &$arr)
{
    //, &$arr)  {
    if (!is_array($arr)) {
        $arr = array();
    }
    if ($i == $n) {
        $arr[] = $str;
        // print "$str\n";
    } else {
        for ($j = $i; $j < $n; $j++) {
            swap($str, $i, $j);
            permute($str, $i + 1, $n, $arr);
            swap($str, $i, $j);
        }
    }
}
Example #11
0
function permute($list, $sofar = array())
{
    global $combos;
    if (count($list) == 0) {
        $combos[] = $sofar;
        return;
    }
    $output = array();
    foreach ($list as $i => $l) {
        $a = $list;
        $b = $sofar;
        $b[] = $l;
        array_splice($a, $i, 1);
        permute($a, $b);
    }
    return $output;
}
Example #12
0
function returnHappiness($input)
{
    $table = processInput($input);
    $results = [];
    $combinations = permute(array_keys($table));
    foreach ($combinations as $c) {
        $happiness = 0;
        for ($i = 0; $i < strlen($c); $i++) {
            $personA = $c[$i];
            $personB = $i === strlen($c) - 1 ? $c[0] : $c[$i + 1];
            $happiness = happiness($happiness, $table[$personA][$personB]);
            $happiness = happiness($happiness, $table[$personB][$personA]);
        }
        // echo $c . ': ' . $happiness . '<br>';
        $results[] = $happiness;
    }
    return $results;
}
Example #13
0
function permute($str)
{
    /* If we only have a single character, return it */
    if (strlen($str) < 2) {
        return array($str);
    }
    /* Initialize the return value */
    $permutations = array();
    /* Copy the string except for the first character */
    $tail = substr($str, 1);
    /* Loop through the permutations of the substring created above */
    foreach (permute($tail) as $permutation) {
        $length = strlen($permutation);
        /* Loop through the permutation and insert the first character of the original
           string between the two parts and store it in the result array */
        for ($i = 0; $i <= $length; $i++) {
            $permutations[] = substr($permutation, 0, $i) . $str[0] . substr($permutation, $i);
        }
    }
    /* Return the result */
    return $permutations;
}
Example #14
0
function permute($str, $i, $n, &$arr)
{
    //, &$arr)  {
    if (!is_array($arr)) {
        $arr = array();
    }
    if ($i == $n) {
        $arr[] = $str;
        // print "$str\n";
    } else {
        for ($j = $i; $j < $n; $j++) {
            // if($str{$i} == $str{$j})	{
            // 	// continue;
            // 	echo "<br>i=" . $i . ", j=" . $j . "; charI = " . $str{$i} . ", charJ = " . $str{$j};
            // }
            if ($str[$i] != $str[$j] && $i === $j) {
                swap($str, $i, $j);
                permute($str, $i + 1, $n, $arr);
                swap($str, $i, $j);
            }
        }
    }
}
Example #15
0
<?php

$input = "43\n3\n4\n10\n21\n44\n4\n6\n47\n41\n34\n17\n17\n44\n36\n31\n46\n9\n27\n38";
$perms = array();
$containers = arsort(explode("\n", $input));
function permute($items, $perms = array())
{
    for ($i = 0; $i < count($items); $i++) {
        $perm = array();
        $max = 150;
        $total = 0;
        while ($total <= $max) {
            # code...
        }
    }
}
$perms = permute($containers);
var_dump($perms);
Example #16
0
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
{
    global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc;
    static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
    static $disabled;
    // Don't waste cycles
    if ($message === '') {
        return '';
    }
    // Never show smileys for wireless clients.  More bytes, can't see it anyway :P.
    if (WIRELESS) {
        $smileys = false;
    } elseif ($smileys !== null && ($smileys == '1' || $smileys == '0')) {
        $smileys = (bool) $smileys;
    }
    if (empty($modSettings['enableBBC']) && $message !== false) {
        if ($smileys === true) {
            parsesmileys($message);
        }
        return $message;
    }
    // Just in case it wasn't determined yet whether UTF-8 is enabled.
    if (!isset($context['utf8'])) {
        $context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
    }
    // If we are not doing every tag then we don't cache this run.
    if (!empty($parse_tags) && !empty($bbc_codes)) {
        $temp_bbc = $bbc_codes;
        $bbc_codes = array();
    }
    // Ohara youtube embed
    $message = OYTE_Preparse($message);
    // Sift out the bbc for a performance improvement.
    if (empty($bbc_codes) || $message === false || !empty($parse_tags)) {
        if (!empty($modSettings['disabledBBC'])) {
            $temp = explode(',', strtolower($modSettings['disabledBBC']));
            foreach ($temp as $tag) {
                $disabled[trim($tag)] = true;
            }
        }
        if (empty($modSettings['enableEmbeddedFlash'])) {
            $disabled['flash'] = true;
        }
        /* The following bbc are formatted as an array, with keys as follows:
        
        			tag: the tag's name - should be lowercase!
        
        			type: one of...
        				- (missing): [tag]parsed content[/tag]
        				- unparsed_equals: [tag=xyz]parsed content[/tag]
        				- parsed_equals: [tag=parsed data]parsed content[/tag]
        				- unparsed_content: [tag]unparsed content[/tag]
        				- closed: [tag], [tag/], [tag /]
        				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
        				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
        				- unparsed_equals_content: [tag=...]unparsed content[/tag]
        
        			parameters: an optional array of parameters, for the form
        			  [tag abc=123]content[/tag].  The array is an associative array
        			  where the keys are the parameter names, and the values are an
        			  array which may contain the following:
        				- match: a regular expression to validate and match the value.
        				- quoted: true if the value should be quoted.
        				- validate: callback to evaluate on the data, which is $data.
        				- value: a string in which to replace $1 with the data.
        				  either it or validate may be used, not both.
        				- optional: true if the parameter is optional.
        
        			test: a regular expression to test immediately after the tag's
        			  '=', ' ' or ']'.  Typically, should have a \] at the end.
        			  Optional.
        
        			content: only available for unparsed_content, closed,
        			  unparsed_commas_content, and unparsed_equals_content.
        			  $1 is replaced with the content of the tag.  Parameters
        			  are replaced in the form {param}.  For unparsed_commas_content,
        			  $2, $3, ..., $n are replaced.
        
        			before: only when content is not used, to go before any
        			  content.  For unparsed_equals, $1 is replaced with the value.
        			  For unparsed_commas, $1, $2, ..., $n are replaced.
        
        			after: similar to before in every way, except that it is used
        			  when the tag is closed.
        
        			disabled_content: used in place of content when the tag is
        			  disabled.  For closed, default is '', otherwise it is '$1' if
        			  block_level is false, '<div>$1</div>' elsewise.
        
        			disabled_before: used in place of before when disabled.  Defaults
        			  to '<div>' if block_level, '' if not.
        
        			disabled_after: used in place of after when disabled.  Defaults
        			  to '</div>' if block_level, '' if not.
        
        			block_level: set to true the tag is a "block level" tag, similar
        			  to HTML.  Block level tags cannot be nested inside tags that are
        			  not block level, and will not be implicitly closed as easily.
        			  One break following a block level tag may also be removed.
        
        			trim: if set, and 'inside' whitespace after the begin tag will be
        			  removed.  If set to 'outside', whitespace after the end tag will
        			  meet the same fate.
        
        			validate: except when type is missing or 'closed', a callback to
        			  validate the data as $data.  Depending on the tag's type, $data
        			  may be a string or an array of strings (corresponding to the
        			  replacement.)
        
        			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
        			  may be not set, 'optional', or 'required' corresponding to if
        			  the content may be quoted.  This allows the parser to read
        			  [tag="abc]def[esdf]"] properly.
        
        			require_parents: an array of tag names, or not set.  If set, the
        			  enclosing tag *must* be one of the listed tags, or parsing won't
        			  occur.
        
        			require_children: similar to require_parents, if set children
        			  won't be parsed if they are not in the list.
        
        			disallow_children: similar to, but very different from,
        			  require_children, if it is set the listed tags will not be
        			  parsed inside the tag.
        
        			parsed_tags_allowed: an array restricting what BBC can be in the
        			  parsed_equals parameter, if desired.
        		*/
        $codes = array(array('tag' => 'abbr', 'type' => 'unparsed_equals', 'before' => '<abbr title="$1">', 'after' => '</abbr>', 'quoted' => 'optional', 'disabled_after' => ' ($1)'), array('tag' => 'acronym', 'type' => 'unparsed_equals', 'before' => '<acronym title="$1">', 'after' => '</acronym>', 'quoted' => 'optional', 'disabled_after' => ' ($1)'), array('tag' => 'anchor', 'type' => 'unparsed_equals', 'test' => '[#]?([A-Za-z][A-Za-z0-9_\\-]*)\\]', 'before' => '<span id="post_$1">', 'after' => '</span>'), array('tag' => 'b', 'before' => '<strong>', 'after' => '</strong>'), array('tag' => 'bdo', 'type' => 'unparsed_equals', 'before' => '<bdo dir="$1">', 'after' => '</bdo>', 'test' => '(rtl|ltr)\\]', 'block_level' => true), array('tag' => 'black', 'before' => '<span style="color: black;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'blue', 'before' => '<span style="color: blue;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'br', 'type' => 'closed', 'content' => '<br />'), array('tag' => 'center', 'before' => '<div align="center">', 'after' => '</div>', 'block_level' => true), array('tag' => 'code', 'type' => 'unparsed_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': <a href="javascript:void(0);" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''), 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
					global $context;

					if (!isset($disabled[\'code\']))
					{
						$php_parts = preg_split(\'~(&lt;\\?php|\\?&gt;)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE);

						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
						{
							// Do PHP code coloring?
							if ($php_parts[$php_i] != \'&lt;?php\')
								continue;

							$php_string = \'\';
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
							{
								$php_string .= $php_parts[$php_i];
								$php_parts[$php_i++] = \'\';
							}
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
						}

						// Fix the PHP code stuff...
						$data = str_replace("<pre style=\\"display: inline;\\">\\t</pre>", "\\t", implode(\'\', $php_parts));

						// Older browsers are annoying, aren\'t they?
						if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
							$data = str_replace("\\t", "<pre style=\\"display: inline;\\">\\t</pre>", $data);
						else
							$data = str_replace("\\t", "<span style=\\"white-space: pre;\\">\\t</span>", $data);

						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
						if ($context[\'browser\'][\'is_opera\'])
							$data .= \'&nbsp;\';
					}'), 'block_level' => true), array('tag' => 'code', 'type' => 'unparsed_equals_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2) <a href="#" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''), 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
					global $context;

					if (!isset($disabled[\'code\']))
					{
						$php_parts = preg_split(\'~(&lt;\\?php|\\?&gt;)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);

						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
						{
							// Do PHP code coloring?
							if ($php_parts[$php_i] != \'&lt;?php\')
								continue;

							$php_string = \'\';
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
							{
								$php_string .= $php_parts[$php_i];
								$php_parts[$php_i++] = \'\';
							}
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
						}

						// Fix the PHP code stuff...
						$data[0] = str_replace("<pre style=\\"display: inline;\\">\\t</pre>", "\\t", implode(\'\', $php_parts));

						// Older browsers are annoying, aren\'t they?
						if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
							$data[0] = str_replace("\\t", "<pre style=\\"display: inline;\\">\\t</pre>", $data[0]);
						else
							$data[0] = str_replace("\\t", "<span style=\\"white-space: pre;\\">\\t</span>", $data[0]);

						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
						if ($context[\'browser\'][\'is_opera\'])
							$data[0] .= \'&nbsp;\';
					}'), 'block_level' => true), array('tag' => 'color', 'type' => 'unparsed_equals', 'test' => '(#[\\da-fA-F]{3}|#[\\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\\(\\d{1,3}, ?\\d{1,3}, ?\\d{1,3}\\))\\]', 'before' => '<span style="color: $1;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'email', 'type' => 'unparsed_content', 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>', 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'<br />\' => \'\'));')), array('tag' => 'email', 'type' => 'unparsed_equals', 'before' => '<a href="mailto:$1" class="bbc_email">', 'after' => '</a>', 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'flash', 'type' => 'unparsed_commas_content', 'test' => '\\d+,\\d+\\]', 'content' => $context['browser']['is_ie'] && !$context['browser']['is_mac_ie'] ? '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$2" height="$3"><param name="movie" value="$1" /><param name="play" value="true" /><param name="loop" value="true" /><param name="quality" value="high" /><param name="AllowScriptAccess" value="never" /><embed src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed></object>' : '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed>', 'validate' => create_function('&$tag, &$data, $disabled', '
					if (isset($disabled[\'url\']))
						$tag[\'content\'] = \'$1\';
					elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0)
						$data[0] = \'http://\' . $data[0];
				'), 'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>'), array('tag' => 'font', 'type' => 'unparsed_equals', 'test' => '[A-Za-z0-9_,\\-\\s]+?\\]', 'before' => '<span style="font-family: $1;" class="bbc_font">', 'after' => '</span>'), array('tag' => 'ftp', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_ftp new_win" target="_blank">$1</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					$data = strtr($data, array(\'<br />\' => \'\'));
					if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
						$data = \'ftp://\' . $data;
				')), array('tag' => 'ftp', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_ftp new_win" target="_blank">', 'after' => '</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
						$data = \'ftp://\' . $data;
				'), 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'glow', 'type' => 'unparsed_commas', 'test' => '[#0-9a-zA-Z\\-]{3,12},([012]\\d{1,2}|\\d{1,2})(,[^]]+)?\\]', 'before' => $context['browser']['is_ie'] ? '<table border="0" cellpadding="0" cellspacing="0" style="display: inline; vertical-align: middle; font: inherit;"><tr><td style="filter: Glow(color=$1, strength=$2); font: inherit;">' : '<span style="text-shadow: $1 1px 1px 1px">', 'after' => $context['browser']['is_ie'] ? '</td></tr></table> ' : '</span>'), array('tag' => 'green', 'before' => '<span style="color: green;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'html', 'type' => 'unparsed_content', 'content' => '$1', 'block_level' => true, 'disabled_content' => '$1'), array('tag' => 'hr', 'type' => 'closed', 'content' => '<hr />', 'block_level' => true), array('tag' => 'i', 'before' => '<em>', 'after' => '</em>'), array('tag' => 'img', 'type' => 'unparsed_content', 'parameters' => array('alt' => array('optional' => true), 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\\d+)'), 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\\d+)')), 'content' => '<img src="$1" alt="{alt}"{width}{height} class="bbc_img resized" />', 'validate' => create_function('&$tag, &$data, $disabled', '
					$data = strtr($data, array(\'<br />\' => \'\'));
					if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				'), 'disabled_content' => '($1)'), array('tag' => 'img', 'type' => 'unparsed_content', 'content' => '<img src="$1" alt="" class="bbc_img" />', 'validate' => create_function('&$tag, &$data, $disabled', '
					$data = strtr($data, array(\'<br />\' => \'\'));
					if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				'), 'disabled_content' => '($1)'), array('tag' => 'iurl', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_link">$1</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					$data = strtr($data, array(\'<br />\' => \'\'));
					if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				')), array('tag' => 'iurl', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_link">', 'after' => '</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					if (substr($data, 0, 1) == \'#\')
						$data = \'#post_\' . substr($data, 1);
					elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				'), 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'left', 'before' => '<div style="text-align: left;">', 'after' => '</div>', 'block_level' => true), array('tag' => 'li', 'before' => '<li>', 'after' => '</li>', 'trim' => 'outside', 'require_parents' => array('list'), 'block_level' => true, 'disabled_before' => '', 'disabled_after' => '<br />'), array('tag' => 'list', 'before' => '<ul class="bbc_list">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li', 'list'), 'block_level' => true), array('tag' => 'list', 'parameters' => array('type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)')), 'before' => '<ul class="bbc_list" style="list-style-type: {type};">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li'), 'block_level' => true), array('tag' => 'ltr', 'before' => '<div dir="ltr">', 'after' => '</div>', 'block_level' => true), array('tag' => 'me', 'type' => 'unparsed_equals', 'before' => '<div class="meaction">* $1 ', 'after' => '</div>', 'quoted' => 'optional', 'block_level' => true, 'disabled_before' => '/me ', 'disabled_after' => '<br />'), array('tag' => 'move', 'before' => '<marquee>', 'after' => '</marquee>', 'block_level' => true, 'disallow_children' => array('move')), array('tag' => 'nobbc', 'type' => 'unparsed_content', 'content' => '$1'), array('tag' => 'php', 'type' => 'unparsed_content', 'content' => '<span class="phpcode">$1</span>', 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', '
					if (!isset($disabled[\'php\']))
					{
						$add_begin = substr(trim($data), 0, 5) != \'&lt;?\';
						$data = highlight_php_code($add_begin ? \'&lt;?php \' . $data . \'?&gt;\' : $data);
						if ($add_begin)
							$data = preg_replace(array(\'~^(.+?)&lt;\\?.{0,40}?php(?:&nbsp;|\\s)~\', \'~\\?&gt;((?:</(font|span)>)*)$~\'), \'$1\', $data, 2);
					}'), 'block_level' => false, 'disabled_content' => '$1'), array('tag' => 'pre', 'before' => '<pre>', 'after' => '</pre>'), array('tag' => 'quote', 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote'] . '</div></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>', 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)', 'quoted' => true)), 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>', 'block_level' => true), array('tag' => 'quote', 'type' => 'parsed_equals', 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': $1</div></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>', 'quoted' => 'optional', 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '([^<>]{1,192}?)'), 'link' => array('match' => '(?:board=\\d+;)?((?:topic|threadid)=[\\dmsg#\\./]{1,40}(?:;start=[\\dmsg#\\./]{1,40})?|action=profile;u=\\d+)'), 'date' => array('match' => '(\\d+)', 'validate' => 'timeformat')), 'before' => '<div class="quoteheader"><div class="topslice_quote"><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></div></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>', 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)')), 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>', 'block_level' => true), array('tag' => 'red', 'before' => '<span style="color: red;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'right', 'before' => '<div style="text-align: right;">', 'after' => '</div>', 'block_level' => true), array('tag' => 'rtl', 'before' => '<div dir="rtl">', 'after' => '</div>', 'block_level' => true), array('tag' => 's', 'before' => '<del>', 'after' => '</del>'), array('tag' => 'shadow', 'type' => 'unparsed_commas', 'test' => '[#0-9a-zA-Z\\-]{3,12},(left|right|top|bottom|[0123]\\d{0,2})\\]', 'before' => $context['browser']['is_ie'] ? '<span style="display: inline-block; filter: Shadow(color=$1, direction=$2); height: 1.2em;">' : '<span style="text-shadow: $1 $2">', 'after' => '</span>', 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', '
					if ($data[1] == \'left\')
						$data[1] = 270;
					elseif ($data[1] == \'right\')
						$data[1] = 90;
					elseif ($data[1] == \'top\')
						$data[1] = 0;
					elseif ($data[1] == \'bottom\')
						$data[1] = 180;
					else
						$data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', '
					if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50))
						$data[1] = \'0 -2px 1px\';
					elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100))
						$data[1] = \'2px 0 1px\';
					elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190))
						$data[1] = \'0 2px 1px\';
					elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280))
						$data[1] = \'-2px 0 1px\';
					else
						$data[1] = \'1px 1px 1px\';')), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '([1-9][\\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\\.[1-9]|[1-9](\\.[\\d][\\d]?)?)?em)\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>'), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '[1-7]\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>', 'validate' => create_function('&$tag, &$data, $disabled', '
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
					$data = $sizes[$data] . \'em\';')), array('tag' => 'sub', 'before' => '<sub>', 'after' => '</sub>'), array('tag' => 'sup', 'before' => '<sup>', 'after' => '</sup>'), array('tag' => 'table', 'before' => '<table class="bbc_table">', 'after' => '</table>', 'trim' => 'inside', 'require_children' => array('tr'), 'block_level' => true), array('tag' => 'td', 'before' => '<td>', 'after' => '</td>', 'require_parents' => array('tr'), 'trim' => 'outside', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'time', 'type' => 'unparsed_content', 'content' => '$1', 'validate' => create_function('&$tag, &$data, $disabled', '
					if (is_numeric($data))
						$data = timeformat($data);
					else
						$tag[\'content\'] = \'[time]$1[/time]\';')), array('tag' => 'tr', 'before' => '<tr>', 'after' => '</tr>', 'require_parents' => array('table'), 'require_children' => array('td'), 'trim' => 'both', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'tt', 'before' => '<tt class="bbc_tt">', 'after' => '</tt>'), array('tag' => 'u', 'before' => '<span class="bbc_u">', 'after' => '</span>'), array('tag' => 'url', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					$data = strtr($data, array(\'<br />\' => \'\'));
					if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				')), array('tag' => 'url', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_link" target="_blank">', 'after' => '</a>', 'validate' => create_function('&$tag, &$data, $disabled', '
					if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
						$data = \'http://\' . $data;
				'), 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'white', 'before' => '<span style="color: white;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'nsfw', 'before' => '<figure class="nsfw"><figcaption><span><strong>NSFW</strong> content. </span><a>Click to show.</a></figcaption><div class="holder" style="display:none;">', 'after' => '</div></figure>'), array('tag' => 'video', 'type' => 'unparsed_content', 'content' => '<video controls autoplay loop muted style="max-width:100%; height:auto; margin:10px 10px 10px 0;" ><source src="$1" /></video>'));
        // Let mods add new BBC without hassle.
        call_integration_hook('integrate_bbc_codes', array(&$codes));
        // This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
        if ($message === false) {
            if (isset($temp_bbc)) {
                $bbc_codes = $temp_bbc;
            }
            return $codes;
        }
        // So the parser won't skip them.
        $itemcodes = array('*' => 'disc', '@' => 'disc', '+' => 'square', 'x' => 'square', '#' => 'square', 'o' => 'circle', 'O' => 'circle', '0' => 'circle');
        if (!isset($disabled['li']) && !isset($disabled['list'])) {
            foreach ($itemcodes as $c => $dummy) {
                $bbc_codes[$c] = array();
            }
        }
        // Inside these tags autolink is not recommendable.
        $no_autolink_tags = array('url', 'iurl', 'ftp', 'email');
        // Shhhh!
        if (!isset($disabled['color'])) {
            $codes[] = array('tag' => 'chrissy', 'before' => '<span style="color: #cc0099;">', 'after' => ' :-*</span>');
            $codes[] = array('tag' => 'kissy', 'before' => '<span style="color: #cc0099;">', 'after' => ' :-*</span>');
        }
        // BBC [you] tag. ;)
        $codes[] = array('tag' => 'you', 'type' => 'closed', 'content' => !$context['user']['is_guest'] ? $context['user']['name'] : (!empty($txt[28]) ? $txt[28] : $txt['guest']));
        foreach ($codes as $code) {
            // If we are not doing every tag only do ones we are interested in.
            if (empty($parse_tags) || in_array($code['tag'], $parse_tags)) {
                $bbc_codes[substr($code['tag'], 0, 1)][] = $code;
            }
        }
        $codes = null;
    }
    // Shall we take the time to cache this?
    if ($cache_id != '' && !empty($modSettings['cache_enable']) && ($modSettings['cache_enable'] >= 2 && strlen($message) > 1000 || strlen($message) > 2400) && empty($parse_tags)) {
        // It's likely this will change if the message is modified.
        $cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . serialize($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
        if (($temp = cache_get_data($cache_key, 240)) != null) {
            return $temp;
        }
        $cache_t = microtime();
    }
    if ($smileys === 'print') {
        // [glow], [shadow], and [move] can't really be printed.
        $disabled['glow'] = true;
        $disabled['shadow'] = true;
        $disabled['move'] = true;
        // Colors can't well be displayed... supposed to be black and white.
        $disabled['color'] = true;
        $disabled['black'] = true;
        $disabled['blue'] = true;
        $disabled['white'] = true;
        $disabled['red'] = true;
        $disabled['green'] = true;
        $disabled['me'] = true;
        // Color coding doesn't make sense.
        $disabled['php'] = true;
        // Links are useless on paper... just show the link.
        $disabled['ftp'] = true;
        $disabled['url'] = true;
        $disabled['iurl'] = true;
        $disabled['email'] = true;
        $disabled['flash'] = true;
        // !!! Change maybe?
        if (!isset($_GET['images'])) {
            $disabled['img'] = true;
        }
        // !!! Interface/setting to add more?
    }
    $open_tags = array();
    $message = strtr($message, array("\n" => '<br />'));
    // The non-breaking-space looks a bit different each time.
    $non_breaking_space = $context['utf8'] ? $context['server']['complex_preg_chars'] ? '\\x{A0}' : " " : '\\xA0';
    // This saves time by doing our break long words checks here.
    if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) {
        if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror']) {
            $breaker = '<span style="margin: 0 -0.5ex 0 0;"> </span>';
        } elseif ($context['browser']['is_opera']) {
            $breaker = '<span style="margin: 0 -0.65ex 0 -1px;"> </span>';
        } else {
            $breaker = '<span style="width: 0; margin: 0 -0.6ex 0 -1px;"> </span>';
        }
        // PCRE will not be happy if we don't give it a short.
        $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']);
    }
    $pos = -1;
    while ($pos !== false) {
        $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
        $pos = strpos($message, '[', $pos + 1);
        // Failsafe.
        if ($pos === false || $last_pos > $pos) {
            $pos = strlen($message) + 1;
        }
        // Can't have a one letter smiley, URL, or email! (sorry.)
        if ($last_pos < $pos - 1) {
            // Make sure the $last_pos is not negative.
            $last_pos = max($last_pos, 0);
            // Pick a block of data to do some raw fixing on.
            $data = substr($message, $last_pos, $pos - $last_pos);
            // Take care of some HTML!
            if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false) {
                $data = preg_replace('~&lt;a\\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\\S+?)\\1&gt;~i', '[url=$2]', $data);
                $data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
                // <br /> should be empty.
                $empty_tags = array('br', 'hr');
                foreach ($empty_tags as $tag) {
                    $data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
                }
                // b, u, i, s, pre... basic tags.
                $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
                foreach ($closable_tags as $tag) {
                    $diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
                    $data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
                    if ($diff > 0) {
                        $data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
                    }
                }
                // Do <img ... /> - with security... action= -> action-.
                preg_match_all('~&lt;img\\s+src=((?:&quot;)?)((?:https?://|ftps?://)\\S+?)\\1(?:\\s+alt=(&quot;.*?&quot;|\\S*?))?(?:\\s?/)?&gt;~i', $data, $matches, PREG_PATTERN_ORDER);
                if (!empty($matches[0])) {
                    $replaces = array();
                    foreach ($matches[2] as $match => $imgtag) {
                        $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
                        // Remove action= from the URL - no funny business, now.
                        if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) {
                            $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
                        }
                        // Check if the image is larger than allowed.
                        if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) {
                            list($width, $height) = url_image_size($imgtag);
                            if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) {
                                $height = (int) ($modSettings['max_image_width'] * $height / $width);
                                $width = $modSettings['max_image_width'];
                            }
                            if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) {
                                $width = (int) ($modSettings['max_image_height'] * $width / $height);
                                $height = $modSettings['max_image_height'];
                            }
                            // Set the new image tag.
                            $replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
                        } else {
                            $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
                        }
                    }
                    $data = strtr($data, $replaces);
                }
            }
            if (!empty($modSettings['autoLinkUrls'])) {
                // Are we inside tags that should be auto linked?
                $no_autolink_area = false;
                if (!empty($open_tags)) {
                    foreach ($open_tags as $open_tag) {
                        if (in_array($open_tag['tag'], $no_autolink_tags)) {
                            $no_autolink_area = true;
                        }
                    }
                }
                // Don't go backwards.
                //!!! Don't think is the real solution....
                $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
                if ($pos < $lastAutoPos) {
                    $no_autolink_area = true;
                }
                $lastAutoPos = $pos;
                if (!$no_autolink_area) {
                    // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses.
                    if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false) {
                        // Switch out quotes really quick because they can cause problems.
                        $data = strtr($data, array('&#039;' => '\'', '&nbsp;' => $context['utf8'] ? " " : " ", '&quot;' => '>">', '"' => '<"<', '&lt;' => '<lt<'));
                        // Only do this if the preg survives.
                        if (is_string($result = preg_replace(array('~(?<=[\\s>\\.(;\'"]|^)((?:http|https)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\w\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i', '~(?<=[\\s>\\.(;\'"]|^)((?:ftp|ftps)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\w\\-_\\~%\\.@,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i', '~(?<=[\\s>(\'<]|^)(www(?:\\.[\\w\\-_]+)+(?::\\d+)?(?:/[\\w\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i'), array('[url]$1[/url]', '[ftp]$1[/ftp]', '[url=http://$1]$1[/url]'), $data))) {
                            $data = $result;
                        }
                        $data = strtr($data, array('\'' => '&#039;', $context['utf8'] ? " " : " " => '&nbsp;', '>">' => '&quot;', '<"<' => '"', '<lt<' => '&lt;'));
                    }
                    // Next, emails...
                    if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false) {
                        $data = preg_replace('~(?<=[\\?\\s' . $non_breaking_space . '\\[\\]()*\\\\;>]|^)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?,\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;|\\.(?:\\.|;|&nbsp;|\\s|$|<br />))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
                        $data = preg_replace('~(?<=<br />)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?\\.,;\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;)~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
                    }
                }
            }
            $data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
            if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) {
                // The idea is, find words xx long, and then replace them with xx + space + more.
                if ($smcFunc['strlen']($data) > $modSettings['fixLongWords']) {
                    // This is done in a roundabout way because $breaker has "long words" :P.
                    $data = strtr($data, array($breaker => '< >', '&nbsp;' => $context['utf8'] ? " " : " "));
                    $data = preg_replace_callback('~(?<=[>;:!? ' . $non_breaking_space . '\\]()]|^)([\\w' . ($context['utf8'] ? '\\pL' : '') . '\\.]{' . $modSettings['fixLongWords'] . ',})~' . ($context['utf8'] ? 'u' : ''), 'word_break__preg_callback', $data);
                    $data = strtr($data, array('< >' => $breaker, $context['utf8'] ? " " : " " => '&nbsp;'));
                }
            }
            // If it wasn't changed, no copying or other boring stuff has to happen!
            if ($data != substr($message, $last_pos, $pos - $last_pos)) {
                $message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
                // Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
                $old_pos = strlen($data) + $last_pos;
                $pos = strpos($message, '[', $last_pos);
                $pos = $pos === false ? $old_pos : min($pos, $old_pos);
            }
        }
        // Are we there yet?  Are we there yet?
        if ($pos >= strlen($message) - 1) {
            break;
        }
        $tags = strtolower(substr($message, $pos + 1, 1));
        if ($tags == '/' && !empty($open_tags)) {
            $pos2 = strpos($message, ']', $pos + 1);
            if ($pos2 == $pos + 2) {
                continue;
            }
            $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
            $to_close = array();
            $block_level = null;
            do {
                $tag = array_pop($open_tags);
                if (!$tag) {
                    break;
                }
                if (!empty($tag['block_level'])) {
                    // Only find out if we need to.
                    if ($block_level === false) {
                        array_push($open_tags, $tag);
                        break;
                    }
                    // The idea is, if we are LOOKING for a block level tag, we can close them on the way.
                    if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) {
                        foreach ($bbc_codes[$look_for[0]] as $temp) {
                            if ($temp['tag'] == $look_for) {
                                $block_level = !empty($temp['block_level']);
                                break;
                            }
                        }
                    }
                    if ($block_level !== true) {
                        $block_level = false;
                        array_push($open_tags, $tag);
                        break;
                    }
                }
                $to_close[] = $tag;
            } while ($tag['tag'] != $look_for);
            // Did we just eat through everything and not find it?
            if (empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)) {
                $open_tags = $to_close;
                continue;
            } elseif (!empty($to_close) && $tag['tag'] != $look_for) {
                if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) {
                    foreach ($bbc_codes[$look_for[0]] as $temp) {
                        if ($temp['tag'] == $look_for) {
                            $block_level = !empty($temp['block_level']);
                            break;
                        }
                    }
                }
                // We're not looking for a block level tag (or maybe even a tag that exists...)
                if (!$block_level) {
                    foreach ($to_close as $tag) {
                        array_push($open_tags, $tag);
                    }
                    continue;
                }
            }
            foreach ($to_close as $tag) {
                $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
                $pos += strlen($tag['after']) + 2;
                $pos2 = $pos - 1;
                // See the comment at the end of the big loop - just eating whitespace ;).
                if (!empty($tag['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
            }
            if (!empty($to_close)) {
                $to_close = array();
                $pos--;
            }
            continue;
        }
        // No tags for this character, so just keep going (fastest possible course.)
        if (!isset($bbc_codes[$tags])) {
            continue;
        }
        $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
        $tag = null;
        foreach ($bbc_codes[$tags] as $possible) {
            // Not a match?
            if (strtolower(substr($message, $pos + 1, strlen($possible['tag']))) != $possible['tag']) {
                continue;
            }
            $next_c = substr($message, $pos + 1 + strlen($possible['tag']), 1);
            // A test validation?
            if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + strlen($possible['tag']) + 1)) == 0) {
                continue;
            } elseif (!empty($possible['parameters'])) {
                if ($next_c != ' ') {
                    continue;
                }
            } elseif (isset($possible['type'])) {
                // Do we need an equal sign?
                if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') {
                    continue;
                }
                // Maybe we just want a /...
                if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + strlen($possible['tag']), 2) != '/]' && substr($message, $pos + 1 + strlen($possible['tag']), 3) != ' /]') {
                    continue;
                }
                // An immediate ]?
                if ($possible['type'] == 'unparsed_content' && $next_c != ']') {
                    continue;
                }
            } elseif ($next_c != ']') {
                continue;
            }
            // Check allowed tree?
            if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) {
                continue;
            } elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) {
                continue;
            } elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) {
                continue;
            }
            $pos1 = $pos + 1 + strlen($possible['tag']) + 1;
            // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
            if ($possible['tag'] == 'quote') {
                // Start with standard
                $quote_alt = false;
                foreach ($open_tags as $open_quote) {
                    // Every parent quote this quote has flips the styling
                    if ($open_quote['tag'] == 'quote') {
                        $quote_alt = !$quote_alt;
                    }
                }
                // Add a class to the quote to style alternating blockquotes
                $possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
            }
            // This is long, but it makes things much easier and cleaner.
            if (!empty($possible['parameters'])) {
                $preg = array();
                foreach ($possible['parameters'] as $p => $info) {
                    $preg[] = '(\\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . ')' . (empty($info['optional']) ? '' : '?');
                }
                // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right.
                $match = false;
                $orders = permute($preg);
                foreach ($orders as $p) {
                    if (preg_match('~^' . implode('', $p) . '\\]~i', substr($message, $pos1 - 1), $matches) != 0) {
                        $match = true;
                        break;
                    }
                }
                // Didn't match our parameter list, try the next possible.
                if (!$match) {
                    continue;
                }
                $params = array();
                for ($i = 1, $n = count($matches); $i < $n; $i += 2) {
                    $key = strtok(ltrim($matches[$i]), '=');
                    if (isset($possible['parameters'][$key]['value'])) {
                        $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
                    } elseif (isset($possible['parameters'][$key]['validate'])) {
                        $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
                    } else {
                        $params['{' . $key . '}'] = $matches[$i + 1];
                    }
                    // Just to make sure: replace any $ or { so they can't interpolate wrongly.
                    $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
                }
                foreach ($possible['parameters'] as $p => $info) {
                    if (!isset($params['{' . $p . '}'])) {
                        $params['{' . $p . '}'] = '';
                    }
                }
                $tag = $possible;
                // Put the parameters into the string.
                if (isset($tag['before'])) {
                    $tag['before'] = strtr($tag['before'], $params);
                }
                if (isset($tag['after'])) {
                    $tag['after'] = strtr($tag['after'], $params);
                }
                if (isset($tag['content'])) {
                    $tag['content'] = strtr($tag['content'], $params);
                }
                $pos1 += strlen($matches[0]) - 1;
            } else {
                $tag = $possible;
            }
            break;
        }
        // Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
        if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) {
            if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) {
                continue;
            }
            $tag = $itemcodes[substr($message, $pos + 1, 1)];
            // First let's set up the tree: it needs to be in a list, or after an li.
            if ($inside === null || $inside['tag'] != 'list' && $inside['tag'] != 'li') {
                $open_tags[] = array('tag' => 'list', 'after' => '</ul>', 'block_level' => true, 'require_children' => array('li'), 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
                $code = '<ul class="bbc_list">';
            } elseif ($inside['tag'] == 'li') {
                array_pop($open_tags);
                $code = '</li>';
            } else {
                $code = '';
            }
            // Now we open a new tag.
            $open_tags[] = array('tag' => 'li', 'after' => '</li>', 'trim' => 'outside', 'block_level' => true, 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
            // First, open the tag...
            $code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
            $pos += strlen($code) - 1 + 2;
            // Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
            $pos2 = strpos($message, '<br />', $pos);
            $pos3 = strpos($message, '[/', $pos);
            if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) {
                preg_match('~^(<br />|&nbsp;|\\s|\\[)+~', substr($message, $pos2 + 6), $matches);
                $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2);
                $open_tags[count($open_tags) - 2]['after'] = '</ul>';
            } else {
                // Move the li over, because we're not sure what we'll hit.
                $open_tags[count($open_tags) - 1]['after'] = '';
                $open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
            }
            continue;
        }
        // Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
        if ($tag === null && $inside !== null && !empty($inside['require_children'])) {
            array_pop($open_tags);
            $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
            $pos += strlen($inside['after']) - 1 + 2;
        }
        // No tag?  Keep looking, then.  Silly people using brackets without actual tags.
        if ($tag === null) {
            continue;
        }
        // Propagate the list to the child (so wrapping the disallowed tag won't work either.)
        if (isset($inside['disallow_children'])) {
            $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
        }
        // Is this tag disabled?
        if (isset($disabled[$tag['tag']])) {
            if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) {
                $tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
                $tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
                $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
            } elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) {
                $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
                $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
            } else {
                $tag['content'] = $tag['disabled_content'];
            }
        }
        // The only special case is 'html', which doesn't need to close things.
        if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) {
            $n = count($open_tags) - 1;
            while (empty($open_tags[$n]['block_level']) && $n >= 0) {
                $n--;
            }
            // Close all the non block level tags so this tag isn't surrounded by them.
            for ($i = count($open_tags) - 1; $i > $n; $i--) {
                $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
                $pos += strlen($open_tags[$i]['after']) + 2;
                $pos1 += strlen($open_tags[$i]['after']) + 2;
                // Trim or eat trailing stuff... see comment at the end of the big loop.
                if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
                array_pop($open_tags);
            }
        }
        // No type means 'parsed_content'.
        if (!isset($tag['type'])) {
            // !!! Check for end tag first, so people can say "I like that [i] tag"?
            $open_tags[] = $tag;
            $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
            $pos += strlen($tag['before']) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_content') {
            $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            if (!empty($tag['block_level']) && substr($data, 0, 6) == '<br />') {
                $data = substr($data, 6);
            }
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
            $last_pos = $pos + 1;
        } elseif ($tag['type'] == 'unparsed_equals_content') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] != 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            $data = array(substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), substr($message, $pos1, $pos2 - $pos1));
            if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '<br />') {
                $data[0] = substr($data[0], 6);
            }
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'closed') {
            $pos2 = strpos($message, ']', $pos);
            $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($tag['content']) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_commas_content') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            // We want $1 to be the content, and the rest to be csv.
            $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
            $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = $tag['content'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_commas') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = explode(',', substr($message, $pos1, $pos2 - $pos1));
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // Fix after, for disabled code mainly.
            foreach ($data as $k => $d) {
                $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
            }
            $open_tags[] = $tag;
            // Replace them out, $1, $2, $3, $4, etc.
            $code = $tag['before'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] != 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // For parsed content, we must recurse to avoid security problems.
            if ($tag['type'] != 'unparsed_equals') {
                $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
            }
            $tag['after'] = strtr($tag['after'], array('$1' => $data));
            $open_tags[] = $tag;
            $code = strtr($tag['before'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7));
            $pos += strlen($code) - 1 + 2;
        }
        // If this is block level, eat any breaks after it.
        if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '<br />') {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7);
        }
        // Are we trimming outside this tag?
        if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos + 1), $matches) != 0) {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
        }
    }
    // Close any remaining tags.
    while ($tag = array_pop($open_tags)) {
        $message .= "\n" . $tag['after'] . "\n";
    }
    // Parse the smileys within the parts where it can be done safely.
    if ($smileys === true) {
        $message_parts = explode("\n", $message);
        for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) {
            parsesmileys($message_parts[$i]);
        }
        $message = implode('', $message_parts);
    } else {
        $message = strtr($message, array("\n" => ''));
    }
    if (substr($message, 0, 1) == ' ') {
        $message = '&nbsp;' . substr($message, 1);
    }
    // Cleanup whitespace.
    $message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br />', '<br /> ' => '<br />&nbsp;', '&#13;' => "\n"));
    // Cache the output if it took some time...
    if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05) {
        cache_put_data($cache_key, $message, 240);
    }
    // If this was a force parse revert if needed.
    if (!empty($parse_tags)) {
        if (empty($temp_bbc)) {
            $bbc_codes = array();
        } else {
            $bbc_codes = $temp_bbc;
            unset($temp_bbc);
        }
    }
    return $message;
}
Example #17
0
 * 
 * 
 */
function swap(&$a, &$b)
{
    $temp = $a;
    $a = $b;
    $b = $temp;
}
function permute_swap($str, $i, $n)
{
    if ($i == $n - 1) {
        foreach ($str as $c) {
            echo $c;
        }
        echo "\n";
    } else {
        for ($j = $i; $j < $n; $j++) {
            // Backtracking
            swap($str[$i], $str[$j]);
            permute_swap($str, $i + 1, $n);
            swap($str[$i], $str[$j]);
        }
    }
}
function permute($str)
{
    permute_swap($str, 0, count($str));
}
permute(str_split($argv[1]));
Example #18
0
function permute($list)
{
    $result = array();
    $inner = function ($items, $perms = array()) use(&$result, &$inner) {
        if (!$items) {
            $result[] = $perms;
        } else {
            foreach ($items as $i => $item) {
                $newItems = $items;
                $newPerms = $perms;
                array_splice($newItems, $i, 1);
                array_unshift($newPerms, $item);
                $inner($newItems, $newPerms);
            }
        }
    };
    $inner($list);
    return $result;
}
for ($i = 7; $i > 3; $i--) {
    $numbers = range(1, $i);
    $permutations = permute($numbers);
    rsort($permutations);
    foreach ($permutations as $numberArr) {
        $number = implode('', $numberArr);
        if (isPrime($number)) {
            DU::show($number);
            exit;
        }
    }
}
Example #19
0
/**
 * Parse bulletin board code in a string, as well as smileys optionally.
 *
 * What it does:
 * - only parses bbc tags which are not disabled in disabledBBC.
 * - handles basic HTML, if enablePostHTML is on.
 * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
 * - only parses smileys if smileys is true.
 * - does nothing if the enableBBC setting is off.
 * - uses the cache_id as a unique identifier to facilitate any caching it may do.
 * - returns the modified message.
 *
 * @param string|false $message if false return list of enabled bbc codes
 * @param bool|string $smileys = true
 * @param string $cache_id = ''
 * @param string[]|null $parse_tags array of tags to parse, null for all
 * @return string
 */
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
{
    global $txt, $scripturl, $context, $modSettings, $user_info;
    // Static variables can't be touched by the benchmark script.
    // Instead of doing eval() (which I should do), I am just going to make them globals and access them like that.
    //static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
    //static $disabled, $default_disabled, $parse_tag_cache;
    global $bbc_codes, $itemcodes, $no_autolink_tags;
    global $disabled, $default_disabled, $parse_tag_cache;
    // Don't waste cycles
    if ($message === '') {
        return '';
    }
    // Clean up any cut/paste issues we may have
    $message = sanitizeMSCutPaste($message);
    // If the load average is too high, don't parse the BBC.
    if (!empty($modSettings['bbc']) && $modSettings['current_load'] >= $modSettings['bbc']) {
        $context['disabled_parse_bbc'] = true;
        return $message;
    }
    if ($smileys !== null && ($smileys == '1' || $smileys == '0')) {
        $smileys = (bool) $smileys;
    }
    if (empty($modSettings['enableBBC']) && $message !== false) {
        if ($smileys === true) {
            parsesmileys($message);
        }
        return $message;
    }
    // Allow addons access before entering the main parse_bbc loop
    call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
    // Sift out the bbc for a performance improvement.
    if (empty($bbc_codes) || $message === false) {
        if (!empty($modSettings['disabledBBC'])) {
            $temp = explode(',', strtolower($modSettings['disabledBBC']));
            foreach ($temp as $tag) {
                $disabled[trim($tag)] = true;
            }
        }
        /* The following bbc are formatted as an array, with keys as follows:
        
        			tag: the tag's name - should be lowercase!
        
        			type: one of...
        				- (missing): [tag]parsed content[/tag]
        				- unparsed_equals: [tag=xyz]parsed content[/tag]
        				- parsed_equals: [tag=parsed data]parsed content[/tag]
        				- unparsed_content: [tag]unparsed content[/tag]
        				- closed: [tag], [tag/], [tag /]
        				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
        				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
        				- unparsed_equals_content: [tag=...]unparsed content[/tag]
        
        			parameters: an optional array of parameters, for the form
        				[tag abc=123]content[/tag].  The array is an associative array
        				where the keys are the parameter names, and the values are an
        				array which may contain the following:
        					- match: a regular expression to validate and match the value.
        					- quoted: true if the value should be quoted.
        					- validate: callback to evaluate on the data, which is $data.
        					- value: a string in which to replace $1 with the data.
        					  either it or validate may be used, not both.
        					- optional: true if the parameter is optional.
        
        			test: a regular expression to test immediately after the tag's
        				'=', ' ' or ']'.  Typically, should have a \] at the end.
        				Optional.
        
        			content: only available for unparsed_content, closed,
        				unparsed_commas_content, and unparsed_equals_content.
        				$1 is replaced with the content of the tag.  Parameters
        				are replaced in the form {param}.  For unparsed_commas_content,
        				$2, $3, ..., $n are replaced.
        
        			before: only when content is not used, to go before any
        				content.  For unparsed_equals, $1 is replaced with the value.
        				For unparsed_commas, $1, $2, ..., $n are replaced.
        
        			after: similar to before in every way, except that it is used
        				when the tag is closed.
        
        			disabled_content: used in place of content when the tag is
        				disabled.  For closed, default is '', otherwise it is '$1' if
        				block_level is false, '<div>$1</div>' elsewise.
        
        			disabled_before: used in place of before when disabled.  Defaults
        				to '<div>' if block_level, '' if not.
        
        			disabled_after: used in place of after when disabled.  Defaults
        				to '</div>' if block_level, '' if not.
        
        			block_level: set to true the tag is a "block level" tag, similar
        				to HTML.  Block level tags cannot be nested inside tags that are
        				not block level, and will not be implicitly closed as easily.
        				One break following a block level tag may also be removed.
        
        			trim: if set, and 'inside' whitespace after the begin tag will be
        				removed.  If set to 'outside', whitespace after the end tag will
        				meet the same fate.
        
        			validate: except when type is missing or 'closed', a callback to
        				validate the data as $data.  Depending on the tag's type, $data
        				may be a string or an array of strings (corresponding to the
        				replacement.)
        
        			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
        				may be not set, 'optional', or 'required' corresponding to if
        				the content may be quoted.  This allows the parser to read
        				[tag="abc]def[esdf]"] properly.
        
        			require_parents: an array of tag names, or not set.  If set, the
        				enclosing tag *must* be one of the listed tags, or parsing won't
        				occur.
        
        			require_children: similar to require_parents, if set children
        				won't be parsed if they are not in the list.
        
        			disallow_children: similar to, but very different from,
        				require_children, if it is set the listed tags will not be
        				parsed inside the tag.
        
        			disallow_parents: similar to, but very different from,
        				require_parents, if it is set the listed tags will not be
        				parsed inside the tag.
        
        			parsed_tags_allowed: an array restricting what BBC can be in the
        				parsed_equals parameter, if desired.
        		*/
        $codes = array(array('tag' => 'abbr', 'type' => 'unparsed_equals', 'before' => '<abbr title="$1">', 'after' => '</abbr>', 'quoted' => 'optional', 'disabled_after' => ' ($1)'), array('tag' => 'anchor', 'type' => 'unparsed_equals', 'test' => '[#]?([A-Za-z][A-Za-z0-9_\\-]*)\\]', 'before' => '<span id="post_$1">', 'after' => '</span>'), array('tag' => 'b', 'before' => '<strong class="bbc_strong">', 'after' => '</strong>'), array('tag' => 'br', 'type' => 'closed', 'content' => '<br />'), array('tag' => 'center', 'before' => '<div class="centertext">', 'after' => '</div>', 'block_level' => true), array('tag' => 'code', 'type' => 'unparsed_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': <a href="javascript:void(0);" onclick="return elkSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div><pre class="bbc_code prettyprint">$1</pre>', 'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) {
            if (!isset($disabled['code'])) {
                $data = str_replace("\t", "<span class=\"tab\">\t</span>", $data);
            }
        }, 'block_level' => true), array('tag' => 'code', 'type' => 'unparsed_equals_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2) <a href="#" onclick="return elkSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div><pre class="bbc_code prettyprint">$1</pre>', 'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) {
            if (!isset($disabled['code'])) {
                $data[0] = str_replace("\t", "<span class=\"tab\">\t</span>", $data[0]);
            }
        }, 'block_level' => true), array('tag' => 'color', 'type' => 'unparsed_equals', 'test' => '(#[\\da-fA-F]{3}|#[\\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\\((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\s?,\\s?){2}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\))\\]', 'before' => '<span style="color: $1;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'email', 'type' => 'unparsed_content', 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
        }), array('tag' => 'email', 'type' => 'unparsed_equals', 'before' => '<a href="mailto:$1" class="bbc_email">', 'after' => '</a>', 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'footnote', 'before' => '<sup class="bbc_footnotes">%fn%', 'after' => '%fn%</sup>', 'disallow_parents' => array('footnote', 'code', 'anchor', 'url', 'iurl'), 'disallow_before' => '', 'disallow_after' => '', 'block_level' => true), array('tag' => 'font', 'type' => 'unparsed_equals', 'test' => '[A-Za-z0-9_,\\-\\s]+?\\]', 'before' => '<span style="font-family: $1;" class="bbc_font">', 'after' => '</span>'), array('tag' => 'ftp', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_ftp new_win" target="_blank">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'ftp://') !== 0 && strpos($data, 'ftps://') !== 0) {
                $data = 'ftp://' . $data;
            }
        }), array('tag' => 'ftp', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_ftp new_win" target="_blank">', 'after' => '</a>', 'validate' => function (&$tag, &$data, $disabled) {
            if (strpos($data, 'ftp://') !== 0 && strpos($data, 'ftps://') !== 0) {
                $data = 'ftp://' . $data;
            }
        }, 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'hr', 'type' => 'closed', 'content' => '<hr />', 'block_level' => true), array('tag' => 'i', 'before' => '<em>', 'after' => '</em>'), array('tag' => 'img', 'type' => 'unparsed_content', 'parameters' => array('alt' => array('optional' => true), 'width' => array('optional' => true, 'value' => 'width:100%;max-width:$1px;', 'match' => '(\\d+)'), 'height' => array('optional' => true, 'value' => 'max-height:$1px;', 'match' => '(\\d+)')), 'content' => '<img src="$1" alt="{alt}" style="{width}{height}" class="bbc_img resized" />', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disabled_content' => '($1)'), array('tag' => 'img', 'type' => 'unparsed_content', 'content' => '<img src="$1" alt="" class="bbc_img" />', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disabled_content' => '($1)'), array('tag' => 'iurl', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_link">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }), array('tag' => 'iurl', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_link">', 'after' => '</a>', 'validate' => function (&$tag, &$data, $disabled) {
            if ($data[0] === '#') {
                $data = '#post_' . substr($data, 1);
            } elseif (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'left', 'before' => '<div style="text-align: left;">', 'after' => '</div>', 'block_level' => true), array('tag' => 'li', 'before' => '<li>', 'after' => '</li>', 'trim' => 'outside', 'require_parents' => array('list'), 'block_level' => true, 'disabled_before' => '', 'disabled_after' => '<br />'), array('tag' => 'list', 'before' => '<ul class="bbc_list">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li', 'list'), 'block_level' => true), array('tag' => 'list', 'parameters' => array('type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)')), 'before' => '<ul class="bbc_list" style="list-style-type: {type};">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li'), 'block_level' => true), array('tag' => 'me', 'type' => 'unparsed_equals', 'before' => '<div class="meaction">&nbsp;$1 ', 'after' => '</div>', 'quoted' => 'optional', 'block_level' => true, 'disabled_before' => '/me ', 'disabled_after' => '<br />'), array('tag' => 'member', 'type' => 'unparsed_equals', 'test' => '[\\d*]', 'before' => '<span class="bbc_mention"><a href="' . $scripturl . '?action=profile;u=$1">@', 'after' => '</a></span>', 'disabled_before' => '@', 'disabled_after' => ''), array('tag' => 'nobbc', 'type' => 'unparsed_content', 'content' => '$1'), array('tag' => 'pre', 'before' => '<pre class="bbc_pre">', 'after' => '</pre>'), array('tag' => 'quote', 'before' => '<div class="quoteheader">' . $txt['quote'] . '</div><blockquote>', 'after' => '</blockquote>', 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)', 'quoted' => true)), 'before' => '<div class="quoteheader">' . $txt['quote_from'] . ': {author}</div><blockquote>', 'after' => '</blockquote>', 'block_level' => true), array('tag' => 'quote', 'type' => 'parsed_equals', 'before' => '<div class="quoteheader">' . $txt['quote_from'] . ': $1</div><blockquote>', 'after' => '</blockquote>', 'quoted' => 'optional', 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '([^<>]{1,192}?)'), 'link' => array('match' => '(?:board=\\d+;)?((?:topic|threadid)=[\\dmsg#\\./]{1,40}(?:;start=[\\dmsg#\\./]{1,40})?|msg=\\d{1,40}|action=profile;u=\\d+)'), 'date' => array('match' => '(\\d+)', 'validate' => 'htmlTime')), 'before' => '<div class="quoteheader"><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . ($modSettings['todayMod'] == 3 ? ' - ' : $txt['search_on']) . ' {date}</a></div><blockquote>', 'after' => '</blockquote>', 'block_level' => true), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)')), 'before' => '<div class="quoteheader">' . $txt['quote_from'] . ': {author}</div><blockquote>', 'after' => '</blockquote>', 'block_level' => true), array('tag' => 'right', 'before' => '<div style="text-align: right;">', 'after' => '</div>', 'block_level' => true), array('tag' => 's', 'before' => '<del>', 'after' => '</del>'), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '([1-9][\\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\\.[1-9]|[1-9](\\.[\\d][\\d]?)?)?em)\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>', 'disallow_parents' => array('size'), 'disallow_before' => '<span>', 'disallow_after' => '</span>'), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '[1-7]\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>', 'validate' => function (&$tag, &$data, $disabled) {
            $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
            $data = $sizes[$data] . 'em';
        }, 'disallow_parents' => array('size'), 'disallow_before' => '<span>', 'disallow_after' => '</span>'), array('tag' => 'spoiler', 'before' => '<span class="spoilerheader">' . $txt['spoiler'] . '</span><div class="spoiler"><div class="bbc_spoiler" style="display: none;">', 'after' => '</div></div>', 'block_level' => true), array('tag' => 'sub', 'before' => '<sub>', 'after' => '</sub>'), array('tag' => 'sup', 'before' => '<sup>', 'after' => '</sup>'), array('tag' => 'table', 'before' => '<div class="bbc_table_container"><table class="bbc_table">', 'after' => '</table></div>', 'trim' => 'inside', 'require_children' => array('tr'), 'block_level' => true), array('tag' => 'td', 'before' => '<td>', 'after' => '</td>', 'require_parents' => array('tr'), 'trim' => 'outside', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'th', 'before' => '<th>', 'after' => '</th>', 'require_parents' => array('tr'), 'trim' => 'outside', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'tr', 'before' => '<tr>', 'after' => '</tr>', 'require_parents' => array('table'), 'require_children' => array('td', 'th'), 'trim' => 'both', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'tt', 'before' => '<span class="bbc_tt">', 'after' => '</span>'), array('tag' => 'u', 'before' => '<span class="bbc_u">', 'after' => '</span>'), array('tag' => 'url', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }), array('tag' => 'url', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_link" target="_blank">', 'after' => '</a>', 'validate' => function (&$tag, &$data, $disabled) {
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'));
        // Inside these tags autolink is not recommendable.
        $no_autolink_tags = array('url', 'iurl', 'ftp', 'email');
        // So the parser won't skip them.
        $itemcodes = array('*' => 'disc', '@' => 'disc', '+' => 'square', 'x' => 'square', '#' => 'decimal', '0' => 'decimal', 'o' => 'circle', 'O' => 'circle');
        // Let addons add new BBC without hassle.
        call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags, &$itemcodes));
        // This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
        if ($message === false) {
            if (isset($temp_bbc)) {
                $bbc_codes = $temp_bbc;
            }
            return $codes;
        }
        if (!isset($disabled['li']) && !isset($disabled['list'])) {
            foreach ($itemcodes as $c => $dummy) {
                $bbc_codes[$c] = array();
            }
        }
        foreach ($codes as $code) {
            $bbc_codes[substr($code['tag'], 0, 1)][] = $code;
        }
    }
    // If we are not doing every enabled tag then create a cache for this parsing group.
    if ($parse_tags !== array() && is_array($parse_tags)) {
        $temp_bbc = $bbc_codes;
        $tags_cache_id = implode(',', $parse_tags);
        if (!isset($default_disabled)) {
            $default_disabled = isset($disabled) ? $disabled : array();
        }
        // Already cached, use it, otherwise create it
        if (isset($parse_tag_cache[$tags_cache_id])) {
            list($bbc_codes, $disabled) = $parse_tag_cache[$tags_cache_id];
        } else {
            foreach ($bbc_codes as $key_bbc => $bbc) {
                foreach ($bbc as $key_code => $code) {
                    if (!in_array($code['tag'], $parse_tags)) {
                        $disabled[$code['tag']] = true;
                        unset($bbc_codes[$key_bbc][$key_code]);
                    }
                }
            }
            $parse_tag_cache[$tags_cache_id] = array($bbc_codes, $disabled);
        }
    } elseif (isset($default_disabled)) {
        $disabled = $default_disabled;
    }
    // Shall we take the time to cache this?
    if ($cache_id != '' && !empty($modSettings['cache_enable']) && ($modSettings['cache_enable'] >= 2 && isset($message[1000]) || isset($message[2400])) && empty($parse_tags)) {
        // It's likely this will change if the message is modified.
        $cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . serialize($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
        if (($temp = cache_get_data($cache_key, 240)) != null) {
            return $temp;
        }
        $cache_t = microtime(true);
    }
    if ($smileys === 'print') {
        // Colors can't well be displayed... supposed to be black and white.
        $disabled['color'] = true;
        $disabled['me'] = true;
        // Links are useless on paper... just show the link.
        $disabled['url'] = true;
        $disabled['iurl'] = true;
        $disabled['email'] = true;
        // @todo Change maybe?
        if (!isset($_GET['images'])) {
            $disabled['img'] = true;
        }
        // @todo Interface/setting to add more?
    }
    $open_tags = array();
    $message = strtr($message, array("\n" => '<br />'));
    // The non-breaking-space looks a bit different each time.
    $non_breaking_space = '\\x{A0}';
    $pos = -1;
    while ($pos !== false) {
        $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
        $pos = strpos($message, '[', $pos + 1);
        // Failsafe.
        if ($pos === false || $last_pos > $pos) {
            $pos = strlen($message) + 1;
        }
        // Can't have a one letter smiley, URL, or email! (sorry.)
        if ($last_pos < $pos - 1) {
            // Make sure the $last_pos is not negative.
            $last_pos = max($last_pos, 0);
            // Pick a block of data to do some raw fixing on.
            $data = substr($message, $last_pos, $pos - $last_pos);
            // Take care of some HTML!
            if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false) {
                $data = preg_replace('~&lt;a\\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\\S+?)\\1&gt;~i', '[url=$2]', $data);
                $data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
                // <br /> should be empty.
                $empty_tags = array('br', 'hr');
                foreach ($empty_tags as $tag) {
                    $data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
                }
                // b, u, i, s, pre... basic tags.
                $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
                foreach ($closable_tags as $tag) {
                    $diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
                    $data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
                    if ($diff > 0) {
                        $data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
                    }
                }
                // Do <img ... /> - with security... action= -> action-.
                preg_match_all('~&lt;img\\s+src=((?:&quot;)?)((?:https?://|ftps?://)\\S+?)\\1(?:\\s+alt=(&quot;.*?&quot;|\\S*?))?(?:\\s?/)?&gt;~i', $data, $matches, PREG_PATTERN_ORDER);
                if (!empty($matches[0])) {
                    $replaces = array();
                    foreach ($matches[2] as $match => $imgtag) {
                        $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
                        // Remove action= from the URL - no funny business, now.
                        if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) {
                            $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
                        }
                        // Check if the image is larger than allowed.
                        // @todo - We should seriously look at deprecating some of this in favour of CSS resizing.
                        if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) {
                            // For images, we'll want this.
                            require_once SUBSDIR . '/Attachments.subs.php';
                            list($width, $height) = url_image_size($imgtag);
                            if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) {
                                $height = (int) ($modSettings['max_image_width'] * $height / $width);
                                $width = $modSettings['max_image_width'];
                            }
                            if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) {
                                $width = (int) ($modSettings['max_image_height'] * $width / $height);
                                $height = $modSettings['max_image_height'];
                            }
                            // Set the new image tag.
                            $replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
                        } else {
                            $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
                        }
                    }
                    $data = strtr($data, $replaces);
                }
            }
            if (!empty($modSettings['autoLinkUrls'])) {
                // Are we inside tags that should be auto linked?
                $no_autolink_area = false;
                if (!empty($open_tags)) {
                    foreach ($open_tags as $open_tag) {
                        if (in_array($open_tag['tag'], $no_autolink_tags)) {
                            $no_autolink_area = true;
                        }
                    }
                }
                // Don't go backwards.
                // @todo Don't think is the real solution....
                $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
                if ($pos < $lastAutoPos) {
                    $no_autolink_area = true;
                }
                $lastAutoPos = $pos;
                if (!$no_autolink_area) {
                    // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses.
                    if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false) {
                        // Switch out quotes really quick because they can cause problems.
                        $data = strtr($data, array('&#039;' => '\'', '&nbsp;' => " ", '&quot;' => '>">', '"' => '<"<', '&lt;' => '<lt<'));
                        // Only do this if the preg survives.
                        if (is_string($result = preg_replace(array('~(?<=[\\s>\\.(;\'"]|^)((?:http|https)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\p{L}\\p{N}\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\p{L}\\p{N}\\-_\\~%@\\?;=#}\\\\])~ui', '~(?<=[\\s>\\.(;\'"]|^)((?:ftp|ftps)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\w\\-_\\~%\\.@,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i', '~(?<=[\\s>(\'<]|^)(www(?:\\.[\\w\\-_]+)+(?::\\d+)?(?:/[\\p{L}\\p{N}\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\p{L}\\p{N}\\-_\\~%@\\?;=#}\\\\])~ui'), array('[url]$1[/url]', '[ftp]$1[/ftp]', '[url=http://$1]$1[/url]'), $data))) {
                            $data = $result;
                        }
                        $data = strtr($data, array('\'' => '&#039;', " " => '&nbsp;', '>">' => '&quot;', '<"<' => '"', '<lt<' => '&lt;'));
                    }
                    // Next, emails...
                    if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false) {
                        $data = preg_replace('~(?<=[\\?\\s' . $non_breaking_space . '\\[\\]()*\\\\;>]|^)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?,\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;|\\.(?:\\.|;|&nbsp;|\\s|$|<br />))~u', '[email]$1[/email]', $data);
                        $data = preg_replace('~(?<=<br />)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?\\.,;\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;)~u', '[email]$1[/email]', $data);
                    }
                }
            }
            $data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
            // If it wasn't changed, no copying or other boring stuff has to happen!
            if ($data != substr($message, $last_pos, $pos - $last_pos)) {
                $message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
                // Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
                $old_pos = strlen($data) + $last_pos;
                $pos = strpos($message, '[', $last_pos);
                $pos = $pos === false ? $old_pos : min($pos, $old_pos);
            }
        }
        // Are we there yet?  Are we there yet?
        if ($pos >= strlen($message) - 1) {
            break;
        }
        $tags = strtolower($message[$pos + 1]);
        if ($tags == '/' && !empty($open_tags)) {
            $pos2 = strpos($message, ']', $pos + 1);
            if ($pos2 == $pos + 2) {
                continue;
            }
            $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
            $to_close = array();
            $block_level = null;
            do {
                $tag = array_pop($open_tags);
                if (!$tag) {
                    break;
                }
                if (!empty($tag['block_level'])) {
                    // Only find out if we need to.
                    if ($block_level === false) {
                        array_push($open_tags, $tag);
                        break;
                    }
                    // The idea is, if we are LOOKING for a block level tag, we can close them on the way.
                    if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) {
                        foreach ($bbc_codes[$look_for[0]] as $temp) {
                            if ($temp['tag'] == $look_for) {
                                $block_level = !empty($temp['block_level']);
                                break;
                            }
                        }
                    }
                    if ($block_level !== true) {
                        $block_level = false;
                        array_push($open_tags, $tag);
                        break;
                    }
                }
                $to_close[] = $tag;
            } while ($tag['tag'] != $look_for);
            // Did we just eat through everything and not find it?
            if (empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)) {
                $open_tags = $to_close;
                continue;
            } elseif (!empty($to_close) && $tag['tag'] != $look_for) {
                if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) {
                    foreach ($bbc_codes[$look_for[0]] as $temp) {
                        if ($temp['tag'] == $look_for) {
                            $block_level = !empty($temp['block_level']);
                            break;
                        }
                    }
                }
                // We're not looking for a block level tag (or maybe even a tag that exists...)
                if (!$block_level) {
                    foreach ($to_close as $tag) {
                        array_push($open_tags, $tag);
                    }
                    continue;
                }
            }
            foreach ($to_close as $tag) {
                $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
                $pos += strlen($tag['after']) + 2;
                $pos2 = $pos - 1;
                // See the comment at the end of the big loop - just eating whitespace ;).
                if (!empty($tag['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
            }
            if (!empty($to_close)) {
                $to_close = array();
                $pos--;
            }
            continue;
        }
        // No tags for this character, so just keep going (fastest possible course.)
        if (!isset($bbc_codes[$tags])) {
            continue;
        }
        $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
        $tag = null;
        foreach ($bbc_codes[$tags] as $possible) {
            $pt_strlen = strlen($possible['tag']);
            // Not a match?
            if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag']) {
                continue;
            }
            $next_c = isset($message[$pos + 1 + $pt_strlen]) ? $message[$pos + 1 + $pt_strlen] : '';
            // A test validation?
            if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0) {
                continue;
            } elseif (!empty($possible['parameters'])) {
                if ($next_c != ' ') {
                    continue;
                }
            } elseif (isset($possible['type'])) {
                // Do we need an equal sign?
                if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') {
                    continue;
                }
                // Maybe we just want a /...
                if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]') {
                    continue;
                }
                // An immediate ]?
                if ($possible['type'] == 'unparsed_content' && $next_c != ']') {
                    continue;
                }
            } elseif ($next_c != ']') {
                continue;
            }
            // Check allowed tree?
            if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) {
                continue;
            } elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) {
                continue;
            } elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) {
                continue;
            } elseif (isset($possible['disallow_parents']) && ($inside !== null && in_array($inside['tag'], $possible['disallow_parents']))) {
                if (!isset($possible['disallow_before'], $possible['disallow_after'])) {
                    continue;
                }
                $possible['before'] = isset($possible['disallow_before']) ? $possible['disallow_before'] : $possible['before'];
                $possible['after'] = isset($possible['disallow_after']) ? $possible['disallow_after'] : $possible['after'];
            }
            $pos1 = $pos + 1 + $pt_strlen + 1;
            // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
            if ($possible['tag'] == 'quote') {
                // Start with standard
                $quote_alt = false;
                foreach ($open_tags as $open_quote) {
                    // Every parent quote this quote has flips the styling
                    if ($open_quote['tag'] == 'quote') {
                        $quote_alt = !$quote_alt;
                    }
                }
                // Add a class to the quote to style alternating blockquotes
                // @todo - Frankly it makes little sense to allow alternate blockquote
                // styling without also catering for alternate quoteheader styling.
                // I do remember coding that some time back, but it seems to have gotten
                // lost somewhere in the Elk processes.
                // Come to think of it, it may be better to append a second class rather
                // than alter the standard one.
                //  - Example: class="bbc_quote" and class="bbc_quote alt_quote".
                // This would mean simpler CSS for themes (like default) which do not use the alternate styling,
                // but would still allow it for themes that want it.
                $possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
            }
            // This is long, but it makes things much easier and cleaner.
            if (!empty($possible['parameters'])) {
                $preg = array();
                foreach ($possible['parameters'] as $p => $info) {
                    $preg[] = '(\\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . ')' . (empty($info['optional']) ? '' : '?');
                }
                // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way
                // of allowing any order of parameters but still parsing them right.
                $match = false;
                $orders = permute($preg);
                foreach ($orders as $p) {
                    if (preg_match('~^' . implode('', $p) . '\\]~i', substr($message, $pos1 - 1), $matches) != 0) {
                        $match = true;
                        break;
                    }
                }
                // Didn't match our parameter list, try the next possible.
                if (!$match) {
                    continue;
                }
                $params = array();
                for ($i = 1, $n = count($matches); $i < $n; $i += 2) {
                    $key = strtok(ltrim($matches[$i]), '=');
                    if (isset($possible['parameters'][$key]['value'])) {
                        $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
                    } elseif (isset($possible['parameters'][$key]['validate'])) {
                        $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
                    } else {
                        $params['{' . $key . '}'] = $matches[$i + 1];
                    }
                    // Just to make sure: replace any $ or { so they can't interpolate wrongly.
                    $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
                }
                foreach ($possible['parameters'] as $p => $info) {
                    if (!isset($params['{' . $p . '}'])) {
                        $params['{' . $p . '}'] = '';
                    }
                }
                $tag = $possible;
                // Put the parameters into the string.
                if (isset($tag['before'])) {
                    $tag['before'] = strtr($tag['before'], $params);
                }
                if (isset($tag['after'])) {
                    $tag['after'] = strtr($tag['after'], $params);
                }
                if (isset($tag['content'])) {
                    $tag['content'] = strtr($tag['content'], $params);
                }
                $pos1 += strlen($matches[0]) - 1;
            } else {
                $tag = $possible;
            }
            break;
        }
        // Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
        if ($smileys !== false && $tag === null && isset($message[$pos + 2]) && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] === ']' && !isset($disabled['list']) && !isset($disabled['li'])) {
            if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>'))) {
                continue;
            }
            $tag = $itemcodes[$message[$pos + 1]];
            // First let's set up the tree: it needs to be in a list, or after an li.
            if ($inside === null || $inside['tag'] != 'list' && $inside['tag'] != 'li') {
                $open_tags[] = array('tag' => 'list', 'after' => '</ul>', 'block_level' => true, 'require_children' => array('li'), 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
                $code = '<ul' . ($tag == '' ? '' : ' style="list-style-type: ' . $tag . '"') . ' class="bbc_list">';
            } elseif ($inside['tag'] == 'li') {
                array_pop($open_tags);
                $code = '</li>';
            } else {
                $code = '';
            }
            // Now we open a new tag.
            $open_tags[] = array('tag' => 'li', 'after' => '</li>', 'trim' => 'outside', 'block_level' => true, 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
            // First, open the tag...
            $code .= '<li>';
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
            $pos += strlen($code) - 1 + 2;
            // Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
            $pos2 = strpos($message, '<br />', $pos);
            $pos3 = strpos($message, '[/', $pos);
            if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) {
                preg_match('~^(<br />|&nbsp;|\\s|\\[)+~', substr($message, $pos2 + 6), $matches);
                $message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
                $open_tags[count($open_tags) - 2]['after'] = '</ul>';
            } else {
                // Move the li over, because we're not sure what we'll hit.
                $open_tags[count($open_tags) - 1]['after'] = '';
                $open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
            }
            continue;
        }
        // Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
        if ($tag === null && $inside !== null && !empty($inside['require_children'])) {
            array_pop($open_tags);
            $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
            $pos += strlen($inside['after']) - 1 + 2;
        }
        // No tag?  Keep looking, then.  Silly people using brackets without actual tags.
        if ($tag === null) {
            continue;
        }
        // Propagate the list to the child (so wrapping the disallowed tag won't work either.)
        if (isset($inside['disallow_children'])) {
            $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
        }
        // Is this tag disabled?
        if (isset($disabled[$tag['tag']])) {
            if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) {
                $tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
                $tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
                $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
            } elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) {
                $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
                $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
            } else {
                $tag['content'] = $tag['disabled_content'];
            }
        }
        // We use this alot
        $tag_strlen = strlen($tag['tag']);
        // The only special case is 'html', which doesn't need to close things.
        if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) {
            $n = count($open_tags) - 1;
            while (empty($open_tags[$n]['block_level']) && $n >= 0) {
                $n--;
            }
            // Close all the non block level tags so this tag isn't surrounded by them.
            for ($i = count($open_tags) - 1; $i > $n; $i--) {
                $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
                $ot_strlen = strlen($open_tags[$i]['after']);
                $pos += $ot_strlen + 2;
                $pos1 += $ot_strlen + 2;
                // Trim or eat trailing stuff... see comment at the end of the big loop.
                if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
                array_pop($open_tags);
            }
        }
        // No type means 'parsed_content'.
        if (!isset($tag['type'])) {
            // @todo Check for end tag first, so people can say "I like that [i] tag"?
            $open_tags[] = $tag;
            $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
            $pos += strlen($tag['before']) - 1 + 2;
        } elseif ($tag['type'] === 'unparsed_content') {
            $pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            if (!empty($tag['block_level']) && substr($data, 0, 6) === '<br />') {
                $data = substr($data, 6);
            }
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
            $pos += strlen($code) - 1 + 2;
            $last_pos = $pos + 1;
        } elseif ($tag['type'] === 'unparsed_equals_content') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] !== 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            $data = array(substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), substr($message, $pos1, $pos2 - $pos1));
            if (!empty($tag['block_level']) && substr($data[0], 0, 6) === '<br />') {
                $data[0] = substr($data[0], 6);
            }
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] === 'closed') {
            $pos2 = strpos($message, ']', $pos);
            $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($tag['content']) - 1 + 2;
        } elseif ($tag['type'] === 'unparsed_commas_content') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            // We want $1 to be the content, and the rest to be csv.
            $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
            $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = $tag['content'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] === 'unparsed_commas') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = explode(',', substr($message, $pos1, $pos2 - $pos1));
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // Fix after, for disabled code mainly.
            foreach ($data as $k => $d) {
                $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
            }
            $open_tags[] = $tag;
            // Replace them out, $1, $2, $3, $4, etc.
            $code = $tag['before'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] === 'unparsed_equals' || $tag['type'] === 'parsed_equals') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] !== 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // For parsed content, we must recurse to avoid security problems.
            if ($tag['type'] !== 'unparsed_equals') {
                $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
                // Unfortunately after we recurse, we must manually reset the static disabled tags to what they were
                parse_bbc('dummy');
            }
            $tag['after'] = strtr($tag['after'], array('$1' => $data));
            $open_tags[] = $tag;
            $code = strtr($tag['before'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7));
            $pos += strlen($code) - 1 + 2;
        }
        // If this is block level, eat any breaks after it.
        if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) === '<br />') {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7);
        }
        // Are we trimming outside this tag?
        if (!empty($tag['trim']) && $tag['trim'] !== 'outside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos + 1), $matches) != 0) {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
        }
    }
    // Close any remaining tags.
    while ($tag = array_pop($open_tags)) {
        $message .= "\n" . $tag['after'] . "\n";
    }
    // Parse the smileys within the parts where it can be done safely.
    if ($smileys === true) {
        $message_parts = explode("\n", $message);
        for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) {
            parsesmileys($message_parts[$i]);
        }
        $message = implode('', $message_parts);
    } else {
        $message = strtr($message, array("\n" => ''));
    }
    if (isset($message[0]) && $message[0] === ' ') {
        $message = '&nbsp;' . substr($message, 1);
    }
    // Cleanup whitespace.
    $message = strtr($message, array('  ' => '&nbsp; ', "\r" => '', "\n" => '<br />', '<br /> ' => '<br />&nbsp;', '&#13;' => "\n"));
    // Finish footnotes if we have any.
    if (strpos($message, '<sup class="bbc_footnotes">') !== false) {
        global $fn_num, $fn_content, $fn_count;
        static $fn_total;
        // @todo temporary until we have nesting
        $message = str_replace(array('[footnote]', '[/footnote]'), '', $message);
        $fn_num = 0;
        $fn_content = array();
        $fn_count = isset($fn_total) ? $fn_total : 0;
        // Replace our footnote text with a [1] link, save the text for use at the end of the message
        $message = preg_replace_callback('~(%fn%(.*?)%fn%)~is', 'footnote_callback', $message);
        $fn_total += $fn_num;
        // If we have footnotes, add them in at the end of the message
        if (!empty($fn_num)) {
            $message .= '<div class="bbc_footnotes">' . implode('', $fn_content) . '</div>';
        }
    }
    // Allow addons access to what parse_bbc created
    call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
    // Cache the output if it took some time...
    if (isset($cache_key, $cache_t) && microtime(true) - $cache_t > 0.05) {
        cache_put_data($cache_key, $message, 240);
    }
    // If this was a force parse revert if needed.
    if (!empty($parse_tags)) {
        if (empty($temp_bbc)) {
            $bbc_codes = array();
        } else {
            $bbc_codes = $temp_bbc;
            unset($temp_bbc);
        }
    }
    return $message;
}
Example #20
0
function sort_array(&$array1, &$array2)
{
    for ($i = 0; $i < sizeof($array1); $i++) {
        for ($j = 0; $j < sizeof($array1); $j++) {
            if ($array1[$i] > $array1[$j]) {
                permute($array1[$i], $array1[$j]);
                permute($array2[$i], $array2[$j]);
            }
        }
    }
}
Example #21
0
        if (empty($rest)) {
            $result[] = $res;
            return;
        }
        foreach ($rest as $val) {
            $newRes = $res;
            $newRes[] = $val;
            $rec($newRes, array_diff($rest, $newRes));
        }
    };
    $rec([], $arr);
    return $result;
}
$people = array_keys($sit);
$people[] = 'Self';
$permutations = permute($people);
function calcScore($seats)
{
    global $sit;
    $len = count($seats);
    $score = 0;
    for ($i = 0; $i < $len; $i++) {
        $self = $seats[$i];
        $left = $seats[($i - 1 + $len) % $len];
        $right = $seats[($i + 1 + $len) % $len];
        if ($left !== 'Self') {
            $score += $sit[$self][$left];
        }
        if ($right !== 'Self') {
            $score += $sit[$self][$right];
        }
                            continue;
                        }
                        $ra = new Regexp_Assemble();
                        $ra->insert($path[$x1])->insert($path[$x2])->insert($path[$x3])->insert($path[$x4])->insert($path[$x5]);
                        is_deeply($ra->__path, $target, 'join: /' . join('/ /', array(join('', $path[$x1]), join('', $path[$x2]), join('', $path[$x3]), join('', $path[$x4]), join('', $path[$x5]))) . '/\\n' . $ra->dump() . ' versus ' . $ra->_dump($target) . "\n");
                    }
                }
            }
        }
    }
}
permute(['a', ['' => 0, 'b' => ['b', ['' => 0, 'c' => ['c', ['' => 0, 'd' => ['d', ['' => 0, 'e' => ['e']]]]]]]]], [['a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd', 'e']]);
permute([['' => 0, 'a' => ['a', ['' => 0, 'b' => ['b', ['' => 0, 'c' => ['c', ['' => 0, 'd' => ['d']]]]]]]]], [[''], ['a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd']]);
permute(['d', 'o', ['n' => ['n', 'a', 't', ['e' => ['e'], 'i' => ['i', 'o', 'n']]], 't' => ['t', ['a' => ['a', 't', 'e'], 'i' => ['i', 'n', 'g']]], '' => 0]], [['d', 'o'], ['d', 'o', 'n', 'a', 't', 'e'], ['d', 'o', 'n', 'a', 't', 'i', 'o', 'n'], ['d', 'o', 't', 'a', 't', 'e'], ['d', 'o', 't', 'i', 'n', 'g']]);
permute(['o', ['' => 0, 'n' => ['n', ['' => 0, 'l' => ['l', 'y'], 'e' => ['e', ['' => 0, 'r' => ['r']]]]]]], [['o'], ['o', 'n'], ['o', 'n', 'e'], ['o', 'n', 'l', 'y'], ['o', 'n', 'e', 'r']]);
permute(['a', 'm', ['a' => ['a', ['s' => ['s', 's'], 'z' => ['z', 'e']]], 'u' => ['u', ['c' => ['c', 'k'], 's' => ['s', 'e']]], 'b' => ['b', 'l', 'e']]], [['a', 'm', 'a', 's', 's'], ['a', 'm', 'a', 'z', 'e'], ['a', 'm', 'b', 'l', 'e'], ['a', 'm', 'u', 'c', 'k'], ['a', 'm', 'u', 's', 'e']]);
$r = new Regexp_Assemble();
$r->Default_Lexer('\\([^(]*(?:\\([^)]*\\))?[^)]*\\)|.');
$r->reset()->add('ab(cd)ef');
is_deeply($r->__path, ['a', 'b', '(cd)', 'e', 'f'], 'ab(cd)ef (with Default parenthetical lexer)');
$r->reset()->add('ab((ef)gh)ij');
is_deeply($r->__path, ['a', 'b', '((ef)gh)', 'i', 'j'], 'ab((ef)gh)ij (with Default parenthetical lexer)');
$r->reset()->add('ab(ef(gh))ij');
is_deeply($r->__path, ['a', 'b', '(ef(gh))', 'i', 'j'], 'ab(ef(gh))ij (with Default parenthetical lexer)');
//    eval { $r->filter('choke') };
//    ok( $@, 'die on non-CODE filter' );
//    eval { $r->pre_filter('choke') };
//    ok( $@, 'die on non-CODE pre_filter' );
/*
is( $_, $fixed, '$_ has not been altered' );
*/
Example #23
0
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
{
    global $txt, $scripturl, $context, $modSettings, $user_info, $board;
    static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
    static $disabled;
    if ($cache_id != '' && stripos($cache_id, '|') > 0) {
        list($cache_id, $cache_unique) = explode('|', $cache_id);
    } else {
        $cache_unique =& $message;
    }
    //echo 'id=',$cache_id,' unique=',$cache_unique;
    // Don't waste cycles
    if ($message === '') {
        return '';
    }
    if ($smileys !== null && ($smileys == '1' || $smileys == '0')) {
        $smileys = (bool) $smileys;
    }
    if (empty($modSettings['enableBBC']) && $message !== false) {
        if ($smileys === true) {
            parsesmileys($message);
        }
        return $message;
    }
    // Just in case it wasn't determined yet whether UTF-8 is enabled.
    $context['utf8'] = true;
    // If we are not doing every tag then we don't cache this run.
    if (!empty($parse_tags) && !empty($bbc_codes)) {
        $temp_bbc = $bbc_codes;
        $bbc_codes = array();
    }
    // Sift out the bbc for a performance improvement.
    if (empty($bbc_codes) || $message === false || !empty($parse_tags)) {
        if (!empty($modSettings['disabledBBC'])) {
            $temp = explode(',', strtolower($modSettings['disabledBBC']));
            foreach ($temp as $tag) {
                $disabled[trim($tag)] = true;
            }
        }
        $disabled['flash'] = true;
        /* The following bbc are formatted as an array, with keys as follows:
        
        			tag: the tag's name - should be lowercase!
        
        			type: one of...
        				- (missing): [tag]parsed content[/tag]
        				- unparsed_equals: [tag=xyz]parsed content[/tag]
        				- parsed_equals: [tag=parsed data]parsed content[/tag]
        				- unparsed_content: [tag]unparsed content[/tag]
        				- closed: [tag], [tag/], [tag /]
        				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
        				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
        				- unparsed_equals_content: [tag=...]unparsed content[/tag]
        
        			parameters: an optional array of parameters, for the form
        			  [tag abc=123]content[/tag].  The array is an associative array
        			  where the keys are the parameter names, and the values are an
        			  array which may contain the following:
        				- match: a regular expression to validate and match the value.
        				- quoted: true if the value should be quoted.
        				- validate: callback to evaluate on the data, which is $data.
        				- value: a string in which to replace $1 with the data.
        				  either it or validate may be used, not both.
        				- optional: true if the parameter is optional.
        
        			test: a regular expression to test immediately after the tag's
        			  '=', ' ' or ']'.  Typically, should have a \] at the end.
        			  Optional.
        
        			content: only available for unparsed_content, closed,
        			  unparsed_commas_content, and unparsed_equals_content.
        			  $1 is replaced with the content of the tag.  Parameters
        			  are replaced in the form {param}.  For unparsed_commas_content,
        			  $2, $3, ..., $n are replaced.
        
        			before: only when content is not used, to go before any
        			  content.  For unparsed_equals, $1 is replaced with the value.
        			  For unparsed_commas, $1, $2, ..., $n are replaced.
        
        			after: similar to before in every way, except that it is used
        			  when the tag is closed.
        
        			disabled_content: used in place of content when the tag is
        			  disabled.  For closed, default is '', otherwise it is '$1' if
        			  block_level is false, '<div>$1</div>' elsewise.
        
        			disabled_before: used in place of before when disabled.  Defaults
        			  to '<div>' if block_level, '' if not.
        
        			disabled_after: used in place of after when disabled.  Defaults
        			  to '</div>' if block_level, '' if not.
        
        			block_level: set to true the tag is a "block level" tag, similar
        			  to HTML.  Block level tags cannot be nested inside tags that are
        			  not block level, and will not be implicitly closed as easily.
        			  One break following a block level tag may also be removed.
        
        			trim: if set, and 'inside' whitespace after the begin tag will be
        			  removed.  If set to 'outside', whitespace after the end tag will
        			  meet the same fate.
        
        			validate: except when type is missing or 'closed', a callback to
        			  validate the data as $data.  Depending on the tag's type, $data
        			  may be a string or an array of strings (corresponding to the
        			  replacement.)
        
        			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
        			  may be not set, 'optional', or 'required' corresponding to if
        			  the content may be quoted.  This allows the parser to read
        			  [tag="abc]def[esdf]"] properly.
        
        			require_parents: an array of tag names, or not set.  If set, the
        			  enclosing tag *must* be one of the listed tags, or parsing won't
        			  occur.
        
        			require_children: similar to require_parents, if set children
        			  won't be parsed if they are not in the list.
        
        			disallow_children: similar to, but very different from,
        			  require_children, if it is set the listed tags will not be
        			  parsed inside the tag.
        
        			parsed_tags_allowed: an array restricting what BBC can be in the
        			  parsed_equals parameter, if desired.
        		*/
        $codes = array(array('tag' => 'abbr', 'type' => 'unparsed_equals', 'before' => '<abbr title="$1">', 'after' => '</abbr>', 'quoted' => 'optional', 'disabled_after' => ' ($1)'), array('tag' => 'acronym', 'type' => 'unparsed_equals', 'before' => '<acronym title="$1">', 'after' => '</acronym>', 'quoted' => 'optional', 'disabled_after' => ' ($1)'), array('tag' => 'align', 'type' => 'unparsed_equals', 'before' => '<div style="text-align:$1;">', 'after' => '</div>', 'test' => '(left|right|center|justify)\\]', 'block_level' => true), array('tag' => 'anchor', 'type' => 'unparsed_equals', 'test' => '[#]?([A-Za-z][A-Za-z0-9_\\-]*)\\]', 'before' => '<span id="post_$1">', 'after' => '</span>'), array('tag' => 'b', 'before' => '<strong>', 'after' => '</strong>'), array('tag' => 'bdo', 'type' => 'unparsed_equals', 'before' => '<bdo dir="$1">', 'after' => '</bdo>', 'test' => '(rtl|ltr)\\]', 'block_level' => true), array('tag' => 'br', 'type' => 'closed', 'content' => '<br />'), array('tag' => 'code', 'type' => 'unparsed_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': </div><pre class="prettyprint lang-php linenums:1">$1</pre>', 'block_level' => true), array('tag' => 'code', 'type' => 'unparsed_equals_content', 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2)</div><pre class="prettyprint lang-$2 linenums:1">$1</pre>', 'block_level' => true), array('tag' => 'color', 'type' => 'unparsed_equals', 'test' => '(#[\\da-fA-F]{3}|#[\\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\\(\\d{1,3}, ?\\d{1,3}, ?\\d{1,3}\\))\\]', 'before' => '<span style="color: $1;" class="bbc_color">', 'after' => '</span>'), array('tag' => 'columns', 'type' => 'unparsed_equals', 'test' => '([1-9])\\]', 'before' => '<div class="bbc_columns" style="column-count:$1;-webkit-column-count:$1;-moz-column-count:$1;">', 'block_level' => true, 'after' => '</div>'), array('tag' => 'css', 'type' => 'unparsed_equals', 'test' => '([a-zA-Z1-9_-\\s]+?)\\]', 'before' => '<div class="$1">', 'block_level' => true, 'after' => '</div>'), array('tag' => 'yt', 'type' => 'unparsed_content', 'validate' => function (&$tag, &$data, $disabled) {
            global $txt, $context;
            $link = !is_array($data) ? $data : $data[0];
            $link = trim(strtr($link, array('<br />' => '')));
            if (preg_match('~^(?:http://((?:www|au|br|ca|es|fr|de|hk|ie|in|il|it|jp|kr|mx|nl|nz|pl|ru|tw|uk)\\.)?youtube\\.com/(?:[^"]*?)(?:(?:video_)?id=|(?:v|p)(?:/|=)))?([0-9a-f]{16}|[0-9a-z-_]{11})~i' . 'u', $link, $matches)) {
                $site = !empty($matches[1]) ? strtolower($matches[1]) : 'www.';
                $type = strlen($matches[2]) == 11 ? 1 : 0;
                if (!is_array($data) || ($data[1] > 780 || $data[1] < 100 || $data[2] > 780 || $data[2] < 100)) {
                    $data = array(0, 425, $type ? 350 : 355);
                }
                $data[0] = $matches[2];
                unset($matches, $link);
                // Set the Content (With conditions on disabled types of BBCode)
                if (isset($disabled['url']) && isset($disabled['youtube'])) {
                    $tag['content'] = "http://" . $site . "youtube.com/" . ($type ? "watch?v" : "view_play_list?p") . "=" . $data[0];
                } elseif (isset($disabled['youtube'])) {
                    $tag['content'] = "<a href=\"http://" . $site . "youtube.com/" . ($type ? "watch?v" : "view_play_list?p") . "=" . $data[0] . "\" target=\"_blank\">http://" . $site . "youtube.com/" . ($type ? "watch?v" : "view_play_list?p") . "=" . $data[0] . "</a>";
                } else {
                    $tag['content'] = '';
                    $tag['content'] = "<div class=\"blue_container mediumpadding\" style=\"width:auto;margin:auto;text-align:center;\"><iframe style=\"width:640px;height:385px;border:0;\" class=\"youtube-player\" src=\"http://www.youtube.com/embed/" . $data[0] . "\"></iframe></div>";
                    //$tag[\'content\'] = "<div class=\"blue_container mediumpadding\" style=\"text-align:center;\"><a rel=\"prettyPhoto\" href=\"http://www.youtube.com/watch?v=".$data[0]."?width=640&height=385\"><img src=\"http://img.youtube.com/vi/".$data[0]."/0.jpg\" alt=\"thumb\" /></a></div>";
                }
            } else {
                $tag['content'] = $txt['youtube_invalid'];
            }
        }, 'disabled_content' => '$1'), array('tag' => 'email', 'type' => 'unparsed_content', 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
        }), array('tag' => 'email', 'type' => 'unparsed_equals', 'before' => '<a href="mailto:$1" class="bbc_email">', 'after' => '</a>', 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'), array('tag' => 'font', 'type' => 'unparsed_equals', 'test' => '[A-Za-z0-9_,\\-\\s]+?\\]', 'before' => '<span style="font-family: $1;" class="bbc_font">', 'after' => '</span>'), array('tag' => 'html', 'type' => 'unparsed_content', 'content' => '$1', 'block_level' => true, 'disabled_content' => '$1'), array('tag' => 'h', 'type' => 'unparsed_equals', 'test' => '([1-4])\\]', 'before' => '<span class="bbc_head l$1">', 'block_level' => false, 'after' => '</span>'), array('tag' => 'hr', 'type' => 'closed', 'content' => '<hr />', 'block_level' => true), array('tag' => 'i', 'before' => '<em>', 'after' => '</em>'), array('tag' => 'img', 'type' => 'unparsed_content', 'parameters' => array('alt' => array('optional' => true), 'width' => array('optional' => true, 'value' => 'width:$1px;', 'match' => '(\\d+)'), 'height' => array('optional' => true, 'value' => 'height:$1px;', 'match' => '(\\d+)'), 'resized' => array('optional' => true, 'value' => '_$1', 'match' => '(\\d+)')), 'content' => '<div class="bbc_img_resizer" style="display:none;">' . $txt['img_resizebar_msg'] . '</div><img style="{width}{height}" src="$1" alt="{alt}" class="bbc_img resize{resized}" />', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disabled_content' => '($1)'), array('tag' => 'img', 'type' => 'unparsed_content', 'content' => '<img src="$1" alt="" class="bbc_img" />', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disabled_content' => '($1)'), array('tag' => 'li', 'before' => '<li>', 'after' => '</li>', 'trim' => 'outside', 'require_parents' => array('list'), 'block_level' => true, 'disabled_before' => '', 'disabled_after' => '<br />'), array('tag' => 'list', 'before' => '<ul class="bbc_list">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li', 'list'), 'block_level' => true), array('tag' => 'list', 'parameters' => array('type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)')), 'before' => '<ul class="bbc_list" style="list-style-type: {type};">', 'after' => '</ul>', 'trim' => 'inside', 'require_children' => array('li'), 'block_level' => true), array('tag' => 'ilink', 'before' => '<a class="bbc_link" href="' . $scripturl . '?topic={topic};msg={post}#msg{post}">', 'after' => '</a>', 'parameters' => array('topic' => array('match' => '([^<>]{1,192}?)'), 'post' => array('match' => '([^<>]{1,192}?)'))), array('tag' => 'ltr', 'before' => '<div dir="ltr">', 'after' => '</div>', 'block_level' => true), array('tag' => 'me', 'type' => 'unparsed_equals', 'before' => '<div class="meaction">* $1 ', 'after' => '</div>', 'quoted' => 'optional', 'block_level' => true, 'disabled_before' => '/me ', 'disabled_after' => '<br />'), array('tag' => 'nobbc', 'type' => 'unparsed_content', 'content' => '$1'), array('tag' => 'php', 'type' => 'unparsed_content', 'content' => '<div class="codeheader">PHP:</div><pre class="prettyprint lang-php linenums:1">$1</pre>', 'block_level' => false, 'disabled_content' => '$1'), array('tag' => 'pre', 'before' => '<pre><code class="bbc_code">', 'after' => '</code></pre>'), array('tag' => 'quote', 'before' => '<div class="quoteheader">' . $txt['quote'] . '</div><blockquote>', 'after' => '</blockquote><div class="quotefooter"></div>', 'block_level' => true, 'disallow_children' => array('yt')), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)', 'quoted' => true)), 'before' => '<div class="quoteheader">{author} ' . $txt['said'] . ':</div><blockquote>', 'after' => '</blockquote><div class="quotefooter"></div>', 'block_level' => true, 'disallow_children' => array('yt')), array('tag' => 'quote', 'type' => 'parsed_equals', 'before' => '<div class="quoteheader">$1 ' . $txt['said'] . ':</div><blockquote>', 'after' => '</blockquote><div class="quotefooter"></div>', 'quoted' => 'optional', 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'), 'block_level' => true, 'disallow_children' => array('yt')), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '([^<>]{1,192}?)'), 'link' => array('match' => '(?:board=\\d+;)?((?:topic|threadid)=[\\dmsg#\\./]{1,40}(?:;start=[\\dmsg#\\./]{1,40})?|action=profile;u=\\d+)'), 'date' => array('match' => '(\\d+)', 'validate' => 'timeformat')), 'before' => '<div class="quoteheader"><a href="' . $scripturl . '?{link}">{author} ' . $txt['said'] . ', {date}</a></div><blockquote>', 'after' => '</blockquote><div class="quotefooter"></div>', 'block_level' => true, 'disallow_children' => array('yt')), array('tag' => 'quote', 'parameters' => array('author' => array('match' => '(.{1,192}?)')), 'before' => '<div class="quoteheader">{author} ' . $txt['said'] . ':</div><blockquote>', 'after' => '</blockquote><div class="quotefooter"></div>', 'block_level' => true, 'disallow_children' => array('yt')), array('tag' => 'rtl', 'before' => '<div dir="rtl">', 'after' => '</div>', 'block_level' => true), array('tag' => 's', 'before' => '<del>', 'after' => '</del>'), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '([1-9][\\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\\.[1-9]|[1-9](\\.[\\d][\\d]?)?)?em)\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>'), array('tag' => 'size', 'type' => 'unparsed_equals', 'test' => '[1-7]\\]', 'before' => '<span style="font-size: $1;" class="bbc_size">', 'after' => '</span>', 'validate' => function (&$tag, &$data, $disabled) {
            $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
            $data = $sizes[$data] . 'em';
        }), array('tag' => 'spoiler', 'before' => '<div class="spoiler head">' . $txt['spoiler_title'] . '</div><div class="spoiler content" style="display:none">', 'after' => '</div>', 'block_level' => true), array('tag' => 'spoiler', 'type' => 'unparsed_equals', 'before' => '<div class="spoiler head">' . sprintf($txt['spoiler_intro'], '$1') . '</div><div class="spoiler content" style="display:none">', 'after' => '</div>', 'block_level' => true), array('tag' => 'sub', 'before' => '<sub>', 'after' => '</sub>'), array('tag' => 'sup', 'before' => '<sup>', 'after' => '</sup>'), array('tag' => 'table', 'before' => '<table class="bbc_table">', 'after' => '</table>', 'trim' => 'inside', 'require_children' => array('tr'), 'block_level' => true), array('tag' => 'td', 'before' => '<td>', 'after' => '</td>', 'require_parents' => array('tr'), 'trim' => 'outside', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'time', 'type' => 'unparsed_content', 'content' => '$1', 'validate' => function (&$tag, &$data, $disabled) {
            if (is_numeric($data)) {
                $data = timeformat($data);
            } else {
                $tag['content'] = '[time]$1[/time]';
            }
        }), array('tag' => 'tr', 'before' => '<tr>', 'after' => '</tr>', 'require_parents' => array('table'), 'require_children' => array('td'), 'trim' => 'both', 'block_level' => true, 'disabled_before' => '', 'disabled_after' => ''), array('tag' => 'tt', 'before' => '<tt class="bbc_tt">', 'after' => '</tt>'), array('tag' => 'u', 'before' => '<span class="bbc_u">', 'after' => '</span>'), array('tag' => 'user', 'type' => 'unparsed_content', 'parameters' => array('id' => array('match' => '(\\d+)', 'value' => '$1')), 'content' => '<a onclick="getMcard({id},$(this));return(false);" href="' . $scripturl . '?action=profile;u={id}">$1</a>'), array('tag' => 'url', 'type' => 'unparsed_content', 'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>', 'validate' => function (&$tag, &$data, $disabled) {
            $data = strtr($data, array('<br />' => ''));
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }), array('tag' => 'url', 'type' => 'unparsed_equals', 'before' => '<a href="$1" class="bbc_link" target="_blank">', 'after' => '</a>', 'validate' => function (&$tag, &$data, $disabled) {
            if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0) {
                $data = 'http://' . $data;
            }
        }, 'disallow_children' => array('email', 'ftp', 'url', 'iurl'), 'disabled_after' => ' ($1)'));
        // Let mods add new BBC without hassle.
        HookAPI::callHook('bbc_codes', array(&$codes));
        // This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
        if ($message === false) {
            if (isset($temp_bbc)) {
                $bbc_codes = $temp_bbc;
            }
            return $codes;
        }
        // So the parser won't skip them.
        $itemcodes = array('*' => 'disc', '@' => 'disc', '+' => 'square', 'x' => 'square', '#' => 'square', 'o' => 'circle', 'O' => 'circle', '0' => 'circle');
        if (!isset($disabled['li']) && !isset($disabled['list'])) {
            foreach ($itemcodes as $c => $dummy) {
                $bbc_codes[$c] = array();
            }
        }
        // Inside these tags autolink is not recommendable.
        $no_autolink_tags = array('url', 'iurl', 'ftp', 'email');
        foreach ($codes as $code) {
            // If we are not doing every tag only do ones we are interested in.
            if (empty($parse_tags) || in_array($code['tag'], $parse_tags)) {
                $bbc_codes[substr($code['tag'], 0, 1)][] = $code;
            }
        }
        $codes = null;
    }
    // Shall we take the time to cache this?
    if ($cache_id != '' && $modSettings['cache_enable'] >= 2) {
        // It's likely this will change if the message is modified.
        $cache_key = 'parse:' . $cache_id . '-' . md5(md5($cache_unique) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
        if (($temp = CacheAPI::getCache($cache_key, 1200)) != null) {
            return $temp;
        }
        $cache_t = microtime();
    }
    if ($smileys === 'print') {
        // [glow], [shadow], and [move] can't really be printed.
        $disabled['glow'] = true;
        $disabled['shadow'] = true;
        $disabled['move'] = true;
        // Colors can't well be displayed... supposed to be black and white.
        $disabled['color'] = true;
        $disabled['black'] = true;
        $disabled['blue'] = true;
        $disabled['white'] = true;
        $disabled['red'] = true;
        $disabled['green'] = true;
        $disabled['me'] = true;
        // Color coding doesn't make sense.
        $disabled['php'] = true;
        // Links are useless on paper... just show the link.
        $disabled['ftp'] = true;
        $disabled['url'] = true;
        $disabled['iurl'] = true;
        $disabled['email'] = true;
        $disabled['flash'] = true;
        // !!! Change maybe?
        if (!isset($_GET['images'])) {
            $disabled['img'] = true;
        }
        // !!! Interface/setting to add more?
    }
    $open_tags = array();
    $message = strtr($message, array("\n" => '<br />'));
    // The non-breaking-space looks a bit different each time.
    $non_breaking_space = $context['server']['complex_preg_chars'] ? '\\x{A0}' : " ";
    // This saves time by doing our break long words checks here.
    if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) {
        if ($context['browser']['is_gecko'] || $context['browser']['is_konqueror']) {
            $breaker = '<span style="margin: 0 -0.5ex 0 0;"> </span>';
        } elseif ($context['browser']['is_opera']) {
            $breaker = '<span style="margin: 0 -0.65ex 0 -1px;"> </span>';
        } else {
            $breaker = '<span style="width: 0; margin: 0 -0.6ex 0 -1px;"> </span>';
        }
        // PCRE will not be happy if we don't give it a short.
        $modSettings['fixLongWords'] = (int) min(65535, $modSettings['fixLongWords']);
    }
    $pos = -1;
    while ($pos !== false) {
        $last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
        $pos = strpos($message, '[', $pos + 1);
        // Failsafe.
        if ($pos === false || $last_pos > $pos) {
            $pos = strlen($message) + 1;
        }
        // Can't have a one letter smiley, URL, or email! (sorry.)
        if ($last_pos < $pos - 1) {
            // Make sure the $last_pos is not negative.
            $last_pos = max($last_pos, 0);
            // Pick a block of data to do some raw fixing on.
            $data = substr($message, $last_pos, $pos - $last_pos);
            // Take care of some HTML!
            if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false) {
                $data = preg_replace('~&lt;a\\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\\S+?)\\1&gt;~i', '[url=$2]', $data);
                $data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
                // <br /> should be empty.
                $empty_tags = array('br', 'hr');
                foreach ($empty_tags as $tag) {
                    $data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
                }
                // b, u, i, s, pre... basic tags.
                $closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
                foreach ($closable_tags as $tag) {
                    $diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
                    $data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
                    if ($diff > 0) {
                        $data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
                    }
                }
                // Do <img ... /> - with security... action= -> action-.
                preg_match_all('~&lt;img\\s+src=((?:&quot;)?)((?:https?://|ftps?://)\\S+?)\\1(?:\\s+alt=(&quot;.*?&quot;|\\S*?))?(?:\\s?/)?&gt;~i', $data, $matches, PREG_PATTERN_ORDER);
                if (!empty($matches[0])) {
                    $replaces = array();
                    foreach ($matches[2] as $match => $imgtag) {
                        $alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
                        // Remove action= from the URL - no funny business, now.
                        if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0) {
                            $imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
                        }
                        // Check if the image is larger than allowed.
                        if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height'])) {
                            list($width, $height) = url_image_size($imgtag);
                            if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width']) {
                                $height = (int) ($modSettings['max_image_width'] * $height / $width);
                                $width = $modSettings['max_image_width'];
                            }
                            if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height']) {
                                $width = (int) ($modSettings['max_image_height'] * $width / $height);
                                $height = $modSettings['max_image_height'];
                            }
                            // Set the new image tag.
                            $replaces[$matches[0][$match]] = '[img resized=1 width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
                        } else {
                            $replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
                        }
                    }
                    $data = strtr($data, $replaces);
                }
            }
            if (!empty($modSettings['autoLinkUrls'])) {
                // Are we inside tags that should be auto linked?
                $no_autolink_area = false;
                if (!empty($open_tags)) {
                    foreach ($open_tags as $open_tag) {
                        if (in_array($open_tag['tag'], $no_autolink_tags)) {
                            $no_autolink_area = true;
                        }
                    }
                }
                // Don't go backwards.
                //!!! Don't think is the real solution....
                $lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
                if ($pos < $lastAutoPos) {
                    $no_autolink_area = true;
                }
                $lastAutoPos = $pos;
                if (!$no_autolink_area) {
                    // Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses.
                    if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false) {
                        // Switch out quotes really quick because they can cause problems.
                        $data = strtr($data, array('&#039;' => '\'', '&nbsp;' => " ", '&quot;' => '>">', '"' => '<"<', '&lt;' => '<lt<'));
                        // Only do this if the preg survives.
                        if (is_string($result = preg_replace(array('~(?<=[\\s>\\.(;\'"]|^)((?:http|https)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\w\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i', '~(?<=[\\s>\\.(;\'"]|^)((?:ftp|ftps)://[\\w\\-_%@:|]+(?:\\.[\\w\\-_%]+)*(?::\\d+)?(?:/[\\w\\-_\\~%\\.@,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i', '~(?<=[\\s>(\'<]|^)(www(?:\\.[\\w\\-_]+)+(?::\\d+)?(?:/[\\w\\-_\\~%\\.@!,\\?&;=#(){}+:\'\\\\]*)*[/\\w\\-_\\~%@\\?;=#}\\\\])~i'), array('[url]$1[/url]', '[ftp]$1[/ftp]', '[url=http://$1]$1[/url]'), $data))) {
                            $data = $result;
                        }
                        $data = strtr($data, array('\'' => '&#039;', " " => '&nbsp;', '>">' => '&quot;', '<"<' => '"', '<lt<' => '&lt;'));
                    }
                    // Next, emails...
                    if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false) {
                        $data = preg_replace('~(?<=[\\?\\s' . $non_breaking_space . '\\[\\]()*\\\\;>]|^)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?,\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;|\\.(?:\\.|;|&nbsp;|\\s|$|<br />))~u', '[email]$1[/email]', $data);
                        $data = preg_replace('~(?<=<br />)([\\w\\-\\.]{1,80}@[\\w\\-]+\\.[\\w\\-\\.]+[\\w\\-])(?=[?\\.,;\\s' . $non_breaking_space . '\\[\\]()*\\\\]|$|<br />|&nbsp;|&gt;|&lt;|&quot;|&#039;)~u', '[email]$1[/email]', $data);
                    }
                }
            }
            $data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
            if (!empty($modSettings['fixLongWords']) && $modSettings['fixLongWords'] > 5) {
                // The idea is, find words xx long, and then replace them with xx + space + more.
                if (commonAPI::strlen($data) > $modSettings['fixLongWords']) {
                    // This is done in a roundabout way because $breaker has "long words" :P.
                    $data = strtr($data, array($breaker => '< >', '&nbsp;' => " "));
                    $data = preg_replace('~(?<=[>;:!? ' . $non_breaking_space . '\\]()]|^)([\\w' . '\\pL' . '\\.]{' . $modSettings['fixLongWords'] . ',})~eu', 'preg_replace(\'/(.{' . ($modSettings['fixLongWords'] - 1) . '})/u' . '\', \'\\$1< >\', \'$1\')', $data);
                    $data = strtr($data, array('< >' => $breaker, " " => '&nbsp;'));
                }
            }
            // If it wasn't changed, no copying or other boring stuff has to happen!
            if ($data != substr($message, $last_pos, $pos - $last_pos)) {
                $message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
                // Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
                $old_pos = strlen($data) + $last_pos;
                $pos = strpos($message, '[', $last_pos);
                $pos = $pos === false ? $old_pos : min($pos, $old_pos);
            }
        }
        // Are we there yet?  Are we there yet?
        if ($pos >= strlen($message) - 1) {
            break;
        }
        $tags = strtolower(substr($message, $pos + 1, 1));
        if ($tags == '/' && !empty($open_tags)) {
            $pos2 = strpos($message, ']', $pos + 1);
            if ($pos2 == $pos + 2) {
                continue;
            }
            $look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
            $to_close = array();
            $block_level = null;
            do {
                $tag = array_pop($open_tags);
                if (!$tag) {
                    break;
                }
                if (!empty($tag['block_level'])) {
                    // Only find out if we need to.
                    if ($block_level === false) {
                        array_push($open_tags, $tag);
                        break;
                    }
                    // The idea is, if we are LOOKING for a block level tag, we can close them on the way.
                    if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]])) {
                        foreach ($bbc_codes[$look_for[0]] as $temp) {
                            if ($temp['tag'] == $look_for) {
                                $block_level = !empty($temp['block_level']);
                                break;
                            }
                        }
                    }
                    if ($block_level !== true) {
                        $block_level = false;
                        array_push($open_tags, $tag);
                        break;
                    }
                }
                $to_close[] = $tag;
            } while ($tag['tag'] != $look_for);
            // Did we just eat through everything and not find it?
            if (empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)) {
                $open_tags = $to_close;
                continue;
            } elseif (!empty($to_close) && $tag['tag'] != $look_for) {
                if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]])) {
                    foreach ($bbc_codes[$look_for[0]] as $temp) {
                        if ($temp['tag'] == $look_for) {
                            $block_level = !empty($temp['block_level']);
                            break;
                        }
                    }
                }
                // We're not looking for a block level tag (or maybe even a tag that exists...)
                if (!$block_level) {
                    foreach ($to_close as $tag) {
                        array_push($open_tags, $tag);
                    }
                    continue;
                }
            }
            foreach ($to_close as $tag) {
                $message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
                $pos += strlen($tag['after']) + 2;
                $pos2 = $pos - 1;
                // See the comment at the end of the big loop - just eating whitespace ;).
                if (!empty($tag['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
            }
            if (!empty($to_close)) {
                $to_close = array();
                $pos--;
            }
            continue;
        }
        // No tags for this character, so just keep going (fastest possible course.)
        if (!isset($bbc_codes[$tags])) {
            continue;
        }
        $inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
        $tag = null;
        foreach ($bbc_codes[$tags] as $possible) {
            $_len = strlen($possible['tag']);
            // Not a match?
            if (strtolower(substr($message, $pos + 1, $_len)) != $possible['tag']) {
                continue;
            }
            $next_c = substr($message, $pos + 1 + $_len, 1);
            // A test validation?
            if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $_len + 1)) == 0) {
                continue;
            } elseif (!empty($possible['parameters'])) {
                if ($next_c != ' ') {
                    continue;
                }
            } elseif (isset($possible['type'])) {
                // Do we need an equal sign?
                if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=') {
                    continue;
                }
                // Maybe we just want a /...
                if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $_len, 2) != '/]' && substr($message, $pos + 1 + $_len, 3) != ' /]') {
                    continue;
                }
                // An immediate ]?
                if ($possible['type'] == 'unparsed_content' && $next_c != ']') {
                    continue;
                }
            } elseif ($next_c != ']') {
                continue;
            }
            // Check allowed tree?
            if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents']))) {
                continue;
            } elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children'])) {
                continue;
            } elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children'])) {
                continue;
            }
            $pos1 = $pos + 1 + $_len + 1;
            // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
            if ($possible['tag'] == 'quote') {
                // Start with standard
                $quote_alt = false;
                foreach ($open_tags as $open_quote) {
                    // Every parent quote this quote has flips the styling
                    if ($open_quote['tag'] == 'quote') {
                        $quote_alt = !$quote_alt;
                    }
                }
                // Add a class to the quote to style alternating blockquotes
                $possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
            }
            // This is long, but it makes things much easier and cleaner.
            if (!empty($possible['parameters'])) {
                $preg = array();
                foreach ($possible['parameters'] as $p => $info) {
                    $preg[] = '(\\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . ')' . (empty($info['optional']) ? '' : '?');
                }
                // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way of allowing any order of parameters but still parsing them right.
                $match = false;
                $orders = permute($preg);
                foreach ($orders as $p) {
                    if (preg_match('~^' . implode('', $p) . '\\]~i', substr($message, $pos1 - 1), $matches) != 0) {
                        $match = true;
                        break;
                    }
                }
                // Didn't match our parameter list, try the next possible.
                if (!$match) {
                    continue;
                }
                $params = array();
                for ($i = 1, $n = count($matches); $i < $n; $i += 2) {
                    $key = strtok(ltrim($matches[$i]), '=');
                    if (isset($possible['parameters'][$key]['value'])) {
                        $params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
                    } elseif (isset($possible['parameters'][$key]['validate'])) {
                        $params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
                    } else {
                        $params['{' . $key . '}'] = $matches[$i + 1];
                    }
                    // Just to make sure: replace any $ or { so they can't interpolate wrongly.
                    $params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
                }
                foreach ($possible['parameters'] as $p => $info) {
                    if (!isset($params['{' . $p . '}'])) {
                        $params['{' . $p . '}'] = '';
                    }
                }
                $tag = $possible;
                // Put the parameters into the string.
                if (isset($tag['before'])) {
                    $tag['before'] = strtr($tag['before'], $params);
                }
                if (isset($tag['after'])) {
                    $tag['after'] = strtr($tag['after'], $params);
                }
                if (isset($tag['content'])) {
                    $tag['content'] = strtr($tag['content'], $params);
                }
                $pos1 += strlen($matches[0]) - 1;
            } else {
                $tag = $possible;
            }
            break;
        }
        // Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
        if ($smileys !== false && $tag === null && isset($itemcodes[substr($message, $pos + 1, 1)]) && substr($message, $pos + 2, 1) == ']' && !isset($disabled['list']) && !isset($disabled['li'])) {
            if (substr($message, $pos + 1, 1) == '0' && !in_array(substr($message, $pos - 1, 1), array(';', ' ', "\t", '>'))) {
                continue;
            }
            $tag = $itemcodes[substr($message, $pos + 1, 1)];
            // First let's set up the tree: it needs to be in a list, or after an li.
            if ($inside === null || $inside['tag'] != 'list' && $inside['tag'] != 'li') {
                $open_tags[] = array('tag' => 'list', 'after' => '</ul>', 'block_level' => true, 'require_children' => array('li'), 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
                $code = '<ul class="bbc_list">';
            } elseif ($inside['tag'] == 'li') {
                array_pop($open_tags);
                $code = '</li>';
            } else {
                $code = '';
            }
            // Now we open a new tag.
            $open_tags[] = array('tag' => 'li', 'after' => '</li>', 'trim' => 'outside', 'block_level' => true, 'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null);
            // First, open the tag...
            $code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
            $pos += strlen($code) - 1 + 2;
            // Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
            $pos2 = strpos($message, '<br />', $pos);
            $pos3 = strpos($message, '[/', $pos);
            if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false)) {
                preg_match('~^(<br />|&nbsp;|\\s|\\[)+~', substr($message, $pos2 + 6), $matches);
                $message = substr($message, 0, $pos2) . "\n" . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . "\n" . substr($message, $pos2);
                $open_tags[count($open_tags) - 2]['after'] = '</ul>';
            } else {
                // Move the li over, because we're not sure what we'll hit.
                $open_tags[count($open_tags) - 1]['after'] = '';
                $open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
            }
            continue;
        }
        // Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
        if ($tag === null && $inside !== null && !empty($inside['require_children'])) {
            array_pop($open_tags);
            $message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
            $pos += strlen($inside['after']) - 1 + 2;
        }
        // No tag?  Keep looking, then.  Silly people using brackets without actual tags.
        if ($tag === null) {
            continue;
        }
        // Propagate the list to the child (so wrapping the disallowed tag won't work either.)
        if (isset($inside['disallow_children'])) {
            $tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
        }
        // Is this tag disabled?
        if (isset($disabled[$tag['tag']])) {
            if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content'])) {
                $tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
                $tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
                $tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
            } elseif (isset($tag['disabled_before']) || isset($tag['disabled_after'])) {
                $tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
                $tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
            } else {
                $tag['content'] = $tag['disabled_content'];
            }
        }
        // The only special case is 'html', which doesn't need to close things.
        if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level'])) {
            $n = count($open_tags) - 1;
            while (empty($open_tags[$n]['block_level']) && $n >= 0) {
                $n--;
            }
            // Close all the non block level tags so this tag isn't surrounded by them.
            for ($i = count($open_tags) - 1; $i > $n; $i--) {
                $message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
                $pos += strlen($open_tags[$i]['after']) + 2;
                $pos1 += strlen($open_tags[$i]['after']) + 2;
                // Trim or eat trailing stuff... see comment at the end of the big loop.
                if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 6) == '<br />') {
                    $message = substr($message, 0, $pos) . substr($message, $pos + 6);
                }
                if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos), $matches) != 0) {
                    $message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
                }
                array_pop($open_tags);
            }
        }
        // No type means 'parsed_content'.
        if (!isset($tag['type'])) {
            // !!! Check for end tag first, so people can say "I like that [i] tag"?
            $open_tags[] = $tag;
            $message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
            $pos += strlen($tag['before']) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_content') {
            $pos2 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            if (!empty($tag['block_level']) && substr($data, 0, 6) == '<br />') {
                $data = substr($data, 6);
            }
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
            $last_pos = $pos + 1;
        } elseif ($tag['type'] == 'unparsed_equals_content') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] != 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            $data = array(substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))), substr($message, $pos1, $pos2 - $pos1));
            if (!empty($tag['block_level']) && substr($data[0], 0, 6) == '<br />') {
                $data[0] = substr($data[0], 6);
            }
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'closed') {
            $pos2 = strpos($message, ']', $pos);
            $message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($tag['content']) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_commas_content') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $pos3 = stripos($message, '[/' . substr($message, $pos + 1, strlen($tag['tag'])) . ']', $pos2);
            if ($pos3 === false) {
                continue;
            }
            // We want $1 to be the content, and the rest to be csv.
            $data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
            $data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            $code = $tag['content'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + strlen($tag['tag']));
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_commas') {
            $pos2 = strpos($message, ']', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = explode(',', substr($message, $pos1, $pos2 - $pos1));
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // Fix after, for disabled code mainly.
            foreach ($data as $k => $d) {
                $tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
            }
            $open_tags[] = $tag;
            // Replace them out, $1, $2, $3, $4, etc.
            $code = $tag['before'];
            foreach ($data as $k => $d) {
                $code = strtr($code, array('$' . ($k + 1) => trim($d)));
            }
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
            $pos += strlen($code) - 1 + 2;
        } elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals') {
            // The value may be quoted for some tags - check.
            if (isset($tag['quoted'])) {
                $quoted = substr($message, $pos1, 6) == '&quot;';
                if ($tag['quoted'] != 'optional' && !$quoted) {
                    continue;
                }
                if ($quoted) {
                    $pos1 += 6;
                }
            } else {
                $quoted = false;
            }
            $pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
            if ($pos2 === false) {
                continue;
            }
            $data = substr($message, $pos1, $pos2 - $pos1);
            // Validation for my parking, please!
            if (isset($tag['validate'])) {
                $tag['validate']($tag, $data, $disabled);
            }
            // For parsed content, we must recurse to avoid security problems.
            if ($tag['type'] != 'unparsed_equals') {
                $data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
            }
            $tag['after'] = strtr($tag['after'], array('$1' => $data));
            $open_tags[] = $tag;
            $code = strtr($tag['before'], array('$1' => $data));
            $message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7));
            $pos += strlen($code) - 1 + 2;
        }
        // If this is block level, eat any breaks after it.
        if (!empty($tag['block_level']) && substr($message, $pos + 1, 6) == '<br />') {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 7);
        }
        // Are we trimming outside this tag?
        if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br />|&nbsp;|\\s)*~', substr($message, $pos + 1), $matches) != 0) {
            $message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
        }
    }
    // Close any remaining tags.
    while ($tag = array_pop($open_tags)) {
        $message .= "\n" . $tag['after'] . "\n";
    }
    // Parse the smileys within the parts where it can be done safely.
    if ($smileys === true) {
        $message_parts = explode("\n", $message);
        for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) {
            parsesmileys($message_parts[$i]);
        }
        $message = implode('', $message_parts);
    } else {
        $message = strtr($message, array("\n" => ''));
    }
    if (substr($message, 0, 1) == ' ') {
        $message = '&nbsp;' . substr($message, 1);
    }
    // Cleanup whitespace.
    $message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br />', '<br /> ' => '<br />&nbsp;', '&#13;' => "\n"));
    /*
     * experimental hook... this could be used to support mods like footnotes, for example
     */
    HookAPI::callHook('parse_bbc_after', array(&$message, &$parse_tags, &$smileys));
    // Cache the output if it took some time...
    if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.03) {
        //if (isset($cache_key, $cache_t)) // && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
        CacheAPI::putCache($cache_key, $message, 1200);
    }
    // If this was a force parse revert if needed.
    if (!empty($parse_tags)) {
        if (empty($temp_bbc)) {
            $bbc_codes = array();
        } else {
            $bbc_codes = $temp_bbc;
            unset($temp_bbc);
        }
    }
    return $message;
}