Thumbnail and user punishment changes.
- User manual moderation page. - Sped up thumbnail loading times majorly. This improves the shop and character editor. - Named punishment types. - Added email address search on the find user page. - Added internal note and pardon note to user punishments. - User admin page shows previous usernames if the user has any. - Username changing support.
This commit is contained in:
parent
131ff27343
commit
f94d6e3555
|
|
@ -166,12 +166,12 @@ class GridHelper
|
|||
|
||||
public static function gameArbiter()
|
||||
{
|
||||
return sprintf('http://%s:9999', self::getArbiter('Game'));
|
||||
return sprintf('http://%s:64989', self::getArbiter('Game'));
|
||||
}
|
||||
|
||||
public static function thumbnailArbiter()
|
||||
{
|
||||
return sprintf('http://%s:9999', self::getArbiter('Thumbnail'));
|
||||
return sprintf('http://%s:64989', self::getArbiter('Thumbnail'));
|
||||
}
|
||||
|
||||
public static function gameArbiterMonitor()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,14 @@
|
|||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DynamicWebConfiguration;
|
||||
use App\Models\PunishmentType;
|
||||
use App\Models\Username;
|
||||
use App\Models\User;
|
||||
use App\Models\UserIp;
|
||||
|
||||
|
|
@ -32,12 +37,126 @@ class AdminController extends Controller
|
|||
// GET admin.useradmin
|
||||
function userAdmin(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'ID' => ['required', 'int', 'exists:users,id']
|
||||
$user = User::where('id', $request->get('ID'));
|
||||
if(!$user->exists())
|
||||
abort(400);
|
||||
|
||||
return view('web.admin.useradmin')->with('user', $user->first());
|
||||
}
|
||||
|
||||
// GET admin.manualmoderateuser
|
||||
function manualModerateUser(Request $request)
|
||||
{
|
||||
$user = User::where('id', $request->get('ID'));
|
||||
if(!$user->exists())
|
||||
abort(400);
|
||||
|
||||
return view('web.admin.manualmoderateuser')->with('user', $user->first());
|
||||
}
|
||||
|
||||
// POST admin.manualmoderateusersubmit
|
||||
function manualModerateUserSubmit(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'ID' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\User', 'id')
|
||||
],
|
||||
'moderate-action' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\PunishmentType', 'id')
|
||||
],
|
||||
'internal-note' => 'required'
|
||||
], [
|
||||
'moderate-action.required' => 'Please provide an account state.',
|
||||
'internal-note.required' => 'An internal note must be provided on why this user\'s state was changed.'
|
||||
]);
|
||||
|
||||
if($validator->fails())
|
||||
return $this->manualModerateUserError($validator);
|
||||
|
||||
$user = User::where('id', $request->get('ID'))->first();
|
||||
return view('web.admin.useradmin')->with('user', $user);
|
||||
|
||||
if(Auth::user()->id == $user->id)
|
||||
{
|
||||
$validator->errors()->add('ID', 'Cannot apply account state to current user.');
|
||||
return $this->manualModerateUserError($validator);
|
||||
}
|
||||
|
||||
if(
|
||||
($user->hasRoleset('ProtectedUser') && !Auth::user()->hasRoleset('Owner'))
|
||||
|
||||
// XlXi: Prevent lower-ranks from banning higher ranks.
|
||||
|| (
|
||||
($user->hasRoleset('Owner') && !Auth::user()->hasRoleset('Owner'))
|
||||
&& ($user->hasRoleset('Administrator') && !Auth::user()->hasRoleset('Administrator'))
|
||||
)
|
||||
)
|
||||
{
|
||||
$validator->errors()->add('ID', 'User is protected. Contact an owner.');
|
||||
return $this->manualModerateUserError($validator);
|
||||
}
|
||||
|
||||
// XlXi: Moderation action type 1 is None.
|
||||
if($request->get('moderate-action') == 1 && !$user->hasActivePunishment())
|
||||
return $this->manualModerateUserSuccess(sprintf('%s already has an account state of None. No changes applied.', $user->username));
|
||||
|
||||
if($request->get('moderate-action') != 1 && $user->hasActivePunishment())
|
||||
{
|
||||
$validator->errors()->add('ID', 'User already has an active punishment.');
|
||||
return $this->manualModerateUserError($validator);
|
||||
}
|
||||
|
||||
if(Auth::user()->hasRoleset('Administrator'))
|
||||
{
|
||||
if($request->has('scrub-username'))
|
||||
{
|
||||
$newUsername = sprintf('[ Content Deleted %d ]', $user->id);
|
||||
|
||||
Username::where('user_id', $user->id)
|
||||
->update([
|
||||
'scrubbed' => true,
|
||||
'scrubbed_by' => Auth::user()->id
|
||||
]);
|
||||
|
||||
Username::create([
|
||||
'username' => $newUsername,
|
||||
'user_id' => $user->id
|
||||
]);
|
||||
|
||||
$user->username = $newUsername;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
PunishmentType::where('id', $request->get('moderate-action'))
|
||||
->first()
|
||||
->applyToUser([
|
||||
'user_id' => $user->id,
|
||||
'user_note' => $request->get('user-note') ?: '',
|
||||
'internal_note' => $request->get('internal-note') ?: '',
|
||||
'moderator_id' => Auth::user()->id
|
||||
]);
|
||||
|
||||
return $this->manualModerateUserSuccess(sprintf('Successfully applied account state to %s.', $user->username));
|
||||
}
|
||||
|
||||
function manualModerateUserError($validator)
|
||||
{
|
||||
$user = User::where('id', request()->get('ID'))->first();
|
||||
|
||||
return view('web.admin.manualmoderateuser')
|
||||
->with('user', $user)
|
||||
->withErrors($validator);
|
||||
}
|
||||
|
||||
function manualModerateUserSuccess($message)
|
||||
{
|
||||
$user = User::where('id', request()->get('ID'))->first();
|
||||
|
||||
return view('web.admin.manualmoderateuser')
|
||||
->with('user', $user)
|
||||
->with('success', $message);
|
||||
}
|
||||
|
||||
// GET admin.usersearch
|
||||
|
|
@ -46,6 +165,7 @@ class AdminController extends Controller
|
|||
$types = [
|
||||
'userid' => 'UserId',
|
||||
'username' => 'UserName',
|
||||
'emailaddress' => 'EmailAddress',
|
||||
'ipaddress' => 'IpAddress'
|
||||
];
|
||||
|
||||
|
|
@ -61,10 +181,6 @@ class AdminController extends Controller
|
|||
// POST admin.usersearchquery
|
||||
function userSearchQueryUserId(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'userid' => ['required', 'int']
|
||||
]);
|
||||
|
||||
$users = User::where('id', $request->get('userid'))
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
|
@ -74,10 +190,6 @@ class AdminController extends Controller
|
|||
|
||||
function userSearchQueryUserName(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => ['required', 'string']
|
||||
]);
|
||||
|
||||
$users = User::where('username', 'like', '%' . $request->get('username') . '%')
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
|
@ -85,11 +197,22 @@ class AdminController extends Controller
|
|||
return view('web.admin.usersearch')->with('users', $users);
|
||||
}
|
||||
|
||||
function userSearchQueryEmailAddress(Request $request)
|
||||
{
|
||||
if(!Auth::user()->hasRoleset('Owner'))
|
||||
abort(403);
|
||||
|
||||
$users = User::where('email', $request->get('emailaddress'))
|
||||
->paginate(25)
|
||||
->appends($request->all());
|
||||
|
||||
return view('web.admin.usersearch')->with('users', $users);
|
||||
}
|
||||
|
||||
function userSearchQueryIpAddress(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'ipaddress' => ['required', 'ip']
|
||||
]);
|
||||
if(!Auth::user()->hasRoleset('Owner'))
|
||||
abort(403);
|
||||
|
||||
$users = UserIp::where('ipAddress', $request->get('ipaddress'))
|
||||
->join('users', 'users.id', '=', 'user_ips.userId')
|
||||
|
|
@ -109,10 +232,6 @@ class AdminController extends Controller
|
|||
// POST admin.userlookupquery
|
||||
function userLookupQuery(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'lookup' => ['required', 'string']
|
||||
]);
|
||||
|
||||
$users = [];
|
||||
|
||||
foreach(preg_split('/\r\n|\r|\n/', $request->get('lookup')) as $username)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use App\Http\Controllers\Controller;
|
|||
use App\Models\AvatarAsset;
|
||||
use App\Models\DefaultUserAsset;
|
||||
use App\Models\UserAsset;
|
||||
use App\Models\Username;
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ class RegisteredUserController extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'username' => ['required', 'string', 'min:3', 'max:20', 'regex:/^[a-zA-Z0-9]+[ _.-]?[a-zA-Z0-9]+$/i', 'unique:users'],
|
||||
'username' => ['required', 'string', 'min:3', 'max:20', 'regex:/^[a-zA-Z0-9]+[ _.-]?[a-zA-Z0-9]+$/i', 'unique:usernames'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
], [
|
||||
|
|
@ -57,6 +58,10 @@ class RegisteredUserController extends Controller
|
|||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
Username::create([
|
||||
'username' => $user->username,
|
||||
'user_id' => $user->id
|
||||
]);
|
||||
|
||||
foreach(DefaultUserAsset::all() as $defaultAsset)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Auth;
|
|||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Models\Username;
|
||||
use App\Models\User;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
|
|
@ -65,13 +66,22 @@ class LoginRequest extends FormRequest
|
|||
$this->merge([
|
||||
$login_type => $this->input('username')
|
||||
]);
|
||||
$loginModel = ($login_type == 'username' ? 'App\\Models\\Username' : 'App\\Models\\User');
|
||||
|
||||
if(!User::where($login_type, $this->only($login_type))->exists()) {
|
||||
$previousUsername = $loginModel::where($login_type, $this->only($login_type))->first();
|
||||
if(!$previousUsername) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => $this->messages()['username.exists'],
|
||||
]);
|
||||
}
|
||||
|
||||
if($login_type == 'username')
|
||||
{
|
||||
$this->merge([
|
||||
'username' => $previousUsername->user->username
|
||||
]);
|
||||
}
|
||||
|
||||
if(!Auth::attempt($this->only($login_type, 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,21 @@ class Punishment extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'punishment_type_id',
|
||||
'active',
|
||||
'user_note',
|
||||
'internal_note',
|
||||
'user_id',
|
||||
'moderator_id',
|
||||
'expiration'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
|
|
@ -81,4 +96,9 @@ class Punishment extends Model
|
|||
->where('active', true)
|
||||
->orderByDesc('id');
|
||||
}
|
||||
|
||||
public function pardoned()
|
||||
{
|
||||
return $this->pardoner_id !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PunishmentAutofill extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class PunishmentType extends Model
|
||||
{
|
||||
|
|
@ -18,4 +20,30 @@ class PunishmentType extends Model
|
|||
'label',
|
||||
'time'
|
||||
];
|
||||
|
||||
|
||||
public function applyNoneState($punishment)
|
||||
{
|
||||
$user = User::where('id', $punishment['user_id'])->first();
|
||||
$userPunishment = $user->getPunishment();
|
||||
if(!$userPunishment) return false;
|
||||
|
||||
$userPunishment->active = false;
|
||||
$userPunishment->pardoner_id = Auth::user()->id;
|
||||
$userPunishment->pardoner_note = $punishment['internal_note'];
|
||||
$userPunishment->save();
|
||||
return $userPunishment;
|
||||
}
|
||||
|
||||
public function applyToUser($punishment)
|
||||
{
|
||||
if($this->name == 'None')
|
||||
return $this->applyNoneState($punishment);
|
||||
|
||||
return Punishment::create(array_merge($punishment, [
|
||||
'punishment_type_id' => $this->id,
|
||||
'active' => true,
|
||||
'expiration' => $this->time !== null ? Carbon::now()->addDays($this->time) : null
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ class User extends Authenticatable
|
|||
|
||||
public function punishments()
|
||||
{
|
||||
return $this->hasMany(Punishment::class, 'user_id');
|
||||
return $this->hasMany(Punishment::class, 'user_id')->orderByDesc('id');
|
||||
}
|
||||
|
||||
public function hasAsset($assetId)
|
||||
|
|
@ -166,24 +166,30 @@ class User extends Authenticatable
|
|||
|
||||
public function getImageUrl()
|
||||
{
|
||||
$renderId = $this->id;
|
||||
if(!$this->thumbnail2DHash)
|
||||
{
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $this->id, 'position' => 'full', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $renderId, 'position' => 'full', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
return route('content', $this->thumbnail2DHash);
|
||||
}
|
||||
|
||||
public function getHeadshotImageUrl()
|
||||
{
|
||||
$renderId = $this->id;
|
||||
if(!$this->thumbnailBustHash)
|
||||
{
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $this->id, 'position' => 'bust', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.user', ['id' => $renderId, 'position' => 'bust', 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return '/images/busy/user.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
return route('content', $this->thumbnailBustHash);
|
||||
}
|
||||
|
||||
public function set2DHash($hash)
|
||||
|
|
@ -250,6 +256,19 @@ class User extends Authenticatable
|
|||
return AvatarColor::newForUser($this->id);
|
||||
}
|
||||
|
||||
public function changeName($newName)
|
||||
{
|
||||
if($newName == $this->username) return false;
|
||||
|
||||
Username::create([
|
||||
'username' => $newName,
|
||||
'user_id' => $this->id
|
||||
]);
|
||||
|
||||
$this->username = $newName;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function userToJson()
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Username extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'user_id',
|
||||
'scrubbed_by'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -139,13 +139,25 @@ class Asset extends Model
|
|||
|
||||
public function getThumbnail()
|
||||
{
|
||||
$renderId = $this->id;
|
||||
if($this->moderated)
|
||||
return '/thumbs/DeletedThumbnail.png';
|
||||
|
||||
$thumbnail = Http::get(route('thumbnails.v1.asset', ['id' => $renderId, 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return ($this->assetTypeId == 9 ? '/images/busy/game.png' : '/images/busy/asset.png');
|
||||
if(!$this->approved)
|
||||
return '/thumbs/PendingThumbnail.png';
|
||||
|
||||
return $thumbnail->json('data');
|
||||
if(!$this->assetType->renderable)
|
||||
return '/thumbs/UnavailableThumbnail.png';
|
||||
|
||||
if(!$this->thumbnail2DHash)
|
||||
{
|
||||
$thumbnail = Http::get(route('thumbnails.v1.asset', ['id' => $this->id, 'type' => '2d']));
|
||||
if($thumbnail->json('status') == 'loading')
|
||||
return ($this->assetTypeId == 9 ? '/images/busy/game.png' : '/images/busy/asset.png');
|
||||
|
||||
return $thumbnail->json('data');
|
||||
}
|
||||
|
||||
return route('content', $this->thumbnail2DHash);
|
||||
}
|
||||
|
||||
public function set2DHash($hash)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class UserPunishments extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.admin.user-punishments');
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,11 @@ return new class extends Migration
|
|||
$table->unsignedTinyInteger('punishment_type_id');
|
||||
$table->boolean('active');
|
||||
$table->string('user_note');
|
||||
$table->string('internal_note');
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->unsignedBigInteger('moderator_id');
|
||||
$table->unsignedBigInteger('pardoner_id')->nullable();
|
||||
$table->string('pardoner_note')->nullable();
|
||||
$table->timestamp('expiration')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ return new class extends Migration
|
|||
Schema::create('punishment_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('name');
|
||||
$table->string('label');
|
||||
$table->integer('time')->nullable();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('usernames', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('username');
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->boolean('scrubbed')->default(false);
|
||||
$table->unsignedBigInteger('scrubbed_by')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('usernames');
|
||||
}
|
||||
};
|
||||
|
|
@ -16,12 +16,13 @@ class PunishmentTypeSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
PunishmentType::create(['label' => 'Reminder', 'time' => 0]);
|
||||
PunishmentType::create(['label' => 'Warning', 'time' => 0]);
|
||||
PunishmentType::create(['label' => 'Banned for 1 Day', 'time' => 1]);
|
||||
PunishmentType::create(['label' => 'Banned for 3 Days', 'time' => 3]);
|
||||
PunishmentType::create(['label' => 'Banned for 7 Days', 'time' => 7]);
|
||||
PunishmentType::create(['label' => 'Banned for 14 Days', 'time' => 14]);
|
||||
PunishmentType::create(['label' => 'Account Deleted']);
|
||||
PunishmentType::create(['name' => 'None', 'label' => 'Unban', 'time' => 0]);
|
||||
PunishmentType::create(['name' => 'Remind', 'label' => 'Reminder', 'time' => 0]);
|
||||
PunishmentType::create(['name' => 'Warn', 'label' => 'Warning', 'time' => 0]);
|
||||
PunishmentType::create(['name' => 'Ban 1 Day', 'label' => 'Banned for 1 Day', 'time' => 1]);
|
||||
PunishmentType::create(['name' => 'Ban 3 Days', 'label' => 'Banned for 3 Days', 'time' => 3]);
|
||||
PunishmentType::create(['name' => 'Ban 7 Days', 'label' => 'Banned for 7 Days', 'time' => 7]);
|
||||
PunishmentType::create(['name' => 'Ban 14 Days', 'label' => 'Banned for 14 Days', 'time' => 14]);
|
||||
PunishmentType::create(['name' => 'Delete', 'label' => 'Account Deleted']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright © XlXi 2023
|
||||
*/
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
const autofills = [
|
||||
{ Label: 'Account Theft', Autofill: 'This account has been closed as compromised.' },
|
||||
{ Label: 'Requested Deletion', Autofill: 'Your account has been deleted as per your request. Thank you for being a part of the VirtuBrick community.' },
|
||||
{ Label: 'Spam', Autofill: 'Do not repeatedly post or spam chat or content in VirtuBrick.' },
|
||||
{ Label: 'Swear', Autofill: 'Do not swear, use profanity or otherwise say inappropriate things in VirtuBrick.' },
|
||||
{ Label: 'Personal Info', Autofill: 'Do not ask for or give out personal, real-life, or private information on VirtuBrick.' },
|
||||
{ Label: 'Spam Alt', Autofill: 'Do not create accounts just for the purpose of breaking the rules.' },
|
||||
{ Label: 'Dating', Autofill: 'Dating, Sexting, or other inappropriate behavior is not acceptable on VirtuBrick.' },
|
||||
{ Label: 'Inappropriate Talk', Autofill: 'This content is not appropriate for VirtuBrick. Do not chat, post, or otherwise discuss inappropriate topics on VirtuBrick.' },
|
||||
{ Label: 'Link', Autofill: 'The only links you are allowed to post on VirtuBrick are virtubrick.net links, youtube.com links, twitter.com links, and twitch.tv links. No other links are allowed. Posting any other links will result in further moderation actions.' },
|
||||
{ Label: 'Harassment', Autofill: 'Do not harass other users. Do not say inappropriate or mean things about others on VirtuBrick.' },
|
||||
{ Label: 'Scam', Autofill: 'Scamming is a violation of the Terms of Service. Do not continue to scam on VirtuBrick.' },
|
||||
{ Label: 'Bad Image', Autofill: 'This image is not appropriate for VirtuBrick. Please review our rules and upload only appropriate content.' }
|
||||
];
|
||||
|
||||
class ModerationAutofills extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
autofill(autofillText) {
|
||||
document.getElementById('user-note').value = autofillText;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
autofills.map(({Label, Autofill}) =>
|
||||
<button type="button" className="btn btn-sm btn-secondary d-block mb-1" onClick={ () => this.autofill(Autofill) }>{ Label }</button>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModerationAutofills;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright © XlXi 2023
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
import ModerationAutofills from '../components/ModerationAutofills';
|
||||
|
||||
const autofillId = 'vb-mod-autofill';
|
||||
|
||||
$(document).ready(function() {
|
||||
if (document.getElementById(autofillId)) {
|
||||
render(<ModerationAutofills />, document.getElementById(autofillId));
|
||||
}
|
||||
});
|
||||
|
|
@ -1692,4 +1692,9 @@ table > tbody > tr > th {
|
|||
html.vbrick-light & {
|
||||
background-color: #0000000f;
|
||||
}
|
||||
}
|
||||
|
||||
// Select
|
||||
.vb-small-select {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<x-admin.navigation-tabs>
|
||||
<x-admin.navigation-tab-link label="Account" :route="route('admin.useradmin', ['ID' => $uid])" />
|
||||
<x-admin.navigation-tab-link label="Moderate" :route="route('admin.dashboard')" />
|
||||
<x-admin.navigation-tab-link label="Moderate" :route="route('admin.manualmoderateuser', ['ID' => $uid])" />
|
||||
<x-admin.navigation-tab-link label="Transactions" :route="route('admin.dashboard')" />
|
||||
<x-admin.navigation-tab-link label="Trades" :route="route('admin.dashboard')" />
|
||||
<x-admin.navigation-tab-link label="Send Personal Msg" :route="route('admin.dashboard')" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
@props([
|
||||
'user'
|
||||
])
|
||||
|
||||
<div class="card">
|
||||
<table class="table virtubrick-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Action</th>
|
||||
<th scope="col">Moderator</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Expiration</th>
|
||||
<th scope="col">Acknowledged</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($user->punishments as $punishment)
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<button class="btn btn-sm p-0 px-1 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#punishment-collapse-{{ $punishment->id }}" aria-expanded="false" aria-controls="punishment-collapse-{{ $punishment->id }}">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
</th>
|
||||
<th scope="col"> {{ $punishment->id }}</th>
|
||||
<th scope="col">{{ $punishment->punishment_type->label }}</th>
|
||||
<th scope="col">
|
||||
<a href="{{ route('admin.useradmin', ['ID' => $punishment->moderator->id]) }}" class="text-decoration-none">
|
||||
<x-user-circle :user="$punishment->moderator" :size=24 />
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col">{{ $punishment->reviewed() }}</th>
|
||||
<th scope="col">{{ $punishment->expirationStr() }}</th>
|
||||
<th scope="col">{{ $punishment->active ? 'No' : 'Yes' }}</th>
|
||||
</tr>
|
||||
<tr class="collapse" id="punishment-collapse-{{ $punishment->id }}">
|
||||
<td colspan="7" class="bg-secondary">
|
||||
<div class="mx-2">
|
||||
<p><b>Note to User:</b> {{ $punishment->user_note }}</p>
|
||||
<p><b>Internal Note:</b> {{ $punishment->internal_note }}</p>
|
||||
@if($punishment->pardoned())
|
||||
<p><b>Pardoned By:</b> <a class="text-decoration-none" href="{{ route('admin.useradmin', ['ID' => $punishment->pardoner->id]) }}">{{ $punishment->pardoner->username }}</a></p>
|
||||
<p><b>Pardoner Note:</b> {{ $punishment->pardoner_note }}</p>
|
||||
@endif
|
||||
@if($punishment->context->count() > 0)
|
||||
<p><b>Abuses:</b></p>
|
||||
@endif
|
||||
@foreach($punishment->context as $context)
|
||||
<div class="card bg-secondary p-2 mb-2 border-1">
|
||||
<p><b>Reason:</b> {{ $context->user_note }} (<a href="#" class="text-decoration-none">TODO: audit</a>)</p>
|
||||
@if($context->description)
|
||||
<p><b>Offensive Item:</b> {{ $context->description }}</p>
|
||||
@endif
|
||||
@if($context->content_hash)
|
||||
<img src="{{ route('content', $context->content_hash) }}" class="img-fluid" width="210" height="210"/>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Moderate User (' . $user->username . ')')
|
||||
|
||||
@section('page-specific')
|
||||
<!-- Secure Page JS -->
|
||||
<script src="{{ mix('js/adm/ManualUserModeration.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@push('content')
|
||||
<div class="container-md">
|
||||
<x-admin.navigation.user-admin :uid="$user->id" />
|
||||
<h4 class="mb-0">Moderate User</h4>
|
||||
<p class="mb-2">{{ $user->username }} <a href="{{ $user->getProfileUrl() }}" class="text-decoration-none">home page</a></p>
|
||||
@if($errors->any())
|
||||
@foreach($errors->all() as $error)
|
||||
<div class="alert alert-danger virtubrick-alert virtubrick-error-popup">{{ $error }}</div>
|
||||
@endforeach
|
||||
@endif
|
||||
@if(isset($success))
|
||||
<div class="alert alert-success virtubrick-alert virtubrick-error-popup">{{ $success }}</div>
|
||||
@endif
|
||||
<div class="card me-2 p-3">
|
||||
<form method="POST" action="{{ route('admin.manualmoderateuser', ['ID' => $user->id]) }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<label class="form-label">Account State override:</label>
|
||||
@foreach(\App\Models\PunishmentType::all() as $punishmentType)
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="moderate-action" id="moderate-action-{{ Str::slug($punishmentType->name, '-') }}" value="{{ $punishmentType->id }}">
|
||||
<label class="form-check-label" for="moderate-action-{{ Str::slug($punishmentType->name, '-') }}">{{ $punishmentType->name }}</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-label">Auto-fill fields:</label>
|
||||
<div id="vb-mod-autofill"></div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="user-note" class="form-label">Note to {{ $user->username }}:</label>
|
||||
<textarea type="text" class="form-control mb-3" name="user-note" id="user-note"></textarea>
|
||||
|
||||
<label for="internal-note" class="form-label">Internal Moderation Note:</label>
|
||||
<textarea type="text" class="form-control mb-3" name="internal-note" id="internal-note"></textarea>
|
||||
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="scrub-username" id="scrub-username">
|
||||
<label class="form-check-label" for="scrub-username"><b>Scrub Username:</b> This will set the user's name to [Content Deleted] and hide the user's name history.</label>
|
||||
</div>
|
||||
@admin
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="poison-ban" id="poison-ban">
|
||||
<label class="form-check-label" for="poison-ban"><b>Posion:</b> Disables new account creation on the user's IP address.</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="ip-ban" id="ip-ban">
|
||||
<label class="form-check-label" for="ip-ban"><b>IP Ban:</b> Restricts the user from accessing the website. If selected, you will be prompted with options for this punishment.</label>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-warning">Please contact an administrator or higher if you believe this user should be poison or IP banned.<br/>If you believe this user to be suicidal, contact an owner and options will be explored to get this user help.</p>
|
||||
@endadmin
|
||||
|
||||
<button type="submit" class="btn btn-danger w-100 mt-1">Ban User</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Past Punishments</h4>
|
||||
<x-admin.user-punishments :user="$user" />
|
||||
</div>
|
||||
@endpush
|
||||
|
|
@ -60,7 +60,17 @@
|
|||
</div>
|
||||
@endif
|
||||
<x-admin.user-admin-label label="Username">{{ $user->username }}</x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="Previous User Names"><b>TODO</b></x-admin.user-admin-label>
|
||||
@php
|
||||
$prevUsernames = \App\Models\Username::where('user_id', $user->id)
|
||||
->where('username', '!=', $user->username);
|
||||
@endphp
|
||||
@if($prevUsernames->count() > 0)
|
||||
<x-admin.user-admin-label label="Previous User Names">
|
||||
@foreach($prevUsernames->get() as &$userName)
|
||||
<p>{{ $userName->username }} ({{ $userName->created_at->isoFormat('lll') }})</p>
|
||||
@endforeach
|
||||
</x-admin.user-admin-label>
|
||||
@endif
|
||||
<x-admin.user-admin-label label="Moderation Status"><x-admin.moderation-status :user="$user" /></x-admin.user-admin-label>
|
||||
<x-admin.user-admin-label label="User Id">
|
||||
<x-admin.user-search-input id="userid" definition="User ID" :value="$user->id" :nolabel=true />
|
||||
|
|
@ -81,63 +91,7 @@
|
|||
</div>
|
||||
|
||||
<h4 class="mt-3">Punishments</h4>
|
||||
<div class="card">
|
||||
<table class="table virtubrick-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Action</th>
|
||||
<th scope="col">Moderator</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Expiration</th>
|
||||
<th scope="col">Acknowledged</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($user->punishments as $punishment)
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<button class="btn btn-sm p-0 px-1 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#punishment-collapse-{{ $punishment->id }}" aria-expanded="false" aria-controls="punishment-collapse-{{ $punishment->id }}">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
</th>
|
||||
<th scope="col"> {{ $punishment->id }}</th>
|
||||
<th scope="col">{{ $punishment->punishment_type->label }}</th>
|
||||
<th scope="col">
|
||||
<a href="{{ route('admin.useradmin', ['ID' => $punishment->moderator->id]) }}" class="text-decoration-none">
|
||||
<x-user-circle :user="$punishment->moderator" :size=24 />
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col">{{ $punishment->reviewed() }}</th>
|
||||
<th scope="col">{{ $punishment->expirationStr() }}</th>
|
||||
<th scope="col">{{ $punishment->active ? 'No' : 'Yes' }}</th>
|
||||
</tr>
|
||||
<tr class="collapse" id="punishment-collapse-{{ $punishment->id }}">
|
||||
<td colspan="7" class="bg-secondary">
|
||||
<div class="mx-2">
|
||||
<p><b>Note to User:</b> {{ $punishment->user_note }}</p>
|
||||
@if($punishment->context->count() > 0)
|
||||
<p><b>Abuses:</b></p>
|
||||
@endif
|
||||
@foreach($punishment->context as $context)
|
||||
<div class="card bg-secondary p-2 mb-2 border-1">
|
||||
<p><b>Reason:</b> {{ $context->user_note }} (<a href="#" class="text-decoration-none">TODO: audit</a>)</p>
|
||||
@if($context->description)
|
||||
<p><b>Offensive Item:</b> {{ $context->description }}</p>
|
||||
@endif
|
||||
@if($context->content_hash)
|
||||
<img src="{{ route('content', $context->content_hash) }}" class="img-fluid" width="210" height="210"/>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<x-admin.user-punishments :user="$user" />
|
||||
|
||||
<h4 class="mt-3">Seller Ban</h4>
|
||||
<div class="card me-2 p-3 pt-0 vb-admin-divider">
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<x-admin.user-search-input id="userid" definition="User ID" />
|
||||
<x-admin.user-search-input id="username" definition="Username" />
|
||||
@owner
|
||||
<x-admin.user-search-input id="emailaddress" definition="Email Address" />
|
||||
<x-admin.user-search-input id="ipaddress" definition="IP Address" />
|
||||
@endowner
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ Route::group(['as' => 'admin.', 'prefix' => 'admin'], function() {
|
|||
|
||||
Route::group(['prefix' => 'users'], function() {
|
||||
Route::get('/useradmin', 'AdminController@userAdmin')->name('useradmin');
|
||||
Route::get('/manualmoderateuser', 'AdminController@manualModerateUser')->name('manualmoderateuser');
|
||||
Route::post('/manualmoderateuser', 'AdminController@manualModerateUserSubmit')->name('manualmoderateusersubmit');
|
||||
Route::get('/find', 'AdminController@userSearch')->name('usersearch');
|
||||
Route::get('/userlookuptool', 'AdminController@userLookup')->name('userlookup');
|
||||
Route::post('/userlookuptool', 'AdminController@userLookupQuery')->name('userlookupquery');
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ mix.js('resources/js/app.js', 'public/js')
|
|||
.js('resources/js/pages/AvatarEditor.js', 'public/js')
|
||||
.js('resources/js/pages/Item.js', 'public/js')
|
||||
.js('resources/js/pages/Place.js', 'public/js')
|
||||
.js('resources/js/pages/ManualUserModeration.js', 'public/js/adm')
|
||||
.js('resources/js/pages/AppDeployer.js', 'public/js/adm')
|
||||
.js('resources/js/pages/SiteConfiguration.js', 'public/js/adm')
|
||||
.react()
|
||||
|
|
|
|||
Loading…
Reference in New Issue