Вход Регистрация
Файл: library/XenForo/BbCode/Formatter/Base.php
Строк: 1803
<?php

/**
 * Base class for defining the formatting used by the BB code parser.
 * This class implements HTML formatting.
 *
 * @package XenForo_BbCode
 */
class XenForo_BbCode_Formatter_Base
{
    
/**
     * Lookup array that translates a smilie replacement text to an untypeable
     * sentinel value (-id-).
     *
     * @var array Format: [smilie replacement text] => sentinel value
     */
    
protected $_smilieTranslate = array();

    
/**
     * Essentially the reverse of the above lookup, this one translates a smilie ID
     * to the actual "rich" replacement (for HTML, an image tag).
     *
     * @var array Format: [smilie id] => final replacement
     */
    
protected $_smilieReverse = array();

    
/**
     * Array to store smilie paths for [IMG] lookup
     *
     * @var array Format [path] => smilie ID
     */
    
protected $_smiliePaths = array();

    
/**
     * List of media sites that are known.
     *
     * @var array Format: [media site id] => info
     */
    
protected $_mediaSites = array();

    
/**
     * List of tags this formatter knows about.
     *
     * @var array|null
     */
    
protected $_tags = null;

    
/**
     * View for rendering tags that require templates.
     *
     * @var XenForo_View|null
     */
    
protected $_view = null;

    
/**
     * String used for outputting [IMG] tags. Will be passed the following params:
     * 1    URL
     * 2    Additional CSS classes
     *
     * @var string
     */
    
protected $_imageTemplate = '<img src="%1$s" class="bbCodeImage%2$s" alt="[&#x200B;IMG]" data-url="%3$s" />';

    
/**
     * String used for outputting smilies. Will be passed the following params:
     * 1    Image URL
     * 2    Smilie text
     * 3    Smilie title
     *
     * @var string
     */
    
protected $_smilieTemplate = '<img src="%1$s" class="mceSmilie" alt="%2$s" title="%3$s    %2$s" />';

    
/**
     * String used for outputting smilies as sprites. Will be passed the following params:
     * 1    Smilie ID
     * 2    Smilie text
     * 3    Smilie title
     *
     * @var string
     */
    
protected $_smilieSpriteTemplate = '<img src="styles/default/xenforo/clear.png" class="mceSmilieSprite mceSmilie%1$d" alt="%2$s" title="%3$s    %2$s" />';

    
/**
     * Cache to store processed smilie URLs, to avoid having to process them for every single smilie.
     *
     * @var array
     */
    
protected $_smilieUrlCache = array();

    
/**
     * List of ignored users, for quoted content mostly.
     *
     * @var array Key: user ID, value: user name
     */
    
protected $_ignoredUsers = array();

    
/**
     * Direction of the text on the page by default.
     *
     * @var string    LTR or RTL
     */
    
protected $_textDirection = 'LTR';

    
/**
     * Maximum depth of tags that will be parsed. 0 to disable.
     *
     * @var int
     */
    
protected $_tagDepthLimit = 20;

    
/**
     * Constructor.
     */
    
public function __construct()
    {
        
$this->_tags = $this->getTags();
        
$this->preLoadData();

        if (
XenForo_Visitor::hasInstance())
        {
            
$visitor = XenForo_Visitor::getInstance();
            if (!empty(
$visitor['ignoredUsers']))
            {
                
$this->_ignoredUsers = $visitor['ignoredUsers'];
            }

            
$language = $visitor->getLanguage();
            
$this->_textDirection = $language['text_direction'];
        }
    }

    
/**
     * Pre-loads any required data, such as templates or phrases that may be be used.
     */
    
public function preLoadData()
    {
    }

    
/**
     * Add the specified list of smilies to the list that will be processed.
     *
     * @param array $smilies List of smilies with data from the DB (smilie_id, smilieText [array], image_url)
     */
    
public function addSmilies(array $smilies)
    {
        foreach (
$smilies AS $smilie)
        {
            foreach (
$smilie['smilieText'] AS $text)
            {
                
$this->_smilieTranslate[$text] = "" . $smilie['smilie_id'] . "";
            }

            if (empty(
$smilie['sprite_params']))
            {
                
$this->_smilieReverse[$smilie['smilie_id']] = $this->_processSmilieTemplate($smilie);
            }
            else
            {
                
$this->_smilieReverse[$smilie['smilie_id']] = $this->_processSmilieSpriteTemplate($smilie);
            }

            
$this->_smiliePaths[$smilie['image_url']] = $smilie['smilie_id'];
        }
    }

    
/**
     * Populates the image smilie template with data
     *
     * @param array $smilie
     *
     * @return string
     */
    
protected function _processSmilieTemplate(array $smilie)
    {
        return 
sprintf($this->_smilieTemplate,
            
$this->_prepareSmilieUrl($smilie['image_url']),
            
htmlspecialchars(reset($smilie['smilieText'])),
            
htmlspecialchars($smilie['title'])
        );
    }

    
/**
     * Populates the sprite smilie template with data
     *
     * @param array $smilie
     *
     * @return string
     */
    
protected function _processSmilieSpriteTemplate(array $smilie)
    {
        return 
sprintf($this->_smilieSpriteTemplate,
            
$smilie['smilie_id'],
            
htmlspecialchars(reset($smilie['smilieText'])),
            
htmlspecialchars($smilie['title']),
            
$this->_prepareSmilieUrl($smilie['image_url']),
            
$smilie['sprite_params']['w'],
            
$smilie['sprite_params']['h'],
            
$smilie['sprite_params']['x'],
            
$smilie['sprite_params']['y']
        );
    }

    
/**
     * Prepares a smilie URL for use in an <img /> tag. Fetches the result from cache if possible.
     *
     * @param string $smilieUrl
     *
     * @return string
     */
    
protected function _prepareSmilieUrl($smilieUrl)
    {
        if (!isset(
$this->_smilieUrlCache[$smilieUrl]))
        {
            
$this->_smilieUrlCache[$smilieUrl] = $this->_prepareSmilieUrlInternal($smilieUrl);
        }

        return 
$this->_smilieUrlCache[$smilieUrl];
    }

    
/**
     * Prepares a smilie URL for use in an <img /> tag.
     *
     * @param string $smilieUrl
     *
     * @return string
     */
    
protected function _prepareSmilieUrlInternal($smilieUrl)
    {
        return 
htmlspecialchars($smilieUrl);
    }

    
/**
     * Adds to the list of acceptable media sites.
     *
     * @param array $sites
     */
    
public function addMediaSites(array $sites)
    {
        
$this->_mediaSites = array_merge($this->_mediaSites, $sites);
    }

    
/**
     * Sets the view that is used to render tags requiring templates.
     *
     * @param XenForo_View $view
     */
    
public function setView(XenForo_View $view = null)
    {
        
$this->_view = $view;
        if (
$view)
        {
            
$this->preLoadTemplates($view);
        }
    }

    
/**
     * @return null|XenForo_View
     */
    
public function getView()
    {
        return 
$this->_view;
    }

    
/**
     * Tells the view to pre-load the templates that are required.
     *
     * @param XenForo_View $view
     */
    
public function preLoadTemplates(XenForo_View $view)
    {
        
$view->preLoadTemplate('bb_code_tag_code');
        
$view->preLoadTemplate('bb_code_tag_php');
        
$view->preLoadTemplate('bb_code_tag_html');
        
$view->preLoadTemplate('bb_code_tag_quote');
        
$view->preLoadTemplate('bb_code_tag_attach');
        
$view->preLoadTemplate('bb_code_tag_spoiler');
    }

    
/**
     * Get the list of parsable tags and their parsing rules.
     *
     * @return array
     */
    
public function getTags()
    {
        if (
$this->_tags !== null)
        {
            return 
$this->_tags;
        }

        return array(
            
'b' => array(
                
'hasOption' => false,
                
'replace' => array('<b>', '</b>')
            ),
            
'i' => array(
                
'hasOption' => false,
                
'replace' => array('<i>', '</i>')
            ),
            
'u' => array(
                
'hasOption' => false,
                
'replace' => array('<span style="text-decoration: underline">', '</span>')
            ),
            
's' => array(
                
'hasOption' => false,
                
'replace' => array('<span style="text-decoration: line-through">', '</span>')
            ),

            
'color' => array(
                
'hasOption' => true,
                
'optionRegex' => '/^(rgb(s*d+%?s*,s*d+%?s*,s*d+%?s*)|#[a-f0-9]{6}|#[a-f0-9]{3}|[a-z]+)$/i',
                
'replace' => array('<span style="color: %s">', '</span>')
            ),
            
'font' => array(
                
'hasOption' => true,
                
'optionRegex' => '/^[a-z0-9 -]+$/i', // regex matched to HTML->BB code regex
                
'replace' => array('<span style="font-family: '%s'">', '</span>')
            ),
            
'size' => array(
                
'hasOption' => true,
                
'optionRegex' => '/^[0-9]+(px)?$/i',
                
'callback' => array($this, 'renderTagSize'),
            ),

            
'left' => array(
                
'hasOption' => false,
                
'callback' => array($this, 'renderTagAlign'),
                
'trimLeadingLinesAfter' => 1,
            ),
            
'center' => array(
                
'hasOption' => false,
                
'callback' => array($this, 'renderTagAlign'),
                
'trimLeadingLinesAfter' => 1,
            ),
            
'right' => array(
                
'hasOption' => false,
                
'callback' => array($this, 'renderTagAlign'),
                
'trimLeadingLinesAfter' => 1,
            ),
            
'indent' => array(
                
'trimLeadingLinesAfter' => 1,
                
'optionRegex' => '/^[0-9]+$/',
                
'callback' => array($this, 'renderTagIndent')
            ),

            
'url' => array(
                
'parseCallback' => array($this, 'parseValidatePlainIfNoOption'),
                
'callback' => array($this, 'renderTagUrl'),
            ),
            
'email' => array(
                
'parseCallback' => array($this, 'parseValidatePlainIfNoOption'),
                
'callback' => array($this, 'renderTagEmail')
            ),

            
'img' => array(
                
'hasOption' => false,
                
'plainChildren' => true,
                
'callback' => array($this, 'renderTagImage')
            ),

            
'quote' => array(
                
'trimLeadingLinesAfter' => 2,
                
'callback' => array($this, 'renderTagQuote')
            ),

            
'code' => array(
                
'parseCallback' => array($this, 'parseValidateTagCode'),
                
'stopSmilies' => true,
                
'stopLineBreakConversion' => true,
                
'trimLeadingLinesAfter' => 2,
                
'callback' => array($this, 'renderTagCode')
            ),
            
'php' => array(
                
'hasOption' => false,
                
'plainChildren' => true,
                
'stopSmilies' => true,
                
'stopLineBreakConversion' => true,
                
'trimLeadingLinesAfter' => 2,
                
'callback' => array($this, 'renderTagPhp')
            ),
            
'html' => array(
                
'hasOption' => false,
                
'plainChildren' => true,
                
'stopSmilies' => true,
                
'stopLineBreakConversion' => true,
                
'trimLeadingLinesAfter' => 2,
                
'callback' => array($this, 'renderTagHtml')
            ),

            
'list' => array(
                
'trimLeadingLinesAfter' => 1,
                
'callback' => array($this, 'renderTagList')
            ),

            
'plain' => array(
                
'hasOption' => false,
                
'plainChildren' => true,
                
'stopSmilies' => true,
                
'replace' => array('', '')
            ),

            
'media' => array(
                
'hasOption' => true,
                
'plainChildren' => true,
                
'callback' => array($this, 'renderTagMedia')
            ),

            
'spoiler' => array(
                
'trimLeadingLinesAfter' => 1,
                
'callback' => array($this, 'renderTagSpoiler')
            ),

            
'attach' => array(
                
'plainChildren' => true,
                
'callback' => array($this, 'renderTagAttach')
            ),

            
'user' => array(
                
'hasOption' => true,
                
'stopSmilies' => true,
                
'callback' => array($this, 'renderTagUser')
            )
        );
    }

    public function 
addCustomTags(array $tags)
    {
        foreach (
$tags AS $tagName => $tag)
        {
            
$tagInfo = $this->_setupCustomTagInfo($tagName, $tag);
            if (
$tagInfo)
            {
                
$this->_tags[$tagName] = $tagInfo;
            }
        }
    }

    protected function 
_setupCustomTagInfo($tagName, array $tag)
    {
        
$output = array();

        if (
$tag['bb_code_mode'] == 'replace')
        {
            
$output['replace'] = $tag['replace_html'];
        }
        else if (
$tag['bb_code_mode'] == 'callback')
        {
            
$output['callback'] = array($tag['callback_class'], $tag['callback_method']);
        }

        if (
$tag['has_option'] == 'yes')
        {
            
$output['hasOption'] = true;
        }
        else if (
$tag['has_option'] == 'no')
        {
            
$output['hasOption'] = false;
        }

        if (
strlen($tag['option_regex']))
        {
            
$output['optionRegex'] = $tag['option_regex'];
        }
        if (
$tag['trim_lines_after'])
        {
            
$output['trimLeadingLinesAfter'] = $tag['trim_lines_after'];
        }
        if (
$tag['plain_children'])
        {
            
$output['plainChildren'] = true;
        }
        if (
$tag['disable_smilies'])
        {
            
$output['stopSmilies'] = true;
        }
        if (
$tag['disable_nl2br'])
        {
            
$output['stopLineBreakConversion'] = true;
        }

        
$output['allowSignature'] = $tag['allow_signature'];

        return 
$output;
    }

    
/**
     * Allows the text to be filtered before parsing.
     *
     * @param string $text
     *
     * @return string
     */
    
public function preFilterText($text)
    {
        return 
$text;
    }

    
/**
     * Resets rendering state and renders a parsed BB code tree
     * to the required output format. Note that this initializes the default states,
     * so it is likely not the correct function to call for child tags.
     *
     * @param array $tree Tree from {@link parse()}.
     * @param array $extraStates A list of extra states to push into the formatter
     *
     * @return string Output text
     */
    
public function renderTree(array $tree, array $extraStates = array())
    {
        
$rendererStates = $extraStates + array(
            
'stopSmilies' => 0,
            
'stopLineBreakConversion' => 0,
            
'tagDataStack' => array(),
            
'noFollowDefault' => true, // add nofollow attributes
            
'shortenUrl' => true, // add ... in middle of long URLs
            
'lightBox' => true, // add 'LbImage' class to [IMG] output
            
'imgToSmilie' => false, // attempt to convert [IMG] to smilie if URL matches
        
);

        
$output = $this->renderSubTree($tree, $rendererStates);
        return 
$this->filterFinalOutput($output);
    }

    
/**
     * Renders a parsed BB code tree to the required output format. This does
     * not reset the rendering states, meaning it is ok for recursive calls.
     *
     * @param array $tree Tree from {@link parse()}
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     *
     * @return string Output text
     */
    
public function renderSubTree(array $tree, array $rendererStates)
    {
        
$output = '';
        
$trimLeadingLines = 0;

        foreach (
$tree AS $element)
        {
            
$output .= $this->renderTreeElement($element, $rendererStates, $trimLeadingLines);
        }

        return 
$output;
    }

    
/**
     * Renders a tree element, that be a tag (valid or not) or a string.
     *
     * @param array|string $element Tree element
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     * @param integer $trimLeadingLines By reference. Number of leading lines to strip off next element.
     *
     * @return string Rendered element.
     */
    
public function renderTreeElement($element, array $rendererStates, &$trimLeadingLines)
    {
        if (
is_array($element))
        {
            return 
$this->renderTag($element, $rendererStates, $trimLeadingLines);
        }
        else
        {
            return 
$this->renderString($element, $rendererStates, $trimLeadingLines);
        }
    }

    
/**
     * Renders a string tree element.
     *
     * @param string $string
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     * @param integer $trimLeadingLines By reference. Number of leading lines to strip off next element.
     *
     * @return string Rendered string
     */
    
public function renderString($string, array $rendererStates, &$trimLeadingLines)
    {
        if (
$trimLeadingLines)
        {
            
$string = $this->trimLeadingLines($string, $trimLeadingLines);
            
$trimLeadingLines = 0;
        }

        return 
$this->filterString($string, $rendererStates);
    }

    
/**
     * Trims the given number of leading blank lines off of the given string.
     *
     * @param string $string
     * @param integer $amount
     *
     * @return string
     */
    
public function trimLeadingLines($string, $amount)
    {
        
$amount = intval($amount);
        if (
$amount <= 0)
        {
            return 
$string;
        }

        return 
preg_replace('#^([ t]*r?n){1,' . $amount . '}#i', '', $string);
    }

    
/**
     * Renders a tag. This tag may be valid or invalid.
     *
     * @param array $element Tag element.
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     * @param integer $trimLeadingLines By reference. Number of leading lines to strip from next element. May be modified by tag.
     *
     * @return string Rendered tag.
     */
    
public function renderTag(array $element, array $rendererStates, &$trimLeadingLines)
    {
        
$trimLeadingLines = 0;

        if (isset(
$rendererStates['tagDataStack']))
        {
            
$rendererStates['tagDataStack'][] = $element;
        }

        
$tagDepthReached = (
            
$this->_tagDepthLimit
            
&& !empty($rendererStates['tagDataStack'])
            && 
count($rendererStates['tagDataStack']) > $this->_tagDepthLimit
        
);

        
$tagInfo = $this->_getTagRule($element['tag']);
        if (!
$tagInfo || $tagDepthReached)
        {
            
$output = $this->renderInvalidTag($element, $rendererStates);
        }
        else
        {
            if (!empty(
$tagInfo['stopSmilies']))
            {
                
$rendererStates['stopSmilies']++;
            }
            if (!empty(
$tagInfo['stopLineBreakConversion']))
            {
                
$rendererStates['stopLineBreakConversion']++;
            }

            
$output = $this->renderValidTag($tagInfo, $element, $rendererStates);

            if (!empty(
$tagInfo['trimLeadingLinesAfter']))
            {
                
$trimLeadingLines = $tagInfo['trimLeadingLinesAfter'];
            }
        }

        return 
$output;
    }

    
/**
     * Gets information about the specified tag.
     *
     * @param string $tagName
     *
     * @return array|false
     */
    
protected function _getTagRule($tagName)
    {
        
$tagName = strtolower($tagName);

        if (!empty(
$this->_tags[$tagName]) && is_array($this->_tags[$tagName]))
        {
            return 
$this->_tags[$tagName];
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * Renders an invalid tag. This tag is simply displayed in its original form.
     *
     * @param array $tag Tag data from tree
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     *
     * @return string Rendered version
     */
    
public function renderInvalidTag(array $tag, array $rendererStates)
    {
        return 
$this->renderTagUnparsed($tag, $rendererStates);
    }

    
/**
     * Renders a tag as if it's unparsed (in its original form).
     *
     * @param array $tag Tag data from tree
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     *
     * @return string Rendered version
     */
    
public function renderTagUnparsed(array $tag, array $rendererStates)
    {
        if (!empty(
$tag['original']) && is_array($tag['original']))
        {
            list(
$prepend, $append) = $tag['original'];
        }
        else
        {
            
$prepend = '';
            
$append = '';
        }

        
$output = $this->filterString($prepend, $rendererStates)
            . 
$this->renderSubTree($tag['children'], $rendererStates)
            . 
$this->filterString($append, $rendererStates);

        return 
$output;
    }

    
/**
     * Renders a tag.
     *
     * @param array $tagInfo Information about how to parse the tag
     * @param array $tag Tag data from tree
     * @param array $rendererStates Renderer states to push down. Except in specific cases, cannot be pushed up.
     *
     * @return string Rendered version
     */
    
public function renderValidTag(array $tagInfo, array $tag, array $rendererStates)
    {
        if (!empty(
$tagInfo['callback']))
        {
            if (
is_array($tagInfo['callback']) && $tagInfo['callback'][0] == '$this')
            {
                
$tagInfo['callback'][0] = $this;
            }
            if (!
is_callable($tagInfo['callback']))
            {
                return 
$this->renderInvalidTag($tag, $rendererStates);
            }
            return 
call_user_func($tagInfo['callback'], $tag, $rendererStates, $this);
        }
        else if (!empty(
$tagInfo['replace']))
        {
            
$text = $this->renderSubTree($tag['children'], $rendererStates);
            
$option = $this->filterString($tag['option'], $rendererStates);

            if (empty(
$tag['keepEmpty']) && trim($text) === '')
            {
                return 
$text;
            }

            if (
is_array($tagInfo['replace']))
            {
                list(
$prepend, $append) = $tagInfo['replace'];
                return 
$this->_wrapInHtml($prepend, $append, $text, $option);
            }
            else
            {
                return 
strtr($tagInfo['replace'], array(
                    
'{text}' => $text,
                    
'{option}' => $option
                
));
            }
        }
        else
        {
            return 
$this->renderInvalidTag($tag, $rendererStates);
        }
    }

    protected function 
_wrapInHtml($prepend, $append, $text, $option = null)
    {
        if (
$option === null)
        {
            return 
$prepend . $text . $append;
        }
        else
        {
            return 
sprintf($prepend, $option) . $text . sprintf($append, $option);
        }
    }

    
/**
     * Similar to rendering the tree, but this function renders all tags to plain text
     * (as if they weren't special tags). This can be useful for functions that can only
     * take plain text children.
     *
     * Note that this output is not escaped in anyway!
     *
     * @param array $tree Tree or sub-tree to stringify
     *
     * @return string Tree as a string (like the original input)
     */
    
public function stringifyTree(array $tree)
    {
        
$output = '';

        foreach (
$tree AS $element)
        {
            if (
is_array($element))
            {
                if (!empty(
$element['original']) && is_array($element['original']))
                {
                    list(
$prepend, $append) = $element['original'];
                }
                else
                {
                    
$prepend = '';
                    
$append = '';
                }

                
$output .= $prepend . $this->stringifyTree($element['children']) . $append;
            }
            else
            {
                
$output .= strval($element);
            }
        }

        return 
$output;
    }

    
/**
     * Filter a string for the current output format. A string is simply the text
     * between tags. This function is responsible for things like word wrap and smilies
     * and output escaping.
     *
     * @param string $string
     * @param array $rendererStates List of states the renderer may be in
     *
     * @return string Filtered/escaped string
     */
    
public function filterString($string, array $rendererStates)
    {
        
$string = XenForo_Helper_String::censorString($string);

        if (empty(
$rendererStates['stopSmilies']))
        {
            
$string = $this->replaceSmiliesInText($string, 'htmlspecialchars');
        }
        else
        {
            
$string = htmlspecialchars($string);
        }

        if (empty(
$rendererStates['stopLineBreakConversion']))
        {
            
$string = nl2br($string);
        }

        return 
$string;
    }

    
/**
     * Filters the final string output.
     *
     * @param string $output
     *
     * @return string
     */
    
public function filterFinalOutput($output)
    {
        return 
trim($output);
    }

    
/**
     * Gets a valid, full URL if possible. False is returned if not possible.
     *
     * @param string $url URL to validate
     *
     * @return string|false
     */
    
protected function _getValidUrl($url)
    {
        
$url = trim($url);

        if (!
$url)
        {
            return 
false;
        }

        switch (
$url[0])
        {
            case 
'#':
            case 
'/':
            case 
' ':
            case 
"r":
            case 
"n":
                return 
false;
        }

        if (
preg_match('/r?n/', $url))
        {
            return 
false;
        }

        if (
preg_match('#^https?://#i', $url))
        {
            return 
$url;
        }

        return 
'http://' . $url;
    }

    
/**
     * Renders an indent tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagIndent(array $tag, array $rendererStates)
    {
        
$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;
        }

        
$invisibleSpace = $this->_endsInBlockTag($text) ? '' : '&#8203;';

        if (
$amount < 1)
        {
            return 
$this->_wrapInHtml('<div>', $invisibleSpace . '</div>', $text);
        }
        else
        {
            
$paddingSide = ($this->_textDirection == 'RTL' ? 'padding-right' : 'padding-left');
            return 
$this->_wrapInHtml('<div style="' . $paddingSide . ': ' . (30 * $amount) . 'px">', $invisibleSpace . '</div>', $text);
        }
    }

    
/**
     * Renders an alignment (left, center, right) tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagAlign(array $tag, array $rendererStates)
    {
        
$text = $this->renderSubTree($tag['children'], $rendererStates);

        
$invisibleSpace = $this->_endsInBlockTag($text) ? '' : '&#8203;';

        switch (
strtolower($tag['tag']))
        {
            case 
'left':
            case 
'center':
            case 
'right':
                return 
$this->_wrapInHtml('<div style="text-align: ' . $tag['tag'] . '">', $invisibleSpace. '</div>', $text);

            default:
                return 
$this->_wrapInHtml('<div>', $invisibleSpace. '</div>', $text);
        }
    }

    protected function 
_endsInBlockTag($text)
    {
        return 
preg_match('#</(p|div)>$#i', substr(rtrim($text), -6));
    }

    
/**
     * Renders a size tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
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)
        {
            return 
$this->_wrapInHtml('<span style="font-size: ' . htmlspecialchars($size) . '">', '</span>', $text);
        }
        else
        {
            return 
$text;
        }
    }

    
/**
     * Gets the effective text size to use.
     *
     * @param string $inputSize
     *
     * @return string|false
     */
    
public function getTextSize($inputSize)
    {
        if (
strval(intval($inputSize)) == strval($inputSize))
        {
            
// int only, translate size
            
if ($inputSize <= 0)
            {
                
$size = false;
            }
            else
            {
                switch (
$inputSize)
                {
                    case 
1: $size = '9px'; break;
                    case 
2: $size = '10px'; break;
                    case 
3: $size = '12px'; break;
                    case 
4: $size = '15px'; break;
                    case 
5: $size = '18px'; break;
                    case 
6: $size = '22px'; break;

                    case 
7:
                    default:
                        
$size = '26px';
                }
            }
        }
        else
        {
            
// int and unit
            
if (preg_match('/^([0-9]+)px$/i', $inputSize, $match))
            {
                if (
$match[1] < 8)
                {
                    
$size = '8px';
                }
                else if (
$match[1] > 36)
                {
                    
$size = '36px';
                }
                else
                {
                    
$size = $inputSize;
                }
            }
            else
            {
                
$size = false;
            }
        }

        return 
$size;
    }

    
/**
     * Disables parsing of child tags if this tag does not have an option.
     * Useful for tags like url/email, where the address may be in the body
     * of the tag.
     *
     * @param array $tagInfo Info about the tag we're parsing.
     * @param string|null $tagOption Any option passed into the tag
     *
     * @return array|boolean True if tag is ok as is, array to change states, false to reject tag
     */
    
public function parseValidatePlainIfNoOption(array $tagInfo, $tagOption)
    {
        if (empty(
$tagOption))
        {
            return array(
'plainChildren' => true);
        }
        else
        {
            return 
true;
        }
    }

    
/**
     * Renders a URL tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagUrl(array $tag, array $rendererStates)
    {
        if (!empty(
$tag['option']))
        {
            
$url = $tag['option'];
            
$text = $this->renderSubTree($tag['children'], $rendererStates);
        }
        else
        {
            
$url = $this->stringifyTree($tag['children']);
            
$text = urldecode($url);
            if (!
preg_match('/./u', $text))
            {
                
$text = $url;
            }
            
$text = XenForo_Helper_String::censorString($text);

            if (!empty(
$rendererStates['shortenUrl']))
            {
                
$length = utf8_strlen($text);
                if (
$length > 100)
                {
                    
$text = utf8_substr_replace($text, '...', 35, $length - 35 - 45);
                }
            }

            
$text = htmlspecialchars($text);
        }

        
$url = $this->_getValidUrl($url);
        if (!
$url)
        {
            return 
$text;
        }
        else
        {
            list(
$class, $target, $type) = XenForo_Helper_String::getLinkClassTarget($url);
            if (
$type == 'internal')
            {
                
$noFollow = '';
            }
            else
            {
                
$noFollow = (empty($rendererStates['noFollowDefault']) ? '' : ' rel="nofollow"');
            }

            
$href = XenForo_Helper_String::censorString($url);
            
$proxyHref = $this->_handleLinkProxyOption($href, $type);

            
$proxyAttr = '';
            if (
$proxyHref)
            {
                
$proxyAttr = ' data-proxy-href="' . htmlspecialchars($proxyHref) . '"';
                
$class .= ' ProxyLink';
            }

            
$class = $class ? " class="$class"" : '';
            
$target = $target ? " target="$target"" : '';

            return 
$this->_wrapInHtml(
                
'<a href="' . htmlspecialchars($href) . '"' . $target . $class . $proxyAttr . $noFollow . '>',
                
'</a>',
                
$text
            
);
        }
    }

    
/**
     * Renders an email tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagEmail(array $tag, array $rendererStates)
    {
        if (!empty(
$tag['option']))
        {
            
$email = $tag['option'];
            
$text = $this->renderSubTree($tag['children'], $rendererStates);
        }
        else
        {
            
$email = $this->stringifyTree($tag['children'], $rendererStates);
            
$text = $this->filterString($email, $rendererStates);
        }

        if (
strpos($email, '@') === false)
        {
            
// invalid URL, ignore
            
return $text;
        }

        
$email = XenForo_Helper_String::censorString($email);

        return 
$this->_wrapInHtml('<a href="mailto:' . htmlspecialchars($email) . '">', '</a>', $text);
    }

    
/**
     * Renders a img tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagImage(array $tag, array $rendererStates)
    {
        
$url = $this->stringifyTree($tag['children']);

        
$validUrl = $this->_getValidUrl($url);
        if (!
$validUrl)
        {
            return 
$this->filterString($url, $rendererStates);
        }

        
$censored = XenForo_Helper_String::censorString($validUrl);
        if (
$censored != $validUrl)
        {
            return 
$this->filterString($url, $rendererStates);
        }

        
// attempts to convert smilies posted as [IMG] tags back into smilies
        
if ($rendererStates['imgToSmilie'])
        {
            foreach (
$this->_smiliePaths AS $smiliePath => $smilieId)
            {
                if (
strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                {
                    return 
$this->_smilieReverse[$smilieId];
                }
            }
        }

        
$imageUrl = $this->_handleImageProxyOption($validUrl);

        return 
sprintf($this->_imageTemplate,
            
htmlspecialchars($imageUrl),
            
$rendererStates['lightBox'] ? ' LbImage' : '',
            
htmlspecialchars($validUrl)
        );
    }

    
/**
     * Pass an image URL to the image proxy system if appropriate
     *
     * @param string $url
     *
     * @return string
     */
    
protected function _handleImageProxyOption($url)
    {
        list(
$class, $target, $type, $schemeMatch) = XenForo_Helper_String::getLinkClassTarget($url);

        if (
$type == 'external' || !$schemeMatch)
        {
            
$options = XenForo_Application::getOptions();
            if (!empty(
$options->imageLinkProxy['images']))
            {
                
$url = $this->_generateProxyLink('image', $url);
            }
        }

        return 
$url;
    }

    
/**
     * Pass a link URL to the proxy / redirect system if appropriate
     *
     * @param string $url
     * @param string $linkType
     *
     * @return string|false
     */
    
protected function _handleLinkProxyOption($url, $linkType)
    {
        if (
$linkType == 'external')
        {
            
$options = XenForo_Application::getOptions();

            if (!empty(
$options->imageLinkProxy['links']))
            {
                return 
$this->_generateProxyLink('link', $url);
            }
        }

        return 
false;
    }

    protected function 
_generateProxyLink($proxyType, $url)
    {
        
$hash = hash_hmac('md5', $url,
            
XenForo_Application::getConfig()->globalSalt . XenForo_Application::getOptions()->imageLinkProxyKey
        
);
        return 
'proxy.php?' . $proxyType . '=' . urlencode($url) . '&hash=' . $hash;
    }

    
/**
     * Modifies the parsing options for a code tag. Users must explicitly
     * opt in to allow BB codes to be used within.
     *
     * @param array $tagInfo Info about the tag we're parsing.
     * @param string|null $tagOption Any option passed into the tag
     *
     * @return array|boolean True if tag is ok as is, array to change states, false to reject tag
     */
    
public function parseValidateTagCode(array $tagInfo, $tagOption)
    {
        if (
strtolower($tagOption) == 'rich')
        {
            return 
true;
        }
        else
        {
            return array(
'plainChildren' => true);
        }
    }

    
/**
     * Renders a code tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagCode(array $tag, array $rendererStates)
    {
        switch (
strtolower(strval($tag['option'])))
        {
            case 
'php':
                return 
$this->renderTagPhp($tag, $rendererStates);

            case 
'html':
                return 
$this->renderTagHtml($tag, $rendererStates);
        }

        
$content = $this->renderSubTree($tag['children'], $rendererStates);

        if (
$this->_view)
        {
            
$template = $this->_view->createTemplateObject('bb_code_tag_code', array(
                
'content' => $content
            
));
            return 
$template->render();
        }
        else
        {
            return 
$this->_wrapInHtml('<pre style="margin: 1em auto" title="Code">', '</pre>', $content);
        }
    }

    
/**
     * Renders an HTML tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagHtml(array $tag, array $rendererStates)
    {
        
$content = $this->stringifyTree($tag['children']);
        
$content = $this->filterString($content, $rendererStates);

        if (
$this->_view)
        {
            
$template = $this->_view->createTemplateObject('bb_code_tag_html', array(
                
'content' => $content
            
));
            return 
$template->render();
        }
        else
        {
            return 
$this->_wrapInHtml('<pre style="margin: 1em auto" title="HTML">', '</pre>', $content);
        }
    }

    
/**
     * Renders a PHP tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagPhp(array $tag, array $rendererStates)
    {
        
$content = $this->stringifyTree($tag['children']);
        
$content = XenForo_Helper_String::censorString($content);
        
$content = preg_replace('/^[ t]*r?n/', '', $content);

        if (
strpos($content, '<?') == false)
        {
            
$tagAdded = true;
            
$content = "<?phpn$content";
        }
        else
        {
            
$tagAdded = false;
        }

        
$content = highlight_string($content, true);

        if (
$tagAdded)
        {
            
$content = preg_replace(
                
'#&lt;?php<brs*/?>#',
                
'',
                
$content,
                
1
            
);
        }

