Files
orderPRO/src/Modules/Email/EmailSendingService.php
2026-03-28 21:16:21 +01:00

377 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Email;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\EmailMailboxRepository;
use App\Modules\Settings\EmailTemplateRepository;
use PDO;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception as PHPMailerException;
use RuntimeException;
use Throwable;
final class EmailSendingService
{
public function __construct(
private readonly PDO $pdo,
private readonly OrdersRepository $orders,
private readonly EmailTemplateRepository $templates,
private readonly EmailMailboxRepository $mailboxes,
private readonly VariableResolver $variableResolver,
private readonly AttachmentGenerator $attachmentGenerator
) {
}
/**
* @return array{success: bool, error: ?string, log_id: int}
*/
public function send(int $orderId, int $templateId, ?int $mailboxId = null, ?string $actorName = null, ?string $recipientEmailOverride = null, ?string $recipientNameOverride = null): array
{
$details = $this->orders->findDetails($orderId);
if ($details === null) {
return ['success' => false, 'error' => 'Zamowienie nie znalezione', 'log_id' => 0];
}
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
$addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
$template = $this->templates->findById($templateId);
if ($template === null) {
return ['success' => false, 'error' => 'Szablon nie znaleziony', 'log_id' => 0];
}
$mailbox = $this->resolveMailbox($mailboxId, $template);
if ($mailbox === null) {
return ['success' => false, 'error' => 'Brak skonfigurowanej skrzynki SMTP', 'log_id' => 0];
}
$recipientEmail = $recipientEmailOverride !== null && $recipientEmailOverride !== ''
? $recipientEmailOverride
: $this->findRecipientEmail($addresses);
if ($recipientEmail === '') {
return ['success' => false, 'error' => 'Brak adresu e-mail odbiorcy', 'log_id' => 0];
}
$recipientName = $recipientNameOverride !== null && $recipientNameOverride !== ''
? $recipientNameOverride
: $this->findRecipientName($addresses);
$companySettings = $this->loadCompanySettings();
$variableMap = $this->variableResolver->buildVariableMap($order, $addresses, $companySettings);
$resolvedSubject = $this->variableResolver->resolve((string) ($template['subject'] ?? ''), $variableMap);
$resolvedBody = $this->variableResolver->resolve((string) ($template['body_html'] ?? ''), $variableMap);
$resolvedBody = $this->composeBody($resolvedBody, $mailbox, $variableMap);
$attachments = [];
$attachmentType = (string) ($template['attachment_1'] ?? '');
if ($attachmentType !== '') {
$attachment = $this->attachmentGenerator->generate($attachmentType, $order);
if ($attachment !== null) {
$attachments[] = $attachment;
}
}
$status = 'sent';
$errorMessage = null;
$sentAt = null;
try {
$this->sendViaSMTP($mailbox, $recipientEmail, $recipientName, $resolvedSubject, $resolvedBody, $attachments);
$sentAt = date('Y-m-d H:i:s');
} catch (Throwable $e) {
$status = 'failed';
$errorMessage = $e->getMessage();
}
$logId = $this->logEmail(
$templateId,
(int) ($mailbox['id'] ?? 0),
$orderId,
$recipientEmail,
$recipientName,
$resolvedSubject,
$resolvedBody,
$attachments,
$status,
$errorMessage,
$sentAt
);
$templateName = (string) ($template['name'] ?? '');
$activitySummary = $status === 'sent'
? 'Wyslano e-mail "' . $resolvedSubject . '" do ' . $recipientEmail
: 'Blad wysylki e-mail "' . $resolvedSubject . '" do ' . $recipientEmail . ': ' . ($errorMessage ?? '');
$this->orders->recordActivity(
$orderId,
'email_' . $status,
$activitySummary,
['template' => $templateName, 'recipient' => $recipientEmail, 'log_id' => $logId],
'user',
$actorName
);
return [
'success' => $status === 'sent',
'error' => $errorMessage,
'log_id' => $logId,
];
}
/**
* @return array{subject: string, body_html: string, attachments: list<string>}
*/
public function preview(int $orderId, int $templateId): array
{
$details = $this->orders->findDetails($orderId);
if ($details === null) {
return ['subject' => '', 'body_html' => '<p>Zamowienie nie znalezione</p>', 'attachments' => []];
}
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
$addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
$template = $this->templates->findById($templateId);
if ($template === null) {
return ['subject' => '', 'body_html' => '<p>Szablon nie znaleziony</p>', 'attachments' => []];
}
$companySettings = $this->loadCompanySettings();
$variableMap = $this->variableResolver->buildVariableMap($order, $addresses, $companySettings);
$resolvedSubject = $this->variableResolver->resolve((string) ($template['subject'] ?? ''), $variableMap);
$resolvedBody = $this->variableResolver->resolve((string) ($template['body_html'] ?? ''), $variableMap);
$mailbox = $this->resolveMailbox(null, $template);
$resolvedBody = $this->composeBody($resolvedBody, $mailbox, $variableMap);
$attachmentNames = [];
$attachmentType = (string) ($template['attachment_1'] ?? '');
if ($attachmentType !== '') {
$attachment = $this->attachmentGenerator->generate($attachmentType, $order);
if ($attachment !== null) {
$attachmentNames[] = $attachment['filename'];
}
}
return [
'subject' => $resolvedSubject,
'body_html' => $resolvedBody,
'attachments' => $attachmentNames,
];
}
/**
* @param array<string, mixed>|null $mailbox
* @param array<string, string> $variableMap
*/
private function composeBody(string $resolvedBody, ?array $mailbox, array $variableMap): string
{
if ($mailbox === null) {
return $resolvedBody;
}
$header = trim((string) ($mailbox['header_html'] ?? ''));
$footer = trim((string) ($mailbox['footer_html'] ?? ''));
if ($header !== '') {
$header = $this->variableResolver->resolve($header, $variableMap);
}
if ($footer !== '') {
$footer = $this->variableResolver->resolve($footer, $variableMap);
}
$parts = [];
if ($header !== '') {
$parts[] = $header;
}
$parts[] = $resolvedBody;
if ($footer !== '') {
$parts[] = $footer;
}
return implode("\n", $parts);
}
/**
* @param array<string, mixed>|null $template
* @return array<string, mixed>|null
*/
private function resolveMailbox(?int $mailboxId, ?array $template): ?array
{
if ($mailboxId !== null && $mailboxId > 0) {
$mailbox = $this->mailboxes->findById($mailboxId);
if ($mailbox !== null && (int) ($mailbox['is_active'] ?? 0) === 1) {
return $mailbox;
}
}
$templateMailboxId = (int) ($template['mailbox_id'] ?? 0);
if ($templateMailboxId > 0) {
$mailbox = $this->mailboxes->findById($templateMailboxId);
if ($mailbox !== null && (int) ($mailbox['is_active'] ?? 0) === 1) {
return $mailbox;
}
}
$active = $this->mailboxes->listActive();
foreach ($active as $m) {
if ((int) ($m['is_default'] ?? 0) === 1) {
return $this->mailboxes->findById((int) $m['id']);
}
}
return $active !== [] ? $this->mailboxes->findById((int) $active[0]['id']) : null;
}
/**
* @param array<int, array<string, mixed>> $addresses
*/
private function findRecipientEmail(array $addresses): string
{
foreach (['customer', 'delivery', 'invoice'] as $type) {
foreach ($addresses as $addr) {
if (($addr['address_type'] ?? '') === $type) {
$email = trim((string) ($addr['email'] ?? ''));
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) !== false) {
return $email;
}
}
}
}
return '';
}
/**
* @param array<int, array<string, mixed>> $addresses
*/
private function findRecipientName(array $addresses): string
{
foreach (['customer', 'delivery'] as $type) {
foreach ($addresses as $addr) {
if (($addr['address_type'] ?? '') === $type) {
$name = trim((string) ($addr['name'] ?? ''));
if ($name !== '') {
return $name;
}
}
}
}
return '';
}
/**
* @return array<string, mixed>
*/
private function loadCompanySettings(): array
{
$stmt = $this->pdo->prepare('SELECT * FROM company_settings LIMIT 1');
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return is_array($row) ? $row : [];
}
/**
* @param array<string, mixed> $mailbox
* @param list<array{filename: string, content: string, mime: string}> $attachments
*/
private function sendViaSMTP(
array $mailbox,
string $recipientEmail,
string $recipientName,
string $subject,
string $body,
array $attachments
): void {
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = (string) ($mailbox['smtp_host'] ?? '');
$mail->Port = (int) ($mailbox['smtp_port'] ?? 587);
$mail->SMTPAuth = true;
$mail->Username = (string) ($mailbox['smtp_username'] ?? '');
$mail->Password = (string) ($mailbox['smtp_password_decrypted'] ?? '');
$mail->CharSet = PHPMailer::CHARSET_UTF8;
$encryption = (string) ($mailbox['smtp_encryption'] ?? 'tls');
if ($encryption === 'tls') {
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
} elseif ($encryption === 'ssl') {
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
} else {
$mail->SMTPSecure = '';
$mail->SMTPAutoTLS = false;
}
$senderEmail = (string) ($mailbox['sender_email'] ?? $mailbox['smtp_username'] ?? '');
$senderName = (string) ($mailbox['sender_name'] ?? '');
$mail->setFrom($senderEmail, $senderName);
$mail->addAddress($recipientEmail, $recipientName);
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $body;
foreach ($attachments as $att) {
$mail->addStringAttachment($att['content'], $att['filename'], PHPMailer::ENCODING_BASE64, $att['mime']);
}
$mail->send();
}
/**
* @param list<array{filename: string, content: string, mime: string}> $attachments
*/
private function logEmail(
int $templateId,
int $mailboxId,
int $orderId,
string $recipientEmail,
string $recipientName,
string $subject,
string $bodyHtml,
array $attachments,
string $status,
?string $errorMessage,
?string $sentAt
): int {
$attachmentsJson = [];
foreach ($attachments as $att) {
$attachmentsJson[] = [
'name' => $att['filename'],
'type' => $att['mime'],
];
}
$stmt = $this->pdo->prepare(
'INSERT INTO email_logs (
template_id, mailbox_id, order_id, recipient_email, recipient_name,
subject, body_html, attachments_json, status, error_message, sent_at, created_at
) VALUES (
:template_id, :mailbox_id, :order_id, :recipient_email, :recipient_name,
:subject, :body_html, :attachments_json, :status, :error_message, :sent_at, NOW()
)'
);
$stmt->execute([
'template_id' => $templateId,
'mailbox_id' => $mailboxId,
'order_id' => $orderId,
'recipient_email' => $recipientEmail,
'recipient_name' => $recipientName,
'subject' => $subject,
'body_html' => $bodyHtml,
'attachments_json' => json_encode($attachmentsJson, JSON_UNESCAPED_UNICODE),
'status' => $status,
'error_message' => $errorMessage,
'sent_at' => $sentAt,
]);
return (int) $this->pdo->lastInsertId();
}
}