(Feature) Replace/Redact Language Censor System

This commit is contained in:
HDVinnie
2017-12-28 13:48:53 -05:00
parent 7c75d86ad5
commit 0691df1d43
7 changed files with 300 additions and 2 deletions
+1
View File
@@ -83,5 +83,6 @@ class Kernel extends HttpKernel
'immune' => \App\Http\Middleware\CheckForImmunity::class,
'check_ip' => \App\Http\Middleware\CheckIfAlreadyVoted::class,
'language' => \App\Http\Middleware\SetLanguage::class,
'censor' => \App\Http\Middleware\LanguageCensor::class,
];
}
+188
View File
@@ -0,0 +1,188 @@
<?php
/**
* NOTICE OF LICENSE
*
* UNIT3D is open-sourced software licensed under the GNU General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D
* @license https://choosealicense.com/licenses/gpl-3.0/ GNU General Public License v3.0
* @author HDVinnie
*/
namespace App\Http\Middleware;
use Closure;
use Config;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Class LanguageCensor
*
* A middleware that can replace or redact(blur) words on a page.
*
*/
class LanguageCensor
{
/** @var An associative array of keys as words or patterns and values as the replacements */
protected $replaceDict;
/** @var An array specifying the words and patterns that are to be redacted */
protected $redactDict;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $ability
* @param string|null $boundModelName
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function handle($request, Closure $next, $ability = null, $boundModelName = null)
{
$response = $next($request);
$this->prepareDictionary();
$content = $response->getContent();
$content = $this->censorResponse($content);
$response->setContent($content);
return $response;
}
/**
* Sets up the dictionaries, normalizes the content provided for censor
* for replacing and redacting stuff on page
*/
private function prepareDictionary()
{
$replaceDict = Config::get('censor.replace', []);
$redactDict = Config::get('censor.redact', []);
$replaceDictKeys = array_keys($replaceDict);
$replaceDictValues = array_values($replaceDict);
$replaceDictKeys = $this->normalizeRegex($replaceDictKeys);
$this->replaceDict = array_combine($replaceDictKeys, $replaceDictValues);
$this->redactDict = $this->normalizeRegex($redactDict);
}
/**
* Converts the words containing wildcards to regex patterns
*
* @param $dictionary
*
* @return array
*/
private function normalizeRegex($dictionary)
{
foreach ($dictionary as &$pattern) {
$pattern = str_replace('%', '(?:[^<\s]*)', $pattern);
}
return $dictionary;
}
/**
* Censor the request response.
*
* @param $source
*
* @return mixed
*/
protected function censorResponse($source)
{
$replaceables = array_keys($this->replaceDict);
$replaceables = array_merge($replaceables, $this->redactDict);
// Word boundary and word matching regex
$replaceables = '\b' . implode('\b|\b', $replaceables) . '\b';
$regex = '/>(?:[^<]*?(' . $replaceables . ')[^<]*?)</i';
// Make the keys lower case so that it is easy to lookup
// the replacements
$toReplace = array_change_key_case($this->replaceDict, CASE_LOWER);
$toRedact = $this->redactDict;
// Find all the matches and keep redacting/replacing
$source = preg_replace_callback($regex, function ($match) use ($toReplace, $toRedact) {
$temp = strtolower($match[1]);
// If we have to replace it
if (isset($toReplace[$temp])) {
return str_replace($match[1], $toReplace[$temp], $match[0]);
} elseif ($regexKey = $this->getReplaceRegexKey($temp)) { // Get the key i.e. pattern of the replace dictionary
return str_replace($match[1], $toReplace[$regexKey], $match[0]);
} elseif ($this->_inArray($temp, $toRedact) || $this->getRedactRegexKey($temp)) { // If it matches a word or pattern to redact
$wrapWith = "<span class='censor'>{$temp}</span>";
return str_replace($match[1], $wrapWith, $match[0]);
} else {
return $match[0];
}
}, $source);
return $source;
}
/**
* Gets a matched item, checks the replace dictionary for any satisfying pattern
* and returns the matching pattern key item if any
*
* @param $matched
*
* @return bool|string
*/
public function getReplaceRegexKey($matched)
{
foreach ($this->replaceDict as $pattern => $replaceWith) {
if (preg_match('/' . $pattern . '/', $matched)) {
return $pattern;
}
}
return false;
}
/**
* Case in-sensitive in_array
*
* @param $needle
* @param $haystack
*
* @return bool
*/
private function _inArray($needle, $haystack)
{
return in_array(strtolower($needle), array_map('strtolower', $haystack));
}
/**
* Gets the matched item and check if it matches any of the patterns
* available in redact dictionary
*
* @param $matched
*
* @return bool
*/
public function getRedactRegexKey($matched)
{
foreach ($this->redactDict as $pattern) {
if (preg_match('/' . $pattern . '/', $matched)) {
return $pattern;
}
}
return false;
}
}
+1
View File
@@ -41,6 +41,7 @@ class Shoutbox extends Model
{
$code = new Decoda($message);
$code->defaults();
$code->removeHook('Censor');
$code->removeHook('Clickable');
$code->addHook(new ClickableHook());
$code->setXhtml(false);
+93
View File
@@ -0,0 +1,93 @@
<?php
/**
* NOTICE OF LICENSE
*
* UNIT3D is open-sourced software licensed under the GNU General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D
* @license https://choosealicense.com/licenses/gpl-3.0/ GNU General Public License v3.0
* @author HDVinnie
*/
return [
/*
|--------------------------------------------------------------------------
| Replace list
|--------------------------------------------------------------------------
| An associative array with a list of words that you want to replace.
| keys of the array will be the words that you want to replace and the
| values will be the words with which the key words will be replaced e.g.
|
| 'replace' => [
| 'seventh' => '7th',
| 'monthly' => 'every month',
| 'yearly' => 'every year',
| 'weekly' => 'every week',
| ],
|
| In this case "seventh" will be replaced with "7th" and so on.
|
*/
'replace' => [
//
],
/*
|--------------------------------------------------------------------------
| Redact List
|--------------------------------------------------------------------------
| Specify the words that you want to completely redact. The words
| specified in here will have a blur effect placed over them.
|
| 'redact' => [
| 'bitch',
| ],
|
| In this case "bitch" will have a censor class wrapped around it
|
*/
'redact' => [
'asshole',
'bitch',
'btch',
'blowjob',
'cock',
'cawk',
'clt',
'clit',
'clitoris',
'cock',
'cocksucker',
'cum',
'cunt',
'cnt',
'dildo',
'dick',
'douche',
'fag',
'faggot',
'fcuk',
'fuck',
'fuk',
'motherfucker',
'nigga',
'nigger',
'nig',
'penis',
'pussy',
'rimjaw',
'scrotum',
'shit',
'sht',
'slut',
'twat',
'whore',
'whre',
'vagina',
'vag',
'rape',
],
];
+15
View File
@@ -22351,3 +22351,18 @@ div.header div.inner_content h1 {
font-weight: 800;
text-align: center;
}
.censor {
filter:blur(3px);
}
.censor:hover{
-webkit-animation: 0.25s linear forwards censor;
-moz-animation: 0.25s linear forwards censor;
-o-animation: 0.25s linear forwards censor;
animation: 0.25s linear forwards censor;
}
@-webkit-keyframes censor { from { filter:blur(3px); } to { filter:blur(0px); } }
@-moz-keyframes censor { from { filter:blur(3px); } to { filter:blur(0px); } }
@-o-keyframes censor { from { filter:blur(3px); } to { filter:blur(0px); } }
@keyframes censor { from { filter:blur(3px); } to { filter:blur(0px); } }
+1 -1
View File
@@ -24,7 +24,7 @@
<link rel="stylesheet" href="{{ url('css/nav/hoe.css?v=05') }}">
<link rel="stylesheet" href="{{ url('css/main/app.css?v=04') }}">
<link rel="stylesheet" href="{{ url('css/main/custom.css?v=37') }}">
<link rel="stylesheet" href="{{ url('css/main/custom.css?v=38') }}">
@if(Auth::check()) @if(Auth::user()->style != 0)
<link rel="stylesheet" href="{{ url('css/main/dark.css?v=02') }}">
@endif @endif
+1 -1
View File
@@ -256,7 +256,7 @@ Route::group(['middleware' => 'auth'], function () {
| ShoutBox Routes Group (when authorized)
|------------------------------------------
*/
Route::group(['prefix' => 'shoutbox', 'middleware' => 'auth'], function () {
Route::group(['prefix' => 'shoutbox', 'middleware' => ['auth','censor']], function () {
Route::get('/', 'HomeController@home')->name('shoutbox-home');
Route::get('/messages/{after?}', 'ShoutboxController@fetch')->name('shoutbox-fetch');
Route::post('/send', 'ShoutboxController@send')->name('shoutbox-send');