        if (
$this->_view)
        {
            
$template = $this->_view->createTemplateObject('bb_code_tag_php', array(
                
'content' => $content
            
));
            return 
$template->render();
        }
        else
        {
            return 
$this->_wrapInHtml('<div style="margin: 1em auto" title="PHP">', '</div>', $content);
        }
    }

    
/**
     * Renders a quote tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagQuote(array $tag, array $rendererStates)
    {
        
$keys = array_keys($tag['children']);
        if (!
$keys)
        {
            return 
'';
        }

        
$first = reset($keys);
        
$last = end($keys);

        if (
is_string($tag['children'][$first]))
        {
            
$tag['children'][$first] = ltrim($tag['children'][$first]);
        }
        if (
is_string($tag['children'][$last]))
        {
            
$tag['children'][$last] = rtrim($tag['children'][$last]);
        }

        
$content = $this->renderSubTree($tag['children'], $rendererStates);
        if (
$content === '')
        {
            return 
'';
        }

        
$source = false;
        
$attributes = array();

        
/*
         * NOTE: changes to this code must also be reflected in
         * XenForo_Model_Post::alertQuotedMembers()
         */
        
if ($tag['option'])
        {
            
$parts = explode(',', $tag['option']);
            
$name = $this->filterString(array_shift($parts),
                
array_merge($rendererStates, array(
                    
'stopSmilies' => true,
                    
'stopLineBreakConversion' => true
                
))
            );

            foreach (
$parts AS $part)
            {
                
$partAttributes = explode(':', $part, 2);
                if (isset(
$partAttributes[1]))
                {
                    
$attrName = trim($partAttributes[0]);
                    
$attrValue = trim($partAttributes[1]);
                    if (
$attrName !== '' && $attrValue !== '')
                    {
                        
$attributes[$attrName] = $attrValue;
                    }
                }
            }

            list(
$firstName, $firstValue) = each($attributes);
            if (
$firstName && $firstName != 'member')
            {
                
$source = array('type' => $firstName, 'id' => intval($firstValue));
            }
        }
        else
        {
            
$name = false;
        }

        if (
$this->_view)
        {
            
$template = $this->_view->createTemplateObject('bb_code_tag_quote', array(
                
'content' => $content,
                
'nameHtml' => $name,
                
'source' => $source,
                
'attributes' => $attributes,
                
'ignored' => (isset($attributes['member']) && isset($this->_ignoredUsers[intval($attributes['member'])]))
            ));
            return 
$template->render();
        }
        else
        {
            return 
$this->_renderTagQuoteFallback($name, $content);
        }
    }

    
