70 lines
2.5 KiB
PHP
70 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Email;
|
|
|
|
use App\Exceptions\InvalidTemplateVariableException;
|
|
use App\Models\Template;
|
|
|
|
/**
|
|
* Render Template subject/message: chỉ thay thế placeholder dạng {{key}} đã khai báo
|
|
* trong metadata.variables. Không eval(), không compile Blade/PHP.
|
|
*/
|
|
class EmailTemplateRenderer
|
|
{
|
|
private const PLACEHOLDER_PATTERN = '/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/';
|
|
|
|
/**
|
|
* @return array{subject: string, html: string}
|
|
*/
|
|
public function render(Template $template, array $data): array
|
|
{
|
|
$variables = $template->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 = $this->extractPlaceholders($content);
|
|
$invalid = array_values(array_diff($used, array_keys($variables)));
|
|
|
|
if ($invalid !== []) {
|
|
throw new InvalidTemplateVariableException($scenario, $invalid);
|
|
}
|
|
}
|
|
|
|
private function extractPlaceholders(string $content): array
|
|
{
|
|
preg_match_all(self::PLACEHOLDER_PATTERN, $content, $matches);
|
|
|
|
return array_values(array_unique($matches[1]));
|
|
}
|
|
|
|
private function replace(string $content, array $variables, array $data): string
|
|
{
|
|
return preg_replace_callback(self::PLACEHOLDER_PATTERN, function (array $match) use ($variables, $data) {
|
|
$key = $match[1];
|
|
$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'),
|
|
};
|
|
}
|
|
}
|