'integer', 'metadata' => 'array', 'builder_data' => 'array', ]; protected static function booted(): void { static::saved(fn (self $template) => $template->forgetCache()); static::deleted(fn (self $template) => $template->forgetCache()); } public function forgetCache(): void { if ($this->scenario) { Cache::forget(self::CACHE_PREFIX . $this->scenario); } if ($this->key) { Cache::forget(self::CACHE_PREFIX . $this->key); } } public function isMarketing(): bool { return EmailScenarioRegistry::describe($this->key, $this->scenario)['category'] === EmailScenarioRegistry::MARKETING; } /** Danh sách variable hợp lệ khai báo trong metadata.variables, key theo variable key. */ public function variables(): array { return collect($this->metadata['variables'] ?? [])->keyBy('key')->all(); } /** * @param array $overrides */ public function syncVariables(array $overrides = []): void { $declared = $this->variables(); $used = MailPlaceholders::extract($this->subject, $this->message); $overrides = collect($overrides)->keyBy('key'); $variables = collect(array_unique([...array_keys($declared), ...$used])) ->reject(fn (string $key) => ($declared[$key]['custom'] ?? false) && !in_array($key, $used, true)) ->map(function (string $key) use ($declared, $overrides) { $variable = $declared[$key] ?? ['key' => $key, 'label' => MailGlobals::LABELS[$key] ?? $key, 'type' => 'string', 'custom' => true]; $override = $overrides->get($key, []); if (filled($override['label'] ?? null)) { $variable['label'] = trim($override['label']); } if (array_key_exists('example', $override)) { $variable['example'] = (string) $override['example']; } return $variable; }) ->values() ->all(); $this->metadata = [...($this->metadata ?? []), 'variables' => $variables]; } /** Mail sender config khai báo trong metadata.mail (from_email, from_name, reply_to, reply_to_name, cc, bcc). */ public function mailConfig(): array { return $this->metadata['mail'] ?? []; } public function invoiceTemplate() { return $this->belongsTo(self::class, 'invoice_template_id'); } /** * Wrap $contentHtml with the admin-editable "layout" template (scenario=layout), * replacing its {{$content}} placeholder. * * Không còn fallback về blade: mọi email đều render từ tbl.template. Thiếu layout thì * dùng khung header/footer chuẩn trong code thay vì gửi ra nội dung trần. */ public static function renderWithLayout(?string $subject, string $contentHtml): string { return EmailTemplateShell::document($subject, self::layoutBody($contentHtml)); } public static function layoutBody(string $contentHtml): string { $layout = self::where('scenario', 'layout')->where('status', 1)->first(); return $layout && $layout->message ? str_replace('{{$content}}', $contentHtml, $layout->message) : EmailTemplateShell::wrap(EmailTemplateShell::body($contentHtml)); } public function type() { return $this->belongsTo(\App\Models\Type::class); } }