Your IP : 216.73.217.79


Current Path : /var/www/v3.cesa.co.za/src/MailQueueBundle/Service/
Upload File :
Current File : /var/www/v3.cesa.co.za/src/MailQueueBundle/Service/MailQueueService.php

<?php

namespace App\MailQueueBundle\Service;

use App\MailQueueBundle\Entity\BulkMailMessage;
use App\MailQueueBundle\Entity\BulkMailToSend;
use App\MailQueueBundle\Model\MailQueueMessageRow;
use App\MailQueueBundle\Model\MailQueueSummary;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MailQueueService
{
    public function __construct(
        #[Autowire(service: 'doctrine.orm.mail_entity_manager')]
        private readonly EntityManagerInterface $mailEntityManager,
    ) {
    }

    /**
     * @param array{pending: int|string, sent: int|string, failed: int|string} $counts
     *
     * @return array{pending: int, sent: int, failed: int}
     */
    public static function aggregateSendCounts(array $counts): array
    {
        return [
            'pending' => (int) ($counts['pending'] ?? 0),
            'sent' => (int) ($counts['sent'] ?? 0),
            'failed' => (int) ($counts['failed'] ?? 0),
        ];
    }

    public static function statusLabel(int $sent): string
    {
        return match ($sent) {
            BulkMailToSend::STATUS_SENT => 'Sent',
            BulkMailToSend::STATUS_FAILED => 'Failed',
            default => 'Pending',
        };
    }

    public static function statusCssClass(int $sent): string
    {
        return match ($sent) {
            BulkMailToSend::STATUS_SENT => 'success',
            BulkMailToSend::STATUS_FAILED => 'danger',
            default => 'warning',
        };
    }

    public function getGlobalSummary(): MailQueueSummary
    {
        $statusCounts = $this->mailEntityManager->createQueryBuilder()
            ->select('t.sent AS status, COUNT(t.toSendId) AS cnt')
            ->from(BulkMailToSend::class, 't')
            ->groupBy('t.sent')
            ->getQuery()
            ->getResult();

        $pending = 0;
        $sent = 0;
        $failed = 0;
        foreach ($statusCounts as $row) {
            $status = (int) $row['status'];
            $count = (int) $row['cnt'];
            if ($status === BulkMailToSend::STATUS_SENT) {
                $sent = $count;
            } elseif ($status === BulkMailToSend::STATUS_FAILED) {
                $failed = $count;
            } else {
                $pending += $count;
            }
        }

        $campaignsInProgress = (int) $this->mailEntityManager->createQueryBuilder()
            ->select('COUNT(m.messageId)')
            ->from(BulkMailMessage::class, 'm')
            ->where('m.sendComplete = 0')
            ->getQuery()
            ->getSingleScalarResult();

        return new MailQueueSummary($campaignsInProgress, $pending, $sent, $failed);
    }

    /**
     * @return list<MailQueueMessageRow>
     */
    public function listMessages(int $limit = 100): array
    {
        $qb = $this->mailEntityManager->createQueryBuilder();
        $qb
            ->select(
                'm.messageId AS messageId',
                'm.subject AS subject',
                'm.sentToGroup AS sentToGroup',
                'm.dateSent AS dateSent',
                'm.numRecipients AS numRecipients',
                'm.sendComplete AS sendComplete',
                'm.dateComplete AS dateComplete',
                'SUM(CASE WHEN t.sent = 0 THEN 1 ELSE 0 END) AS pending',
                'SUM(CASE WHEN t.sent = 1 THEN 1 ELSE 0 END) AS sent',
                'SUM(CASE WHEN t.sent = 2 THEN 1 ELSE 0 END) AS failed'
            )
            ->from(BulkMailMessage::class, 'm')
            ->leftJoin('m.toSendRows', 't')
            ->groupBy('m.messageId')
            ->orderBy('m.messageId', 'DESC')
            ->setMaxResults($limit);

        $rows = [];
        foreach ($qb->getQuery()->getResult(Query::HYDRATE_ARRAY) as $row) {
            $counts = self::aggregateSendCounts([
                'pending' => $row['pending'] ?? 0,
                'sent' => $row['sent'] ?? 0,
                'failed' => $row['failed'] ?? 0,
            ]);
            $rows[] = new MailQueueMessageRow(
                (int) $row['messageId'],
                $row['subject'] ?? null,
                $row['sentToGroup'] ?? null,
                $row['dateSent'] ?? null,
                isset($row['numRecipients']) ? (int) $row['numRecipients'] : null,
                (int) ($row['sendComplete'] ?? 0) === 1,
                $row['dateComplete'] ?? null,
                $counts['pending'],
                $counts['sent'],
                $counts['failed'],
            );
        }

        return $rows;
    }

    public function findMessage(int $messageId): ?BulkMailMessage
    {
        return $this->mailEntityManager->find(BulkMailMessage::class, $messageId);
    }

    /**
     * Fetch the message body for a campaign (template on Messages, or first personalized ToSend row).
     */
    public function findMessageBody(int $messageId): ?string
    {
        $connection = $this->mailEntityManager->getConnection();

        $row = $connection->fetchAssociative(
            'SELECT * FROM Messages WHERE MessageID = :id LIMIT 1',
            ['id' => $messageId]
        );
        $body = self::extractMessageBodyFromRow(is_array($row) ? $row : null);
        if ($body !== null) {
            return $body;
        }

        $toSendBody = $connection->fetchOne(
            'SELECT Message FROM ToSend
             WHERE MessageID = :id AND Message IS NOT NULL AND TRIM(Message) <> \'\'
             ORDER BY ToSendID ASC LIMIT 1',
            ['id' => $messageId]
        );

        if (is_string($toSendBody) && trim($toSendBody) !== '') {
            return $toSendBody;
        }

        return null;
    }

    /**
     * Build a full HTML document for safe display inside a sandboxed iframe srcdoc attribute.
     */
    public static function buildMessageBodySrcdoc(?string $body): string
    {
        if ($body === null || trim($body) === '') {
            return '';
        }

        if (self::looksLikeHtmlMessage($body)) {
            if (stripos($body, '<html') !== false) {
                return $body;
            }

            return '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>' . $body . '</body></html>';
        }

        return '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:Arial,sans-serif;font-size:13px;margin:8px;white-space:pre-wrap;">'
            . htmlspecialchars($body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
            . '</body></html>';
    }

    /**
     * @param array<string, mixed>|null $row
     */
    private static function extractMessageBodyFromRow(?array $row): ?string
    {
        if ($row === null || count($row) === 0) {
            return null;
        }

        $normalized = [];
        foreach ($row as $key => $value) {
            if (!is_string($key) && !is_int($key)) {
                continue;
            }
            $normalized[strtolower((string) $key)] = $value;
        }

        $candidates = [
            'message',
            'messagebody',
            'messagetext',
            'body',
            'htmlbody',
            'textbody',
            'bodyhtml',
            'bodytext',
            'mailbody',
            'mailtext',
        ];

        foreach ($candidates as $column) {
            if (!isset($normalized[$column]) || !is_string($normalized[$column])) {
                continue;
            }
            if (trim($normalized[$column]) !== '') {
                return $normalized[$column];
            }
        }

        return null;
    }

    private static function looksLikeHtmlMessage(string $body): bool
    {
        return preg_match('/<\s*(html|body|head|p|div|br|table|span|a|ul|ol|li)\b/i', $body) === 1;
    }

    /**
     * @return Query
     */
    public function getToSendQueryForMessage(int $messageId): Query
    {
        return $this->mailEntityManager->createQueryBuilder()
            ->select('t')
            ->from(BulkMailToSend::class, 't')
            ->innerJoin('t.message', 'm')
            ->where('m.messageId = :messageId')
            ->setParameter('messageId', $messageId)
            ->orderBy('t.sent', 'ASC')
            ->addOrderBy('t.toSendId', 'ASC')
            ->getQuery();
    }
}