Просмотр файла system/src-legacy/Bbcode.php

Размер файла: 13.11Kb
<?php

/**
 * This file is part of JohnCMS Content Management System.
 *
 * @copyright JohnCMS Community
 * @license   https://opensource.org/licenses/GPL-3.0 GPL-3.0
 * @link      https://johncms.com JohnCMS Project
 */

declare(strict_types=1);

namespace Johncms\System\Legacy;

use Johncms\System\Legacy\Tools;
use Johncms\System\Users\User;
use Johncms\System\Container\Factory;
use Johncms\System\Users\UserConfig;
use Johncms\System\View\Extension\Assets;
use Johncms\System\View\Render;
use Psr\Container\ContainerInterface;

class Bbcode
{
    /** @var Assets */
    protected $asset;

    /**
     * @var array
     */
    protected $config;

    /**
     * @var User
     */
    protected $user;

    /**
     * @var UserConfig
     */
    protected $userConfig;

    protected $tags;

    protected $codeId;

    protected $codeIndex;

    protected $codeParts;

    public function __invoke(ContainerInterface $container)
    {
        $this->asset = $container->get(Assets::class);
        $config = $container->get('config');
        $this->config = $config['johncms'];
        $this->user = $container->get(User::class);
        $this->userConfig = $this->user->config;

        $globalcnf = $container->get('config');
        $this->tags = $globalcnf['bbcode'] ?? [];

        $this->codeId = uniqid('', true);
        $this->codeIndex = 0;
        $this->codeParts = [];

        return $this;
    }

    // Обработка тэгов и ссылок
    public function tags(string $var): string
    {
        $var = $this->highlightCode($var);           // Подсветка кода
        $var = $this->parseTime($var);               // Обработка тэга времени
        $var = $this->highlightBb($var);             // Обработка ссылок
        $var = $this->highlightUrl($var);            // Обработка ссылок
        $var = $this->highlightBbcodeUrl($var);      // Обработка ссылок в BBcode
        $var = $this->youtube($var);

        return $var;
    }

    public function notags(string $var = ''): string
    {
        $replacements = array_values($this->tags);
        $search = array_column($replacements, 'from');
        $replace = array_column($replacements, 'data');
        $var = preg_replace($search, $replace, $var);

        $var = preg_replace('#\[timestamp\](.+?)\[/timestamp]#si', '$2', $var);
        $var = preg_replace('#\[code=(.+?)\](.+?)\[/code]#si', '$2', $var);

        $replace = [
            '[youtube]'  => '',
            '[/youtube]' => '',
            '[php]'      => '',
            '[/php]'     => '',
        ];

        return strtr($var, $replace);
    }

    /**
     * BbCode Toolbar
     */
    public function buttons(string $form, string $field): string
    {
        /** @var Render $render */
        $render = di(Render::class);

        // Смайлы
        $smiles = ! empty($this->user->smileys) ? unserialize($this->user->smileys, ['allowed_classes' => false]) : [];

        /** @var Tools $tools */
        $tools = Factory::getContainer()->get(Tools::class);
        $arr_smiles = [];
        foreach ($smiles as $smile) {
            $arr_smiles[] = [
                'title'  => $smile,
                'img'    => $tools->smilies(':' . $smile . ':', $this->user->rights >= 1 ? 1 : 0),
                'bbcode' => ':' . $smile . ':',
            ];
        }

        return $render->render('system::app/bbcode', ['input' => $field, 'smiles' => $arr_smiles]);
    }

    /**
     * Обработка тэга [time]
     *
     * @param string $var
     * @return string
     */
    protected function parseTime($var)
    {
        $var = preg_replace_callback(
            '#\[time\](.+?)\[\/time\]#s',
            function ($matches) {
                $shift = ($this->config['timeshift'] + $this->userConfig->timeshift) * 3600;

                if (($out = strtotime($matches[1])) !== false) {
                    return date('d.m.Y / H:i', $out + $shift);
                }

                return $matches[1];
            },
            $var
        );

        $var = preg_replace_callback(
            '#\[timestamp\](.+?)\[\/timestamp\]#s',
            function ($matches) {
                $shift = ($this->config['timeshift'] + $this->userConfig->timeshift) * 3600;

                if (($out = strtotime($matches[1])) !== false) {
                    return '<small class="gray">' . d__('system', 'Added') . ': ' . date('d.m.Y / H:i', $out + $shift) . '</small>'; // phpcs:ignore
                }

                return $matches[1];
            },
            $var
        );

        return $var;
    }

