<?php
class XenForo_BbCode_Formatter_Wysiwyg extends XenForo_BbCode_Formatter_Base
{
protected $_undisplayableTags = array('quote', 'plain', 'media', 'spoiler', 'attach', 'user', 'code', 'php', 'html');
protected $_tagReplacements = array(
'u' => array(
'replace' => array('<u>', '</u>'),
),
's' => array(
'replace' => array('<strike>', '</strike>'),
),
'font' => array(
'replace' => array('<font face="%s">', '</font>')
)
);
protected $_imageTemplate = '<img src="%1$s" class="bbCodeImage wysiwygImage" alt="" unselectable="on" />';
protected $_smilieTemplate = '<img src="%1$s" class="mceSmilie" alt="%2$s" unselectable="on" />';
protected $_smilieSpriteTemplate = '<img src="styles/default/xenforo/clear.png" class="mceSmilieSprite mceSmilie%1$d" alt="%2$s" unselectable="on" unselectable="on" />';
public function getTags()
{
if ($this->_tags !== null)
{
return $this->_tags;
}
$tags = parent::getTags();
foreach ($tags AS $tagName => &$tag)
{
if (in_array($tagName, $this->_undisplayableTags))
{
$tag['callback'] = array($this, 'renderTagUndisplayable');
unset($tag['trimLeadingLinesAfter'], $tag['stopLineBreakConversion']);
}
else if (isset($this->_tagReplacements[$tagName]))
{
$tag = array_merge($tag, $this->_tagReplacements[$tagName]);
if (isset($this->_tagReplacements['replace']))
{
unset($tag['callback']);
}
}
}
return $tags;
}
protected function _setupCustomTagInfo($tagName, array $tag)
{
$tag = parent::_setupCustomTagInfo($tagName, $tag);
$tag['callback'] = array($this, 'renderTagUndisplayable');
unset($tag['trimLeadingLinesAfter'], $tag['stopLineBreakConversion']);
return $tag;
}
protected $_blockTags = '<p|<div|<blockquote|<ul|<ol';
protected $_blockTagsEnd = '</p>|</div>|</blockquote>|</ul>|</ol>';
public function filterFinalOutput($output)
{
$debug = false;
$blockTags = $this->_blockTags;
$blockTagsEnd = $this->_blockTagsEnd;
if ($debug) { echo '<hr /><b>Original:</b><br />'. nl2br(htmlspecialchars($output)); }
if ($debug) { echo '<hr /><b>Pre-break:</b><br />'. nl2br(htmlspecialchars($output)); }
$output = preg_replace('#\s*<break-start />(?>\s*)(?!' . $blockTags . '|' . $blockTagsEnd . '|$)#i', ($debug ? "\n" : '') . "<p>", $output);
$output = preg_replace('#\s*<break-start />#i', '', $output);
$output = preg_replace('#(' . $blockTagsEnd . ')\s*<break />#i', "\\1", $output);
$output = preg_replace('#<break />\s*(' . $blockTags . ')#i', "</p>" . ($debug ? "\n" : '') . "\\1", $output);
$output = preg_replace('#<break />\s*#i', "</p>" . ($debug ? "\n" : '') . "<p>", $output);
if ($debug) { echo '<hr /><b>Post-break:</b><br />'. nl2br(htmlspecialchars($output)); }
$output = trim($output);
if (!preg_match('#^(' . $blockTags . ')#i', $output))
{
$output = '<p>' . $output;
}
if (!preg_match('#(' . $blockTagsEnd . ')$#i', $output))
{
$output .= '</p>';
}
$output = preg_replace_callback('#(<p[^>]*>)(.*)(</p>)#siU',
array($this, '_replaceEmptyContent'), $output
);
$output = str_replace('<empty-content />', '', $output); // just in case
$output = $this->_fixListStyles($output);
if ($debug) { echo '<hr /><b>Final:</b><br />'. nl2br(htmlspecialchars($output)); }
return $output;
}
protected function _filterTagContents($open, $inner, $close)
{
$isBlock = (preg_match('#^(' . $this->_blockTags . ')#i', $open));
$inner = preg_replace('#(<break />\s*)(?=<break />|$)#i', '\\1<empty-content />', $inner);
if ($isBlock)
{
$inner = preg_replace(
'#<break-start />(?>\s*)(?!' . $this->_blockTags . '|' . $this->_blockTagsEnd . '|$)#i',
"$close\\0$open",
$inner
);
$inner = preg_replace(
'#<break />(?>\s*)(?!' . $this->_blockTags . ')#i',
"$close\\0$open",
$inner
);
}
else
{
if (preg_match('#^<break />#i', $inner))
{
$inner = '<empty-content />' . $inner;
}
$inner = preg_replace('#<break />\s*((' . $this->_blockTags . ')[^>]*>)?#i', "$close\\0$open", $inner);
}
return $open . $inner . $close;
}
protected function _replaceEmptyContent(array $match)
{
$isOldIE = false;
if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/MSIE (\d+)/', $_SERVER['HTTP_USER_AGENT'], $ieMatch))
{
$isOldIE = (intval($ieMatch[1]) <= 10);
}
$emptyParaText = ($isOldIE ? ' ' : '<br />');
if (strlen(trim($match[2])) == 0)
{
// paragraph is actually empty
$output = $emptyParaText;
}
else
{
$test = strip_tags($match[2], '<empty-content><img><br><hr>');
if (trim($test) == '<empty-content />')
{
$output = str_replace('<empty-content />', $emptyParaText, $match[2]);
}
else
{
// we had a break
$output = str_replace('<empty-content />', '', $match[2]);
}
}
return $match[1] . $output . $match[3];
}
protected function _fixListStyles($output)
{
do
{
$original = $output;
$output = preg_replace_callback(
'#<p([^>]*)>\s*<(ul|ol)([^>]*)>(.*)</\\2>\s*</p>#siU',
array($this, '_fixListStylesCallback'),
$output
);
}
while ($original != $output);
return $output;
}
protected function _fixListStylesCallback(array $match)
{
$pAttrs = $match[1];
$listTag = $match[2];
$listAttrs = $match[3];
$listContent = $match[4];
if (!$listAttrs)
{
$listAttrs = $pAttrs;
}
else
{
// TODO: this is not actually correct as it doesn't merge style attributes, but it's likely to be uncommon
$listAttrs = $listAttrs . ' ' . $pAttrs;
}
return "<{$listTag}{$listAttrs}>{$listContent}</{$listTag}>";
}
public function filterString($string, array $rendererStates)
{
if (empty($rendererStates['stopSmilies']))
{
$string = $this->replaceSmiliesInText($string, 'htmlspecialchars');
}
else
{
$string = htmlspecialchars($string);
}
$string = str_replace("\t", ' ', $string);
// doing this twice handles situations with 3 spaces, also need to handle 1 leading space (particularly in code)
$string = str_replace("\n ", "\n ", $string);
$string = str_replace(' ', ' ', $string);
$string = str_replace(' ', ' ', $string);
if (empty($rendererStates['stopLineBreakConversion']))
{
if (!empty($rendererStates['inList']))
{
$string = nl2br($string);
}
else
{
$string = preg_replace('/\r\n|\n|\r/', "<break />\n", $string);
}
}
return $string;
}
public function renderTagUrl(array $tag, array $rendererStates)
{
$rendererStates['shortenUrl'] = false;
return parent::renderTagUrl($tag, $rendererStates);
}
public function renderTagSize(array $tag, array $rendererStates)
{
$text = $this->renderSubTree($tag['children'], $rendererStates);
if (trim($text) === '')
{
return $text;
}
$size = $this->getTextSize($tag['option']);
if ($size)
{
$fontSize = false;
switch ($size)
{
case 'xx-small':
case '9px':
$fontSize = 1; break;
case 'x-small':
case '10px':
$fontSize = 2; break;
case 'small':
case '12px':
$fontSize = 3; break;
case 'medium':
case '15px':
case '100%':
$fontSize = 4; break;
case 'large':
case '18px':
$fontSize = 5; break;
case 'x-large':
case '22px':
$fontSize = 6; break;
case 'xx-large':
case '26px':
$fontSize = 7; break;
}
if ($fontSize)
{
return $this->_wrapInHtml('<font size="' . htmlspecialchars($fontSize) . '">', '</font>', $text);
}
else
{
return $this->_wrapInHtml('<span style="font-size: ' . htmlspecialchars($size) . '">', '</span>', $text);
}
}
else
{
return $text;
}
}
public function renderTagAlign(array $tag, array $rendererStates)
{
$text = $this->renderSubTree($tag['children'], $rendererStates);
switch (strtolower($tag['tag']))
{
case 'left':
case 'center':
case 'right':
return $this->_wrapInHtml('<p style="text-align: ' . $tag['tag'] . '">', '</p>', $text) . "<break-start />\n";
default:
return $this->_wrapInHtml('<p>', '</p>', $text) . "<break-start />\n";
}
}
public function renderTagList(array $tag, array $rendererStates)
{
$wasInList = !empty($rendererStates['inList']);
$rendererStates['inList'] = true;
$output = parent::renderTagList($tag, $rendererStates);
$output = preg_replace('#\s*<break-start />\s*#i', "\n", $output);
if (!$wasInList)
{
$output = "<break-start />\n$output<break-start />\n";
}
return $output;
}
public function renderTagIndent(array $tag, array $rendererStates)
{
$wasInIndent = !empty($rendererStates['inIndent']);
$rendererStates['inIndent'] = true;
$text = $this->renderSubTree($tag['children'], $rendererStates);
if (trim($text) === '')
{
$text = '<br />';
}
if (isset($tag['option']))
{
$amount = intval($tag['option']);
if ($amount > 10)
{
$amount = 10;
}
}
else
{
$amount = 1;
}
$prepend = '';
$append = '';
for ($i = 1; $i <= $amount; $i++)
{
$prepend .= '<blockquote>';
$append = '</blockquote>' . $append;
}
if (strpos($text, '<blockquote') === false)
{
$text = '<p>' . $text . '</p>';
}
$output = $prepend . $text . $append;
if (!$wasInIndent)
{
$output .= "<break-start />\n";
}
return $output;
}
protected function _renderListOutput($listType, array $elements)
{
$output = "<$listType>";
foreach ($elements AS $element)
{
$output .= "\n<li>$element</li>";
}
$output .= "\n</$listType>";
return $output;
}
public function renderTagUndisplayable(array $tag, array $rendererStates)
{
return $this->renderInvalidTag($tag, $rendererStates);
}
protected function _wrapInHtml($prepend, $append, $text, $option = null)
{
if ($option !== null)
{
$prepend = sprintf($prepend, $option);
$append = sprintf($append, $option);
}
return $this->_filterTagContents($prepend, $text, $append);
}
protected function _handleImageProxyOption($url)
{
return $url;
}
protected function _handleLinkProxyOption($url, $linkType)
{
return $url;
}
}