config = new email_c(); } public function testConfigRequest(string $test_recipient): string { if ($test_recipient) { try { $this->sendEmail($test_recipient, 'Test recipient', 'Test email', 'This is a test email'); return 'Email sent successfully to ' . $test_recipient; } catch (Exception $e) { return 'Email error: ' . $e->getMessage(); } } else { return 'No test recipient provided'; } } /** * Send an email using the preferred email service * @param string $to Email address to send the email to * @param string $recipient_name Name of the recipient * @param string $subject Subject of the email * @param string $message Message of the email * @param string|null $references Optional references header value * @param array $attachments Array of attachments, where each attachment is an array with the first element being the file content and the second element being the filename * @throws Exception If an error occurs while sending the email */ public function sendEmail(string $to, string $recipient_name, string $subject, string $message, string $references = null, array $attachments = []): void { if ($this->config->mailersend_enabled->getVariableValue()) { try { // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= $message; // Add email footer $html .= self::generateHtmlFooter(); $this->sendEmailMailerSend($to, $recipient_name, $subject, $message, $html, $references, $attachments); } catch (JsonException|MailerSendException|ClientExceptionInterface $e) { throw new Exception('Error sending email: ' . $e->getMessage()); } } else { $this->sendEmailDefault($to, $subject, $message); } } private static function generateHtmlHeader(): false|string { $email_template_head = (new email_template_head())->generate_html(); $email_template_header = (new email_template_header())->generate_html(); return " $email_template_head $email_template_header"; } private static function generateHtmlFooter(): string { $email_template_footer = (new email_template_footer())->generate_html(); return " $email_template_footer "; } /** * @throws ClientExceptionInterface * @throws JsonException * @throws MailerSendAssertException * @throws MailerSendException */ private function sendEmailMailerSend(string $to, string $recipient_name, string $subject, string $message, string $html = null, string $references = null, array $attachments = []): void { if (self::isFakeDeliveryEnabled()) { self::recordFakeDelivery([ 'to' => $to, 'recipient_name' => $recipient_name, 'subject' => $subject, 'message' => $message, 'html' => $html, ]); return; } // Check if the email is blacklisted $blacklisted_emails = [ 'invoice.dk@freja.com', // TODO: Make this dynamic. ]; // If the email is blacklisted, return without sending the email if (in_array($to, $blacklisted_emails)) { return; } // Send POST request to email service $mailersend = new MailerSend(['api_key' => $this->config->mailersend_api_key->getVariableValue()]); $recipients = [ new Recipient($to, $recipient_name) ]; $emailParams = (new EmailParams()) ->setFrom($this->config->smtp_from->getVariableValue()) ->setFromName($this->config->smtp_from_name->getVariableValue()) ->setRecipients($recipients) ->setSubject($subject) ->setHtml($html ?? $message) ->setReplyTo($this->config->smtp_reply_to->getVariableValue()) ->setReplyToName($this->config->smtp_reply_to_name->getVariableValue()); if ($attachments) { $attachments = array_map(function ($attachment) { // Read the data from the path (Attachment[0]) and set the filename (Attachment[1]) $attachment[0] = file_get_contents($attachment[0]); if ($attachment[0] === false) { throw new Exception('Failed to read file: ' . $attachment[0]); } if (empty($attachment[1])) { throw new Exception('Filename is empty'); } return new Attachment($attachment[0], $attachment[1]); }, $attachments); } if ($attachments) { $emailParams->setAttachments($attachments); } if ($references) { $emailParams->setHeaders([ new Header('References', $references) ]); } $mailersend->email->send($emailParams); } /** * Send an email using the default SMTP service * @param string $to Email address to send the email to * @param string $subject Subject of the email * @param string $message Message of the email * @throws Exception If an error occurs while sending the email */ private function sendEmailDefault(string $to, string $subject, string $message): void { // Send email using default SMTP service throw new Exception('Default SMTP service not implemented'); } /** * @throws MailerSendException * @throws ClientExceptionInterface * @throws JsonException * @throws MailerSendAssertException */ public function sendStripeInvoiceEmail(string $to, string|null $recipient_name, string $payment_link, $order_id): void { $recipient_name = $recipient_name ?? 'Kunde'; $recipient_name = ucfirst($recipient_name); // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= (new email_template_stripe_invoice( $order_id, $payment_link, $recipient_name ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); $this->sendEmailMailerSend($to, $recipient_name, 'Betalingslink for bestilling #' . $order_id, '', $html); } public static function resetFakeDeliveries(): void { self::$fake_deliveries = []; $path = self::getFakeDeliveriesPath(); if ($path !== null && is_file($path)) { unlink($path); } } public static function syncFakeDeliveries(): void { $path = self::getFakeDeliveriesPath(); if ($path === null || !is_file($path)) { self::$fake_deliveries = []; return; } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if ($lines === false) { self::$fake_deliveries = []; return; } $deliveries = []; foreach ($lines as $line) { $delivery = json_decode($line, true); if (is_array($delivery)) { $deliveries[] = $delivery; } } self::$fake_deliveries = $deliveries; } private static function recordFakeDelivery(array $delivery): void { self::$fake_deliveries[] = $delivery; $path = self::getFakeDeliveriesPath(); if ($path === null) { return; } $directory = dirname($path); if (!is_dir($directory)) { mkdir($directory, 0777, true); } file_put_contents($path, json_encode($delivery, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, FILE_APPEND | LOCK_EX); } private static function getFakeDeliveriesPath(): ?string { if (!self::isFakeDeliveryEnabled()) { return null; } $configuredPath = trim((string)(getenv('EMAIL_FAKE_DELIVERIES_PATH') ?: '')); if ($configuredPath !== '') { return $configuredPath; } if (getenv('RUN_API_TESTS') !== '1') { return null; } return rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'truckwash-email-fake-deliveries-' . md5((string)getcwd()) . '.jsonl'; } private static function isFakeDeliveryEnabled(): bool { return getenv('EMAIL_FAKE_MODE') === '1'; } /** * Send a booking confirmation email * @throws MailerSendException * @throws ClientExceptionInterface * @throws JsonException * @throws MailerSendAssertException * @throws Exception */ public function sendBookingConfirmationEmail( int $booking_id, ): void { // Get the booking details from the database $booking = (new bookings_o())->select((int)$booking_id); $customer = (new users_o())->getUserByCustomerNumber((int)$booking->customer_number->value()); $department = (new departments_o())->select((int)$booking->department->value()); // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= (new email_template_booking_confirmation( $booking->id, $customer->getCustomerName((int)$customer->customer_number->value()), $customer->customer_number->value(), $booking->contact_email->value(), $booking->reference_number->value(), $booking->regNrTraekker->value(), $booking->regNrTrailer->value(), ($booking->washCertificateStatus->value() === 'cancelled' ? 'Nej' : 'Ja'), (!empty($booking->washCertificateEmail->value()) ? (string)$booking->washCertificateEmail->value() : ''), $booking->date->value(), (string)$department->getDepartmentName((int)$department->id), (string)$department->getBranding()->address->value(), $booking->pickup_bool->value() === 'yes' ? 'Ja' : 'Nej', $booking->notes->value(), ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); // Send email $this->sendEmailMailerSend( $booking->contact_email->value(), $customer->getCustomerName((int)$customer->customer_number->value()), 'Truck Wash Booking ( ID: ' . $booking_id . ', REF: ' . $booking->reference_number->value() . ' )', '', $html ); } /** * Send a wash certificate email * @throws MailerSendException * @throws ClientExceptionInterface * @throws JsonException * @throws MailerSendAssertException */ public function sendWashCertificateEmail( $booking_id, $company_name, $contact_email, $reference_number, $wash_certificate_url, $wash_certificate_email, ): void { // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= (new email_template_wash_certificate( $booking_id, $company_name, ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); // Attach the wash certificate $pdf = (new pdf_store())->download($wash_certificate_url); // Send email $this->sendEmailMailerSend( $wash_certificate_email, $company_name, 'Truck Wash Booking ( ID: ' . $booking_id . ', REF: ' . $reference_number . ' )', '', $html, null, [ [ $pdf, 'wash_certificate_' . $booking_id . '_' . $reference_number . '.pdf' ] ] ); } /** * @throws Exception * @throws ClientExceptionInterface */ public function sendBookingNotification(int $booking_id): void { // Get the booking details from the database $booking = (new bookings_o())->select((int)$booking_id); $customer = (new users_o())->getUserByCustomerNumber((int)$booking->customer_number->value()); $department = (new departments_o())->select((int)$booking->department->value()); // Generate HTML email $html = self::generateHtmlHeader(); $html .= (new email_template_booking_notification( $booking->id, $customer->getCustomerName((int)$customer->customer_number->value()), $customer->customer_number->value(), $booking->contact_email->value(), $booking->reference_number->value(), $booking->regNrTraekker->value(), $booking->regNrTrailer->value(), ($booking->washCertificateStatus->value() === 'cancelled' ? 'Nej' : 'Ja'), (!empty($booking->washCertificateEmail->value()) ? (string)$booking->washCertificateEmail->value() : ''), $booking->date->value(), (string)$department->getDepartmentName((int)$department->id), (string)$department->getBranding()->address->value(), $booking->pickup_bool->value() === 'yes' ? 'Ja' : 'Nej', $booking->notes->value(), ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); // Send email $this->sendEmailMailerSend( $department->getBranding()->email->value(), $department->getDepartmentName((int)$department->id), 'Truck Wash Booking ( ID: ' . $booking_id . ', REF: ' . $booking->reference_number->value() . ' )', '', $html ); } /** * @throws JsonException * @throws Exception */ public function sendOrderBookingConfirmationEmail(\objects\order_bookings_o $order_booking): void { // Validate the booking object $order_booking->requireSelected(); // Get customer details $customer = (new users_o())->getUserByCustomerNumber((int)$order_booking->customer_number->value()); $customer->requireSelected(); $recipient_name = $customer->getCustomerName((int)$customer->customer_number->value()); $recipient_email = $customer->email->value(); $customer_contact_email = ($recipient_email ?: ''); // Check if the customer has a booking-specific email provided if (!empty($customer->wash_certificate_email->value())) { $recipient_email = (string)$customer->wash_certificate_email->value(); } // Get the department details $department = (new departments_o())->select((int)$order_booking->department->value()); $department->requireSelected(); // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= (new email_template_booking_confirmation( $order_booking->id, $recipient_name, $customer->customer_number->value(), $customer_contact_email, $order_booking->reference->value(), $order_booking->reg_1->value(), $order_booking->reg_2->value(), ($order_booking->containsWashCertificateItem() ? 'Ja' : 'Nej'), $recipient_email, $order_booking->datetime->value(), (string)$department->getDepartmentName((int)$department->id), (string)$department->getBranding()->address->value(), !!$order_booking->pickup->value() ? 'Ja' : 'Nej', $order_booking->note->value() ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); // Send email $this->sendEmailMailerSend( $recipient_email, $department->getDepartmentName((int)$department->id), 'Truck Wash Booking ( ID: ' . $order_booking->id . ', REF: ' . $order_booking->reference->value() . ' )', '', $html ); } /** * @throws JsonException * @throws Exception */ public function sendWashCertificateEmailToCustomer(\objects\order_bookings_o $order_booking): void { // Validate the booking object $order_booking->requireSelected(); if (!$order_booking->hasTransaction()) return; // Only send a wash certificate if the order has been created. // Get the order details $order = $order_booking->getOrder(); // Get customer details $customer = (new users_o())->getUserByCustomerNumber((int)$order_booking->customer_number->value()); $customer->requireSelected(); $recipient_name = $customer->getCustomerName((int)$customer->customer_number->value()); $recipient_email = $customer->email->value(); // Check if the customer has a booking-specific email provided if (!empty($customer->wash_certificate_email->value())) { $recipient_email = (string)$customer->wash_certificate_email->value(); } // Generate HTML email $html = self::generateHtmlHeader(); // Add email content $html .= (new email_template_wash_certificate( $order_booking->id, $recipient_name, ))->generate_html(); // Add email footer $html .= self::generateHtmlFooter(); // Attach the wash certificate $attachments = $order->listAttachments(); $pdf = null; foreach ($attachments as $attachment) { if ($attachment->isWashCertificate()) { $pdf = (new pdf_store())->download($attachment->content->document); break; } } if (!$pdf) { throw new Exception('Wash certificate PDF not found for order booking ID: ' . $order_booking->id); } // Send email $this->sendEmailMailerSend( $recipient_email, $recipient_name, 'Truck Wash Booking ( ID: ' . $order_booking->id . ', REF: ' . $order_booking->reference->value() . ' )', '', $html, null, [ [ $pdf, 'wash_certificate_' . $order_booking->id . '_' . $order_booking->reference->value() . '.pdf' ] ] ); } /** * @throws Exception */ public function sendWelcomeEmailToCustomer(int $customer_number, string $email): void { $customer = (new users_o())->getUserByCustomerNumber($customer_number); if (!$customer->exists()) { throw new Exception('Customer not found with customer number: ' . $customer_number); } $password_reset_link = $customer->generatePasswordResetLink(); $company_name = $customer->getCustomerName((int)$customer->customer_number->value()); // Generate HTML email using the new customer template $html = self::generateHtmlHeader(); $html .= (new email_template_new_customer( (int)$customer->customer_number->value(), $password_reset_link ))->generate_html(); $html .= self::generateHtmlFooter(); // Prepare attachment (departments flyer) $this->attachments = [ /** * [ * 'https://www.truckwash.dk/wp-content/uploads/2024/10/Truck-Wash_Flyer_A5.pdf', * 'Truck Wash Afdelinger.pdf' * ] */ ]; // Send the email via MailerSend $this->sendEmailMailerSend( $email, $company_name, 'Velkommen til Truck Wash', '', $html, null, $this->attachments ); } /** * @throws Exception */ public function sendNewCustomerRegistrationNotifications(int $customer_number): void { $customer = (new users_o())->getUserByCustomerNumber($customer_number); if (!$customer->exists()) { throw new Exception('Customer not found with customer number: ' . $customer_number); } $customerName = $customer->getCustomerName((int)$customer->customer_number->value()) ?: 'Unknown customer'; $safeCustomerName = htmlspecialchars($customerName, ENT_QUOTES, 'UTF-8'); $safeCustomerNumber = (int)$customer->customer_number->value(); $customerUrl = 'https://truckwash.io/superuser/users?search=' . $safeCustomerNumber; $message = "

A new customer has registered on truckwash.io.

Customer number: $safeCustomerNumber
Customer name: $safeCustomerName

Open customer in Superuser

"; foreach ((new users_o())->getSuperuserNewCustomerEmailNotificationRecipients() as $recipient) { $recipientEmail = trim((string)($recipient['email'] ?? '')); if ($recipientEmail === '') { continue; } $recipientName = trim((string)($recipient['display_name'] ?? '')); if ($recipientName === '') { $recipientName = $recipientEmail; } $this->sendEmail( $recipientEmail, $recipientName, 'New customer registered on Truck Wash', $message, ); } } }