/**
     * Returns HTML output for a quote tag when the view is not available
     *
     * @param string $name Name of quoted user
     * @param string $content Quoted text
     *
     * @return string
     */
    
protected function _renderTagQuoteFallback($name, $content)
    {
        if (
$name)
        {
            
$name = '<div>' . $name . '</div>';
        }

        return 
$this->_wrapInHtml('<blockquote>', '</blockquote>', $name . $content);
    }

    
/**
     * Renders a list tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagList(array $tag, array $rendererStates)
    {
        
$listType = ($tag['option'] == '1' ? 'ol' : 'ul');

        
$elements = array();
        
$lastElement = '';
        
$trimLeadingLines = 0;

        foreach (
$tag['children'] AS $child)
        {
            if (
is_array($child))
            {
                
$childText = $this->renderTag($child, $rendererStates, $trimLeadingLines);
                if (
preg_match('#^<(ul|ol)#', $childText))
                {
                    
$lastElement = rtrim($lastElement);
                    if (
substr($lastElement, -6) == '<br />')
                    {
                        
$lastElement = substr($lastElement, 0, -6);
                    }
                }
                
$lastElement .= $childText;
            }
            else
            {
                if (
strpos($child, '[*]') !== false)
                {
                    
$parts = explode('[*]', $child);

                    
$beforeFirst = array_shift($parts);
                    if (
$lastElement !== '' || trim($beforeFirst) !== '')
                    {
                        
$lastElement .= $this->renderString($beforeFirst, $rendererStates, $trimLeadingLines);
                    }

                    foreach (
$parts AS $part)
                    {
                        
$this->_appendListElement($elements, $lastElement);
                        
$lastElement = $this->renderString($part, $rendererStates, $trimLeadingLines);
                    }
                }
                else
                {
                    
$lastElement .= $this->renderString($child, $rendererStates, $trimLeadingLines);
                }
            }
        }

        
$this->_appendListElement($elements, $lastElement);

        if (!
$elements)
        {
            return 
'';
        }

        return 
$this->_renderListOutput($listType, $elements);
    }

    
/**
     * Given already parsed list elements, gets the output for the list.
     *
     * @param string $listType Type of list (ol or ul)
     * @param array $elements List of elements in the list. These are already rendered.
     *
     * @return string
     */
    
