<?php

/**
 * Функция определяет наличие нецензурной лексики в HTML-тексте в кодировке UTF-8.
 * Возвращает false, если мат не обнаружен, иначе фрагмент текста с запрещенным словом.
 *
 * Алгоритм надежен и быстр, даже для больших объемов данных.
 * Метод основан на корнях и предлогах русского языка, а не на словаре.
 * Слова "лох", "хер", "залупа", "сука" не считаются матерными (по словарю Даля).
 *
 * @param string $s Текст в кодировке UTF-8
 * @param int $delta Ширина найденного фрагмента в словах (количество слов слева и справа от матного слова, максимум 10)
 * @param string $continue Строка, которая будет вставлена в начале и конце фрагмента (многоточие по умолчанию)
 * @return string|false Фрагмент текста с матным словом или false, если мат не найден
 */
function censure(string $s, int $delta = 3, string $continue = "…"): string|false
{
    // Предлоги русского языка
    static $pretext = [
        // 1 символ
        '[уyоo]_?(?=[еёeхx])',               // у, о (уебать, охуеть)
        '[вvbсc]_?(?=[хпбмгжxpmgj])',        // в, с (впиздячить, схуярить)
        '[вvbсc]_?[ъь]_?(?=[еёe])',          // въ, съ (съебаться, въебать)
        'ё_?(?=[бb])',                       // ё (ёбля)
        
        // 2 символа
        '[вvb]_?[ыi]_?',                     // вы
        '[зz3]_?[аa]_?',                     // за
        '[нnh]_?[аaеeиi]_?',                 // на, не, ни
        '[вvb]_?[сc]_?(?=[хпбмгжxpmgj])',    // вс (вспизднуть)
        '[оo]_?[тtбb]_?(?=[хпбмгжxpmgj])',   // от, об
        '[оo]_?[тtбb]_?[ъь]_?(?=[еёe])',     // отъ, объ
        '[иiвvb]_?[зz3]_?(?=[хпбмгжxpmgj])', // из, вз
        '[иiвvb]_?[зz3]_?[ъь]_?(?=[еёe])',   // изъ, взъ
        '[иi]_?[сc]_?(?=[хпбмгжxpmgj])',     // ис
        '[пpдdg]_?[оo]_?(?>[бb]_?(?=[хпбмгжxpmgj])|[бb]_?[ъь]_?(?=[еёe])|[зz3]_?[аa]_?)?', // по, до
        
        // 3 символа
        '[пp]_?[рr]_?[оoиi]_?',              // про, при
        '[зz3]_?[лl]_?[оo]_?',               // зло (злоебучая)
        '[нnh]_?[аa]_?[дdg]_?(?=[хпбмгжxpmgj])', // над
        '[нnh]_?[аa]_?[дdg]_?[ъь]_?(?=[еёe])',   // надъ
        '[пp]_?[оo]_?[дdg]_?(?=[хпбмгжxpmgj])',  // под
        '[пp]_?[оo]_?[дdg]_?[ъь]_?(?=[еёe])',    // подъ
        '[рr]_?[аa]_?[зz3сc]_?(?=[хпбмгжxpmgj])', // раз, рас
        '[рr]_?[аa]_?[зz3сc]_?[ъь]_?(?=[еёe])',   // разъ, расъ
        '[вvb]_?[оo]_?[зz3сc]_?(?=[хпбмгжxpmgj])', // воз, вос
        '[вvb]_?[оo]_?[зz3сc]_?[ъь]_?(?=[еёe])',  // возъ, восъ
        
        // 4 символа
        '[нnh]_?[еe]_?[дdg]_?[оo]_?',        // недо
        '[пp]_?[еe]_?[рr]_?[еe]_?',          // пере
        '[oо]_?[дdg]_?[нnh]_?[оo]_?',        // одно
        '[кk]_?[oо]_?[нnh]_?[оo]_?',         // коно (коноебиться)
        '[мm]_?[уy]_?[дdg]_?[оoаa]_?',       // мудо, муда (мудаёб)
        '[oо]_?[сc]_?[тt]_?[оo]_?',          // осто (остопиздело)
        '[дdg]_?[уy]_?[рpr]_?[оoаa]_?',      // дур[оа]
        '[хx]_?[уy]_?[дdg]_?[оoаa]_?',       // худ[оа] (худоебина)
        
        // 5 символов
        '[мm]_?[нnh]_?[оo]_?[гg]_?[оo]_?',    // много
        '[мm]_?[оo]_?[рpr]_?[дdg]_?[оoаa]_?', // морд[оа]
        '[мm]_?[оo]_?[зz3]_?[гg]_?[оoаa]_?',  // мозг[оа]
        '[дdg]_?[оo]_?[лl]_?[бb6]_?[оoаa]_?', // долб[оа]
    ];

    // Матерные слова и выражения
    static $badwords = [
        // Слова на букву Х
        '(?<=[_\d]) {RE_PRETEXT}?[hхx]_?[уyu]_?[йiеeёяюju](?<!_hue(?=_)|_hue(?=so_)|_хуе(?=дин))',
        
        // Слова на букву П
        '(?<=[_\d]) {RE_PRETEXT}?[пp]_?[иi]_?[зz3]_?[дd]_?[:vowel:]',
        
        // Слова на букву Е
        '(?<=[_\d]) {RE_PRETEXT}?[eеё]_?(?<!не[её]_)[бb6]_?(?:[уyиi]_|[ыиiоoaаеeёуy]_?[:consonant:]|[лl][оoаaыиi]|[нn]_?[уy]|[кk]_?[аa])',
        '(?<=[_\d]) {RE_PRETEXT}(?<=[^_\d][^_\d]|[^_\d]_[^_\d]_)[eеё]_?[бb6](?:_|_?[аa]_?[^_\d])',
        
        // Слова на букву Б
        '(?<=[_\d]) {RE_PRETEXT}?[бb6]_?[лl]_?(?:я|ya)(?:_|_?[тд])',
        
        // ПИДОР
        '(?<=[_\d])[пp]_?[иieе]_?[дdg]_?[eеaаoо]_?[rpр]',
        
        // ПИДР
        '(?<=[_\d])[пp]_?[иieе]_?[дdg]_?[rpр]',
        
        // МУДАК
        '(?<=[_\d])[мm]_?[уy]_?[дdg]_?[аa]',
        
        // ЖОПА
        '(?<=[_\d])[zж]_?h?_?[оo]_?[pп]_?[aаyуыiеeoо]',
        
        // МАНДА (исключая Мандалай, Мандаль, Мандан, Мандель)
        '(?<=[_\d])[мm]_?[аa]_?[нnh]_?[дdg]_?[aаyуыiеeoо](?<!манда(?=[лн])|манде(?=ль))',
        
        // ГОВНО
        '(?<=[_\d])[гg]_?[оo]_?[вvb]_?[нnh]_?[оoаaяеeyу]',
        
        // FUCK
        '(?<=[_\d])f_?u_?[cс]_?k',
    ];

    // Замены для регулярных выражений
    static $re_trans = [
        '_' => '\x20',
        '[:vowel:]' => '[аеиоуыэюяёaeioyu]',
        '[:consonant:]' => '[^аеиоуыэюяёaeioyu\x20\d]',
    ];

    // Собираем регулярное выражение
    $re_badwords = str_replace(
        '{RE_PRETEXT}', 
        '(?>' . implode('|', $pretext) . ')', 
        '~' . implode('|', $badwords) . '~sxu'
    );
    $re_badwords = strtr($re_badwords, $re_trans);

    // Очищаем текст от HTML-тегов (кроме скриптов, чтобы избежать обхода через JS)
    if (!function_exists('strip_tags_smart')) {
        include_once 'sys/strip_tags_smart.php';
    }
    $s = strip_tags_smart($s, null, true, ['comment', 'style', 'map', 'frameset', 'object', 'applet']);

    // Преобразуем HTML-сущности в UTF-8
    if (!function_exists('utf8_html_entity_decode')) {
        include_once 'sys/utf8_html_entity_decode.php';
    }
    $s = utf8_html_entity_decode($s, true);

    // Приводим к нижнему регистру
    if (!function_exists('utf8_convert_case')) {
        include_once 'sys/utf8_convert_case.php';
    }
    $s = utf8_convert_case($s, CASE_LOWER);

    // Дополнительные замены
    static $trans = [
        "\xc2\xad" => '',    // мягкий перенос
        "\xcc\x81" => '',    // знак ударения
        '/\\' => 'л',        // Б/\Я
        '/|' => 'л',        // Б/|Я
        "\xd0\xb5\xd0\xb5" => "\xd0\xb5\xd1\x91", // ее => её
    ];
    $s = strtr($s, $trans);

    // Извлекаем только буквы и цифры
    preg_match_all('/(?>\xd0[\xb0-\xbf]|\xd1[\x80-\x8f\x91]|[a-z\d]+)+/sx', $s, $m);
    $s = ' ' . implode(' ', $m[0]) . ' ';

    // Удаляем повторяющиеся символы
    $s = preg_replace('/([\xd0\xd1][\x80-\xbf]|[a-z\d])\s\1+/sx', '$1', $s);

    // Поиск матерных слов
    if (preg_match($re_badwords, $s, $m, PREG_OFFSET_CAPTURE)) {
        [$word, $offset] = $m[0];
        $s1 = substr($s, 0, $offset);
        $s2 = substr($s, $offset + strlen($word));
        
        $delta = max(1, min(10, $delta)); // Ограничиваем delta от 1 до 10
        
        // Ищем контекст слева
        preg_match('/\s(?>\s(?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)+){1,' . $delta . '}\s?$/sx', $s1, $m1);
        
        // Ищем контекст справа
        preg_match('/^(?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)*\s?(?>(?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)+\s){1,' . $delta . '}/sx', $s2, $m2);
        
        // Формируем фрагмент с контекстом
        $fragment = (ltrim($m1[0] ?? '') !== ltrim($s1) ? $continue : '') .
            trim(($m1[0] ?? '') . '[' . trim($word) . ']' . ($m2[0] ?? '')) .
            (rtrim($m2[0] ?? '') !== rtrim($s2) ? $continue : '');
        
        return $fragment;
    }

    return false;
}

?>