variables(); $this->assertKnownVariables($template->scenario ?? $template->name ?? 'unknown', (string) $template->subject, $variables); $this->assertKnownVariables($template->scenario ?? $template->name ?? 'unknown', (string) $template->message, $variables); return [ 'subject' => $this->replace((string) $template->subject, $variables, $data), 'html' => $this->replace((string) $template->message, $variables, $data), ]; } private function assertKnownVariables(string $scenario, string $content, array $variables): void { $used = MailPlaceholders::extract($content); $invalid = array_values(array_diff($used, array_keys($variables))); if ($invalid !== []) { throw new InvalidTemplateVariableException($scenario, $invalid); } } private function replace(string $content, array $variables, array $data): string { $examples = array_map(fn (array $definition) => $definition['example'] ?? '', $variables); $content = MailPlaceholders::resolveConditionals($content, [...$examples, ...$data]); return preg_replace_callback(self::PLACEHOLDER_PATTERN, function (array $match) use ($variables, $data) { $key = $match[1]; if (!isset($variables[$key]) && !array_key_exists($key, $data)) { return $match[0]; } $definition = $variables[$key] ?? []; $value = $data[$key] ?? $definition['example'] ?? ''; return $this->formatValue((string) $value, $definition['type'] ?? 'string'); }, $content) ?? $content; } private function formatValue(string $value, string $type): string { return match ($type) { // html: nội dung tin cậy do Email Builder sinh ra sẵn (đã được builder escape khi build). 'html' => $value, 'url' => filter_var($value, FILTER_VALIDATE_URL) ? htmlspecialchars($value, ENT_QUOTES, 'UTF-8') : '', default => htmlspecialchars($value, ENT_QUOTES, 'UTF-8'), }; } }