protected function _renderListOutput($listType, array $elements)
    {
        return 
"<$listType>n<li>" . implode("</li>n<li>", $elements) . "</li>n</$listType>";
    }

    
/**
     * Appends a list element if it is not empty.
     *
     * @param array $elements By reference. List of existing elements.
     * @param string $appendString String to append (if not empty)
     */
    
protected function _appendListElement(array &$elements, $appendString)
    {
        if (
$appendString !== '')
        {
            
$appendString = rtrim($appendString);
            if (
substr($appendString, -6) == '<br />')
            {
                
$appendString = substr($appendString, 0, -6);
            }

            
$elements[] = $appendString;
        }
    }

    public function 
renderTagAttach(array $tag, array $rendererStates)
    {
        
$id = intval($this->stringifyTree($tag['children']));
        if (!
$id)
        {
            return 
'';
        }

        if (!
$this->_view)
        {
            
$phrase = new XenForo_Phrase('view_attachment_x', array('name' => $id));
            return 
'<a href="' . XenForo_Link::buildPublicLink('full:attachments', array('attachment_id' => $id)) . '">' . $phrase . '</a>';
        }

        if (empty(
$rendererStates['attachments'][$id]))
        {
            
$attachment = array('attachment_id' => $id);
            
$validAttachment = false;
            
$canView = false;
        }
        else
        {
            
$attachment = $rendererStates['attachments'][$id];
            
$validAttachment = true;
            
$canView = empty($rendererStates['viewAttachments']) ? false : true;
        }

        
$template = $this->_view->createTemplateObject('bb_code_tag_attach', array(
            
'attachment' => $attachment,
            
'validAttachment' => $validAttachment,
            
'canView' => $canView,
            
'full' => (strtolower($tag['option']) == 'full')
        ));
        return 
$template->render();
    }

    public function 
