Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/burlakastudio.realcommenter/lib/ |
Upload File : |
<?php /** * Модуль "Полноценные комментарии 2.0" под Битрикс * Официальный сайт модуля: www.realcommenter.com * Официальный сайт разработчика: burlaka.studio * Автор и разработчик: Алексей Бурлака (AlexeyGfi) -> alexeygfi@gmail.com */ namespace Burlakastudio\Realcommenter; class STOP_FILTER { public static function trim_html(&$text = '', $editor_type = 'html') { if ($editor_type != 'html') { self::stripTags($text); self::replace_new_string_to_html($text); } else { self::tag_attributes_sanitarium($text); $text = self::normalizeTextInParagraph($text); $text = preg_replace('~^\s*(<br\s*\/*>\s*)+~', '<br>', $text); $text = preg_replace('~\s*(<br\s*\/*>\s*)+$~', '<br>', $text); } $text = trim($text); } /** * Мы принимаем на вход указатель! * Чтобы если мы нашли что-то плохое, но настройки модуля говорят о том, * чтобы мы пропустили, предварительно обезопасив данные - делаем это напрямую в переменную * * !!! Исходя из этого, заходить на проверку нужно только тогда, когда это действительно необходимо. * То есть если комментирует / редактирует модератор — не нужно зря текст потрошить * * Плюс, учитывая, что мы на вход получаем как раз указатель, * можно провести с текстом санитарные действия: почистить двойные enter-ы, пустые символы в начале, * замену символов * * @param string $text * @param bool $skip_snitarium * * @return array|bool */ public static function text_must_be_stopped(&$text = '', $skip_snitarium = false) { /** * ...если для текущего пользователя отключен визуальный редактор (== skip any html-formatting) */ if (!$skip_snitarium || !GRANT_AND_ACCESS::is_use_text_editor()) { self::clear_up($text); } /** * Встречаются ли в очищенном тексте плохие слова */ $bad_words = self::has_bad_words($text); if ($bad_words) { return ['STOP_FILTER_WORDS' => $bad_words['WORD_FILTER']]; } /** * Встречаются ли запрещённые теги */ $bad_tags = self::has_bad_tags($text); if ($bad_tags) { /** * Если стоит опция, касающаяся ТЕГОВ - чистить и пропускать */ $clear_not_allowed_and_go = OPTIONS::get('clear_not_allowed_and_go'); if ($clear_not_allowed_and_go == 'Y') { $text = $bad_tags['filtered_text']; return false; } else { return ['STOP_TAGS' => $bad_tags['garbage_tags']]; } } return false; } public static function has_bad_words($text = '') { $_text = $text; $_text = preg_replace('~<\/?([a-z]+)[^>]*?>~is' . BX_UTF_PCRE_MODIFIER, '', $_text); $_text = self::replace_danger_symbols($_text); $_text = self::replace_smiles($_text); $_text = preg_replace('~[\r\n]~', '', $_text); if (self::censored_text($_text)) { global $censored_info; return $censored_info; } return false; } /** * Список разрешённых и запрещённых тегов - может быть оба окошка пустых * Разрешённые, если заполнены, имеют приоритет перед запрещёнными. * То есть если заполнен список разрешённых тегов, * на запрещённые уже просто не смотрим - действуют только они (и встречаться в тексте должны только они). * * @param string $text * * @return array|bool */ public static function has_bad_tags($text = '') { $tag_allowed = TOOLS::strip_option_value(OPTIONS::get('tag_allowed')); $tag_rejected = TOOLS::strip_option_value(OPTIONS::get('tag_rejected')); // Нет ограничений == нет плохих тегов if (empty($tag_allowed) && empty($tag_rejected)) { return false; } if (!empty($tag_allowed)) { /** * Заменяем все разрешённые теги в тексте и смотрим, * встречается ли в тексте ещё какой-то тег */ $_text = preg_replace( '~<\/?(' . implode('|', $tag_allowed) . ')(>|\s[^>]*?>)~is' . BX_UTF_PCRE_MODIFIER, '', $text ); if (preg_match_all( '~<\/?([a-z]+)[^>]*?>~is' . BX_UTF_PCRE_MODIFIER, $_text, $matches ) ) { $garbage_tags = array_values(array_filter(array_unique($matches[1]))); $filtered_text = preg_replace( '~<\/?(' . implode('|', $garbage_tags) . ')(>|\s[^>]*?>)~is' . BX_UTF_PCRE_MODIFIER, '', $text ); return [ 'garbage_tags' => $garbage_tags, 'filtered_text' => $filtered_text ]; } } else { /** * Есть ли в тексте запрещённые теги? */ if (preg_match_all('~<\/?(' . implode('|', $tag_rejected) . ')(>|\s[^>]*?>)~is' . BX_UTF_PCRE_MODIFIER, $text, $matches)) { $garbage_tags = array_values(array_filter(array_unique($matches[1]))); $filtered_text = preg_replace('~<\/?(' . implode('|', $garbage_tags) . ')(>|\s[^>]*?>)~is' . BX_UTF_PCRE_MODIFIER, '', $text); return [ 'garbage_tags' => $garbage_tags, 'filtered_text' => $filtered_text ]; } } return false; } public static function clear_up(&$text = '') { $text = self::replace_danger_symbols($text); $text = self::replace_smiles($text); /** * Перенёс это после удаления опасных символов, * потому что мы новые строки меняем на <br>, * ...и тут же их портим */ self::trim_html($text, 'text'); } private static function replace_danger_symbols($text) { return htmlspecialchars( $text, ENT_COMPAT | ENT_HTML5, \Bitrix\Main\Context::getCurrent()->getCulture()->getCharset() ); } private static function getRegisteredSmiles() { return ['ag', 'ab', 'ac', 'ai', 'af', 'ax', 'ah', 'ak', 'aq', 'ad', 'ar', 'bw', 'bl', 'bs', 'aa', 'ae', 'bt', 'az', 'bq', 'at', 'bb', 'bu', 'bm', 'br']; } private static function replace_smiles($text, $mode = 1) { if (!$_REQUEST['smile_allow']) { return $text; } //Сюда же - замена смайликов. $use_smiles = OPTIONS::get('use_smiles'); if ($use_smiles == 'Y') { $registered_smiles = self::getRegisteredSmiles(); if ($mode == 1) { $map_of_replace = []; //Читаем смайлы foreach ($registered_smiles as $rs) { $tmp_smile = OPTIONS::get('smile_' . $rs); if ($tmp_smile) { //У нас список, указанный через пробел. Нам нужно только первое значение $smile_variants = explode(' ', $tmp_smile); foreach ($smile_variants as $sv) $map_of_replace[strlen($sv)][] = ['s' => $sv, 'r' => $rs]; } } krsort($map_of_replace); if (count($map_of_replace)) { foreach ($map_of_replace as $mmv) { foreach ($mmv as $mv) $text = str_replace( $mv['s'], '<img src= "/bitrix/modules/' . TOOLS::getModuleName() . '/images/' . $mv['r'] . '.gif" alt= "" />', $text ); } } } else { $smiles_list = []; //Читаем смайлы foreach ($registered_smiles as $rs) { $tmp_smile = OPTIONS::get('smile_' . $rs); if ($tmp_smile) { //У нас список, указанный через пробел. Нам нужно только первое значение $smile_variants = explode(' ', $tmp_smile); $smiles_list['[ /bitrix/modules/' . TOOLS::getModuleName() . '/images/' . $rs . '.gif ]'] = $smile_variants[0]; $smiles_list['<img src= "/bitrix/modules/' . TOOLS::getModuleName() . '/images/' . $rs . '.gif" alt= "" />'] = $smile_variants[0]; } } if (count($smiles_list)) { foreach ($smiles_list as $s_search => $s_replace) { $text = str_replace($s_search, $s_replace, $text); } } } } return $text; } private static function tag_attributes_sanitarium(&$text = '') { $text = preg_replace_callback( '~(<[a-z]+\s+)([^>]+)(>)~is' . BX_UTF_PCRE_MODIFIER, static function ($matches) { $matches[2] = preg_replace('~([a-z-]+?)(?<!class|id|href|title)\s*=\s*([\'"])[^\2]+?\2~is' . BX_UTF_PCRE_MODIFIER, '', $matches[2]); return $matches[1] . $matches[2] . $matches[3]; }, $text ); } private static function tag_find_and_strip(&$text = '') { $text = strip_tags($text); } private static function censored_text($text) { if (!$text) { return false; } global $censored_info; if (!is_array($censored_info)) { $censored_info = []; } $stop_words = []; //Читаем правила для стоп-фильтра $stop_words_simple = array_filter(TOOLS::strip_option_value(OPTIONS::get("stop_words_simple"))); $stop_words_rexp = array_filter(TOOLS::strip_option_value(OPTIONS::get("stop_words_rexp"))); if (!empty($stop_words_simple) || !empty($stop_words_rexp)) { $stop_words = array_merge($stop_words_simple, $stop_words_rexp); foreach ($stop_words as $w_pos => $w) { $stop_words[$w_pos] = "~" . $w . "~i" . BX_UTF_PCRE_MODIFIER; } } $return_with_alert = false; if (count($stop_words)) { if (strtolower(LANG_CHARSET) != 'utf-8') { setlocale(LC_CTYPE, 'ru_RU.CP1251'); } //Для тех умников, которые пишут х-у-й, например // Ситуация: Вашего внимания превращается в вашеговнимания и внутри обнаруживается "говн" o_O // $text_tinted = preg_replace("![\s\.\,\-\_\=\d]+!", '', $text); $text_tinted = preg_replace("![\.\,\-\_\=\d]+!", '', $text); foreach ($stop_words as $sw) { if ( preg_match($sw, $text, $matches) || preg_match($sw, $text_tinted, $matches) ) { $censored_info['WORD_FILTER'][] = $matches[0]; $return_with_alert = true; } } } return $return_with_alert; } /** * Все тексты сразу переконвертируются в HTML режим * * Текст от HTML различается разве что переносами строки. * Сами расставляем в тексте <br /> * * @param $text */ public static function replace_new_string_to_html(&$text) { $text = preg_replace("!\n!", '<br>', $text); } /** * @param string $text * @param string|array $allowed_tags * @return mixed|string|string[]|null * @throws \Bitrix\Main\ArgumentNullException * @throws \Bitrix\Main\ArgumentOutOfRangeException */ public static function normalizeTextInParagraph($text = '', $allowed_tags = '') { /** * Манипуляции, которые проводим с текстом: * * === чистим неразрывные пробелы * ~\x{00a0}~siu * * 0 === Получаем разрешённые теги, все теги, которые не входят в этот набор отмечаем как запрещённые. * Все запрещённые закрывающиеся теги (например </div>) заменяем на: * него + "<br />" (</div><br />) * * !!! У нас к тегу <br /> максимально дружественное отношение (внутри рабочей единицы — абзаца) * По-этому, если пользователь нафигачил <br /><br /><br />, предполагается, что сделал это сознательно (нужна ему отбивка, он Энтерами нашлёпал) * А значит, мы вместо технического <br /> (который требуется нам только чтобы провести нормализацию), используем * /_/ (помним, что == <br />) *Техническое задвоение, которое может образоваться в результате чистки не разрешённых тегов, мы сведём к одному * /_/ и его уже заменим на <br /> * * Чтобы СТРУКТУРНЫЙ АБЗАЦНЫЙ текст, который вычистится следующим пунктом не слился в сплошную строку. * Ниже мы дубликаты br почистим * * 1 === Чистим внутри те теги, которые нам не разрешены. * То есть при запрещённых тегах div, b структура * <div> * <div> * <div><b> * Первое предложение * </b></div><br /> * </div> * <div> * Второе предложение * </div><br /> * </div> * * ...вернётся как: * Первое предложение<br /> * Второе предложение * * === Чистим пустышки типа <p> </p>. Вместо таких абзацев останется <br /> * * === Получаем теги на замену. То есть например мы можем не чистить <div>-ы, а просто заменить их все на <p> * Аналогично можно приводить <b> к <strong> * * === (перекрывается пунктом выше) * Распаковываем вложенности в линейное следование. Наша идеология предполагает последовательность абзацев, а не их вложенность * * === Чистим левые class-ы, стили, неймы, id * * === Чистим множестенное повторение <br /><br /> на одинарный сброс * */ if (!$text) { return $text; } $text = self::replace_difficult_symbols($text); $inner_text = $text; //Имеем возможность переопределить под тестирование if ($allowed_tags) { $allowed_tags = explode(' ', $allowed_tags); } else { $allowed_tags = TOOLS::strip_option_value(OPTIONS::get('tag_allowed')); // Принудительно $allowed_tags[] = 'div'; } //Неразрывные пробелы, переносы строк $inner_text = preg_replace('~\x{00a0}~si' . BX_UTF_PCRE_MODIFIER, " ", $inner_text); $inner_text = preg_replace("~[\n\r]~si" . BX_UTF_PCRE_MODIFIER, '', $inner_text); /** * фиксим такие конструкции: <b><br></b> <em><br></em>... br нужно достать наружу. * Делать нужно в этом месте, чтобы например <div><br ></div> который выдаёт редактор преобразовалось просто в <br /> * Потому что ниже код из запрещённых тегов достаёт контент и обрамляет его ещё BR-ами */ $inner_text = preg_replace('~<([^>]+)><br\s*\/?></\1>~is' . BX_UTF_PCRE_MODIFIER, '<br>', $inner_text); $inner_text = preg_replace('~<br\s*\/?>~is' . BX_UTF_PCRE_MODIFIER, '<br>', $inner_text); // http://php.net/manual/ru/function.strip-tags.php - comment by mariusz.tarnaski at wp dot pl /** * работает: * preg_replace('@(<(?!/?(?:b|em))[^>]*>)@si', '$1xxx', $inner_text); * это просто на заметку. * * Потому что на данном конкретном шаге нам нужны закрывающиеся теги */ $inner_text = preg_replace( '@(</(?!(?:' . implode('|', $allowed_tags) . '))[^>]*>)@si' . BX_UTF_PCRE_MODIFIER, //"$1/_/", // Потому что так генерятся лишние переносы строк "$1", $inner_text ); /** * Фиксим такое: * <p><br>ТЕКСТ<br></p> * * Должно стать: * <p>Текст</p> */ /** * неправильно - рубит нах всё подряд * $inner_text= preg_replace( '~(\<br\>)*\<(\/?)(' . implode( '|', $allowed_tags ) . ')\>(\<br\>)*~si', "<$2$3>", $inner_text ); * * То есть текст: * <b>Объект:</b><br /> * Раменский район, снт Смородинки;<br /> * <b>Проект:</b><br> * Усманов<br> * <b>Исполнители:</b><br> * Давыдовский, Калинин; * ...должен сохраниться * */ $inner_text = preg_replace('~<(' . implode('|', $allowed_tags) . ')>(<br>)*~si' . BX_UTF_PCRE_MODIFIER, "<$1>", $inner_text); $inner_text = preg_replace('~(<br>)*<\/(' . implode('|', $allowed_tags) . ')>~si' . BX_UTF_PCRE_MODIFIER, "$1</$2>", $inner_text); /** * Одни только закрывающиеся теги не катят, поскольку редактор делает так: * * текст * <div> * кууууууча текста и всякой хуйни * <b>текст</b> * кууууууча текста и всякой хуйни * <b>текст</b> * кууууууча текста и всякой хуйни * <b>текст</b> * </div> * * === в результате мы добавляем наш BR после </div>, * но бля это не даёт эффекта, * поскольку <div> не обработан и нужен разрыв именно перед ним * */ /** * Расшифровка * Такой запрос: * (<(?!(?:\/|b|p))[^>]*>) * * значит, что мы ищем теги, внутри которых НЕ содержатся допустимые теги * https://regex101.com/ * Negative Lookahead -----> (?!(?:\/|b|p)) */ $inner_text = preg_replace('@(<(?!(?:/|' . implode('|', $allowed_tags) . '))[^>]*>)@si' . BX_UTF_PCRE_MODIFIER, "/_/$1", $inner_text); // 1 === // Чистим все, кроме разрешённых тегов //$inner_text = strip_tags($inner_text, '<' . implode('><', $allowed_tags) . '>'); $inner_text = self::stripTags($allowed_tags, $inner_text); /** * В случае: * <b> текст <img src= "" alt= ""> <br> текст </b> * * br должен разрезать тег: * <b> текст <img src= "" alt= ""></b> * <br> * <b>текст </b> * */ // $inner_text= preg_replace( '@<(' . implode( '|', $allowed_tags ) . ')>([\w\W\s]*)(<br>)+([\w\W\s]*)(<\/\1>)@si', "<$1>$2$4$5$3/_/", $inner_text ); $inner_text = preg_replace('@<(' . implode('|', $allowed_tags) . ')([^>]*?)>([^(<\/\1>)]*?)(<br>)([^(<\/\1>)]*?)<\/\1>@si' . BX_UTF_PCRE_MODIFIER, "<$1$2>$3</$1>/_/<$1>$5</$1>", $inner_text); /** * Ситуация: список захуярен таким образом * <ul><li></li></ul><ul><li></li></ul><ul><li></li></ul> * или * <ul><li>/_/</li></ul><ul><li>/_/</li></ul><ul><li>/_/</li></ul> * Нужно это всё склеить в общий <ul>...</ul> */ $inner_text = preg_replace("~</ul>(/_/|\s|<[\s/]*br>)*<ul>~si" . BX_UTF_PCRE_MODIFIER, '', $inner_text); // === Возвращаем <br />ы и расставляем там, где они были добавлены взамен структурных закрывающихся тегов $inner_text = preg_replace("~(/_/\s?)+~is" . BX_UTF_PCRE_MODIFIER, "<br>", $inner_text); // ==== // Чистим левые class-ы, стили, неймы, id $inner_text = preg_replace('~\s(style|class|name|id)\s*=[\'\"][^\'\"]*[\'\"]~is' . BX_UTF_PCRE_MODIFIER, '', $inner_text); /** * Также фиксим такое: * <br>Текст <br> * === убираем <br> вначале и вконце */ $inner_text = preg_replace('~^([\s\n\t\r]*\<br\>[\s\n\t\r]*)+~is' . BX_UTF_PCRE_MODIFIER, '', $inner_text); $inner_text = preg_replace('~([\s\n\t\r]*\<br\>[\s\n\t\r]*)+$~is' . BX_UTF_PCRE_MODIFIER, '', $inner_text); $inner_text = preg_replace('~\s\s~is' . BX_UTF_PCRE_MODIFIER, ' ', $inner_text); /** * Чистим теги с пустотой внутри * * <b> <br> </b> * */ $inner_text = preg_replace('~<(' . implode('|', $allowed_tags) . ')>(\s|<br>)*<\/\1>~is' . BX_UTF_PCRE_MODIFIER, '', $inner_text); return $inner_text; } protected static function stripTags($tags = [], $text = null) { if (empty($tags) || empty($text)) { return ''; } if (!is_array($tags)) { $tags = [$tags]; } /** * Сразу и открывающиеся и закрывающиеся * https://regex101.com/r/rroSjX/1 */ $regExp = sprintf( "~<(?!\/?(%s))[^>]*?>~is" . BX_UTF_PCRE_MODIFIER, implode('|', $tags) ); return preg_replace($regExp, '', $text); } /** * Спецсимволы * * @param $text * @param string $mode * * @return mixed */ private static function replace_difficult_symbols($text, $mode = 'html') { $charset_D7 = strtolower(\Bitrix\Main\Context::getCurrent()->getCulture()->getCharset()); if ($charset_D7 == 'utf-8') { /** * http://qaru.site/questions/433490/converting-microsoft-word-special-characters-with-php * "\xC2\xAB", // « (U+00AB) in UTF-8 * "\xC2\xBB", // » (U+00BB) in UTF-8 * "\xE2\x80\x98", // ‘ (U+2018) in UTF-8 * "\xE2\x80\x99", // ’ (U+2019) in UTF-8 * "\xE2\x80\x9A", // ‚ (U+201A) in UTF-8 * "\xE2\x80\x9B", // ‛ (U+201B) in UTF-8 * "\xE2\x80\x9C", // " (U+201C) in UTF-8 * "\xE2\x80\x9D", // " (U+201D) in UTF-8 * "\xE2\x80\x9E", // „ (U+201E) in UTF-8 * "\xE2\x80\x9F", // ‟ (U+201F) in UTF-8 * "\xE2\x80\xB9", // ‹ (U+2039) in UTF-8 * "\xE2\x80\xBA", // › (U+203A) in UTF-8 * "\xE2\x80\x93", // – (U+2013) in UTF-8 * "\xE2\x80\x94", // — (U+2014) in UTF-8 * "\xE2\x80\xA6" // … (U+2026) in UTF-8 */ $replace_map = [ "\xC2\xAB" => '«', // « (U+00AB) in UTF-8 "\xC2\xBB" => '»', // » (U+00BB) in UTF-8 "\xe2\x80\x98" => '\'', "\xe2\x80\x99" => '\'', "\xE2\x80\x9A" => ',', // ‚ (U+201A) in UTF-8 "\xE2\x80\x9B" => '\'', // ‛ (U+201B) in UTF-8 "\xe2\x80\x9C" => '"', "\xe2\x80\x9D" => '"', "\xE2\x80\x9E" => '"', // „ (U+201E) in UTF-8 "\xE2\x80\x9F" => '"', // ‟ (U+201F) in UTF-8 "\xE2\x80\xB9" => '<', // ‹ (U+2039) in UTF-8 "\xE2\x80\xBA" => '>', // › (U+203A) in UTF-8 "\xe2\x80\x93" => '–', "\xe2\x80\x94" => '—', "\xe2\x80\xa6" => '...', ]; } else { // https://www.php.net/manual/ru/function.chr.php $replace_map = [ chr('130') => ',', // ',', $str); // baseline single quote chr('132') => '"', // '"', $str); // baseline double quote chr('133') => '...', // '...', $str); // ellipsis chr('136') => '^', // '^', $str); // circumflex accent chr('139') => '<', // '<', $str); // left single guillemet chr('155') => '>', // '>', $str); // right single guillemet chr('145') => '\'', // "'", $str); // left single quote chr('146') => '\'', // "'", $str); // right single quote chr('147') => '"', // '"', $str); // left double quote chr('148') => '"', // '"', $str); // right double quote chr('149') => '-', // '-', $str); // bullet chr('150') => '–', // '-', $str); // endash chr('151') => '—', // '--', $str); // emdash chr('152') => '~', // '~', $str); // tilde accent chr('153') => '(tm)', // '(TM)', $str); // trademark ligature ]; } $text = str_replace(array_keys($replace_map), array_values($replace_map), $text); return $text; } }