Refactored ban system
Implemented a Reddit-like account suspension system (similar to subreddit bans). This makes it easier to ban users from the app, and the code has also been cleaned up. The interface was also revamped.
This commit is contained in:
parent
6cda1fe183
commit
cbcc1f025a
|
@ -30,13 +30,13 @@ class Ban extends Model
|
||||||
'userID',
|
'userID',
|
||||||
'reason',
|
'reason',
|
||||||
'bannedUntil',
|
'bannedUntil',
|
||||||
'userAgent',
|
'isPermanent',
|
||||||
'authorUserID',
|
'authorUserID',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public $dates = [
|
public $dates = [
|
||||||
'bannedUntil',
|
'suspendedUntil',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CleanBans;
|
use App\Jobs\ProcessDueSuspensions;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel
|
||||||
->daily();
|
->daily();
|
||||||
// Production value: Every day
|
// Production value: Every day
|
||||||
|
|
||||||
$schedule->job(new CleanBans)
|
$schedule->job(new ProcessDueSuspensions)
|
||||||
->daily();
|
->daily();
|
||||||
// Production value: Every day
|
// Production value: Every day
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,48 +34,26 @@ class BanController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('create', [Ban::class, $user]);
|
$this->authorize('create', [Ban::class, $user]);
|
||||||
|
|
||||||
// FIXME: Needs refactoring to a simpler format, e.g. parse the user's given date directly.
|
|
||||||
|
|
||||||
if (is_null($user->bans)) {
|
if (is_null($user->bans)) {
|
||||||
|
|
||||||
|
$duration = $request->duration;
|
||||||
$reason = $request->reason;
|
$reason = $request->reason;
|
||||||
$duration = strtolower($request->durationOperator);
|
$type = $request->suspensionType; // ON: Temporary | OFF: Permanent
|
||||||
$durationOperand = $request->durationOperand;
|
|
||||||
|
|
||||||
$expiryDate = now();
|
if ($type == "on") {
|
||||||
|
$expiryDate = now()->addDays($duration);
|
||||||
if (! empty($duration)) {
|
|
||||||
switch ($duration) {
|
|
||||||
case 'days':
|
|
||||||
$expiryDate->addDays($durationOperand);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'weeks':
|
|
||||||
$expiryDate->addWeeks($durationOperand);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'months':
|
|
||||||
$expiryDate->addMonths($durationOperand);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'years':
|
|
||||||
$expiryDate->addYears($durationOperand);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Essentially permanent
|
|
||||||
$expiryDate->addYears(40);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$ban = Ban::create([
|
$ban = Ban::create([
|
||||||
'userID' => $user->id,
|
'userID' => $user->id,
|
||||||
'reason' => $reason,
|
'reason' => $reason,
|
||||||
'bannedUntil' => $expiryDate->format('Y-m-d H:i:s'),
|
'bannedUntil' => ($type == "on") ? $expiryDate->format('Y-m-d H:i:s') : null,
|
||||||
'userAgent' => 'Unknown',
|
|
||||||
'authorUserID' => Auth::user()->id,
|
'authorUserID' => Auth::user()->id,
|
||||||
|
'isPermanent' => ($type == "off") ? true : false
|
||||||
]);
|
]);
|
||||||
|
|
||||||
event(new UserBannedEvent($user, $ban));
|
$request->session()->flash('success', __('Account suspended.'));
|
||||||
$request->session()->flash('success', __('Account suspended. Suspension ID #:susId', ['susId', $ban->id]));
|
|
||||||
} else {
|
} else {
|
||||||
$request->session()->flash('error', __('Account already suspended!'));
|
$request->session()->flash('error', __('Account already suspended!'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,17 @@ class ProfileController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$suspensionInfo = null;
|
||||||
|
if ($user->isBanned())
|
||||||
|
{
|
||||||
|
$suspensionInfo = [
|
||||||
|
|
||||||
|
'isPermanent' => $user->bans->isPermanent,
|
||||||
|
'reason' => $user->bans->reason,
|
||||||
|
'bannedUntil' => $user->bans->bannedUntil
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
if (Auth::user()->is($user) || Auth::user()->can('profiles.view.others')) {
|
if (Auth::user()->is($user) || Auth::user()->can('profiles.view.others')) {
|
||||||
return view('dashboard.user.profile.displayprofile')
|
return view('dashboard.user.profile.displayprofile')
|
||||||
->with([
|
->with([
|
||||||
|
@ -82,6 +93,7 @@ class ProfileController extends Controller
|
||||||
'since' => $createdDate->englishMonth.' '.$createdDate->year,
|
'since' => $createdDate->englishMonth.' '.$createdDate->year,
|
||||||
'ipInfo' => IP::lookup($user->originalIP),
|
'ipInfo' => IP::lookup($user->originalIP),
|
||||||
'roles' => $roleList,
|
'roles' => $roleList,
|
||||||
|
'suspensionInfo' => $suspensionInfo
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
abort(403, __('You cannot view someone else\'s profile.'));
|
abort(403, __('You cannot view someone else\'s profile.'));
|
||||||
|
|
|
@ -45,8 +45,15 @@ class BanUserRequest extends FormRequest
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'reason' => 'required|string',
|
'reason' => 'required|string',
|
||||||
'durationOperand' => 'nullable|string',
|
'suspensionType' => 'required|string',
|
||||||
'durationOperator' => 'nullable|string',
|
'duration' => 'required_if:suspensionType,on|nullable|integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'duration.required_if' => __('You must provide a duration if the suspension is temporary.')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class CleanBans implements ShouldQueue
|
class ProcessDueSuspensions implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
@ -52,15 +52,15 @@ class CleanBans implements ShouldQueue
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
Log::debug('Running automatic ban cleaner...');
|
Log::debug('Running automatic suspension cleaner...');
|
||||||
$bans = Ban::all();
|
$bans = Ban::all();
|
||||||
|
|
||||||
if (! is_null($bans)) {
|
if (! is_null($bans)) {
|
||||||
foreach ($this->bans as $ban) {
|
foreach ($this->bans as $ban) {
|
||||||
$bannedUntil = Carbon::parse($ban->bannedUntil);
|
$bannedUntil = Carbon::parse($ban->bannedUntil);
|
||||||
|
|
||||||
if ($bannedUntil->equalTo(now())) {
|
if ($bannedUntil->isToday()) {
|
||||||
Log::debug('Deleted ban '.$ban->id.' belonging to '.$ban->user->name);
|
Log::debug('Lifted expired suspension ID '.$ban->id.' for '.$ban->user->name);
|
||||||
$ban->delete();
|
$ban->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -611,5 +611,21 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => 'BootstrapSwitch',
|
||||||
|
'active' => true,
|
||||||
|
'files' => [
|
||||||
|
[
|
||||||
|
'type' => 'js',
|
||||||
|
'asset' => false,
|
||||||
|
'location' => 'https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/js/bootstrap4-toggle.min.js'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'css',
|
||||||
|
'asset' => false,
|
||||||
|
'location' => 'https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/css/bootstrap4-toggle.min.css'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class ChangeBansTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('bans', function (Blueprint $table) {
|
||||||
|
|
||||||
|
$table->dropColumn('userAgent');
|
||||||
|
$table->boolean('isPermanent')->default(false);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('bans', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('isPermanent');
|
||||||
|
$table->string('userAgent')->after('bannedUntil');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -411,14 +411,13 @@ return [
|
||||||
'title' => ':name\'s profile',
|
'title' => ':name\'s profile',
|
||||||
'profile' => 'Profile',
|
'profile' => 'Profile',
|
||||||
'users' => 'Users',
|
'users' => 'Users',
|
||||||
'account_banned' => 'Account banned',
|
'account_banned' => 'Account suspended',
|
||||||
'account_banned_exp' => 'This user has been banned by the moderators.',
|
'account_banned_exp' => 'This user has been suspended by the admins.',
|
||||||
'ban_confirm' => 'Please confirm that you want to ban this user account. You\'ll need to add a reason and expiration date to confirm this. Bans don\'t transfer to connected Minecraft networks (yet).',
|
'ban_confirm' => 'Please confirm that you want to suspend this account. You\'ll need to add a reason and expiration date to confirm this.',
|
||||||
'leave_empty' => 'Leave empty for a permanent ban',
|
|
||||||
'duration' => 'Duration',
|
'duration' => 'Duration',
|
||||||
'p_duration' => 'Punishment duration',
|
'p_duration' => 'Suspension duration',
|
||||||
'p_duration_exp' => 'e.g. Spamming',
|
'p_duration_exp' => 'e.g. Spamming',
|
||||||
'ban' => 'Ban',
|
'ban' => 'Suspend',
|
||||||
|
|
||||||
'terminate_notice' => 'You are about to terminate a staff member',
|
'terminate_notice' => 'You are about to terminate a staff member',
|
||||||
'terminate_notice_warning' => 'Terminating a staff member will remove their privileges on the team management site and Network.
|
'terminate_notice_warning' => 'Terminating a staff member will remove their privileges on the team management site and Network.
|
||||||
|
|
|
@ -18,16 +18,16 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
@if ($profile->user->isBanned())
|
@if (is_array($suspensionInfo))
|
||||||
|
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
|
|
||||||
<span><i class="fa fa-ban"></i> <b>{{__('messages.profile.account_banned')}}</b></span>
|
<span><i class="fa fa-ban"></i> <b>{{__('messages.profile.account_banned')}} {{ ($suspensionInfo['isPermanent']) ? __('permanently.') : __('until :date.', ['date' => $suspensionInfo['bannedUntil']]) }}</b></span>
|
||||||
|
|
||||||
<p>{{__('messages.profile.account_banned_exp')}}</p>
|
<p>{{__('messages.profile.account_banned_exp')}}</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<i class="fas fa-chevron-right"></i> <b>{{$profile->user->bans->reason}}</>
|
<i class="fas fa-chevron-right"></i> <b>{{$suspensionInfo['reason']}}</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,32 +43,33 @@
|
||||||
<form id="banAccountForm" name="banAccount" method="POST" action="{{route('banUser', ['user' => $profile->user->id])}}">
|
<form id="banAccountForm" name="banAccount" method="POST" action="{{route('banUser', ['user' => $profile->user->id])}}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<label for="reason">{{__('messages.reusable.reason')}}</label>
|
<div class="row">
|
||||||
<input type="text" name="reason" id="reason" class="form-control" placeholder="{{__('messages.profile.p_duration_exp')}}">
|
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="col">
|
||||||
<input type="text" class="form-control" name="durationOperator" aria-label="{{__('messages.profile.p_duration')}}">
|
<label for="reason">{{__('Public note')}}</label>
|
||||||
<div class="input-group-append">
|
<input type="text" name="reason" id="reason" class="form-control" placeholder="{{__('messages.profile.p_duration_exp')}}">
|
||||||
<button id="durationDropdown" class="btn btn-outline-secondary dropdown-toggle duration-btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{__('messages.profile.duration')}}</button>
|
</div>
|
||||||
<div class="dropdown-menu">
|
|
||||||
<a class="dropdown-item" href="#">Days</a>
|
<div class="col">
|
||||||
<a class="dropdown-item" href="#">Weeks</a>
|
<label for="duration">{{ __('Duration') }}</label>
|
||||||
<a class="dropdown-item" href="#">Months</a>
|
<input type="text" name="duration" id="duration" class="form-control" placeholder="{{ __('in days') }}">
|
||||||
<div role="separator" class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#">Years</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<p class="text-muted text-sm">{{__('messages.profile.leave_empty')}}</p>
|
|
||||||
|
|
||||||
<input id="operator" type="hidden" value="" name="durationOperand" class="duration-operator-fld">
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="hidden" name="suspensionType" value="off">
|
||||||
|
|
||||||
|
<label for="suspensionType">Suspension type</label><br>
|
||||||
|
<input type="checkbox" id="suspensionType" name="suspensionType" checked data-toggle="toggle" data-on="Temporary" data-off="Permanent" data-onstyle="success" data-offstyle="danger" data-width="130" data-height="40">
|
||||||
|
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> {{ __('Temporary suspensions will be automatically lifted. The suspension note is visible to all users. Suspended users will not be able to login or register.') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<x-slot name="modalFooter">
|
<x-slot name="modalFooter">
|
||||||
|
<button id="banAccountButton" type="button" class="btn btn-danger"><i class="fa fa-gavel"></i> {{__('Confirm')}}</button>
|
||||||
<button id="banAccountButton" type="button" class="btn btn-danger"><i class="fa fa-ban"></i> {{__('messages.profile.ban')}}</button>
|
|
||||||
|
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
</x-modal>
|
</x-modal>
|
||||||
|
@ -334,13 +335,13 @@
|
||||||
<div class="management-btn text-center">
|
<div class="management-btn text-center">
|
||||||
|
|
||||||
@if (!$profile->user->isBanned())
|
@if (!$profile->user->isBanned())
|
||||||
<button class="btn btn-danger mb-2" id="banAccountTrigger"><i class="fa fa-ban"></i> {{__('messages.profile.ban_acc')}}</button><br>
|
<button class="btn btn-danger mb-2" id="banAccountTrigger"><i class="fa fa-ban"></i> {{__('Suspend')}}</button><br>
|
||||||
@else
|
@else
|
||||||
<form method="post" action="{{route('unbanUser', ['user' => $profile->user->id])}}">
|
<form method="post" action="{{route('unbanUser', ['user' => $profile->user->id])}}">
|
||||||
|
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
@csrf
|
@csrf
|
||||||
<button type="submit" class="btn btn-warning mb-2"><i class="fa fa-check"></i> {{__('messages.profile.unban_acc')}}</button>
|
<button type="submit" class="btn btn-warning mb-2"><i class="fa fa-check"></i> {{__('Lift Suspension')}}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|
Loading…
Reference in New Issue