View file MMO-Hide-2.3.4/upload/src/addons/MMO/Hide/BbCode/Tag/Hide.php

File size: 9.94Kb
<?php

namespace MMO\Hide\BbCode\Tag;

use MMO\CoreLib\Util\Plural;
use MMO\CoreLib\Util\Str;
use MMO\Hide\BbCode\AbstractTag;
use MMO\Hide\BbCode\Provider\CustomFunctionProvider;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use XF\Repository\UserGroupRepository;

class Hide extends AbstractTag
{
    /**
     * @inheritDoc
     */
    public function render($content, array $tagChildren, $tagOption, array $tag, array $options)
    {
        $visitor = $this->visitor();
        $option = $this->options()->mhHideTagComprassion;

        if($option != 'default')
        {
            return call_user_func_array([\XF::stringToClass($option, '%s\BbCode\Tag\%s'), 'renderHide'], [
                $tagChildren,
                $tagOption,
                $tag,
                $options,
                $this->renderer
            ]);
        }

        if (preg_match ('/^\(.*\)$/', (string) $tag['option']))
        {
            $expression = $this->stripParentheses($tag['option']);
            if(Str::contains($expression, $this->getAllowedTags()))
            {
                return $this->renderExpressionTag($expression, $visitor, $content, $options, $tag);
            }
        }

        $title = [
            'visible' => $this->getTitleVisible('mh_tag_hide'),
            'hidden' => $this->getTitleHidden('mh_tag_hide')
        ];

        return $this->renderTemplate($content, $title, [
            'canView' => $visitor->user_id,
            'options' => $options,
            'tag' => $tag['tag']
        ]);
    }

    private function stripParentheses(string $expression): string
    {
        return Str::of($expression)->replaceFirst('(', '')->replaceLast(')', '')->__toString();
    }

    private function renderExpressionTag($expression, $visitor, $content, $options, $tag)
    {
        static $expressionLanguage = null;
        if (!$expressionLanguage)
        {
            $expressionLanguage = new ExpressionLanguage();
            $expressionLanguage->registerProvider(new CustomFunctionProvider());
        }

        $allowTag = [
            'posts' => $visitor->message_count,
            'likes' => $visitor->reaction_score,
            'reactions' => $visitor->reaction_score,
            'trophies' => $visitor->trophy_points,
            'days' => \intval((\XF::$time - $visitor->register_date) / 86400),
            'username' => $visitor->username,
            'user_id' => $visitor->user_id,
            'user_state' => $visitor->user_state,
            'is_staff' => $visitor->is_staff,
            'is_admin' => $visitor->is_admin,
            'is_moderator' => $visitor->is_moderator
        ];

        $string = $expression;

        $string = $this->replaceIsMemberOf($string);
        $string = $this->replaceComparison($string, $visitor);
        $string = $this->replaceReplacements($string);

        try
        {
            $canView = (bool)$expressionLanguage->evaluate($expression, $allowTag);
        }
        catch (SyntaxError $err)
        {
            return $this->handleSyntaxError($err);
        }

        return $this->renderTemplate($content, $string, [
            'canView' => $canView,
            'options' => $options,
            'tag' => $tag['tag']
        ]);
    }

    private function replaceIsMemberOf($string)
    {
        return \preg_replace_callback('/isMemberOf\((.*?)\)/', function ($matches)
        {
            $groupIds = \array_map('intval', explode(',', $matches[1]));
            /** @var UserGroupRepository $groupRepository */
            $groupRepository = $this->repository(UserGroupRepository::class);
            $groupTitles = $groupRepository->getUserGroupTitlePairs();

            $groupNames = [];
            foreach ($groupIds as $groupId)
            {
                if (isset($groupTitles[$groupId]))
                {
                    $groupNames[] = $groupTitles[$groupId];
                }
            }

            return \XF::phrase('mh_hide_tag_hide_is_member_of', [
                'groups' => \implode(', ', $groupNames)
            ]);
        }, $string);
    }

