Files
breadcrumb-the-shire/lib/Service/Mail/MailService.php

153 lines
5.9 KiB
PHP
Raw Normal View History

2026-02-04 23:31:53 +01:00
<?php
2026-02-11 19:28:12 +01:00
namespace MintyPHP\Service\Mail;
2026-02-04 23:31:53 +01:00
2026-03-04 15:56:58 +01:00
use MintyPHP\Domain\Taxonomy\MailLogStatus;
2026-02-04 23:31:53 +01:00
use MintyPHP\I18n;
2026-03-05 08:26:51 +01:00
use MintyPHP\Repository\Mail\MailLogRepositoryInterface;
2026-03-06 00:44:52 +01:00
use MintyPHP\Service\Settings\SettingsSmtpGateway;
2026-02-04 23:31:53 +01:00
use PHPMailer\PHPMailer\Exception as MailerException;
2026-02-23 12:58:19 +01:00
use PHPMailer\PHPMailer\PHPMailer;
2026-02-04 23:31:53 +01:00
class MailService
{
2026-02-23 12:58:19 +01:00
public function __construct(
2026-03-05 08:26:51 +01:00
private readonly MailLogRepositoryInterface $mailLogRepository,
2026-03-06 00:44:52 +01:00
private readonly SettingsSmtpGateway $settingsSmtpGateway
2026-02-23 12:58:19 +01:00
) {
}
public function sendTemplate(
2026-02-04 23:31:53 +01:00
string $template,
array $vars,
string $to,
string $subject,
?string $locale = null
): array {
$locale = $locale ?: (I18n::$locale ?? I18n::$defaultLocale);
2026-02-23 12:58:19 +01:00
[$html, $text] = $this->renderTemplate($template, $vars, $locale);
2026-02-04 23:31:53 +01:00
2026-02-23 12:58:19 +01:00
return $this->send($to, $subject, $html, $text, [
2026-02-04 23:31:53 +01:00
'template' => $template,
]);
}
2026-02-23 12:58:19 +01:00
public function send(
2026-02-04 23:31:53 +01:00
string $to,
string $subject,
string $html,
string $text,
array $meta = []
): array {
2026-03-06 00:44:52 +01:00
// Log before sending so there's a record even if the send throws an exception.
2026-02-23 12:58:19 +01:00
$logId = $this->mailLogRepository->create([
2026-02-04 23:31:53 +01:00
'to_email' => $to,
'subject' => $subject,
'template' => $meta['template'] ?? null,
2026-03-04 15:56:58 +01:00
'status' => MailLogStatus::Queued->value,
2026-02-04 23:31:53 +01:00
]);
if (!class_exists(PHPMailer::class)) {
if ($logId) {
2026-02-23 12:58:19 +01:00
$this->mailLogRepository->markFailed($logId, 'PHPMailer is not installed');
2026-02-04 23:31:53 +01:00
}
return ['ok' => false, 'error' => 'mailer_missing'];
}
try {
2026-02-23 12:58:19 +01:00
$mailer = $this->createMailer();
2026-02-04 23:31:53 +01:00
$mailer->addAddress($to);
$mailer->Subject = $subject;
$mailer->Body = $html;
$mailer->AltBody = $text;
$mailer->isHTML(true);
$mailer->send();
if ($logId) {
2026-02-23 12:58:19 +01:00
$this->mailLogRepository->markSent($logId, $mailer->getLastMessageID() ?: null);
2026-02-04 23:31:53 +01:00
}
return ['ok' => true];
} catch (MailerException $e) {
if ($logId) {
2026-02-23 12:58:19 +01:00
$this->mailLogRepository->markFailed($logId, $e->getMessage());
2026-02-04 23:31:53 +01:00
}
return ['ok' => false, 'error' => 'send_failed'];
}
}
2026-02-23 12:58:19 +01:00
private function renderTemplate(string $template, array $vars, string $locale): array
2026-02-04 23:31:53 +01:00
{
2026-02-11 19:28:12 +01:00
$base = dirname(__DIR__, 3) . '/templates/emails';
2026-02-04 23:31:53 +01:00
$htmlPath = $base . '/' . $locale . '/' . $template . '.html';
$textPath = $base . '/' . $locale . '/' . $template . '.txt';
$htmlHeaderPath = $base . '/' . $locale . '/partials/header.html';
$htmlFooterPath = $base . '/' . $locale . '/partials/footer.html';
$textHeaderPath = $base . '/' . $locale . '/partials/header.txt';
$textFooterPath = $base . '/' . $locale . '/partials/footer.txt';
if (!is_file($htmlPath)) {
$htmlPath = $base . '/' . $template . '.html';
}
if (!is_file($textPath)) {
$textPath = $base . '/' . $template . '.txt';
}
$html = is_file($htmlPath) ? file_get_contents($htmlPath) : '';
$text = is_file($textPath) ? file_get_contents($textPath) : '';
if (!isset($vars['email_header'])) {
$vars['email_header'] = is_file($htmlHeaderPath) ? file_get_contents($htmlHeaderPath) : '';
}
if (!isset($vars['email_footer'])) {
$vars['email_footer'] = is_file($htmlFooterPath) ? file_get_contents($htmlFooterPath) : '';
}
if (!isset($vars['email_header_text'])) {
$vars['email_header_text'] = is_file($textHeaderPath) ? file_get_contents($textHeaderPath) : '';
}
if (!isset($vars['email_footer_text'])) {
$vars['email_footer_text'] = is_file($textFooterPath) ? file_get_contents($textFooterPath) : '';
}
foreach ($vars as $key => $value) {
$placeholder = '{{' . $key . '}}';
$html = str_replace($placeholder, (string) $value, $html);
$text = str_replace($placeholder, (string) $value, $text);
}
// Second pass to resolve placeholders inside injected header/footer content.
foreach ($vars as $key => $value) {
$placeholder = '{{' . $key . '}}';
$html = str_replace($placeholder, (string) $value, $html);
$text = str_replace($placeholder, (string) $value, $text);
}
return [$html, $text];
}
2026-02-23 12:58:19 +01:00
private function createMailer(): PHPMailer
2026-02-04 23:31:53 +01:00
{
$mailer = new PHPMailer(true);
$mailer->isSMTP();
2026-02-23 12:58:19 +01:00
2026-03-06 00:44:52 +01:00
// DB setting takes priority; env var fallback allows Docker-compose-style configuration without a DB record.
$host = $this->settingsSmtpGateway->getSmtpHost() ?? (getenv('SMTP_HOST') ?: '');
$port = $this->settingsSmtpGateway->getSmtpPort() ?? (int) (getenv('SMTP_PORT') ?: 587);
$user = $this->settingsSmtpGateway->getSmtpUser() ?? (getenv('SMTP_USER') ?: '');
$pass = $this->settingsSmtpGateway->getSmtpPassword() ?? (getenv('SMTP_PASS') ?: '');
$secure = $this->settingsSmtpGateway->getSmtpSecure() ?? strtolower((string) (getenv('SMTP_SECURE') ?: 'tls'));
$from = $this->settingsSmtpGateway->getSmtpFrom() ?? (getenv('SMTP_FROM') ?: ($user ?: 'no-reply@localhost'));
$fromName = $this->settingsSmtpGateway->getSmtpFromName() ?? (getenv('SMTP_FROM_NAME') ?: appTitle());
2026-02-04 23:31:53 +01:00
$mailer->Host = $host;
$mailer->Port = $port;
2026-03-06 00:44:52 +01:00
// Enable SMTP auth only if credentials are configured — supports relay servers without auth.
2026-02-04 23:31:53 +01:00
$mailer->SMTPAuth = $user !== '' || $pass !== '';
$mailer->Username = $user;
$mailer->Password = $pass;
if (in_array($secure, ['tls', 'ssl'], true)) {
$mailer->SMTPSecure = $secure;
}
$mailer->setFrom($from, $fromName);
$mailer->CharSet = 'UTF-8';
2026-02-23 12:58:19 +01:00
2026-02-04 23:31:53 +01:00
return $mailer;
}
}