<?php

class typograph extends cmsFrontend {

    use icms\traits\oneable;

    /**
     * Массив смайлов
     * @var ?array
     */
    private static $smiles;
    /**
     * Директория смайлов
     * @var string
     */
    private $smiles_dir = 'static/smiles/';
    /**
     * Массив ссылок, которые не нужно заворачивать в редирект-ссылку
     * @var array
     */
    private $no_redirect_list = [];
    /**
     * Заворачивать внешние ссылки через редирект
     * @var ?boolean
     */
    private $build_redirect_link = null;
    /**
     * Автоматическая простановка тегов <br> на символ переноса строки
     * @var ?boolean
     */
    private $is_auto_br = null;
    /**
     * Пресет типографа
     * @var array
     */
    private $preset = [];

    /**
     * Экшен списка смайлов
     * @return string
     */
    public function actionGetSmiles() {
        return $this->cms_template->renderJSON([
            'smiles' => $this->loadSmiles()->getSmiles()
        ]);
    }

    /**
     * Заменяет составные символы смайлов на тег <img>
     * с соответствующей гифкой
     *
     * @param string $text
     * @return string
     */
    public function replaceEmotionToSmile($text) {

        $smiles_emotion = [
            ' :) ' => 'smile',
            ' =) ' => 'smile',
            ':-)'  => 'smile',
            ' :( ' => 'sad',
            ':-('  => 'sad',
            ';-)'  => 'wink',
            ' ;) ' => 'wink',
            ' :D ' => 'laugh',
            ':-D'  => 'laugh',
            '=-0'  => 'wonder',
            ':-0'  => 'wonder',
            ':-P'  => 'tongue'
        ];

        foreach ($smiles_emotion as $find => $tag) {
            $text = str_replace($find, ':' . $tag . ':', $text);
        }

        $smiles = $this->loadSmiles()->getSmiles();

        if ($smiles) {
            foreach ($smiles as $tag => $smile_path) {
                $text = str_replace(':' . $tag . ':', ' <img src="' . $smile_path . '" alt="' . $tag . '"> ', $text);
            }
        }

        return $text;
    }

    /**
     * Загружает в память все gif файлы из директории $smiles_dir
     * имя_файла -> URL
     *
     * @return $this
     */
    private function loadSmiles() {

        if (self::$smiles !== null) {
            return $this;
        }

        $cache = cmsCache::getInstance();
        $cache_key = 'smiles';

        if (false !== (self::$smiles = $cache->get($cache_key))) {
            return $this;
        }

        self::$smiles = [];

        $pattern = $this->cms_config->root_path . $this->smiles_dir . '*.gif';

        $files = glob($pattern);

        if ($files) {
            foreach ($files as $file) {
                self::$smiles[pathinfo($file, PATHINFO_FILENAME)] = $this->cms_config->root . $this->smiles_dir . basename($file);
            }
        }

        $cache->set($cache_key, self::$smiles, 86400);

        return $this;
    }

    /**
     * Возвращает список смайлов
     * @return array
     */
    private function getSmiles() {
        return self::$smiles;
    }

