whereNotNull('logo') ->get(['id', 'name', 'logo']); if ($banks->isEmpty()) { $this->info('No bank logos found to validate.'); return self::SUCCESS; } $updatedBanks = []; foreach ($banks as $bank) { $logoUrl = (string) $bank->logo; if ($this->hasWorkingImage($logoUrl)) { continue; } $bank->update(['logo' => null]); $updatedBanks[] = [ 'id' => $bank->id, 'name' => $bank->name, 'previous_logo' => $logoUrl, ]; $this->warn("Cleared broken logo for {$bank->name}."); } if ($updatedBanks === []) { $this->info('All bank logos are valid.'); return self::SUCCESS; } $updatedCount = count($updatedBanks); $this->info("Cleared broken logos for {$updatedCount} bank(s)."); $adminEmail = (string) config('mail.admin_email'); if ($adminEmail === '') { $this->warn('ADMIN_EMAIL is not configured. Skipping report email.'); return self::SUCCESS; } Mail::to($adminEmail)->send(new BrokenBankLogosReportEmail($updatedBanks)); $this->info("Sent broken logo report to {$adminEmail}."); return self::SUCCESS; } private function hasWorkingImage(string $logoUrl): bool { if (! filter_var($logoUrl, FILTER_VALIDATE_URL)) { return false; } try { $headResponse = Http::timeout(10)->head($logoUrl); if ($headResponse->successful() && $this->isImageResponse($headResponse)) { return true; } if ($headResponse->failed() && $headResponse->status() !== 405) { return false; } $getResponse = Http::timeout(10)->get($logoUrl); return $getResponse->successful() && $this->isImageResponse($getResponse); } catch (ConnectionException) { return false; } } private function isImageResponse(Response $response): bool { $contentType = strtolower((string) $response->header('Content-Type')); if ($contentType === '') { return false; } return str_starts_with($contentType, 'image/') || str_contains($contentType, 'application/octet-stream'); } }