Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/BbCode/Processor.php
<?php

namespace XF\BbCode;

use
XF\BbCode\ProcessorAction\AnalyzerInterface;
use
XF\BbCode\ProcessorAction\FiltererInterface;
use
XF\BbCode\ProcessorAction\ProcessorAwareInterface;

use function
call_user_func, call_user_func_array, is_array, is_string;

class
Processor extends Traverser
{
   
/**
     * @var AnalyzerInterface[]
     */
   
protected $analyzers = [];

    protected
$analyzeParseCallbacks = [];

    protected
$analyzeSetupCallbacks = [];

    protected
$analyzeGlobalTagCallbacks = [];

    protected
$analyzeTagCallbacks = [];

    protected
$analyzeStringCallbacks = [];

    protected
$analyzeFinalCallbacks = [];

   
/**
     * @var FiltererInterface
     */
   
protected $filterers = [];

    protected
$filterParseCallbacks = [];

    protected
$filterSetupCallbacks = [];

    protected
$filterGlobalTagCallbacks = [];

    protected
$filterTagCallbacks = [];

    protected
$filterStringCallbacks = [];

    protected
$filterTagOptionCallbacks = [];

    protected
$filterFinalCallbacks = [];

    public function
__construct()
    {
    }

    public function
addProcessorAction($name, $processor)
    {
       
$valid = false;

        if (
$processor instanceof AnalyzerInterface)
        {
           
$this->addAnalyzer($name, $processor);
           
$valid = true;
        }
        if (
$processor instanceof FiltererInterface)
        {
           
$this->addFilterer($name, $processor);
           
$valid = true;
        }

        if (!
$valid)
        {
            throw new \
InvalidArgumentException("Process provided must implement AnalyzerInterface or FiltererInterface");
        }

        return
$this;
    }

    public function
addAnalyzer($name, AnalyzerInterface $analyzer)
    {
        if (isset(
$this->analyzers[$name]))
        {
            throw new \
LogicException("Analyzer '$name' has already been registered");
        }

        if (
$analyzer instanceof ProcessorAwareInterface)
        {
           
$analyzer->setProcessor($this);
        }

       
$hooks = new ProcessorAction\AnalyzerHooks($analyzer);
       
$analyzer->addAnalysisHooks($hooks);

       
$parse = $hooks->getParseHooks();
       
$this->analyzeParseCallbacks = array_merge($this->analyzeParseCallbacks, $parse);

       
$setup = $hooks->getSetupHooks();
       
$this->analyzeSetupCallbacks = array_merge($this->analyzeSetupCallbacks, $setup);

       
$string = $hooks->getStringHooks();
       
$this->analyzeStringCallbacks = array_merge($this->analyzeStringCallbacks, $string);

       
$final = $hooks->getFinalHooks();
       
$this->analyzeFinalCallbacks = array_merge($this->analyzeFinalCallbacks, $final);

       
$globalTags = $hooks->getGlobalTagHooks();
       
$this->analyzeGlobalTagCallbacks = array_merge($this->analyzeGlobalTagCallbacks, $globalTags);

       
$tags = $hooks->getTagHooks();
        foreach (
$tags AS $tag => $hooks)
        {
            if (isset(
$this->analyzeTagCallbacks[$tag]))
            {
               
$this->analyzeTagCallbacks[$tag] = array_merge($this->analyzeTagCallbacks[$tag], $hooks);
            }
            else
            {
               
$this->analyzeTagCallbacks[$tag] = $hooks;
            }
        }

       
$this->analyzers[$name] = $analyzer;

        return
$this;
    }

    public function
getAnalyzer($name)
    {
        return
$this->analyzers[$name] ?? null;
    }

    public function
addFilterer($name, FiltererInterface $filterer)
    {
        if (isset(
$this->filterers[$name]))
        {
            throw new \
LogicException("Filterer '$name' has already been registered");
        }

        if (
$filterer instanceof ProcessorAwareInterface)
        {
           
$filterer->setProcessor($this);
        }

       
$hooks = new ProcessorAction\FiltererHooks($filterer);
       
$filterer->addFiltererHooks($hooks);

       
$parse = $hooks->getParseHooks();
       
$this->filterParseCallbacks = array_merge($this->filterParseCallbacks, $parse);

       
$setup = $hooks->getSetupHooks();
       
$this->filterSetupCallbacks = array_merge($this->filterSetupCallbacks, $setup);

       
$string = $hooks->getStringHooks();
       
$this->filterStringCallbacks = array_merge($this->filterStringCallbacks, $string);

       
$option = $hooks->getTagOptionHooks();
       
$this->filterTagOptionCallbacks = array_merge($this->filterTagOptionCallbacks, $option);

       
$final = $hooks->getFinalHooks();
       
$this->filterFinalCallbacks = array_merge($this->filterFinalCallbacks, $final);

       
$globalTags = $hooks->getGlobalTagHooks();
       
$this->filterGlobalTagCallbacks = array_merge($this->filterGlobalTagCallbacks, $globalTags);

       
$tags = $hooks->getTagHooks();
        foreach (
$tags AS $tag => $hooks)
        {
            if (isset(
$this->filterTagCallbacks[$tag]))
            {
               
$this->filterTagCallbacks[$tag] = array_merge($this->filterTagCallbacks[$tag], $hooks);
            }
            else
            {
               
$this->filterTagCallbacks[$tag] = $hooks;
            }
        }

       
$this->filterers[$name] = $filterer;

        return
$this;
    }

    public function
getFilterer($name)
    {
        return
$this->filterers[$name] ?? null;
    }

    public function
getDefaultOptions()
    {
        return [
           
'stopSmilies' => 0,
           
'stopAutoLink' => 0,
           
'plain' => false,
        ];
    }

    protected function
setupParse($string, Parser $parser, RuleSet $rules, array &$options)
    {
       
$string = parent::setupParse($string, $parser, $rules, $options);

        foreach (
$this->filterParseCallbacks AS $callback)
        {
           
$string = call_user_func_array($callback, [$string, $parser, $rules, &$options]);
        }

        foreach (
$this->analyzeParseCallbacks AS $callback)
        {
           
$string = call_user_func_array($callback, [$string, $parser, $rules, &$options]);
        }

        return
$string;
    }

    protected function
setupRender(array $ast, array $options)
    {
       
parent::setupRender($ast, $options);

        foreach (
$this->analyzeSetupCallbacks AS $callback)
        {
           
call_user_func($callback, $ast, $options, $this);
        }

        foreach (
$this->filterSetupCallbacks AS $callback)
        {
           
call_user_func($callback, $ast, $options, $this);
        }
    }

    public function
filterFinalOutput($output)
    {
       
$output = parent::filterFinalOutput($output);

        foreach (
$this->filterFinalCallbacks AS $callback)
        {
           
$output = call_user_func($callback, $output, $this);
        }

        foreach (
$this->analyzeFinalCallbacks AS $callback)
        {
           
call_user_func($callback, $output, $this);
        }

        return
$output;
    }

    public function
renderTag(array $tag, array $options)
    {
       
$tagName = $tag['tag'];
       
$rule = $this->rules->getTag($tagName);

        if (
$rule)
        {
            if (!empty(
$rule['stopSmilies']))
            {
               
$options['stopSmilies']++;
            }
            if (!empty(
$rule['stopAutoLink']))
            {
               
$options['stopAutoLink']++;
            }
            if (!empty(
$rule['plain']))
            {
               
$options['plain'] = true;
            }
        }

       
$output = $this->filterTag($tag, $options);
       
$this->analyzeTag($tag, $options, $output);
        return
$output;
    }

    protected function
filterTag(array $tag, array $options)
    {
       
$filter = function($callback) use ($tag, $options)
        {
           
$output = call_user_func($callback, $tag, $options, $this);
            if (
$output === true)
            {
                return
$this->renderValidTag($tag, $options);
            }
            else if (
$output === false)
            {
                return
$this->renderSubTree($tag['children'], $options);
            }
            else if (
is_string($output))
            {
                return
$output;
            }
            else
            {
                return
null;
            }
        };

        foreach (
$this->filterGlobalTagCallbacks AS $callback)
        {
           
$output = $filter($callback);
            if (
is_string($output))
            {
                return
$output;
            }
        }

       
$tagName = $tag['tag'];

        if (!empty(
$this->filterTagCallbacks[$tagName]))
        {
            foreach (
$this->filterTagCallbacks[$tagName] AS $callback)
            {
               
$output = $filter($callback);
                if (
is_string($output))
                {
                    return
$output;
                }
            }
        }

        return
$this->renderValidTag($tag, $options);
    }

    public function
renderValidTag(array $tag, array $options)
    {
        if (
$tag['option'] && $this->filterTagOptionCallbacks)
        {
           
$optionValue = $tag['option'];
            foreach (
$this->filterTagOptionCallbacks AS $callback)
            {
               
$optionValue = call_user_func($callback, $optionValue, $tag, $options, $this);
            }

           
$open = $this->replaceOptionInTagOpen($tag['original'][0], $optionValue);
        }
        else
        {
           
$open = $tag['original'][0];
        }

        return
$open
           
. $this->renderSubTree($tag['children'], $options)
            .
$tag['original'][1];
    }

    protected function
analyzeTag(array $tag, array $options, $finalOutput)
    {
        foreach (
$this->analyzeGlobalTagCallbacks AS $callback)
        {
           
call_user_func($callback, $tag, $options, $finalOutput, $this);
        }

       
$tagName = $tag['tag'];

        if (!empty(
$this->analyzeTagCallbacks[$tagName]))
        {
            foreach (
$this->analyzeTagCallbacks[$tagName] AS $callback)
            {
               
call_user_func($callback, $tag, $options, $finalOutput, $this);
            }
        }
    }

    public function
renderString($string, array $options)
    {
       
$output = $this->filterString($string, $options);
       
$this->analyzeString($string, $options, $output);
        return
$output;
    }

    protected function
analyzeString($string, array $options, $finalOutput)
    {
        foreach (
$this->analyzeStringCallbacks AS $callback)
        {
           
call_user_func($callback, $string, $options, $finalOutput, $this);
        }
    }

    protected function
filterString($string, array $options)
    {
        foreach (
$this->filterStringCallbacks AS $callback)
        {
           
$string = call_user_func($callback, $string, $options, $this);
        }

        return
$string;
    }

    public function
replaceOptionInTagOpen($open, $newValue)
    {
        if (
is_array($newValue))
        {
            foreach (
$newValue AS $k => $v)
            {
               
$open = preg_replace_callback(
                   
'#' . $k . '=("|\').*("|\')#U',
                    function (
$match) use ($k, $v)
                    {
                        return
"$k={$match[1]}{$v}{$match[2]}";
                    },
                   
$open
               
);
            }

            return
$open;
        }
        else
        {
            return
preg_replace_callback(
               
'#=("|\'|).*\]$#s',
                function (
$match) use ($newValue)
                {
                    if (
strpos($newValue, '"') !== false)
                    {
                       
$newValue = "'$newValue'";
                    }
                    else if (
strpos($newValue, "'") !== false)
                    {
                       
$newValue = "\"$newValue\"";
                    }
                    else
                    {
                       
$newValue = $match[1] . $newValue . $match[1];
                    }
                    return
'=' . $newValue . ']';
                },
               
$open
           
);
        }
    }
}