    /**
     * Типографирует текст посредством Jevix
     *
     * @param string $text
     * @return string
     */
    public function parseText($text) {

        // Нет пресета по какой-то причине, убираем все теги
        if(!$this->preset){
            return strip_tags($text);
        }

        $errors = null;

        $jevix = new Jevix();

        $jevix->cfgSetXHTMLMode(false);

        // Протокол ссылок
        $jevix->cfgSetLinkProtocol($this->cms_core->request->getScheme() . '://');

        // Устанавливаем разрешённые теги. (Все не разрешенные теги считаются запрещенными.)
        $jevix->cfgAllowTags($this->preset['options']['allowed_tags']);

        // После тега не нужно добавлять дополнительный <br/>
        $jevix->cfgSetTagBlockType([
            'p', 'li'
        ]);

        // Устанавливаем коротие теги. (не имеющие закрывающего тега)
        $jevix->cfgSetTagShort([
            'br', 'img', 'hr', 'input', 'source'
        ]);

        // Устанавливаем теги, которые необходимо вырезать из текста вместе с контентом.
        $jevix->cfgSetTagCutWithContent([
            'script', 'style', 'meta'
        ]);

        $jevix->cfgSetTagIsEmpty([
            'a', 'iframe', 'div', 'td', 'video'
        ]);

        // Устанавливаем разрешённые параметры тегов. Также можно устанавливать допустимые значения этих параметров.
        foreach ($this->preset['options']['tags'] as $tag => $attrs) {

            if (!$attrs) {
                continue;
            }

            $params = [];

            foreach ($attrs as $attr) {

                if ($attr['params']) {
                    $attr['params'] = explode("\n", $attr['params']);
                    $attr['params'] = array_map(function($val){ return trim($val); }, $attr['params']);
                } else {
                    $attr['params'] = [];
                }

                switch ($attr['type']) {
                    case '#text':
                    case '#int':
                    case '#link':
                    case '#image':
                        $params[$attr['name']] = $attr['type'];
                        break;
                    case '#domain':
                        $params[$attr['name']] = ['#domain' => $attr['params']];
                        break;
                    case '#array':
                        $params[$attr['name']] = $attr['params'];
                        break;
                }
            }

            $jevix->cfgAllowTagParams($tag, $params);
        }

        // Устанавливаем параметры тегов являющиеся обязательными. Без них вырезает тег оставляя содержимое.
        $jevix->cfgSetTagParamsRequired('img', 'src');
        $jevix->cfgSetTagParamsRequired('a', 'href');
        $jevix->cfgSetTagParamsRequired('iframe', 'src');
        $jevix->cfgSetTagParamsRequired('source', 'src');

        // Устанавливаем теги которые может содержать тег контейнер
        $jevix->cfgSetTagChilds('video', ['source'], false, true);
        $jevix->cfgSetTagChilds('ul', ['li'], false, true);
        $jevix->cfgSetTagChilds('ol', ['li'], false, true);
        $jevix->cfgSetTagChilds('table', ['tr', 'tbody', 'thead', 'tfoot', 'th', 'td'], false, true);
        $jevix->cfgSetTagChilds('tbody', ['tr', 'td', 'th'], false, true);
        $jevix->cfgSetTagChilds('thead', ['tr', 'td', 'th'], false, true);
        $jevix->cfgSetTagChilds('tfoot', ['tr', 'td', 'th'], false, true);
        $jevix->cfgSetTagChilds('tr', ['td'], false, true);
        $jevix->cfgSetTagChilds('tr', ['th'], false, true);
        $jevix->cfgSetTagChilds('picture', ['source', 'img'], true);

        // Устанавливаем преформатированные теги. (в них все будет заменятся на HTML сущности)
        $jevix->cfgSetTagPreformatted([
            'code'
        ]);

        // Устанавливаем автозамену
        if($this->preset['options']['autoreplace']){

            $search = []; $replace = [];

            foreach ($this->preset['options']['autoreplace'] as $autoreplace) {
                $search[] = $autoreplace['search'];
                $replace[] = $autoreplace['replace'];
            }

            $jevix->cfgSetAutoReplace($search, $replace);
        }

        // включаем режим замены переноса строк на тег <br/>
        $jevix->cfgSetAutoBrMode($this->preset['options']['is_auto_br']);

        // включаем режим автоматического определения ссылок
        $jevix->cfgSetAutoLinkMode($this->preset['options']['is_auto_link_mode']);

        // Теги, внутри которых не менять переносы на <br>
        $jevix->cfgSetTagNoAutoBr(['ul', 'ol', 'code', 'pre', 'video', 'table', 'tr', 'tbody', 'thead']);

        // Надо ли обрабатывать колбэки
        if($this->preset['options']['is_process_callback']){

            // Добавляем pre тег
            $jevix->cfgSetTagPreformatted([
                'pre'
            ]);

            foreach ($this->preset['options']['callback'] as $tag => $callback) {
                if ($callback) {

                    $controller = $this;
                    $method = null;

                    $callback_param = explode('|', $callback);

                    // Только имя метода
                    if(count($callback_param) === 1){
                        $method = $callback_param[0];
                    } else {
                        if($callback_param[0] !== 'typograph'){
                            $controller = cmsCore::getController($callback_param[0]);
                        }
                        $method = $callback_param[1];
                    }

                    if(method_exists($controller, $method)){
                        $jevix->cfgSetTagCallbackFull($tag, [$controller, $method]);
                    }
                }
            }
        }

        // Библиотека может вызвать исключение
        try {
            $text = $jevix->parse($text, $errors);
        } catch (Exception $exc) {
            // Убираем всё, что-то пошло не так
            $text = strip_tags($text);
        }

        return $text;
    }