renderTagUser(array $tag, array $rendererStates)
    {
        
$content = $this->renderSubTree($tag['children'], $rendererStates);
        if (
$content === '')
        {
            return 
'';
        }

        
$userId = intval($tag['option']);
        if (!
$userId)
        {
            return 
$content;
        }

        
$link = XenForo_Link::buildPublicLink('full:members', array('user_id' => $userId));
        
$username = $this->stringifyTree($tag['children']);

        return 
$this->_wrapInHtml('<a href="' . htmlspecialchars($link) . '" class="username" data-user="' . $userId . ', ' . htmlspecialchars($username) . '">', '</a>', $content);
    }

    
/**
     * Renders a media tag. Media tags embed rich media (usually videos). To embed a video,
     * the source must be known.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagMedia(array $tag, array $rendererStates)
    {
        
$mediaKey = trim($this->stringifyTree($tag['children']));
        if (
preg_match('#[&?"'<>rn]#', $mediaKey) || strpos($mediaKey, '..') !== false)
        
{
            return 
'';
        }

        
$mediaSiteId = strtolower($tag['option']);
        if (
$mediaSiteId == 'youtube')
        {
            
// youtube iframe embed bug workaround
            
$mediaKey = str_replace('/', '', $mediaKey);
        }
        if (isset(
$this->_mediaSites[$mediaSiteId]))
        {
            
$embedHtml = $this->_getMediaSiteHtmlFromCallback($mediaKey, $this->_mediaSites[$mediaSiteId]);

            if (!
$embedHtml)
            {
                
$embedHtml = strtr($this->_mediaSites[$mediaSiteId]['embed_html'], array(
                    
'{$id}' => urlencode($mediaKey),
                    
'{$id:digits}' => intval($mediaKey)
                ));
            }

            return 
$embedHtml;
        }
        else
        {
            return 
'';
        }
    }

    
/**
     * Renders a spoiler tag.
     *
     * @param array $tag Information about the tag reference; keys: tag, option, children
     * @param array $rendererStates Renderer states to push down
     *
     * @return string Rendered tag
     */
    
