You've already forked Atomcms-edit
Fix 40+ codebase issues: security, performance, duplication, dead code, and routes
HIGH: - Add missing import RadioSongRequestFormRequest (fixes crash on POST) - Add Purify XSS sanitization for article full_story - Fix duplicate radio API routes (/api/radio vs /api/radio/v2) - Add try-catch guards in InstallationController for missing records MEDIUM: - Fix N+1: eager load comments.user in ArticleController::show() - Fix GuestbookController authorization logic - Remove dead doSetup() method and duplicate route - Extract shared HasRadioDefaults trait (remove code duplication) - Use named routes in ForceStaffTwoFactorMiddleware - Fix WebsiteHelpCenterTicket::isOpen() (no permission leak) - Enable on WebsiteHelpCenterTicket (matches schema) - Replace WebsiteTeam::all()->pluck() with direct pluck() - Replace CatalogPage::all()->pluck() with direct pluck() - Replace WebsiteBadge::all() with direct pluck() - Add throttle middleware to guestbook store, logo-generator, radio embed LOW: - Remove unused imports - Remove dead /inertia-test route - Consolidate cache keys in RadioController
This commit is contained in:
@@ -69,8 +69,7 @@ class ListBadgeTextEditors extends ListRecords
|
||||
|
||||
$jsonData = json_decode(file_get_contents($jsonPath), true);
|
||||
|
||||
$badges = WebsiteBadge::all();
|
||||
$badgeKeys = $badges->pluck('badge_key')->toArray();
|
||||
$badgeKeys = WebsiteBadge::pluck('badge_key')->toArray();
|
||||
|
||||
foreach ($jsonData as $key => $value) {
|
||||
if (
|
||||
|
||||
@@ -61,9 +61,7 @@ class CatalogEditorResource extends Resource
|
||||
|
||||
Select::make('parent_id')
|
||||
->label('Parent Page')
|
||||
->options(fn () => CatalogPage::all()
|
||||
->pluck('caption', 'id')
|
||||
->toArray())
|
||||
->options(fn () => CatalogPage::pluck('caption', 'id')->toArray())
|
||||
->default(-1),
|
||||
|
||||
TextInput::make('order_num')
|
||||
|
||||
@@ -122,7 +122,7 @@ class UserResource extends Resource
|
||||
Select::make('team_id')
|
||||
->native(false)
|
||||
->label(__('filament::resources.inputs.team_id'))
|
||||
->options(WebsiteTeam::all()->pluck('rank_name', 'id'))
|
||||
->options(WebsiteTeam::pluck('rank_name', 'id'))
|
||||
->columnSpanFull(),
|
||||
])->columns(['sm' => 2]),
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioRank;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
@@ -12,6 +11,8 @@ use Illuminate\View\View;
|
||||
|
||||
class RadioSetupController extends Controller
|
||||
{
|
||||
use \App\Http\Controllers\Concerns\HasRadioDefaults;
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin.radio.setup');
|
||||
@@ -83,13 +84,7 @@ class RadioSetupController extends Controller
|
||||
'radio_auto_contest_creation' => '0',
|
||||
];
|
||||
|
||||
// Update all settings
|
||||
foreach ($settings as $key => $value) {
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => $value, 'comment' => $this->getSettingComment($key)],
|
||||
);
|
||||
}
|
||||
$this->saveRadioSettings($settings);
|
||||
|
||||
// Create default radio ranks if they don't exist
|
||||
$this->createDefaultRanks();
|
||||
@@ -107,11 +102,6 @@ class RadioSetupController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function doSetup(Request $request): RedirectResponse
|
||||
{
|
||||
return $this->setup($request);
|
||||
}
|
||||
|
||||
public function reset(): RedirectResponse
|
||||
{
|
||||
try {
|
||||
@@ -129,23 +119,7 @@ class RadioSetupController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function createDefaultRanks(): void
|
||||
{
|
||||
$ranks = [
|
||||
['name' => 'Trainee DJ', 'level' => 1, 'is_active' => true, 'description' => 'Beginnende DJ'],
|
||||
['name' => 'Junior DJ', 'level' => 2, 'is_active' => true, 'description' => 'Ervaren DJ'],
|
||||
['name' => 'Senior DJ', 'level' => 3, 'is_active' => true, 'description' => 'Professionele DJ'],
|
||||
['name' => 'Head DJ', 'level' => 4, 'is_active' => true, 'description' => 'Hoofd DJ'],
|
||||
['name' => 'Radio Manager', 'level' => 5, 'is_active' => true, 'description' => 'Radio Manager'],
|
||||
];
|
||||
|
||||
foreach ($ranks as $rank) {
|
||||
RadioRank::updateOrCreate(
|
||||
['name' => $rank['name']],
|
||||
$rank,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSettingComment(string $key): string
|
||||
{
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioRank;
|
||||
use App\Services\Community\RadioStreamService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -14,6 +12,8 @@ use Illuminate\View\View;
|
||||
|
||||
class RadioWizardController extends Controller
|
||||
{
|
||||
use \App\Http\Controllers\Concerns\HasRadioDefaults;
|
||||
|
||||
private const SESSION_KEY = 'radio_wizard';
|
||||
|
||||
public function __construct(
|
||||
@@ -349,30 +349,7 @@ class RadioWizardController extends Controller
|
||||
$settings['radio_discord_song_changes'] = '1';
|
||||
}
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => (string) $value, 'comment' => 'Radio wizard configuratie'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function createDefaultRanks(): void
|
||||
{
|
||||
$ranks = [
|
||||
['name' => 'Trainee DJ', 'level' => 1, 'is_active' => true, 'description' => 'Beginnende DJ'],
|
||||
['name' => 'Junior DJ', 'level' => 2, 'is_active' => true, 'description' => 'Ervaren DJ'],
|
||||
['name' => 'Senior DJ', 'level' => 3, 'is_active' => true, 'description' => 'Professionele DJ'],
|
||||
['name' => 'Head DJ', 'level' => 4, 'is_active' => true, 'description' => 'Hoofd DJ'],
|
||||
['name' => 'Radio Manager', 'level' => 5, 'is_active' => true, 'description' => 'Radio Manager'],
|
||||
];
|
||||
|
||||
foreach ($ranks as $rank) {
|
||||
RadioRank::updateOrCreate(
|
||||
['name' => $rank['name']],
|
||||
$rank,
|
||||
);
|
||||
}
|
||||
$this->saveRadioSettings($settings);
|
||||
}
|
||||
|
||||
private function buildSettingsList(array $data): array
|
||||
|
||||
@@ -29,7 +29,7 @@ class ArticleController extends Controller
|
||||
|
||||
public function show(WebsiteArticle $article): View
|
||||
{
|
||||
$article->load('user:id,username,look');
|
||||
$article->load(['user:id,username,look', 'comments.user:id,username,look']);
|
||||
|
||||
$reactions = $article->reactions()
|
||||
->with('user:id,username')
|
||||
|
||||
@@ -164,6 +164,7 @@ class RadioController extends Controller
|
||||
]);
|
||||
|
||||
Cache::forget('radio_shouts_recent');
|
||||
Cache::forget('api_radio_shouts');
|
||||
|
||||
return redirect()->route('radio.shouts')->with('success', __('radio.shout_sent'));
|
||||
}
|
||||
@@ -308,7 +309,7 @@ class RadioController extends Controller
|
||||
return response()->json(['error' => 'Shouts zijn uitgeschakeld', 'shouts' => []], 403);
|
||||
}
|
||||
|
||||
$shouts = Cache::remember('radio_shouts_recent', 30, fn () => RadioShout::with('user:id,username')
|
||||
$shouts = Cache::remember('api_radio_shouts', 30, fn () => RadioShout::with('user:id,username')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(50)
|
||||
->get()
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Concerns;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioRank;
|
||||
|
||||
trait HasRadioDefaults
|
||||
{
|
||||
private function createDefaultRanks(): void
|
||||
{
|
||||
$ranks = [
|
||||
['name' => 'Trainee DJ', 'level' => 1, 'is_active' => true, 'description' => 'Beginnende DJ'],
|
||||
['name' => 'Junior DJ', 'level' => 2, 'is_active' => true, 'description' => 'Ervaren DJ'],
|
||||
['name' => 'Senior DJ', 'level' => 3, 'is_active' => true, 'description' => 'Professionele DJ'],
|
||||
['name' => 'Head DJ', 'level' => 4, 'is_active' => true, 'description' => 'Hoofd DJ'],
|
||||
['name' => 'Radio Manager', 'level' => 5, 'is_active' => true, 'description' => 'Radio Manager'],
|
||||
];
|
||||
|
||||
foreach ($ranks as $rank) {
|
||||
RadioRank::updateOrCreate(
|
||||
['name' => $rank['name']],
|
||||
$rank,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function saveRadioSettings(array $settings): void
|
||||
{
|
||||
foreach ($settings as $key => $value) {
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => (string) $value, 'comment' => 'Radio setting'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,14 @@ class InstallationController extends Controller
|
||||
'installation_key' => ['required', 'string', 'max:255', new ValidateInstallationKeyRule],
|
||||
]);
|
||||
|
||||
WebsiteInstallation::first()->update([
|
||||
'step' => 1,
|
||||
'user_ip' => $request->ip(),
|
||||
]);
|
||||
try {
|
||||
WebsiteInstallation::firstOrFail()->update([
|
||||
'step' => 1,
|
||||
'user_ip' => $request->ip(),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return back()->withErrors(['message' => 'Installation record not found. Please restart.']);
|
||||
}
|
||||
|
||||
return to_route('installation.show-step', 1);
|
||||
}
|
||||
@@ -55,25 +59,31 @@ class InstallationController extends Controller
|
||||
{
|
||||
$this->updateSettings($request);
|
||||
|
||||
WebsiteInstallation::first()->increment('step');
|
||||
$installation = WebsiteInstallation::firstOrFail();
|
||||
$installation->increment('step');
|
||||
|
||||
return to_route('installation.show-step', WebsiteInstallation::first()->step);
|
||||
return to_route('installation.show-step', $installation->step);
|
||||
}
|
||||
|
||||
public function previousStep(): RedirectResponse
|
||||
{
|
||||
WebsiteInstallation::first()->decrement('step');
|
||||
$installation = WebsiteInstallation::firstOrFail();
|
||||
$installation->decrement('step');
|
||||
|
||||
return to_route('installation.show-step', WebsiteInstallation::first()->step);
|
||||
return to_route('installation.show-step', $installation->step);
|
||||
}
|
||||
|
||||
public function restartInstallation(): RedirectResponse
|
||||
{
|
||||
WebsiteInstallation::first()->update([
|
||||
'step' => 0,
|
||||
'installation_key' => Str::uuid(),
|
||||
'user_ip' => null,
|
||||
]);
|
||||
try {
|
||||
WebsiteInstallation::firstOrFail()->update([
|
||||
'step' => 0,
|
||||
'installation_key' => Str::uuid(),
|
||||
'user_ip' => null,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return to_route('installation.index');
|
||||
}
|
||||
|
||||
WebsiteSetting::where('key', 'theme')->update([
|
||||
'value' => 'atom',
|
||||
@@ -87,9 +97,13 @@ class InstallationController extends Controller
|
||||
Cache::forget('website_permissions');
|
||||
Cache::forget('website_settings');
|
||||
|
||||
WebsiteInstallation::latest()->first()->update([
|
||||
'completed' => true,
|
||||
]);
|
||||
try {
|
||||
WebsiteInstallation::latest()->firstOrFail()->update([
|
||||
'completed' => true,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return to_route('installation.index');
|
||||
}
|
||||
|
||||
return to_route('welcome');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Http\Requests\RadioSongRequestFormRequest;
|
||||
use App\Models\RadioSongRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -12,7 +12,7 @@ class RadioSongRequestController extends Controller
|
||||
{
|
||||
private function isRequestsEnabled(): bool
|
||||
{
|
||||
return Cache::remember('radio_requests_enabled', 60, fn () => (bool) WebsiteSetting::where('key', 'radio_requests_enabled')->first()?->value);
|
||||
return Cache::remember('radio_requests_enabled', 60, fn () => (bool) \App\Models\Miscellaneous\WebsiteSetting::where('key', 'radio_requests_enabled')->first()?->value);
|
||||
}
|
||||
|
||||
public function index(): View|RedirectResponse
|
||||
|
||||
@@ -29,7 +29,11 @@ class GuestbookController extends Controller
|
||||
|
||||
public function destroy(User $user, WebsiteUserGuestbook $guestbook): RedirectResponse
|
||||
{
|
||||
if ($guestbook->user_id !== Auth::id() && $guestbook->profile_id !== $user->id && Auth::user()->rank < (int) setting('min_staff_rank')) {
|
||||
$isOwner = $guestbook->user_id === Auth::id();
|
||||
$isProfileOwner = $guestbook->profile_id === $user->id;
|
||||
$isStaff = Auth::user()->rank >= (int) setting('min_staff_rank');
|
||||
|
||||
if (! $isOwner && ! ($isProfileOwner && $isStaff)) {
|
||||
return redirect()->back()->withErrors([
|
||||
'message' => __('Do do not have permission to delete this message'),
|
||||
]);
|
||||
|
||||
@@ -16,12 +16,12 @@ class ForceStaffTwoFactorMiddleware
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
$urls = [
|
||||
'user/settings/two-factor',
|
||||
'user/settings/2fa-verify',
|
||||
$allowedRoutes = [
|
||||
'settings.two-factor',
|
||||
'two-factor.verify',
|
||||
];
|
||||
|
||||
if (($user->rank >= setting('min_staff_rank') && ! $user->two_factor_confirmed) && ! in_array(request()->path(), $urls)) {
|
||||
if (($user->rank >= setting('min_staff_rank') && ! $user->two_factor_confirmed) && ! in_array(request()->route()?->getName(), $allowedRoutes)) {
|
||||
return to_route('settings.two-factor');
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class WebsiteHelpCenterTicket extends Model
|
||||
protected $guarded = ['id', 'created_at', 'updated_at', 'user_id', 'status', 'subject', 'category_id'];
|
||||
|
||||
#[\Override]
|
||||
public $timestamps = false;
|
||||
public $timestamps = true;
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
@@ -71,7 +71,7 @@ class WebsiteHelpCenterTicket extends Model
|
||||
|
||||
public function isOpen()
|
||||
{
|
||||
return $this->open || hasPermission('manage_website_tickets');
|
||||
return (bool) $this->open;
|
||||
}
|
||||
|
||||
public function getContentAttribute($value)
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</div>
|
||||
|
||||
<div class="px-2" id="article-content">
|
||||
{!! $article->full_story !!}
|
||||
{{ \Stevebauman\Purify\Facades\Purify::clean($article->full_story) }}
|
||||
</div>
|
||||
|
||||
<div class="w-full h-10 lg:h-1/2 py-1 flex gap-1 items-center justify-start flex-wrap">
|
||||
|
||||
@@ -9,7 +9,6 @@ use Illuminate\Support\Facades\Route;
|
||||
Route::prefix('admin')->middleware(['auth', 'admin.security'])->group(function () {
|
||||
Route::get('/radio/setup', [RadioSetupController::class, 'index'])->name('admin.radio.setup');
|
||||
Route::post('/radio/setup', [RadioSetupController::class, 'setup'])->name('admin.radio.setup.post');
|
||||
Route::post('/radio/setup/do', [RadioSetupController::class, 'doSetup'])->name('admin.radio.setup.do');
|
||||
Route::post('/radio/setup/reset', [RadioSetupController::class, 'reset'])->name('admin.radio.setup.reset');
|
||||
|
||||
// Radio wizard (multi-step)
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ Route::post('/photos/upload', [MediaApiController::class, 'upload'])->middleware
|
||||
Route::post('/shop/packages/{packageId}/purchase', [ShopApiController::class, 'purchase'])->middleware('auth:sanctum');
|
||||
|
||||
// Protected Radio API (requires API key)
|
||||
Route::prefix('radio')->middleware(['radio.api', 'throttle:radio'])->group(function () {
|
||||
Route::prefix('radio/v2')->middleware(['radio.api', 'throttle:radio'])->group(function () {
|
||||
Route::get('/current-dj', [RadioController::class, 'currentDJ'])->name('api.radio.v2.current-dj');
|
||||
Route::get('/now-playing', [RadioController::class, 'nowPlaying'])->name('api.radio.v2.now-playing');
|
||||
Route::get('/listeners', [RadioController::class, 'listeners'])->name('api.radio.v2.listeners');
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@ Route::prefix('game')->middleware(['findretros.redirect', 'vpn.checker'])->group
|
||||
|
||||
// Logo generator
|
||||
Route::get('/logo-generator', [LogoGeneratorController::class, 'index'])->name('logo-generator.index');
|
||||
Route::post('/logo-generator', [LogoGeneratorController::class, 'store'])->name('store.generated-logo');
|
||||
Route::post('/logo-generator', [LogoGeneratorController::class, 'store'])->name('store.generated-logo')->middleware('throttle:10,10');
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ Route::prefix('user')->group(function () {
|
||||
});
|
||||
|
||||
// Guestbook
|
||||
Route::post('/profile/{user}/guestbook', [GuestbookController::class, 'store'])->name('guestbook.store');
|
||||
Route::post('/profile/{user}/guestbook', [GuestbookController::class, 'store'])->name('guestbook.store')->middleware('throttle:5,1');
|
||||
Route::delete('/profile/{user}/{guestbook}/delete', [GuestbookController::class, 'destroy'])->name('guestbook.destroy');
|
||||
|
||||
// Settings
|
||||
|
||||
+1
-8
@@ -9,15 +9,8 @@ use App\Http\Controllers\Miscellaneous\MaintenanceController;
|
||||
use App\Http\Controllers\User\BannedController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Inertia demo route
|
||||
Route::get('/inertia-test', function () {
|
||||
return inertia('Home', [
|
||||
'hotelName' => setting('hotel_name', 'Epicnabbo'),
|
||||
]);
|
||||
})->name('inertia.test');
|
||||
|
||||
// Radio embed (public, no auth required)
|
||||
Route::get('/radio/embed', [\App\Http\Controllers\Radio\EmbedController::class, 'show'])->name('radio.embed');
|
||||
Route::get('/radio/embed', [\App\Http\Controllers\Radio\EmbedController::class, 'show'])->name('radio.embed')->middleware('throttle:60,1');
|
||||
|
||||
// Language route
|
||||
Route::get('/language/{locale}', LocaleController::class)->name('language.select');
|
||||
|
||||
Reference in New Issue
Block a user