EmailService.php

23.70 KB
04/08/2025 08:23
PHP
EmailService.php
<?php

namespace App\Services;

use Exception;

/**
 * Email Service
 * Handles sending emails for various purposes
 */
class EmailService
{
    /**
     * @var mixed
     */
    private $config;
    /**
     * @var mixed
     */
    private $templates;

    public function __construct()
    {
        $this->config = [
            'driver' => $_ENV['MAIL_DRIVER'] ?? 'smtp',
            'host' => $_ENV['MAIL_HOST'] ?? 'localhost',
            'port' => (int) ($_ENV['MAIL_PORT'] ?? 587),
            'username' => $_ENV['MAIL_USERNAME'] ?? '',
            'password' => $_ENV['MAIL_PASSWORD'] ?? '',
            'encryption' => $_ENV['MAIL_ENCRYPTION'] ?? 'tls',
            'from_address' => $_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@example.com',
            'from_name' => $_ENV['MAIL_FROM_NAME'] ?? 'E-commerce Store'
        ];

        $this->templates = [
            'verification' => $this->getVerificationTemplate(),
            'password_reset' => $this->getPasswordResetTemplate(),
            'welcome' => $this->getWelcomeTemplate(),
            'order_confirmation' => $this->getOrderConfirmationTemplate()
        ];
    }