public function renderTagSpoiler(array $tag, array $rendererStates)
    {
        
$keys = array_keys($tag['children']);
        if (!
$keys)
        {
            return 
'';
        }

        
$first = reset($keys);
        
$last = end($keys);

        if (
is_string($tag['children'][$first]))
        {
            
$tag['children'][$first] = ltrim($tag['children'][$first]);
        }
        if (
is_string($tag['children'][$last]))
        {
            
$tag['children'][$last] = rtrim($tag['children'][$last]);
        }

        
$content = $this->renderSubTree($tag['children'], $rendererStates);
        if (
$content === '')
        {
            return 
'';
        }

        if (
$tag['option'])
        {
            
$title = $this->filterString($tag['option'],
                
array_merge($rendererStates, array(
                    
'stopSmilies' => true,
                    
'stopLineBreakConversion' => true
                
))
            );
        }
        else
        {
            
$title = false;
        }

        if (
$this->_view)
        {
            
$template = $this->_view->createTemplateObject('bb_code_tag_spoiler', array(
                
'content' => $content,
                
'titleHtml' => $title
            
));
            return 
$template->render();
        }
        else
        {
            return 
$this->_renderTagSpoilerFallback($title, $content, $rendererStates);
        }
    }

    
/**
     * Returns HTML output for a spoiler tag when the view is not available
     *
     * @param string $title Title of spoiler
     * @param string $content Spoiler text
     * @param array $rendererStates
     *
     * @return string
     */
    