    /**
     * Парсинг ссылок
     * За основу взята доработанная функция от форума phpBB 3.x.x
     *
     * @param $text
     * @return mixed
     */
    protected function highlightUrl($text)
    {
        $homeurl = $this->config['homeurl'];

        // Обработка внутренних ссылок
        $text = preg_replace_callback(
            '#(^|[\n\t (>.])(' . preg_quote(
                $homeurl,
                '#'
            ) . ')/((?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@/?|]+|%[\dA-F]{2})*)?)#iu',
            // phpcs:ignore
            // phpcs:ignore
            function ($matches) {
                return $this->urlCallback(1, $matches[1], $matches[2], $matches[3]);
            },
            $text
        );

        // Обработка обычных ссылок типа xxxx://aaaaa.bbb.cccc. ...
        $text = preg_replace_callback(
            '#(^|[\n\t (>.])([a-z][a-z\d+]*:/{2}(?:(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-zа-яё0-9.]+:[a-zа-яё0-9.]+:[a-zа-яё0-9.:]+\])(?::\d*)?(?:/(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-zа-яё0-9\-._~!$&\'(*+,;=:@/?|]+|%[\dA-F]{2})*)?)#iu',
            // phpcs:ignore
            function ($matches) {
                return $this->urlCallback(2, $matches[1], $matches[2], '');
            },
            $text
        );

        return $text;
    }

    private function urlCallback($type, $whitespace, $url, $relative_url)
    {
        $orig_url = $url;
        $orig_relative = $relative_url;
        $url = htmlspecialchars_decode($url);
        $relative_url = htmlspecialchars_decode($relative_url);
        $text = '';
        $chars = ['<', '>', '"'];
        $split = false;

        foreach ($chars as $char) {
            $next_split = strpos($url, $char);
            if ($next_split !== false) {
                $split = ($split !== false) ? min($split, $next_split) : $next_split;
            }
        }

        if ($split !== false) {
            $url = substr($url, 0, $split);
            $relative_url = '';
        } else {
            if ($relative_url) {
                $split = false;
                foreach ($chars as $char) {
                    $next_split = strpos($relative_url, $char);
                    if ($next_split !== false) {
                        $split = ($split !== false) ? min($split, $next_split) : $next_split;
                    }
                }
                if ($split !== false) {
                    $relative_url = substr($relative_url, 0, $split);
                }
            }
        }

        $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];

        switch ($last_char) {
            case '.':
            case '?':
            case '!':
            case ':':
            case ',':
                $append = $last_char;
                if ($relative_url) {
                    $relative_url = substr($relative_url, 0, -1);
                } else {
                    $url = substr($url, 0, -1);
                }
                break;

            default:
                $append = '';
                break;
        }

        $short_url = (mb_strlen($url) > 40) ? mb_substr($url, 0, 30) . ' ... ' . mb_substr($url, -5) : $url;

