Failed Conditions
Push — master ( 398dce...01f535 )
by Adrien
02:21
created

Mailer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 7
dl 0
loc 16
ccs 8
cts 8
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
    /**
30
     * @var EntityManager
31
     */
32
    private $entityManager;
33
34
    /**
35
     * @var TransportInterface
36
     */
37
    private $transport;
38
39
    /**
40
     * @var null|string
41
     */
42
    private $toEmailOverride;
43
44
    /**
45
     * @var string
46
     */
47
    private $fromEmail;
48
49
    /**
50
     * @var string
51
     */
52
    private $phpPath;
53
54
    /**
55
     * @var string
56
     */
57
    private $fromName;
58
59
    /**
60
     * @var MessageRepository
61
     */
62
    private $messageRepository;
63
64 1
    public function __construct(
65
        EntityManager $entityManager,
66
        MessageRepository $messageRepository,
67
        TransportInterface $transport,
68
        ?string $toEmailOverride,
69
        string $fromEmail,
70
        string $phpPath,
71
        string $fromName
72
    ) {
73 1
        $this->entityManager = $entityManager;
74 1
        $this->messageRepository = $messageRepository;
75 1
        $this->transport = $transport;
76 1
        $this->toEmailOverride = $toEmailOverride;
77 1
        $this->fromEmail = $fromEmail;
78 1
        $this->phpPath = $phpPath;
79 1
        $this->fromName = $fromName;
80 1
    }
81
82
    /**
83
     * Send a message asynchronously in a separate process.
84
     *
85
     * This should be the preferred way to send a message, unless if we are the cron.
86
     *
87
     * @param Message $message
88
     */
89
    public function sendMessageAsync(Message $message): void
90
    {
91
        // Be sure we have an ID before "forking" process
92
        if ($message->getId() === null) {
93
            $this->entityManager->flush();
94
        }
95
96
        $args = [
97
            realpath('bin/send-message.php'),
98
            $message->getId(),
99
        ];
100
101
        $escapedArgs = array_map('escapeshellarg', $args);
102
103
        $cmd = escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
104
        exec($cmd);
105
    }
106
107
    /**
108
     * Send a message
109
     *
110
     * @param Message $message
111
     */
112 1
    public function sendMessage(Message $message): void
113
    {
114 1
        $mailMessage = $this->modelMessageToMailMessage($message);
115
116 1
        $email = $message->getEmail();
117 1
        $overriddenBy = '';
118 1
        if ($this->toEmailOverride) {
119
            $email = $this->toEmailOverride;
120
            $overriddenBy = ' overridden by ' . $email;
121
        }
122
123 1
        $recipient = $message->getRecipient();
124 1
        $recipientName = $recipient ? $recipient->getName() : null;
125 1
        if ($email) {
126 1
            $mailMessage->addTo($email, $recipientName);
127 1
            $this->transport->send($mailMessage);
128
        }
129
130 1
        $message->setDateSent(new Chronos());
131 1
        $this->entityManager->flush();
132
133 1
        $addressList = $mailMessage->getFrom();
134 1
        $addressList->rewind();
135 1
        echo 'email from ' . $addressList->current()->getEmail() . ' sent to: ' . $message->getEmail() . "\t" . $overriddenBy . "\t" . $message->getSubject() . PHP_EOL;
136 1
    }
137
138
    /**
139
     * Convert our model message to a mail message
140
     *
141
     * @param Message $modelMessage
142
     *
143
     * @return Mail\Message
144
     */
145 1
    private function modelMessageToMailMessage(Message $modelMessage): Mail\Message
146
    {
147
        // set Mime type html
148 1
        $htmlPart = new MimePart($modelMessage->getBody());
149 1
        $htmlPart->type = Mime::TYPE_HTML;
150 1
        $htmlPart->charset = 'UTF-8';
151 1
        $htmlPart->encoding = Mime::ENCODING_BASE64;
152
153 1
        $body = new MimeMessage();
154 1
        $body->setParts([$htmlPart]);
155
156 1
        $mailMessage = new Mail\Message();
157 1
        $mailMessage->setEncoding('UTF-8');
158 1
        $mailMessage->setSubject($modelMessage->getSubject());
159 1
        $mailMessage->setBody($body);
160
161 1
        $mailMessage->setFrom($this->fromEmail, $this->fromName);
162
163 1
        return $mailMessage;
164
    }
165
166
    /**
167
     * Send all messages that are not sent yet
168
     */
169
    public function sendAllMessages(): void
170
    {
171
        $this->acquireLock();
172
173
        $messages = $this->messageRepository->getAllMessageToSend();
174
        foreach ($messages as $message) {
175
            $this->sendMessage($message);
176
        }
177
    }
178
179
    /**
180
     * Acquire an exclusive lock
181
     *
182
     * This is to ensure only one mailer can run at any given time. This is to prevent sending the same email twice.
183
     */
184
    private function acquireLock(): void
185
    {
186
        $lockFile = 'data/tmp/mailer.lock';
187
        touch($lockFile);
188
        $this->lock = fopen($lockFile, 'r+');
189
        if ($this->lock === false) {
190
            throw new Exception('Could not read lock file. This is not normal and might be a permission issue');
191
        }
192
193
        if (!flock($this->lock, LOCK_EX | LOCK_NB)) {
194
            $message = LogRepository::MAILER_LOCKED;
195
            _log()->info($message);
196
197
            echo $message . PHP_EOL;
198
            echo 'If the problem persist and another mailing is not in progress, try deleting ' . $lockFile . PHP_EOL;
199
200
            // Not getting the lock is not considered as error to avoid being spammed
201
            die();
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...
202
        }
203
    }
204
}
205