    /**
     * Подготоваливает параметры и текст для Jevix
     *
     * @param array|string $data
     * @return string Текст для обработки
     */
    public function preparePresetParamsAndGetText($data) {

        $build_smiles = false;
        $is_process_callback = null;

        if (is_array($data)) {

            $text = $data['text'];

            if (isset($data['is_auto_br'])) {
                $this->is_auto_br = $data['is_auto_br'];
            }

            if (isset($data['build_redirect_link'])) {
                $this->build_redirect_link = $data['build_redirect_link'];
            }

            if (isset($data['is_process_callback'])) {
                $is_process_callback = $data['is_process_callback'];
            }

            $typograph_id = $data['typograph_id'] ?? 1;

        } else {

            $typograph_id = 1;

            $text = $data;
        }

        $this->preset = $this->getOnce($this->model)->getPreset($typograph_id);

        if (!cmsController::enabled('redirect')) {
            $this->build_redirect_link = false;
        }

        // Перезаписываем опции, если были заданы
        if ($this->preset) {

            if ($is_process_callback !== null) {
                $this->preset['options']['is_process_callback'] = $is_process_callback;
            }

            if ($this->is_auto_br !== null) {
                $this->preset['options']['is_auto_br'] = $this->is_auto_br;
            }

            if ($this->build_redirect_link !== null) {
                $this->preset['options']['build_redirect_link'] = $this->build_redirect_link;
            }

            // Только если обрабатываем колбэки
            if(!empty($this->preset['options']['build_smiles']) && $this->preset['options']['is_process_callback']) {
                $build_smiles = true;
            }

            if($this->preset['options']['build_redirect_link']) {

                $redirect_options = cmsController::loadOptions('redirect');

                if(!empty($redirect_options['no_redirect_list'])){
                    $no_redirect_list = explode("\n", $redirect_options['no_redirect_list']);
                    $this->no_redirect_list = array_map(function($val){ return trim($val); }, $no_redirect_list);
                }
            }
        }

        if ($build_smiles) {
            $text = $this->replaceEmotionToSmile($text);
        }

        return $text;
    }