protected function _renderTagSpoilerFallback($title, $content, array $rendererStates)
    {
        if (!empty(
$rendererStates['spoilerTextWithFallback']))
        {
            return 
'<div>' . $content . '</div>';
        }
        else
        {
            
$spoilerText = new XenForo_Phrase('spoiler');
            return 
'<div><b>' . ($title ? ($spoilerText . ': ' . $title) : $spoilerText) . '</b></div>';
        }
    }

    
/**
     * Attempts to fetch media tag embed HTML using the callback method defined for a media site, if one is specified.
     *
     * @param string $mediaKey
     * @param array $site Information about the site to render this media
     *
     * @return string|boolean Returns false if callback is invalid
     */
    
protected function _getMediaSiteHtmlFromCallback($mediaKey, array $site)
    {
        if (!empty(
$site['callback']) && is_array($site['callback']))
        {
            
$class = $site['callback'][0];
            
$method = $site['callback'][1];

            if (
XenForo_Application::autoload($class) && method_exists($class, $method))
            {
                return 
call_user_func_array($site['callback'], array($mediaKey, $site));
            }
        }

        return 
false;
    }

    
/**
     * Replaces smilie strings in text with the appropriate "rich" markup.
     * This method also escapes the output before the smilies are ultimately replaced.
     * This is necessary to prevent the rich output from being escaped.
     *
     * @param string $text Text to replace in
     * @param mixed $escapeCallback Callback for escaping. If empty, no escaping is done.
     *
     * @return string
     */
    
public function replaceSmiliesInText($text, $escapeCallback = '')
    {
        if (
$this->_smilieTranslate)
        {
            
$text = strtr($text, $this->_smilieTranslate);
        }

        if (
$escapeCallback)
        {
            if (
$escapeCallback == 'htmlspecialchars')
            {
                
$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
            }
            else
            {
                
$text = call_user_func($escapeCallback, $text);
            }
        }

        if (
$this->_smilieTranslate)
        {
            
$split = preg_split("#\0(d+)\0#", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
            
$text = '';
            foreach (
$split AS $key => $value)
            {
                
// odd keys contain the delimiter we want
                
if ($key % 2 == 0)
                {
                    
$text .= $value;
                }
                else if (isset(
$this->_smilieReverse[$value]))
                {
                    
$text .= $this->_smilieReverse[$value];
                }
            }
        }

        return 
$text;
    }

    
/**
     * Create the specified BB code formatter.
     *
     * @param string $class Name of the class. If empty, uses this class; if doesn't contain an underscore, assumes a partial name
     * @param array|boolean Set of options to configure formatter; defaults to pulling as necessary; if false, doesn't look in registry etc
     *
     * @return XenForo_BbCode_Formatter_Base
     */
    
public static function create($class = '', $options = array())
    {
        if (!
$class)
        {
            
$class = __CLASS__;
        }
        else if (
strpos($class, '_') === false)
        {
            
$class = 'XenForo_BbCode_Formatter_' . $class;
        }

        
$class = XenForo_Application::resolveDynamicClass($class, 'bb_code');

        
/** @var XenForo_BbCode_Formatter_Base $formatter */
        
$formatter = new $class();

        if (
is_array($options))
        {
            
$baseOptions = array(
                
'smilies' => null,
                
'bbCode' => null,
                
'view' => null
            
);
            
$options = array_merge($baseOptions, $options);
        }
        else
        {
            
$options = array(
                
'smilies' => array(),
                
// omit bbCode as we basically always want this for custom tags
                
'bbCode' => null,
                
'view' => false
            
);
        }

        if (!
is_array($options['smilies']))
        {
            if (
XenForo_Application::isRegistered('smilies'))
            {
                
$options['smilies'] = XenForo_Application::get('smilies');
            }
            else
            {
                
$options['smilies'] = XenForo_Model::create('XenForo_Model_Smilie')->getAllSmiliesForCache();
                
XenForo_Application::set('smilies', $options['smilies']);
            }
        }

        if (
$options['smilies'])
        {
            
$formatter->addSmilies($options['smilies']);
        }

        if (!
is_array($options['bbCode']))
        {
            if (
XenForo_Application::isRegistered('bbCode'))
            {
                
$options['bbCode'] = XenForo_Application::get('bbCode');
            }
            else
            {
                
$options['bbCode'] = XenForo_Model::create('XenForo_Model_BbCode')->getBbCodeCache();
                
XenForo_Application::set('bbCode', $options['bbCode']);
            }
        }

        if (!empty(
$options['bbCode']['mediaSites']))
        {
            
$formatter->addMediaSites($options['bbCode']['mediaSites']);
        }

        if (!empty(
$options['bbCode']['bbCodes']))
        {
            
$formatter->addCustomTags($options['bbCode']['bbCodes']);
        }

        if (
$options['view'])
        {
            
$formatter->setView($options['view']);
        }

        return 
$formatter;
    }
}
Онлайн: 2
Реклама