Last active
October 29, 2023 10:28
-
-
Save serebro/c440ca77050dc31c3109 to your computer and use it in GitHub Desktop.
Censure.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Определение наличия мата (нецензурных слов) в тексте, матотест | |
* | |
* Алгоритм достаточно надёжен и быстр, в т.ч. на больших объёмах данных | |
* Метод обнаружения мата основывается на корнях и предлогах русского языка, а не на словаре | |
* Слова "лох", "хер", "залупа", "сука" матерными словами не считаются (см. словарь Даля) | |
* Разработка ведётся с 2005 года | |
* | |
* Класс явл. хорошим учебным пособием по изучению регулярных выражений и... русского мата! =) | |
* | |
* http://www.google.com/search?q=%F2%EE%EB%EA%EE%E2%FB%E9%20%F1%EB%EE%E2%E0%F0%FC%20%F0%F3%F1%F1%EA%EE%E3%EE%20%EC%E0%F2%E0&ie=cp1251&oe=UTF-8 | |
* http://www.awd.ru/dic.htm (Толковый словарь русского мата) | |
* | |
* Согласно статье 20.1 КоАП РФ нецензурная брань в общественных местах (интернет — место общественное) расценивается как мелкое хулиганство, | |
* за что установлена административная ответственность — наложение штрафа в размере от пятисот до одной тысячи рублей или административный арест на срок до пятнадцати суток. | |
* | |
* TODO | |
* * добавить цифровую подделку с нулём под букву O | |
* | |
* @link http://code.google.com/p/php-censure/ | |
* @license http://creativecommons.org/licenses/by-sa/3.0/ | |
* @author Nasibullin Rinat | |
* @version 3.2.7 | |
*/ | |
class Text_Censure | |
{ | |
#запрещаем создание экземпляра класса, вызов методов этого класса только статически! | |
private function __construct() {} | |
/** | |
* | |
* @param string $s строка для проверки | |
* @param string $delta ширина найденного фрагмента в словах | |
* (кол-во слов от матного слова слева и справа, максимально 10) | |
* @param string $continue строка, которая будет вставлена в начале и в конце фрагмента | |
* @param bool $is_html расценивать строку как HTML код? | |
* в режиме $is_html === TRUE html код игнорируется, а html сущности заменяются в "чистый" UTF-8 | |
* @param string|null $replace строка, на которую заменять матный фрагмент, например: '[ой]' ($replace д.б. в кодировке $charset) | |
* опция работает в PHP >= 5.2.0 | |
* @param string $charset кодировка символов (родная кодировка -- UTF-8, для других будет прозрачное перекодирование) | |
* @return bool|string|int|null Если $replace === NULL, то возвращает FALSE, если мат не обнаружен, иначе фрагмент текста с матерным словом. | |
* Если $replace !== NULL, то возвращает исходную строку, где фрагменты мата заменены на $replace. | |
* В случае возникновения ошибки возвращает код ошибки > 0 (integer): | |
* * PREG_INTERNAL_ERROR | |
* * PREG_BACKTRACK_LIMIT_ERROR (see also pcre.backtrack_limit) | |
* * PREG_RECURSION_LIMIT_ERROR (see also pcre.recursion_limit) | |
* * PREG_BAD_UTF8_ERROR | |
* * PREG_BAD_UTF8_OFFSET_ERROR (since PHP 5.3.0) | |
* Или -1, если ReflectionTypeHint вернул ошибку | |
*/ | |
public static function parse( | |
$s, | |
$delta = 3, | |
$continue = "\xe2\x80\xa6", | |
$is_html = true, | |
$replace = null, | |
$charset = 'UTF-8') | |
{ | |
if (! ReflectionTypeHint::isValid()) return -1; | |
if ($s === null) return null; | |
static $re_badwords = null; | |
if ($re_badwords === null) | |
{ | |
#предлоги русского языка: | |
#[всуо]| | |
#по|за|на|об|до|от|вы|вс|вз|из|ис| | |
#под|про|при|над|низ|раз|рас|воз|вос| | |
#пооб|повы|пона|поза|недо|пере|одно| | |
#полуза|произ|пораз|много| | |
$pretext = array( | |
#1 | |
'[уyоoаa]_? (?=[еёeхx])', #у, о (уебать, охуеть, ахуеть) | |
'[вvbсc]_? (?=[хпбмгжxpmgj])', #в, с (впиздячить, схуярить) | |
'[вvbсc]_?[ъь]_? (?=[еёe])', #въ, съ (съебаться, въебать) | |
'ё_? (?=[бb6])', #ё (ёбля) | |
#2 | |
'[вvb]_?[ыi]_?', #вы | |
'[зz3]_?[аa]_?', #за | |
'[нnh]_?[аaеeиi]_?', #на, не, ни | |
'[вvb]_?[сc]_? (?=[хпбмгжxpmgj])', #вс (вспизднуть) | |
'[оo]_?[тtбb6]_? (?=[хпбмгжxpmgj])', #от, об | |
'[оo]_?[тtбb6]_?[ъь]_? (?=[еёe])', #отъ, объ | |
'[иiвvb]_?[зz3]_? (?=[хпбмгжxpmgj])', #[ив]з | |
'[иiвvb]_?[зz3]_?[ъь]_? (?=[еёe])', #[ив]зъ | |
'[иi]_?[сc]_? (?=[хпбмгжxpmgj])', #ис | |
'[пpдdg]_?[оo]_? (?> [бb6]_? (?=[хпбмгжxpmgj]) | |
| [бb6]_? [ъь]_? (?=[еёe]) | |
| [зz3]_? [аa] _? | |
)?', #по, до, пообъ, дообъ, поза, доза (двойные символы вырезаются!) | |
#3 | |
'[пp]_?[рr]_?[оoиi]_?', #пр[ои] | |
'[зz3]_?[лl]_?[оo]_?', #зло (злоебучая) | |
'[нnh]_?[аa]_?[дdg]_? (?=[хпбмгжxpmgj])', #над | |
'[нnh]_?[аa]_?[дdg]_?[ъь]_? (?=[еёe])', #надъ | |
'[пp]_?[оoаa]_?[дdg]_? (?=[хпбмгжxpmgj])', #под | |
'[пp]_?[оoаa]_?[д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]_?', #долб[оа] | |
'[оo]_?[сc]_?[тt]_?[рpr]_?[оo]_?', #остро | |
); | |
$badwords = array( | |
#Слово на букву Х | |
'(?<=\PL) %RE_PRETEXT%? | |
[hхx]_?[уyu]_?[ийiеeёяюju] #хуй, хуя, хую, хуем, хуёвый, охуительный | |
#исключения: | |
(?<! _hue(?=_) #HUE -- цветовая палитра | |
| _hue(?=so_) #hueso -- испанское слово | |
| _хуе(?=дин) #Хуедин -- город в Румынии | |
| _hyu(?=ndai_) #Hyundai -- марка корейского автомобиля | |
)', | |
#Слово на букву П | |
'(?<=\PL) %RE_PRETEXT%? | |
[пp]_?[иieеё]_?[зz3]_?[дd](?=_?[:vowel:])', #п[ие]зда, пизде, пиздёж, пизду, пиздюлина, пиздобол, опиздинеть, пиздых, подпёздывать | |
#Слово на букву Е | |
'(?<=\PL) %RE_PRETEXT%? | |
[eеё]_? | |
#исключения | |
(?<!н[eе][её]_|т_е_) #неё, т.е. большие | |
[бb6]_? (?= [уyиi]_ #ебу, еби | |
| [ыиiоoaаеeёуy]_?[:consonant:] #ебут, ебать, ебись, ебёт, поеботина, выебываться, ёбарь | |
#исключения | |
(?<!_ebo[kt](?=_)|буд) #ebook, eboot, ее будут | |
| [лl](?:[оoаaыиiя]|ya) #ебло, ебла, ебливая, еблись, еблысь, ёбля | |
| [нn]_?[уy] #ёбнул, ёбнутый | |
| [кk]_?[аa] #взъёбка | |
| [сc]_?[тt] #ебсти | |
)', | |
#Слово на букву Е (c обязательной приставкой от 2-х и более букв!) | |
'(?<=\PL) %RE_PRETEXT% | |
(?<= \pL\pL|\pL_\pL_) | |
[eеё]_?[бb6] #долбоёб, дураёб, изъёб, заёб, заебай, разъебай, мудоёбы | |
', | |
#Слово на букву Е | |
'(?<=\PL) ёб (?=\PL)', #ёб твою мать | |
#Слово на букву Б | |
'(?<=\PL) %RE_PRETEXT%? | |
[бb6]_?[лl]_?(?:я|ya)(?: _ #бля | |
| _?[тдtd] #блять, бляди | |
)', | |
#ПИДОР | |
'(?<=\PL) [пp]_?[иieе]_?[дdg]_?[eеaаoо]_?[rpр]', #п[ие]д[оеа]р | |
#МУДАК | |
'(?<=\PL) [мm]_?[уy]_?[дdg]_?[аa] #мудак, мудачок | |
#исключения: | |
(?<!_myda(?=s_)) #Chelonia mydas -- морская зеленая (суповая) черепаха | |
', | |
#ЖОПА | |
'(?<=\PL) [zж]_?h?_?[оo]_?[pп]_?[aаyуыiеeoо]', #жоп[ауыео] | |
#МАНДА | |
#исключения: город Мандалай, округ Мандаль, индейский народ Мандан, фамилия Мандель, мандарин | |
'(?<=\PL) [мm]_?[аa]_?[нnh]_?[дdg]_?[aаyуыiеeoо] #манд[ауыео] | |
#исключения: | |
(?<! манда(?=[лн]|рин) | |
| manda(?=[ln]|rin) | |
| манде(?=ль) | |
)', | |
#ГОВНО | |
'(?<=\PL) [гg]_?[оo]_?[вvb]_?[нnh]_?[оoаaяеeyу]', #говн[оаяеу] | |
#FUCK | |
'(?<=\PL) f_?u_?[cс]_?k', #fuck, fucking | |
/* | |
#ЛОХ | |
' л_?[оo]_?[хx]', | |
#СУКА | |
'[^р]_?[scс]_?[yуu]_?[kк]_?[aаiи]', #сука (кроме слова "барсука" - это животное-грызун) | |
'[^р]_?[scс]_?[yуu]_?[4ч]_?[кk]', #сучк(и) (кроме слова "барсучка") | |
#ХЕР | |
' %RE_PRETEXT%?[хxh]_?[еe]_?[рpr](_?[нnh]_?(я|ya)| )', #%RE_PRETEXT%хер(ня) | |
#ЗАЛУПА | |
' [зz3]_?[аa]_?[лl]_?[уy]_?[пp]_?[аa]', | |
*/ | |
); | |
$trans = array( | |
'_' => '\x20', #пробел | |
'\pL' => '[^\x20\d]', #буква | |
'\PL' => '[\x20\d]', #не буква | |
'[:vowel:]' => '[аеиоуыэюяёaeioyu]', #гласные буквы | |
'[:consonant:]' => '[^аеиоуыэюяёaeioyu\x20\d]', #согласные буквы | |
); | |
$re_badwords = str_replace( | |
'%RE_PRETEXT%', | |
'(?:' . implode('|', $pretext) . ')', #однократный шаблон с альтернативами использовать нельзя! | |
'~' . implode('|', $badwords) . '~sxuSX' | |
); | |
$re_badwords = strtr($re_badwords, $trans); | |
} | |
$s = UTF8::convert_from($s, $charset); | |
$replace = UTF8::convert_from($replace, $charset); | |
$ss = $s; #saves original string | |
if ($is_html) | |
{ | |
#скрипты не вырезаем, т.к. м.б. обходной маневр на с кодом на javascript: | |
#<script>document.write('сло'+'во')</script> | |
#хотя давать пользователю возможность использовать код на javascript нехорошо | |
$s = is_callable(array('HTML', 'strip_tags')) ? HTML::strip_tags($s, null, true, array('comment', 'style', 'map', 'frameset', 'object', 'applet')) | |
: strip_tags($s); | |
#заменяем html-сущности в "чистый" UTF-8 | |
$s = UTF8::html_entity_decode($s, $is_htmlspecialchars = true); | |
} | |
if (strtoupper(substr($charset, 0, 3)) === 'UTF') #UTF-8, UTF-16, UTF-32 | |
{ | |
#remove combining diactrical marks | |
$additional_chars = array( | |
"\xc2\xad", #"мягкие" переносы строк (­) | |
); | |
$s = UTF8::diactrical_remove($s, $additional_chars); | |
} | |
#ВотБ/\яПидорыОхуелиБлятьНахуйПохуйПи3децПолный | |
if (version_compare(PHP_VERSION, '5.2.0', '>=')) | |
{ | |
$s = preg_replace('~ [\p{Lu}3] (?>\p{Ll}+|/\\\\|[@36]+)++ #Вот | |
(?= [\p{Lu}3] (?:\p{Ll} |/\\\\|[@36] ) ) #Бля | |
~sxuSX', '$0 ', $s); | |
} | |
$s = UTF8::lowercase($s); | |
#получаем в массив только буквы и цифры | |
#"с_л@о#во,с\xc2\xa7лово.Слово" -> "с л о во с лово слово слово слово слово" | |
preg_match_all('~(?> \xd0[\xb0-\xbf]|\xd1[\x80-\x8f\x91] #[а-я] | |
| /\\\\ #л | |
| @ #а | |
| [a-z\d]+ | |
)+ | |
~sxSX', $s, $m); | |
$s = ' ' . implode(' ', $m[0]) . ' '; | |
$trans = array( | |
'/\\' => 'л', #Б/\ЯТЬ --> БЛЯТЬ | |
'@' => 'а', #пизд@ --> пизда | |
); | |
$s = strtr($s, $trans); | |
#цифровые подделки под буквы | |
$trans = array( | |
'~ [3з]++ [3з\x20]*+ ~sxuSX' => 'з', | |
'~ [6б]++ [6б\x20]*+ ~sxuSX' => 'б', | |
); | |
$s = preg_replace(array_keys($trans), array_values($trans), $s); | |
#убираем все повторяющиеся символы, ловим обман типа "х-у-у-й" | |
#"сллоооовоо слово х у у й" --> "слово слово х у й" | |
$s = preg_replace('/( [\xd0\xd1][\x80-\xbf] \x20? #optimized [а-я] | |
| [a-z\d] \x20? | |
) \\1+ | |
/sxSX', '$1', $s); | |
if ($replace === null || version_compare(PHP_VERSION, '5.2.0', '<')) | |
{ | |
$result = preg_match($re_badwords, $s, $m, PREG_OFFSET_CAPTURE); | |
if (function_exists('preg_last_error') && preg_last_error() !== PREG_NO_ERROR) return preg_last_error(); | |
if ($result === false) return 1; #PREG_INTERNAL_ERROR = 1 | |
if ($result && $replace === null) | |
{ | |
list($word, $offset) = $m[0]; | |
$s1 = substr($s, 0, $offset); | |
$s2 = substr($s, $offset + strlen($word)); | |
$delta = intval($delta); | |
if ($delta === 0) $fragment = '[' . trim($word) . ']'; | |
else | |
{ | |
if ($delta < 1 || $delta > 10) $delta = 3; | |
preg_match('/ (?> \x20 (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)++ ){1,' . $delta . '}+ | |
\x20?+ | |
$/sxSX', $s1, $m1); | |
preg_match('/^ (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)*+ #ending | |
\x20?+ | |
(?> (?>[\xd0\xd1][\x80-\xbf]|[a-z\d]+)++ \x20 ){0,' . $delta . '}+ | |
/sxSX', $s2, $m2); | |
$fragment = (ltrim(@$m1[0]) !== ltrim($s1) ? $continue : '') . | |
trim((isset($m1[0]) ? $m1[0] : '') . '[' . trim($word) . ']' . (isset($m2[0]) ? $m2[0] : '')) . | |
(rtrim(@$m2[0]) !== rtrim($s2) ? $continue : ''); | |
} | |
return UTF8::convert_to($fragment, $charset); | |
} | |
return false; | |
} | |
$result = preg_match_all($re_badwords, $s, $m); | |
if (function_exists('preg_last_error') && preg_last_error() !== PREG_NO_ERROR) return preg_last_error(); | |
if ($result === false) return 1; #PREG_INTERNAL_ERROR = 1 | |
if ($result > 0) | |
{ | |
#d($s, $m[0]); | |
$s = $ss; | |
#замена матного фрагмента на $replace | |
foreach ($m[0] as $w) | |
{ | |
$re_w = '~' . preg_replace_callback('~(?:/\\\\|[^\x20])~suSX', array('self', '_make_regexp_callback'), $w) . '~sxuiSX'; | |
$ss = preg_replace($re_w, $replace, $ss); | |
#d($re_w); | |
} | |
while ($ss !== $s) $ss = self::parse($s = $ss, $delta, $continue, $is_html, $replace, 'UTF-8'); | |
} | |
return UTF8::convert_to($ss, $charset); | |
} | |
private static function _make_regexp_callback(array $m) | |
{ | |
#$re_holes = '[\x00-\x20\-_\*\~\.\'"\^=`:]'; | |
#$re_holes = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]'; | |
$re_holes = '(?!/\\\\)[^\p{L}\d]'; #non letter, non digit, non '/\' | |
if ($m[0] === 'а') $re = '[@аА]++ (?>[:holes:]|[@аА]+)*+'; | |
elseif ($m[0] === 'з') $re = '[3зЗ]++ (?>[:holes:]|[3зЗ]+)*+'; | |
elseif ($m[0] === 'б') $re = '[6бБ]++ (?>[:holes:]|[6бБ]+)*+'; | |
elseif ($m[0] === 'л') $re = '(?>[лЛ]+|/\\\\)++ (?>[:holes:]|[лЛ]+|/\\\\)*+'; | |
else | |
{ | |
#в PCRE-7.2 флаг /i в комбинации с /u в регулярном выражении почему-то не работает (BUG?) | |
#поэтому делаем класс символов с буквами в обоих регистрах | |
$char = '[' . preg_quote($m[0] . UTF8::uppercase($m[0]), '~') . ']'; | |
$re = str_replace('$0', $char, '$0++ (?>[:holes:]|$0+)*+'); | |
} | |
return str_replace('[:holes:]', $re_holes, $re . "\r\n"); | |
} | |
} | |
/* | |
$pattern = "\w{0,5}[хx]([хx\s\!@#\$%\^&*+-\|\/]{0,6})[уy]([уy\s\!@#\$%\^&*+-\|\/]{0,6})[ёiлeеюийя]\w{0,7}|\w{0,6}[пp]([пp\s\!@#\$%\^&*+-\|\/]{0,6})[iие]([iие\s\!@#\$%\^&*+-\|\/]{0,6})[3зс]([3зс\s\!@#\$%\^&*+-\|\/]{0,6})[дd]\w{0,10}|[сcs][уy]([уy\!@#\$%\^&*+-\|\/]{0,6})[4чkк]\w{1,3}|\w{0,4}[bб]([bб\s\!@#\$%\^&*+-\|\/]{0,6})[lл]([lл\s\!@#\$%\^&*+-\|\/]{0,6})[yя]\w{0,10}|\w{0,8}[её][bб][лске@eыиаa][наи@йвл]\w{0,8}|\w{0,4}[еe]([еe\s\!@#\$%\^&*+-\|\/]{0,6})[бb]([бb\s\!@#\$%\^&*+-\|\/]{0,6})[uу]([uу\s\!@#\$%\^&*+-\|\/]{0,6})[н4ч]\w{0,4}|\w{0,4}[еeё]([еeё\s\!@#\$%\^&*+-\|\/]{0,6})[бb]([бb\s\!@#\$%\^&*+-\|\/]{0,6})[нn]([нn\s\!@#\$%\^&*+-\|\/]{0,6})[уy]\w{0,4}|\w{0,4}[еe]([еe\s\!@#\$%\^&*+-\|\/]{0,6})[бb]([бb\s\!@#\$%\^&*+-\|\/]{0,6})[оoаa@]([оoаa@\s\!@#\$%\^&*+-\|\/]{0,6})[тnнt]\w{0,4}|\w{0,10}[ё]([ё\!@#\$%\^&*+-\|\/]{0,6})[б]\w{0,6}|\w{0,4}[pп]([pп\s\!@#\$%\^&*+-\|\/]{0,6})[иeеi]([иeеi\s\!@#\$%\^&*+-\|\/]{0,6})[дd]([дd\s\!@#\$%\^&*+-\|\/]{0,6})[oоаa@еeиi]([oоаa@еeиi\s\!@#\$%\^&*+-\|\/]{0,6})[рr]\w{0,12}"; | |
$string = 'Да вы, бля, все суки охуевшие совсем ебанулись, вашу мать, пидорасня хренова.'; | |
echo mb_eregi_replace($pattern, '[у нас не ругаются матом]', $string); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment