|
@@ -909,6 +909,411 @@ function fetchTagAttributes($text)
|
|
|
return $attribs;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * !!!Compatibility!!!
|
|
|
+ * Attempt to clean up illegal BBC caused by browsers like Opera which don't obey the rules
|
|
|
+ * @param string $text
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+function legalise_bbc($text)
|
|
|
+{
|
|
|
+ global $modSettings;
|
|
|
+
|
|
|
+ // Don't care about the texts that are too short.
|
|
|
+ if (strlen($text) < 3)
|
|
|
+ return $text;
|
|
|
+
|
|
|
+ // We are going to cycle through the BBC and keep track of tags as they arise - in order. If get to a block level tag we're going to make sure it's not in a non-block level tag!
|
|
|
+ // This will keep the order of tags that are open.
|
|
|
+ $current_tags = array();
|
|
|
+
|
|
|
+ // This will quickly let us see if the tag is active.
|
|
|
+ $active_tags = array();
|
|
|
+
|
|
|
+ // A list of tags that's disabled by the admin.
|
|
|
+ $disabled = empty($modSettings['disabledBBC']) ? array() : array_flip(explode(',', strtolower($modSettings['disabledBBC'])));
|
|
|
+
|
|
|
+ // Add flash if it's disabled as embedded tag.
|
|
|
+ if (empty($modSettings['enableEmbeddedFlash']))
|
|
|
+ $disabled['flash'] = true;
|
|
|
+
|
|
|
+ // Get a list of all the tags that are not disabled.
|
|
|
+ $all_tags = parse_bbc(false);
|
|
|
+ $valid_tags = array();
|
|
|
+ $self_closing_tags = array();
|
|
|
+ foreach ($all_tags as $tag)
|
|
|
+ {
|
|
|
+ if (!isset($disabled[$tag['tag']]))
|
|
|
+ $valid_tags[$tag['tag']] = !empty($tag['block_level']);
|
|
|
+ if (isset($tag['type']) && $tag['type'] == 'closed')
|
|
|
+ $self_closing_tags[] = $tag['tag'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Don't worry if we're in a code/nobbc.
|
|
|
+ $in_code_nobbc = false;
|
|
|
+
|
|
|
+ // Right - we're going to start by going through the whole lot to make sure we don't have align stuff crossed as this happens load and is stupid!
|
|
|
+ $align_tags = array('left', 'center', 'right', 'pre');
|
|
|
+
|
|
|
+ // Remove those align tags that are not valid.
|
|
|
+ $align_tags = array_intersect($align_tags, array_keys($valid_tags));
|
|
|
+
|
|
|
+ // These keep track of where we are!
|
|
|
+ if (!empty($align_tags) && count($matches = preg_split('~(\\[/?(?:' . implode('|', $align_tags) . ')\\])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1)
|
|
|
+ {
|
|
|
+ // The first one is never a tag.
|
|
|
+ $isTag = false;
|
|
|
+
|
|
|
+ // By default we're not inside a tag too.
|
|
|
+ $insideTag = null;
|
|
|
+
|
|
|
+ foreach ($matches as $i => $match)
|
|
|
+ {
|
|
|
+ // We're only interested in tags, not text.
|
|
|
+ if ($isTag)
|
|
|
+ {
|
|
|
+ $isClosingTag = substr($match, 1, 1) === '/';
|
|
|
+ $tagName = substr($match, $isClosingTag ? 2 : 1, -1);
|
|
|
+
|
|
|
+ // We're closing the exact same tag that we opened.
|
|
|
+ if ($isClosingTag && $insideTag === $tagName)
|
|
|
+ $insideTag = null;
|
|
|
+
|
|
|
+ // We're opening a tag and we're not yet inside one either
|
|
|
+ elseif (!$isClosingTag && $insideTag === null)
|
|
|
+ $insideTag = $tagName;
|
|
|
+
|
|
|
+ // In all other cases, this tag must be invalid
|
|
|
+ else
|
|
|
+ unset($matches[$i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // The next one is gonna be the other one.
|
|
|
+ $isTag = !$isTag;
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're still inside a tag and had no chance for closure?
|
|
|
+ if ($insideTag !== null)
|
|
|
+ $matches[] = '[/' . $insideTag . ']';
|
|
|
+
|
|
|
+ // And a complete text string again.
|
|
|
+ $text = implode('', $matches);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Quickly remove any tags which are back to back.
|
|
|
+ $backToBackPattern = '~\\[(' . implode('|', array_diff(array_keys($valid_tags), array('td', 'anchor'))) . ')[^<>\\[\\]]*\\]\s*\\[/\\1\\]~';
|
|
|
+ $lastlen = 0;
|
|
|
+ while (strlen($text) !== $lastlen)
|
|
|
+ $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text));
|
|
|
+
|
|
|
+ // Need to sort the tags my name length.
|
|
|
+ uksort($valid_tags, 'sort_array_length');
|
|
|
+
|
|
|
+ // These inline tags can compete with each other regarding style.
|
|
|
+ $competing_tags = array(
|
|
|
+ 'color',
|
|
|
+ 'size',
|
|
|
+ );
|
|
|
+
|
|
|
+ // In case things changed above set these back to normal.
|
|
|
+ $in_code_nobbc = false;
|
|
|
+ $new_text_offset = 0;
|
|
|
+
|
|
|
+ // These keep track of where we are!
|
|
|
+ if (count($parts = preg_split(sprintf('~(\\[)(/?)(%1$s)((?:[\\s=][^\\]\\[]*)?\\])~', implode('|', array_keys($valid_tags))), $text, -1, PREG_SPLIT_DELIM_CAPTURE)) > 1)
|
|
|
+ {
|
|
|
+ // Start with just text.
|
|
|
+ $isTag = false;
|
|
|
+
|
|
|
+ // Start outside [nobbc] or [code] blocks.
|
|
|
+ $inCode = false;
|
|
|
+ $inNoBbc = false;
|
|
|
+
|
|
|
+ // A buffer containing all opened inline elements.
|
|
|
+ $inlineElements = array();
|
|
|
+
|
|
|
+ // A buffer containing all opened block elements.
|
|
|
+ $blockElements = array();
|
|
|
+
|
|
|
+ // A buffer containing the opened inline elements that might compete.
|
|
|
+ $competingElements = array();
|
|
|
+
|
|
|
+ // $i: text, $i + 1: '[', $i + 2: '/', $i + 3: tag, $i + 4: tag tail.
|
|
|
+ for ($i = 0, $n = count($parts) - 1; $i < $n; $i += 5)
|
|
|
+ {
|
|
|
+ $tag = $parts[$i + 3];
|
|
|
+ $isOpeningTag = $parts[$i + 2] === '';
|
|
|
+ $isClosingTag = $parts[$i + 2] === '/';
|
|
|
+ $isBlockLevelTag = isset($valid_tags[$tag]) && $valid_tags[$tag] && !in_array($tag, $self_closing_tags);
|
|
|
+ $isCompetingTag = in_array($tag, $competing_tags);
|
|
|
+
|
|
|
+ // Check if this might be one of those cleaned out tags.
|
|
|
+ if ($tag === '')
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // Special case: inside [code] blocks any code is left untouched.
|
|
|
+ elseif ($tag === 'code')
|
|
|
+ {
|
|
|
+ // We're inside a code block and closing it.
|
|
|
+ if ($inCode && $isClosingTag)
|
|
|
+ {
|
|
|
+ $inCode = false;
|
|
|
+
|
|
|
+ // Reopen tags that were closed before the code block.
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']';
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're outside a coding and nobbc block and opening it.
|
|
|
+ elseif (!$inCode && !$inNoBbc && $isOpeningTag)
|
|
|
+ {
|
|
|
+ // If there are still inline elements left open, close them now.
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ {
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']';
|
|
|
+ //$inlineElements = array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $inCode = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Nothing further to do.
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Special case: inside [nobbc] blocks any BBC is left untouched.
|
|
|
+ elseif ($tag === 'nobbc')
|
|
|
+ {
|
|
|
+ // We're inside a nobbc block and closing it.
|
|
|
+ if ($inNoBbc && $isClosingTag)
|
|
|
+ {
|
|
|
+ $inNoBbc = false;
|
|
|
+
|
|
|
+ // Some inline elements might've been closed that need reopening.
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ $parts[$i + 4] .= '[' . implode('][', array_keys($inlineElements)) . ']';
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're outside a nobbc and coding block and opening it.
|
|
|
+ elseif (!$inNoBbc && !$inCode && $isOpeningTag)
|
|
|
+ {
|
|
|
+ // Can't have inline elements still opened.
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ {
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']';
|
|
|
+ //$inlineElements = array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $inNoBbc = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // So, we're inside one of the special blocks: ignore any tag.
|
|
|
+ elseif ($inCode || $inNoBbc)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // We're dealing with an opening tag.
|
|
|
+ if ($isOpeningTag)
|
|
|
+ {
|
|
|
+ // Everyting inside the square brackets of the opening tag.
|
|
|
+ $elementContent = $parts[$i + 3] . substr($parts[$i + 4], 0, -1);
|
|
|
+
|
|
|
+ // A block level opening tag.
|
|
|
+ if ($isBlockLevelTag)
|
|
|
+ {
|
|
|
+ // Are there inline elements still open?
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ {
|
|
|
+ // Close all the inline tags, a block tag is coming...
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']';
|
|
|
+
|
|
|
+ // Now open them again, we're inside the block tag now.
|
|
|
+ $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5];
|
|
|
+ }
|
|
|
+
|
|
|
+ $blockElements[] = $tag;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inline opening tag.
|
|
|
+ elseif (!in_array($tag, $self_closing_tags))
|
|
|
+ {
|
|
|
+ // Can't have two opening elements with the same contents!
|
|
|
+ if (isset($inlineElements[$elementContent]))
|
|
|
+ {
|
|
|
+ // Get rid of this tag.
|
|
|
+ $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = '';
|
|
|
+
|
|
|
+ // Now try to find the corresponding closing tag.
|
|
|
+ $curLevel = 1;
|
|
|
+ for ($j = $i + 5, $m = count($parts) - 1; $j < $m; $j += 5)
|
|
|
+ {
|
|
|
+ // Find the tags with the same tagname
|
|
|
+ if ($parts[$j + 3] === $tag)
|
|
|
+ {
|
|
|
+ // If it's an opening tag, increase the level.
|
|
|
+ if ($parts[$j + 2] === '')
|
|
|
+ $curLevel++;
|
|
|
+
|
|
|
+ // A closing tag, decrease the level.
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $curLevel--;
|
|
|
+
|
|
|
+ // Gotcha! Clean out this closing tag gone rogue.
|
|
|
+ if ($curLevel === 0)
|
|
|
+ {
|
|
|
+ $parts[$j + 1] = $parts[$j + 2] = $parts[$j + 3] = $parts[$j + 4] = '';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Otherwise, add this one to the list.
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if ($isCompetingTag)
|
|
|
+ {
|
|
|
+ if (!isset($competingElements[$tag]))
|
|
|
+ $competingElements[$tag] = array();
|
|
|
+
|
|
|
+ $competingElements[$tag][] = $parts[$i + 4];
|
|
|
+
|
|
|
+ if (count($competingElements[$tag]) > 1)
|
|
|
+ $parts[$i] .= '[/' . $tag . ']';
|
|
|
+ }
|
|
|
+
|
|
|
+ $inlineElements[$elementContent] = $tag;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Closing tag.
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Closing the block tag.
|
|
|
+ if ($isBlockLevelTag)
|
|
|
+ {
|
|
|
+ // Close the elements that should've been closed by closing this tag.
|
|
|
+ if (!empty($blockElements))
|
|
|
+ {
|
|
|
+ $addClosingTags = array();
|
|
|
+ while ($element = array_pop($blockElements))
|
|
|
+ {
|
|
|
+ if ($element === $tag)
|
|
|
+ break;
|
|
|
+
|
|
|
+ // Still a block tag was open not equal to this tag.
|
|
|
+ $addClosingTags[] = $element['type'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($addClosingTags))
|
|
|
+ $parts[$i + 1] = '[/' . implode('][/', array_reverse($addClosingTags)) . ']' . $parts[$i + 1];
|
|
|
+
|
|
|
+ // Apparently the closing tag was not found on the stack.
|
|
|
+ if (!is_string($element) || $element !== $tag)
|
|
|
+ {
|
|
|
+ // Get rid of this particular closing tag, it was never opened.
|
|
|
+ $parts[$i + 1] = substr($parts[$i + 1], 0, -1);
|
|
|
+ $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = '';
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Get rid of this closing tag!
|
|
|
+ $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = '';
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inline elements are still left opened?
|
|
|
+ if (!empty($inlineElements))
|
|
|
+ {
|
|
|
+ // Close them first..
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']';
|
|
|
+
|
|
|
+ // Then reopen them.
|
|
|
+ $parts[$i + 5] = '[' . implode('][', array_keys($inlineElements)) . ']' . $parts[$i + 5];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Inline tag.
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Are we expecting this tag to end?
|
|
|
+ if (in_array($tag, $inlineElements))
|
|
|
+ {
|
|
|
+ foreach (array_reverse($inlineElements, true) as $tagContentToBeClosed => $tagToBeClosed)
|
|
|
+ {
|
|
|
+ // Closing it one way or the other.
|
|
|
+ unset($inlineElements[$tagContentToBeClosed]);
|
|
|
+
|
|
|
+ // Was this the tag we were looking for?
|
|
|
+ if ($tagToBeClosed === $tag)
|
|
|
+ break;
|
|
|
+
|
|
|
+ // Nope, close it and look further!
|
|
|
+ else
|
|
|
+ $parts[$i] .= '[/' . $tagToBeClosed . ']';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($isCompetingTag && !empty($competingElements[$tag]))
|
|
|
+ {
|
|
|
+ array_pop($competingElements[$tag]);
|
|
|
+
|
|
|
+ if (count($competingElements[$tag]) > 0)
|
|
|
+ $parts[$i + 5] = '[' . $tag . $competingElements[$tag][count($competingElements[$tag]) - 1] . $parts[$i + 5];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Unexpected closing tag, ex-ter-mi-nate.
|
|
|
+ else
|
|
|
+ $parts[$i + 1] = $parts[$i + 2] = $parts[$i + 3] = $parts[$i + 4] = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Close the code tags.
|
|
|
+ if ($inCode)
|
|
|
+ $parts[$i] .= '[/code]';
|
|
|
+
|
|
|
+ // The same for nobbc tags.
|
|
|
+ elseif ($inNoBbc)
|
|
|
+ $parts[$i] .= '[/nobbc]';
|
|
|
+
|
|
|
+ // Still inline tags left unclosed? Close them now, better late than never.
|
|
|
+ elseif (!empty($inlineElements))
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($inlineElements)) . ']';
|
|
|
+
|
|
|
+ // Now close the block elements.
|
|
|
+ if (!empty($blockElements))
|
|
|
+ $parts[$i] .= '[/' . implode('][/', array_reverse($blockElements)) . ']';
|
|
|
+
|
|
|
+ $text = implode('', $parts);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Final clean up of back to back tags.
|
|
|
+ $lastlen = 0;
|
|
|
+ while (strlen($text) !== $lastlen)
|
|
|
+ $lastlen = strlen($text = preg_replace($backToBackPattern, '', $text));
|
|
|
+
|
|
|
+ return $text;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * !!!Compatibility!!!
|
|
|
+ * A help function for legalise_bbc for sorting arrays based on length.
|
|
|
+ * @param string $a
|
|
|
+ * @param string $b
|
|
|
+ * @return int 1 or -1
|
|
|
+ */
|
|
|
+function sort_array_length($a, $b)
|
|
|
+{
|
|
|
+ return strlen($a) < strlen($b) ? 1 : -1;
|
|
|
+}
|
|
|
|
|
|
/**
|
|
|
* Creates the javascript code for localization of the editor (SCEditor)
|