*/ public function template(): BelongsTo { // Keep resolving the template even after it has been soft-deleted, so past // generations stay downloadable and keep their labels. return $this->belongsTo(Template::class)->withTrashed(); } /** * @return BelongsTo */ public function requester(): BelongsTo { return $this->belongsTo(User::class, 'requested_by'); } /** * A human-friendly name for this generation, used in the admin panel and as the * downloaded file name. Falls back to the template title plus a short id. */ public function displayName(): string { $base = $this->title ?: $this->template?->title ?: 'presentation'; return trim($base).' - '.$this->uuid; } /** * Sanitized file name (without extension) for downloads. Keeps Persian characters * but drops anything a filesystem or HTTP header would choke on. */ public function downloadFilename(): string { $base = $this->title ?: $this->template?->title ?: $this->template?->slug ?: 'presentation'; $clean = preg_replace('/[\/\\\\?%*:|"<>\r\n]+/u', ' ', (string) $base) ?? ''; $clean = trim(preg_replace('/\s+/u', ' ', $clean) ?? ''); return ($clean !== '' ? $clean : 'presentation').' - '.substr($this->uuid, 0, 8); } /** * @return array */ protected function casts(): array { return [ 'source' => TemplateGenerationSource::class, 'status' => TemplateGenerationStatus::class, 'values' => 'array', 'slide_count' => 'integer', ]; } }