    /**
     * Колбэк Jevix для тега <a>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function linkRedirectPrefix($tag, $params, $content) {

        $href_params = parse_url($params['href']);

        $is_external_link = !empty($href_params['host']) && strpos($params['href'], $this->cms_config->host) !== 0;

        if ($is_external_link) {

            $params['class']  = (isset($params['class']) ? $params['class'] . ' external_link' : 'external_link');
            $params['target'] = '_blank';

            if ($this->preset['options']['build_redirect_link']) {

                $build_redirect_link = true;

                if($this->no_redirect_list) {

                    $host = parse_url($params['href'], PHP_URL_HOST);

                    if(in_array($host, $this->no_redirect_list, true)){
                        $build_redirect_link = false;
                    }
                }

                if($build_redirect_link) {
                    $params['href'] = href_to('redirect') . '?url=' . urlencode($params['href']);
                }
            }
        }

        $tag_string = '<a';

        foreach ($params as $param => $value) {
            if ($value) {
                $tag_string .= ' ' . $param . '="' . $value . '"';
            }
        }

        $tag_string .= '>' . $content . '</a>';

        return $tag_string;
    }

    /**
     * Колбэк Jevix для тега <img>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseImg($tag, $params, $content) {

        if (!empty($params['style'])) {

            $styles = explode(';', rtrim(trim($params['style']), ';'));

            foreach ($styles as $k => $style) {

                list($css_name, $css_value) = explode(':', $style);

                if (trim($css_name) == 'height') {
                    unset($styles[$k]);
                }
            }

            $params['style'] = implode(';', $styles);
        }

        if (!in_array('alt', array_keys($params))) {
            $params['alt'] = LANG_PHOTO;
        }

        $tag_string = '<img';

        foreach ($params as $param => $value) {
            if ($param === 'height') {
                continue;
            }
            if ($value) {
                $tag_string .= ' ' . $param . '="' . $value . '"';
            }
        }

        $tag_string .= '>';

        if (!empty($params['width'])) {
            $tag_string = '<a href="' . $params['src'] . '" class="icms-image__modal ajax-modal d-inline-block" style="max-width:' . $params['width'] . 'px">' . $tag_string . '</a>';
        }

        return $tag_string;
    }

    /**
     * Колбэк Jevix для тега <spoiler>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseSpoiler($tag, $params, $content) {

        if (empty($content)) {
            return '';
        }

        $id = string_random();
        $title = !empty($params['title']) ? $params['title'] : '';

        return '<div class="spoiler"><input tabindex="-1" type="checkbox" id="' . $id . '"><label for="' . $id . '">' . $title . '</label><div class="spoiler_body">' . $content . '</div></div>';
    }

    /**
     * Колбэк Jevix для тега <iframe>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseIframe($tag, $params, $content) {

        if (empty($params['src'])) {
            return '';
        }

        if (!empty($params['data-as-is'])) {

            unset($params['data-as-is']);

            $tag_string = '<iframe';

            foreach ($params as $param => $value) {
                if ($value) {
                    $tag_string .= ' ' . $param . '="' . $value . '"';
                }
            }

            $tag_string .= '></iframe>';

            return $tag_string;
        }

        return $this->getVideoCode($params['src']);
    }

    /**
     * Колбэк Jevix для тега <facebook>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseFacebookVideo($tag, $params, $content) {

        $video_link = (trim(strip_tags($content)));

        $pattern = '#^(?:(?:https|http)?://)?(?:www\.)?(?:facebook\.com(?:/[^\/]+/videos/|/video\.php\?v=))([0-9]+)(?:.+)?$#x';
        preg_match($pattern, $video_link, $matches);

        if (empty($matches[1])) {
            $pattern = '#^(?:(?:https|http)?://)?(?:www\.)?(?:facebook\.com(?:/[^\/]+/videos/[^\/]+))/([0-9]+)(?:.+)?$#x';
            preg_match($pattern, $video_link, $matches);
        }

        if (empty($matches[1])) {
            return '';
        }

        return $this->getVideoCode('https://www.facebook.com/video/embed?video_id=' . $matches[1]);
    }

    /**
     * Колбэк Jevix для тега <youtube>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseYouTubeVideo($tag, $params, $content) {

        $video_id = $this->parseYouTubeVideoID(trim(strip_tags($content)));

        return $this->getVideoCode('//www.youtube.com/embed/' . $video_id);
    }

    private function getVideoCode($src) {
        return '<div class="video_wrap embed-responsive embed-responsive-16by9"><iframe class="video_frame embed-responsive-item" src="' . $src . '" frameborder="0" allowfullscreen></iframe></div>';
    }

    private function parseYouTubeVideoID($url) {

        $pattern = '#^(?:(?:https|http)?://)?(?:www\.)?(?:youtu\.be/|youtube\.com(?:/embed/|/v/|/watch\?v=|/watch\?.+&v=))([\w-]{11})(?:.+)?$#x';
        preg_match($pattern, $url, $matches);

        return (isset($matches[1])) ? $matches[1] : false;
    }

    /**
     * Колбэк Jevix для тега <code>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parseCode($tag, $params, $content) {

        $geshi = new GeSHi(htmlspecialchars_decode($content), (isset($params['type']) ? $params['type'] : 'php'));
        $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);

        return '<div class="bb_tag_code">' . $geshi->parse_code() . '</div>';
    }

    /**
     * Колбэк Jevix для тега <pre>
     *
     * @param string $tag
     * @param array $params
     * @param string $content
     * @return string
     */
    public function parsePre($tag, $params, $content) {

        $content = htmlspecialchars_decode($content);
        $content = preg_replace('#^<code>(.*)<\/code>$#uis', '$1', $content);

        $geshi = new GeSHi($content, (isset($params['class']) ? str_replace('language-', '', $params['class']) : 'php'));
        $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);

        return '<div class="bb_tag_code">' . $geshi->parse_code() . '</div>';
    }

}
