'integer', 'metadata' => 'array', ]; public function ticket(): BelongsTo { return $this->belongsTo(Ticket::class, 'ticket_id', 'id'); } public function ticketUpdate(): BelongsTo { return $this->belongsTo(TicketUpdate::class, 'ticket_update_id', 'id'); } public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_id', 'id'); } public function resolvedMimeType(): string { return self::normalizeMimeType( (string) ($this->mime_type ?? ''), (string) ($this->original_file_name ?? ''), (string) ($this->file_path ?? '') ); } public function isImage(): bool { return str_starts_with($this->resolvedMimeType(), 'image/'); } public function isPdf(): bool { return $this->resolvedMimeType() === 'application/pdf'; } public static function normalizeMimeType(?string $mimeType, ?string $originalName = null, ?string $path = null): string { $mime = strtolower(trim((string) $mimeType)); if ($mime !== '' && $mime !== 'application/octet-stream') { return $mime; } $extension = strtolower((string) pathinfo((string) ($originalName ?: $path), PATHINFO_EXTENSION)); $fromExtension = match ($extension) { 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'pdf' => 'application/pdf', 'txt' => 'text/plain', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', default => '', }; if ($fromExtension !== '') { return $fromExtension; } $diskPath = trim((string) $path); if ($diskPath !== '') { try { $detected = Storage::disk('public')->mimeType($diskPath); if (is_string($detected) && trim($detected) !== '') { return strtolower(trim($detected)); } } catch (\Throwable) { } } return 'application/octet-stream'; } }