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

namespace XF\BbCode;

use function
call_user_func, in_array, is_array, is_scalar, strlen;

class
RuleSet
{
    const
OPTION_KEYS_NO = 0;
    const
OPTION_KEYS_ONLY = 1;
    const
OPTION_KEYS_BOTH = 2;

    protected
$tags = [];

    protected
$context = null;
    protected
$subContext = null;

    public function
__construct($context, $subContext = null, $addDefault = true)
    {
       
$this->context = $context;
       
$this->subContext = $subContext;

        if (
$addDefault)
        {
           
$this->addDefaultTags();
        }
    }

    public function
getContext()
    {
        return
$this->context;
    }

    public function
getSubContext()
    {
        return
$this->subContext;
    }

    public function
addDefaultTags()
    {
       
$this->addTag('b', ['hasOption' => false]);
       
$this->addTag('i', ['hasOption' => false]);
       
$this->addTag('u', ['hasOption' => false]);
       
$this->addTag('s', ['hasOption' => false]);

       
$this->addTag('color', [
           
'hasOption' => true,
           
'optionMatch' => '/^(rgb\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*\)|#[a-f0-9]{6}|#[a-f0-9]{3}|[a-z]+)$/i'
       
]);

       
$this->addTag('font', [
           
'hasOption' => true,
           
'optionMatch' => '/^[a-z0-9 \-]+$/i' // regex matched to HTML->BB code regex
       
]);

       
$this->addTag('size', [
           
'hasOption' => true,
           
'optionMatch' => '/^[0-9]+(px)?$/i'
       
]);

       
$this->addTag('url', [
           
'supportOptionKeys' => RuleSet::OPTION_KEYS_BOTH,
           
'parseValidate' => [$this, 'parseValidateLink'],
           
'stopAutoLink' => true
       
]);
       
$this->addTag('email', [
           
'parseValidate' => [$this, 'parseValidateLink'],
           
'stopAutoLink' => true
       
]);

       
$this->addTag('left', [
           
'hasOption' => false
       
]);
       
$this->addTag('center', [
           
'hasOption' => false
       
]);
       
$this->addTag('right', [
           
'hasOption' => false
       
]);
       
$this->addTag('justify', [
           
'hasOption' => false
       
]);

       
$this->addTag('indent', [
           
'optionMatch' => '/^[0-9]+$/'
       
]);

       
$this->addTag('heading', [
           
'optionMatch' => '/^[0-9]+$/'
       
]);

       
$this->addTag('hr', [
           
'hasOption' => false
       
]);

       
$this->addTag('img', [
           
'supportOptionKeys' => RuleSet::OPTION_KEYS_ONLY,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('quote', []);

       
$this->addTag('code', [
           
'parseValidate' => [$this, 'parseValidateCode'],
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('icode', [
           
'parseValidate' => [$this, 'parseValidateCode'],
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('php', [
           
'hasOption' => false,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('html', [
           
'hasOption' => false,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('list', []);

       
$this->addTag('plain', [
           
'hasOption' => false,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('media', [
           
'hasOption' => true,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('spoiler', []);

       
$this->addTag('ispoiler', []);

       
$this->addTag('attach', [
           
'supportOptionKeys' => RuleSet::OPTION_KEYS_BOTH,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('user', [
           
'hasOption' => true,
           
'plain' => true,
           
'stopSmilies' => true,
           
'stopAutoLink' => true
       
]);

       
$this->addTag('table', []);
       
$this->addTag('tr', [
           
'validParents' => ['table']
        ]);
       
$this->addTag('th', [
           
'validParents' => ['tr']
        ]);
       
$this->addTag('td', [
           
'validParents' => ['tr']
        ]);
    }

    public function
addTag($tag, array $config)
    {
       
$this->tags[$tag] = $config;
    }

    public function
modifyTag($tag, array $modification)
    {
        if (isset(
$this->tags[$tag]))
        {
           
$this->tags[$tag] = array_merge($this->tags[$tag], $modification);
        }
    }

    public function
removeTag($tag)
    {
        unset(
$this->tags[$tag]);
    }

    public function
getTag($tag)
    {
        return
$this->tags[$tag] ?? null;
    }

    public function
getTags()
    {
        return
$this->tags;
    }

    public function
getCustomTagConfig(array $tag)
    {
       
$output = [];

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

        if (
strlen($tag['option_regex']))
        {
           
$output['optionMatch'] = $tag['option_regex'];
        }

        if (
$tag['disable_smilies'])
        {
           
$output['stopSmilies'] = true;
        }

        if (
$tag['disable_autolink'] || $tag['plain_children'])
        {
           
$output['stopAutoLink'] = true;
        }

        if (
$tag['plain_children'])
        {
           
$output['plain'] = true;
        }

        return
$output;
    }

    public function
validateTag($tag, $option = null, &$parsingModifiers = [], array $tagStack = [])
    {
       
$parsingModifiers = [];

       
$definition = $this->getTag($tag);
        if (!
is_array($definition))
        {
            return
false;
        }

        if (!empty(
$definition['validParents']))
        {
            if (!
$tagStack)
            {
                return
false;
            }

           
$lastTag = $tagStack[0];
            if (!
in_array($lastTag['tag'], $definition['validParents']))
            {
                return
false;
            }
        }

        if (!
$this->validateOption($option, $definition))
        {
            return
false;
        }

        if (
$option !== null && !empty($definition['optionMatch']) && is_scalar($option))
        {
            if (!
preg_match($definition['optionMatch'], $option))
            {
                return
false;
            }
        }

       
$parsingModifiers = $definition;

        if (isset(
$definition['parseValidate']))
        {
           
$validated = call_user_func($definition['parseValidate'], $tag, $option, $tagStack);
            if (
$validated === false)
            {
                return
false;
            }

            if (
is_array($validated))
            {
               
$parsingModifiers = array_merge($parsingModifiers, $validated);
            }
        }

        return
true;
    }

    public function
validateOption($option, array $definition)
    {
        if (isset(
$definition['hasOption']))
        {
            if (!
$definition['hasOption'])
            {
               
// no option permitted
               
return $option === null ? true : false;
            }
            else if (
$option === null)
            {
               
// option required but not set
               
return false;
            }
            else
            {
                return
$this->validateOptionKeys($option, $definition);
            }
        }
        else if (
$option !== null)
        {
            return
$this->validateOptionKeys($option, $definition);
        }
        else
        {
            return
true;
        }
    }

    public function
validateOptionKeys($option, array $definition)
    {
        if (isset(
$definition['supportOptionKeys']))
        {
            switch (
$definition['supportOptionKeys'])
            {
                case
RuleSet::OPTION_KEYS_BOTH:
                    return
is_array($option) || is_scalar($option);

                case
RuleSet::OPTION_KEYS_ONLY:
                    return
is_array($option);

                case
RuleSet::OPTION_KEYS_NO:
                default:
                    return
is_scalar($option);
            }
        }
        else
        {
            return
is_scalar($option);
        }
    }

    public function
parsePlainNoOption($tag, $option)
    {
        if (
$option === null)
        {
            return [
'plain' => true];
        }

        return
true;
    }

    public function
parseValidateLink($tag, $option, array $tagStack)
    {
        foreach (
$tagStack AS $tagAbove)
        {
            switch (
$tagAbove['tag'])
            {
                case
'url':
                case
'email':
                   
// can't nest these
                   
return false;
            }
        }

        return
self::parsePlainNoOption($tag, $option);
    }

    public function
parseValidateCode($tag, $option)
    {
        if (
$option && strtolower($option) == 'rich')
        {
            return
true;
        }

        return [
'plain' => true];
    }
}