    /**
     * Send verification email
     */
    public function sendVerificationEmail(string $email, string $name, string $token): bool
    {
        $verificationUrl = $this->getAppUrl()."/api/auth/verify-email/{$token}";

        $subject = 'Verify Your Email Address';
        $body = $this->renderTemplate('verification', [
            'name' => $name,
            'verification_url' => $verificationUrl,
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($email, $subject, $body);
    }

    /**
     * Send password reset email
     */
    public function sendPasswordResetEmail(string $email, string $name, string $token): bool
    {
        $resetUrl = $this->getAppUrl()."/reset-password?token={$token}";

        $subject = 'Reset Your Password';
        $body = $this->renderTemplate('password_reset', [
            'name' => $name,
            'reset_url' => $resetUrl,
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($email, $subject, $body);
    }

    /**
     * Send welcome email
     */
    public function sendWelcomeEmail(string $email, string $name): bool
    {
        $subject = 'Welcome to '.$this->config['from_name'];
        $body = $this->renderTemplate('welcome', [
            'name' => $name,
            'app_name' => $this->config['from_name'],
            'app_url' => $this->getAppUrl()
        ]);

        return $this->sendEmail($email, $subject, $body);
    }

    /**
     * Send order confirmation email
     */
    public function sendOrderConfirmationEmail(string $email, string $name, array $orderData): bool
    {
        $subject = 'Order Confirmation - '.$orderData['order_number'];
        $body = $this->renderTemplate('order_confirmation', [
            'name' => $name,
            'order' => $orderData,
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($email, $subject, $body);
    }

    /**
     * Send email using configured driver
     */
    private function sendEmail(string $to, string $subject, string $body): bool
    {
        try {
            switch ($this->config['driver']) {
                case 'smtp':
                    return $this->sendViaSMTP($to, $subject, $body);
                case 'mail':
                    return $this->sendViaMail($to, $subject, $body);
                case 'log':
                    return $this->sendViaLog($to, $subject, $body);
                default:
                    throw new Exception('Unsupported mail driver: '.$this->config['driver']);
            }
        } catch (Exception $e) {
            error_log('Email sending failed: '.$e->getMessage());
            return false;
        }
    }

    /**
     * Send email via SMTP
     */
    private function sendViaSMTP(string $to, string $subject, string $body): bool
    {
        // For production, you would implement proper SMTP sending
        // For now, we'll log the email
        return $this->sendViaLog($to, $subject, $body);
    }

    /**
     * Send email via PHP mail() function
     */
    private function sendViaMail(string $to, string $subject, string $body): bool
    {
        $headers = [
            'From: '.$this->config['from_name'].' <'.$this->config['from_address'].'>',
            'Reply-To: '.$this->config['from_address'],
            'Content-Type: text/html; charset=UTF-8',
            'MIME-Version: 1.0'
        ];

        return mail($to, $subject, $body, implode("\r\n", $headers));
    }

    /**
     * Log email instead of sending (for development)
     */
    private function sendViaLog(string $to, string $subject, string $body): bool
    {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'to' => $to,
            'subject' => $subject,
            'body' => $body
        ];

        $logFile = __DIR__.'/../../storage/logs/emails.log';
        $logDir = dirname($logFile);

        if (!is_dir($logDir)) {
            @mkdir($logDir, 0755, true);
        }

        return file_put_contents($logFile, json_encode($logEntry)."\n", FILE_APPEND | LOCK_EX) !== false;
    }

    /**
     * Render email template
     */
    private function renderTemplate(string $template, array $variables): string
    {
        if (!isset($this->templates[$template])) {
            throw new Exception("Email template '{$template}' not found");
        }

        $content = $this->templates[$template];

        // Replace variables
        foreach ($variables as $key => $value) {
            if (is_array($value)) {
                // Handle complex variables (like order data)
                $content = $this->replaceComplexVariable($content, $key, $value);
            } else {
                $content = str_replace('{{'.$key.'}}', $value, $content);
            }
        }

        return $content;
    }

    /**
     * Replace complex variables in templates
     */
    private function replaceComplexVariable(string $content, string $key, array $data): string
    {
        // Handle order data
        if ($key === 'order') {
            $content = str_replace('{{order.number}}', $data['order_number'] ?? '', $content);
            $content = str_replace('{{order.total}}', number_format($data['total_amount'] ?? 0, 2), $content);
            $content = str_replace('{{order.status}}', $data['status'] ?? '', $content);
            $content = str_replace('{{order.date}}', date('F j, Y', strtotime($data['created_at'] ?? 'now')), $content);

            // Handle order items
            if (isset($data['items'])) {
                $itemsHtml = '';
                foreach ($data['items'] as $item) {
                    $itemsHtml .= '<tr>';
                    $itemsHtml .= '<td>'.htmlspecialchars($item['product_name'] ?? '').'</td>';
                    $itemsHtml .= '<td>'.($item['quantity'] ?? 0).'</td>';
                    $itemsHtml .= '<td>฿'.number_format($item['price'] ?? 0, 2).'</td>';
                    $itemsHtml .= '<td>฿'.number_format($item['total'] ?? 0, 2).'</td>';
                    $itemsHtml .= '</tr>';
                }
                $content = str_replace('{{order.items}}', $itemsHtml, $content);
            }
        }

        return $content;
    }

    /**
     * Get application URL
     */
    private function getAppUrl(): string
    {
        return $_ENV['APP_URL'] ?? 'http://localhost';
    }

    /**
     * Get verification email template
     */
    private function getVerificationTemplate(): string
    {
        return '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Verify Your Email</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #007bff; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{{app_name}}</h1>
        </div>
        <div class="content">
            <h2>Verify Your Email Address</h2>
            <p>Hello {{name}},</p>
            <p>Thank you for registering with {{app_name}}. Please click the button below to verify your email address:</p>
            <p style="text-align: center;">
                <a href="{{verification_url}}" class="button">Verify Email</a>
            </p>
            <p>If you cannot click the button, copy and paste this link into your browser:</p>
            <p><a href="{{verification_url}}">{{verification_url}}</a></p>
            <p>If you did not create an account, please ignore this email.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';
    }

    /**
     * Get password reset email template
     */
    private function getPasswordResetTemplate(): string
    {
        return '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Reset Your Password</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #dc3545; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .button { display: inline-block; padding: 12px 24px; background: #dc3545; color: white; text-decoration: none; border-radius: 4px; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{{app_name}}</h1>
        </div>
        <div class="content">
            <h2>Reset Your Password</h2>
            <p>Hello {{name}},</p>
            <p>You have requested to reset your password. Click the button below to reset it:</p>
            <p style="text-align: center;">
                <a href="{{reset_url}}" class="button">Reset Password</a>
            </p>
            <p>If you cannot click the button, copy and paste this link into your browser:</p>
            <p><a href="{{reset_url}}">{{reset_url}}</a></p>
            <p>This link will expire in 1 hour for security reasons.</p>
            <p>If you did not request a password reset, please ignore this email.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';
    }

    /**
     * Get welcome email template
     */
    private function getWelcomeTemplate(): string
    {
        return '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #28a745; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .button { display: inline-block; padding: 12px 24px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Welcome to {{app_name}}!</h1>
        </div>
        <div class="content">
            <h2>Welcome {{name}}!</h2>
            <p>Thank you for joining {{app_name}}. We are excited to have you as part of our community.</p>
            <p>You can now:</p>
            <ul>
                <li>Browse our products</li>
                <li>Add items to your wishlist</li>
                <li>Track your orders</li>
                <li>Manage your profile and addresses</li>
            </ul>
            <p style="text-align: center;">
                <a href="{{app_url}}" class="button">Start Shopping</a>
            </p>
            <p>If you have any questions, feel free to contact our support team.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';
    }

    /**
     * Get order confirmation email template
     */
    private function getOrderConfirmationTemplate(): string
    {
        return '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Order Confirmation</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #007bff; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .order-details { background: white; padding: 15px; margin: 15px 0; border-radius: 4px; }
        table { width: 100%; border-collapse: collapse; }
        th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
        th { background: #f8f9fa; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Order Confirmation</h1>
        </div>
        <div class="content">
            <h2>Thank you for your order, {{name}}!</h2>
            <p>Your order has been received and is being processed.</p>

            <div class="order-details">
                <h3>Order Details</h3>
                <p><strong>Order Number:</strong> {{order.number}}</p>
                <p><strong>Order Date:</strong> {{order.date}}</p>
                <p><strong>Status:</strong> {{order.status}}</p>

                <h4>Items Ordered</h4>
                <table>
                    <thead>
                        <tr>
                            <th>Product</th>
                            <th>Quantity</th>
                            <th>Price</th>
                            <th>Total</th>
                        </tr>
                    </thead>
                    <tbody>
                        {{order.items}}
                    </tbody>
                </table>

                <p style="text-align: right; font-size: 18px; font-weight: bold;">
                    Total: ฿{{order.total}}
                </p>
            </div>

            <p>We will send you another email when your order ships.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';
    }

    /**
     * Send order status update email
     */
    public function sendOrderStatusUpdate(string $email, string $name, array $orderData, string $newStatus): bool
    {
        $statusMessages = [
            'confirmed' => 'Your order has been confirmed and is being prepared',
            'processing' => 'Your order is being processed and will be shipped soon',
            'shipped' => 'Your order has been shipped and is on its way',
            'delivered' => 'Your order has been delivered successfully',
            'cancelled' => 'Your order has been cancelled'
        ];

        $subject = 'Order Status Update - '.$orderData['order_number'];
        $message = $this->getOrderStatusTemplate([
            'customer_name' => $name,
            'order_number' => $orderData['order_number'],
            'new_status' => ucfirst($newStatus),
            'status_message' => $statusMessages[$newStatus] ?? 'Your order status has been updated',
            'order_total' => number_format($orderData['total_amount'], 2),
            'tracking_url' => $this->getAppUrl()."/orders/{$orderData['order_number']}/track",
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($email, $subject, $message);
    }

    /**
     * Send order cancellation email
     */
    public function sendOrderCancellation(array $orderData): bool
    {
        $shippingAddress = is_string($orderData['shipping_address']) ?
        json_decode($orderData['shipping_address'], true) :
        $orderData['shipping_address'];

        $customerName = $shippingAddress['first_name'].' '.$shippingAddress['last_name'];

        $subject = 'Order Cancelled - '.$orderData['order_number'];
        $message = $this->getCancellationTemplate([
            'customer_name' => $customerName,
            'order_number' => $orderData['order_number'],
            'order_total' => number_format($orderData['total_amount'], 2),
            'cancellation_reason' => $orderData['notes'] ?? 'Order cancelled by customer',
            'refund_info' => 'If payment was made, refund will be processed within 3-5 business days',
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($orderData['email'], $subject, $message);
    }

    /**
     * Send low stock alert to admin
     */
    public function sendLowStockAlert(array $productData): bool
    {
        $adminEmail = $_ENV['ADMIN_EMAIL'] ?? 'admin@example.com';

        $subject = 'Low Stock Alert - '.$productData['name'];
        $message = $this->getLowStockTemplate([
            'product_name' => $productData['name'],
            'product_sku' => $productData['sku'],
            'current_stock' => $productData['current_stock'],
            'min_stock_level' => $productData['min_stock_level'],
            'product_url' => $this->getAppUrl()."/products/{$productData['id']}",
            'app_name' => $this->config['from_name']
        ]);

        return $this->sendEmail($adminEmail, $subject, $message);
    }

    /**
     * Get order status update template
     */
    private function getOrderStatusTemplate(array $data): string
    {
        $template = '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Order Status Update</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #28a745; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .status-badge { display: inline-block; padding: 8px 16px; background: #28a745; color: white; border-radius: 20px; font-weight: bold; }
        .order-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
        .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{{app_name}}</h1>
        </div>
        <div class="content">
            <h2>Order Status Update</h2>
            <p>Dear {{customer_name}},</p>
            <p>{{status_message}}</p>
            <div class="order-details">
                <p><strong>Order Number:</strong> {{order_number}}</p>
                <p><strong>Status:</strong> <span class="status-badge">{{new_status}}</span></p>
                <p><strong>Order Total:</strong> ฿{{order_total}}</p>
            </div>
            <p style="text-align: center;">
                <a href="{{tracking_url}}" class="button">Track Your Order</a>
            </p>
            <p>Thank you for shopping with us!</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';

        foreach ($data as $key => $value) {
            $template = str_replace('{{'.$key.'}}', $value, $template);
        }

        return $template;
    }

    /**
     * Get cancellation email template
     */
    private function getCancellationTemplate(array $data): string
    {
        $template = '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Order Cancelled</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #dc3545; color: white; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .order-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{{app_name}}</h1>
        </div>
        <div class="content">
            <h2>Order Cancellation</h2>
            <p>Dear {{customer_name}},</p>
            <p>Your order has been cancelled as requested.</p>
            <div class="order-details">
                <p><strong>Order Number:</strong> {{order_number}}</p>
                <p><strong>Order Total:</strong> ฿{{order_total}}</p>
                <p><strong>Reason:</strong> {{cancellation_reason}}</p>
            </div>
            <p>{{refund_info}}</p>
            <p>We apologize for any inconvenience and hope to serve you again in the future.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';

        foreach ($data as $key => $value) {
            $template = str_replace('{{'.$key.'}}', $value, $template);
        }

        return $template;
    }

    /**
     * Get low stock alert template
     */
    private function getLowStockTemplate(array $data): string
    {
        $template = '
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Low Stock Alert</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #ffc107; color: #212529; padding: 20px; text-align: center; }
        .content { padding: 20px; background: #f9f9f9; }
        .alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
        .product-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
        .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
        .footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>{{app_name}} - Admin Alert</h1>
        </div>
        <div class="content">
            <h2>⚠️ Low Stock Alert</h2>
            <div class="alert">
                <p><strong>Action Required:</strong> The following product is running low on stock and needs to be restocked.</p>
            </div>
            <div class="product-details">
                <p><strong>Product:</strong> {{product_name}}</p>
                <p><strong>SKU:</strong> {{product_sku}}</p>
                <p><strong>Current Stock:</strong> {{current_stock}} units</p>
                <p><strong>Minimum Level:</strong> {{min_stock_level}} units</p>
            </div>
            <p style="text-align: center;">
                <a href="{{product_url}}" class="button">Manage Product</a>
            </p>
            <p>Please restock this item as soon as possible to avoid stockouts.</p>
        </div>
        <div class="footer">
            <p>&copy; 2024 {{app_name}}. All rights reserved.</p>
        </div>
    </div>
</body>
</html>';

        foreach ($data as $key => $value) {
            $template = str_replace('{{'.$key.'}}', $value, $template);
        }

        return $template;
    }
}