Passed
Push — master ( 73bfc2...a878a7 )
by Adrien
13:45 queued 10:48
created

Mailer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 7
dl 0
loc 9
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Service;
6
7
use Cake\Chronos\Chronos;
8
use Doctrine\ORM\EntityManager;
9
use Ecodev\Felix\Model\Message;
10
use Ecodev\Felix\Repository\LogRepository;
11
use Ecodev\Felix\Repository\MessageRepository;
12
use Exception;
13
use Laminas\Mail;
14
use Laminas\Mail\Transport\TransportInterface;
15
use Laminas\Mime\Message as MimeMessage;
16
use Laminas\Mime\Mime;
17
use Laminas\Mime\Part as MimePart;
18
19
/**
20
 * Service to send a message as an email.
21
 */
22
class Mailer
23
{
24
    /**
25
     * @var false|resource
26
     */
27
    private $lock;
28
29 1
    public function __construct(
30
        private readonly EntityManager $entityManager,
31
        private readonly MessageRepository $messageRepository,
32
        private readonly TransportInterface $transport,
33
        private readonly string $phpPath,
34
        private readonly ?string $toEmailOverride,
35
        private readonly string $fromEmail,
36
        protected string $fromName
37
    ) {
38
    }
39
40
    /**
41
     * Send a message asynchronously in a separate process.
42
     *
43
     * This should be the preferred way to send a message, unless if we are the cron.
44
     */
45
    public function sendMessageAsync(Message $message): void
46
    {
47
        // Be sure we have an ID before "forking" process
48
        if ($message->getId() === null) {
49
            $this->entityManager->flush();
50
        }
51
52
        $args = [
53
            (string) realpath('bin/send-message.php'),
54
            (string) $message->getId(),
55
        ];
56
57
        $escapedArgs = array_map('escapeshellarg', $args);
58
59
        $cmd = escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
60
        exec($cmd);
61
    }
62
63
    /**
64
     * Send a message.
65
     */
66 1
    public function sendMessage(Message $message): void
67
    {
68 1
        $mailMessage = $this->modelMessageToMailMessage($message);
69
70 1
        $email = $message->getEmail();
71 1
        $overriddenBy = '';
72 1
        if ($this->toEmailOverride) {
73
            $email = $this->toEmailOverride;
74
            $overriddenBy = ' overridden by ' . $email;
75
        }
76
77 1
        $recipient = $message->getRecipient();
78 1
        $recipientName = $recipient ? $recipient->getName() : null;
79 1
        if ($email) {
80 1
            $mailMessage->addTo($email, $recipientName);
81 1
            $this->transport->send($mailMessage);
82
        }
83
84 1
        $message->setDateSent(new Chronos());
85 1
        $this->entityManager->flush();
86
87 1
        $addressList = $mailMessage->getFrom();
88 1
        $addressList->rewind();
89 1
        echo 'email from ' . $addressList->current()->getEmail() . ' sent to: ' . $message->getEmail() . "\t" . $overriddenBy . "\t" . $message->getSubject() . PHP_EOL;
90
    }
91
92
    /**
93
     * Convert our model message to a mail message.
94
     */
95 1
    protected function modelMessageToMailMessage(Message $modelMessage): Mail\Message
96
    {
97
        // set Mime type html
98 1
        $htmlPart = new MimePart($modelMessage->getBody());
99 1
        $htmlPart->type = Mime::TYPE_HTML;
100 1
        $htmlPart->charset = 'UTF-8';
101 1
        $htmlPart->encoding = Mime::ENCODING_BASE64;
102
103 1
        $body = new MimeMessage();
104 1
        $body->setParts([$htmlPart]);
105
106 1
        $mailMessage = new Mail\Message();
107 1
        $mailMessage->setEncoding('UTF-8');
108 1
        $mailMessage->setSubject($modelMessage->getSubject());
109 1
        $mailMessage->setBody($body);
110
111 1
        $mailMessage->setFrom($this->fromEmail, $this->fromName);
112
113 1
        return $mailMessage;
114
    }
115
116
    /**
117
     * Send all messages that are not sent yet.
118
     */
119
    public function sendAllMessages(): void
120
    {
121
        $this->acquireLock();
122
123
        $messages = $this->messageRepository->getAllMessageToSend();
124
        foreach ($messages as $message) {
125
            $this->sendMessage($message);
126
        }
127
    }
128
129
    /**
130
     * Acquire an exclusive lock.
131
     *
132
     * This is to ensure only one mailer can run at any given time. This is to prevent sending the same email twice.
133
     */
134
    private function acquireLock(): void
135
    {
136
        $lockFile = 'data/tmp/mailer.lock';
137
        touch($lockFile);
138
        $this->lock = fopen($lockFile, 'r+b');
139
        if ($this->lock === false) {
140
            throw new Exception('Could not read lock file. This is not normal and might be a permission issue');
141
        }
142
143
        if (!flock($this->lock, LOCK_EX | LOCK_NB)) {
144
            $message = LogRepository::MAILER_LOCKED;
145
            _log()->info($message);
146
147
            echo $message . PHP_EOL;
148
            echo 'If the problem persist and another mailing is not in progress, try deleting ' . $lockFile . PHP_EOL;
149
150
            // Not getting the lock is not considered as error to avoid being spammed
151
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
152
        }
153
    }
154
}
155