377 lines
13 KiB
PHP
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();
|
|
}
|
|
}
|