    private function replaceComparison($string, $visitor)
    {
        return preg_replace_callback('/(\w+)\s*([><=]{1,2})\s*(\d+|"[^"]*")/', function ($matches) use ($visitor)
        {
            $param = $matches[1];
            $operator = $matches[2];
            $value = $matches[3];

            $paramMap = [
                'posts' => \XF::phrase('mh_hide_tag_hide_cond_posts', [
                    'plural' => Plural::choice('mh_hide_tag_hide_cond_posts_plural', \intval($value)),
                ]),
                'likes' => \XF::phrase('mh_hide_tag_hide_cond_likes', [
                    'plural' => Plural::choice('mh_hide_tag_hide_cond_likes_plural', \intval($value))
                ]),
                'reactions' => \XF::phrase('mh_hide_tag_hide_cond_likes', [
                    'plural' => Plural::choice('mh_hide_tag_hide_cond_reactions_plural', \intval($value))
                ]),
                'trophies' => \XF::phrase('mh_hide_tag_hide_cond_trophies', [
                    'plural' => Plural::choice('mh_hide_tag_hide_cond_trophies_plural', \intval($value))
                ]),
                'days' => \XF::phrase('mh_hide_tag_hide_cond_days', [
                    'plural' => Plural::choice('mh_hide_tag_hide_cond_days_plural', \intval($value))
                ]),
                'user_state' => \XF::phrase('user_state'),
                'username' => \XF::phrase('username_is'),
                'user_id' => \XF::phrase('user_id'),
            ];

            $param_str = $paramMap[$param] ?? $param;

            $propertyMap = [
                'posts' => 'message_count',
                'likes' => 'reaction_score',
                'reactions' => 'reaction_score',
                'trophies' => 'trophy_points',
                'days' => 'register_date',
            ];

            $property = $propertyMap[$param] ?? $param;
            $operatorMap = [
                '>' => function ($a, $b) { return $a > $b; },
                '>=' => function ($a, $b) { return $a >= $b; },
                '<' => function ($a, $b) { return $a < $b; },
                '<=' => function ($a, $b) { return $a <= $b; },
                '==' => function ($a, $b) { return $a == $b; },
                '===' => function ($a, $b) { return $a === $b; },
                '!=' => function ($a, $b) { return $a != $b; },
                '!==' => function ($a, $b) { return $a !== $b; },
            ];

            $propertyValue = $property === 'register_date' ? \intval((\XF::$time - $visitor->$property) / 86400) : $visitor->$property;

            if (isset($operatorMap[$operator]))
            {
                $comparison = $operatorMap[$operator];
                $result = $comparison($propertyValue, is_numeric($value) ? $value : str_replace('"', '', $value));
                $color = $result ? 'now' : 'require';
            }
            else
            {
                $color = 'now'; // default value
            }

            return sprintf('%s %s %s <span class="bbCodeBlock-count--%s">(%s)</span>', $param_str, $operator, $value, $color, $propertyValue);

        }, $string);
    }

    private function replaceReplacements($string)
    {
        $replacements = [
            'and' => \XF::phrase('mh_hide_and'),
            '&&' => \XF::phrase('mh_hide_and'),
            'or' => \XF::phrase('mh_hide_or'),
            '||' => \XF::phrase('mh_hide_or'),
            '==' => \XF::phrase('mh_hide_is'),
            'is_staff' => \XF::phrase('staff_member'),
            'is_admin' => \XF::phrase('user_is_administrator'),
            'is_moderator' => \XF::phrase('user_is_moderator'),
        ];
        return Str::replace(array_keys($replacements), array_values($replacements), $string);
    }

    private function handleSyntaxError($error)
    {
        $phrase = $error->getMessage();
        $params = [];
        preg_replace_callback_array(
            [
                '/variable "(?<variable>[a-z_]+)" is not valid around position (?<position>[0-9]+) for expression (?<expression>.+?)\./i' => function ($match) use (&$phrase, &$params)
                {
                    $phrase = 'mh_tag_hide_variable_x_is_not_valid_around_position_y_for_expression_z';
                    $params = array_filter($match, 'is_string', ARRAY_FILTER_USE_KEY);
                },
                '/Unexpected character "(?<variable>[!-\/:-@[-`{-~])" around position (?<position>[0-9]+) for expression (?<expression>.+?)\./i' => function ($match) use (&$phrase, &$params)
                {
                    $phrase = 'mh_tag_hide_unexpected_character_variable_x_is_not_valid_around_position_y_for_expression_z';
                    $params = array_filter($match, 'is_string', ARRAY_FILTER_USE_KEY);
                },
                '/other exception with (?<param>[a-z]+)\./i' => function ($match) use (&$phrase, &$params)
                {
                    $phrase = 'other_exception_with_x';
                    $params = array_filter($match, 'is_string', ARRAY_FILTER_USE_KEY);
                }
            ],
            $error->getMessage()
        );

        if ($phrase != $error->getMessage())
        {
            $error = \XF::phrase($phrase, $params);
        }
        else
        {
            $error = $error->getMessage();
        }

        return $this->templater->renderTemplate('public:mh_tag_hide_error_conditions', [
            'error' => $error
        ]);
    }

    public function getAllowedTags(): array
    {
        return [
            'posts',
            'likes',
            'trophies',
            'days',
            'username',
            'user_id',
            'user_state',
            'isMemberOf',
            'is_staff'
        ];
    }
}