        switch ($type) {
            case 1:
                $relative_url = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url)); // phpcs:ignore
                $url = $url . '/' . $relative_url;
                $text = $relative_url;
                if (! $relative_url) {
                    return $whitespace . $orig_url . '/' . $orig_relative;
                }
                break;

            case 2:
                $text = $short_url;
                if (! $this->userConfig->directUrl) {
                    $url = $this->config['homeurl'] . '/redirect/?url=' . rawurlencode($url);
                }
                break;

            case 4:
                $text = $short_url;
                $url = 'mailto:' . $url;
                break;
        }
        $url = htmlspecialchars($url);
        $text = htmlspecialchars($text);
        $append = htmlspecialchars($append);

        return $whitespace . '<a href="' . $url . '">' . $text . '</a>' . $append;
    }

    /**
     * Подсветка кода
     *
     * Вырезает содержимое тега code и помещает в отдельный массив
     * во избежание последующей обработки другими тегами
     *
     * @param string $var
     * @return mixed
     */
    protected function highlightCode($var)
    {
        $var = preg_replace_callback('#\[php\](.+?)\[\/php\]#s', [$this, 'phpCodeCallback'], $var);
        $var = preg_replace_callback('#\[code=(.+?)\](.+?)\[\/code]#is', [$this, 'codeCallback'], $var);

        return $var;
    }

    private function phpCodeCallback($code)
    {
        return $this->codeCallback([1 => 'php', 2 => $code[1]]);
    }

    private function codeCallback($code)
    {
        $parsers = [
            'php'  => 'php',
            'css'  => 'css',
            'html' => 'html',
            'js'   => 'javascript',
            'sql'  => 'sql',
            'xml'  => 'xml',
        ];
        $text = trim(strtr($code[2], ['<br />' => '']));

        $code_lang = (array_key_exists($code[1], $parsers) ? $parsers[$code[1]] : 'php');

        return '<pre class="line-numbers"><code class="language-' . $code_lang . '">' . $text . '</code></pre>';
    }

    /**
     * Обработка URL в тэгах BBcode
     *
     * @param $var
     * @return mixed
     */
    protected function highlightBbcodeUrl($var)
    {
        return preg_replace_callback(
            '~\[url=(https?://.+?|//.+?)](.+?)\[/url]~iu',
            function ($url) {
                $home = parse_url($this->config['homeurl']);
                $tmp = parse_url($url[1]);

                if ($home['host'] == $tmp['host'] || $this->userConfig->directUrl) {
                    return '<a href="' . $url[1] . '">' . $url[2] . '</a>';
                }

                return '<a href="' . $this->config['homeurl'] . '/redirect/?url=' . urlencode(htmlspecialchars_decode($url[1])) . '">' . $url[2] . '</a>'; // phpcs:ignore
            },
            $var
        );
    }

    /**
     * Обработка bbCode
     *
     * @param string $var
     * @return string
     */
    protected function highlightBb($var)
    {
        $replacements = array_values($this->tags);
        $search = array_column($replacements, 'from');
        $replace = array_column($replacements, 'to');

        return preg_replace($search, $replace, $var);
    }

    /**
     * Youtube bbcode
     *
     * @param string $var
     * @return string
     */
    protected function youtube($var)
    {
        return preg_replace_callback(
            '#\[youtube\](.+?)\[\/youtube\]#s',
            function ($matches) {
                if (preg_match('/youtube.com/', $matches[1])) {
                    $values = explode('=', $matches[1]);
                    $valuesto = explode('&', $values[1]);

                    return $this->youtubePlayer($valuesto[0]);
                } elseif (preg_match('/youtu.be/', $matches[1])) {
                    return $this->youtubePlayer(trim(parse_url($matches[1])['path'], '//'));
                }
                $valuesto = explode('&', $matches[1]);

                return $this->youtubePlayer($valuesto[0]);
            },
            $var,
            3
        );
    }

    protected function youtubePlayer($result)
    {
        if ($this->userConfig->youtube) {
            return '
<div style="max-width: 600px">
<div class="embed-responsive embed-responsive-16by9">
<iframe allowfullscreen="allowfullscreen" src="//www.youtube.com/embed/' . $result . '" frameborder="0"></iframe>
</div></div>';
        }

        return '<div>
        <a class="youtube-preview" target="_blank" href="//m.youtube.com/watch?v=' . $result . '">
            <div class="play-button">
                <svg class="icon icon-youtube">
                    <use xlink:href="' . $this->asset->url('icons/sprite.svg') . '#youtube"/>
                </svg>
            </div>
            <img src="//img.youtube.com/vi/' . $result . '/mqdefault.jpg" alt="youtube.com/embed/' . $result . '">
        </a>
        </div>';
    }
}