You've already forked Atomcms-edit
f29ba72591
Security:
- Replace unescaped {!! !!} with Purify::clean() in 15+ Blade templates (XSS)
- Add rate limiting to register (3/hr), upload (10/min), SSE (6/min)
- Add max:5000 validation on article comments
- Remove duplicate exception handler callback
Hardcoded paths:
- Replace ~44 /var/www/ hardcoded paths with env() configs
- CatalogService (13), AutoDetectService (18), Commandocentrum (11), AppServiceProvider (2)
Performance:
- Add 10 missing database indexes (radio_song_requests, help_center_tickets, etc.)
- Replace Cache::flush() with targeted Cache::forget() in RadioSettings
- Cache getCachedCategories() in TicketController (N+1 fix)
- Remove redundant top-3 leaderboard query
Bug fixes:
- Fix undefined $enabled variable → $isOnline in radio index view
- Add getAvatarAttribute() accessor for non-existent avatar column
- Fix User::guilds() from wrong HasMany to HasManyThrough
Code quality:
- Replace file_get_contents with Http::timeout(10) in TraxService
- Remove commented Echo/Pusher boilerplate in bootstrap.js
- Remove TODO/FIXME comments from logo-generator templates
- Replace hardcoded Turnstile CDN URL with config()
- Restore QUEUE_CONNECTION=redis in .env.example files
166 lines
4.0 KiB
PHP
Executable File
166 lines
4.0 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Exceptions;
|
|
|
|
use App\Services\AlertService;
|
|
use Illuminate\Auth\Access\AuthorizationException;
|
|
use Illuminate\Auth\AuthenticationException;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Throwable;
|
|
|
|
class Handler extends ExceptionHandler
|
|
{
|
|
#[\Override]
|
|
protected $levels = [
|
|
//
|
|
];
|
|
|
|
#[\Override]
|
|
protected $dontReport = [
|
|
//
|
|
];
|
|
|
|
#[\Override]
|
|
protected $dontFlash = [
|
|
'current_password',
|
|
'password',
|
|
'password_confirmation',
|
|
];
|
|
|
|
private const string CACHE_KEY_ERROR_COUNT = 'error_count_';
|
|
|
|
private const int ERROR_COUNT_DURATION = 300;
|
|
|
|
#[\Override]
|
|
public function register(): void
|
|
{
|
|
$this->reportable(function (Throwable $e) {
|
|
$this->handleExceptionAlert($e);
|
|
});
|
|
}
|
|
|
|
private function handleExceptionAlert(Throwable $e): void
|
|
{
|
|
if (! $this->shouldAlertException($e)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$alertService = app(AlertService::class);
|
|
|
|
$errorMessage = $e->getMessage() ?: $e::class;
|
|
|
|
$alertService->sendCriticalError($errorMessage, $e);
|
|
|
|
$this->trackErrorRate();
|
|
|
|
Log::channel('emergency')->error('Critical exception reported via alert', [
|
|
'exception' => $e::class,
|
|
'message' => $e->getMessage(),
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
]);
|
|
} catch (\Exception $alertException) {
|
|
Log::error('Failed to send exception alert: ' . $alertException->getMessage());
|
|
}
|
|
}
|
|
|
|
private function shouldAlertException(Throwable $e): bool
|
|
{
|
|
if (! app()->isBooted()) {
|
|
return false;
|
|
}
|
|
|
|
if (! (bool) setting('alert_errors_enabled', true)) {
|
|
return false;
|
|
}
|
|
|
|
$criticalExceptions = [
|
|
QueryException::class,
|
|
RconConnectionException::class,
|
|
];
|
|
|
|
foreach ($criticalExceptions as $criticalException) {
|
|
if ($e instanceof $criticalException) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ($this->isHighErrorRate()) {
|
|
return true;
|
|
}
|
|
|
|
$minSeverity = setting('alert_min_severity', 'error');
|
|
|
|
return $this->isSeverityHighEnough($e, $minSeverity);
|
|
}
|
|
|
|
private function trackErrorRate(): void
|
|
{
|
|
if (! app()->isBooted()) {
|
|
return;
|
|
}
|
|
|
|
$key = self::CACHE_KEY_ERROR_COUNT . now()->format('YmdHi');
|
|
|
|
$count = Cache::get($key, 0);
|
|
Cache::put($key, $count + 1, self::ERROR_COUNT_DURATION);
|
|
}
|
|
|
|
private function isHighErrorRate(): bool
|
|
{
|
|
if (! app()->isBooted()) {
|
|
return false;
|
|
}
|
|
|
|
$threshold = (int) setting('alert_error_threshold', 10);
|
|
$key = self::CACHE_KEY_ERROR_COUNT . now()->format('YmdHi');
|
|
|
|
$count = Cache::get($key, 0);
|
|
|
|
return $count >= $threshold;
|
|
}
|
|
|
|
private function isSeverityHighEnough(Throwable $e, string $minSeverity): bool
|
|
{
|
|
$severityLevel = [
|
|
'info' => 0,
|
|
'warning' => 1,
|
|
'error' => 2,
|
|
'critical' => 3,
|
|
];
|
|
|
|
$exceptionSeverity = $this->determineExceptionSeverity($e);
|
|
$minLevel = $severityLevel[$minSeverity] ?? 2;
|
|
|
|
return $exceptionSeverity >= $minLevel;
|
|
}
|
|
|
|
private function determineExceptionSeverity(Throwable $e): int
|
|
{
|
|
if ($e instanceof \Error) {
|
|
return 3;
|
|
}
|
|
|
|
if ($e instanceof ValidationException) {
|
|
return 0;
|
|
}
|
|
|
|
if ($e instanceof AuthenticationException) {
|
|
return 1;
|
|
}
|
|
|
|
if ($e instanceof AuthorizationException) {
|
|
return 1;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
}
|