Passed
Push — master ( 082bae...9693d7 )
by P.R.
02:22
created

MailerCommand::changeCompany()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 5
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Plaisio\Mail\Command;
5
6
use PHPMailer\PHPMailer\Exception;
7
use PHPMailer\PHPMailer\PHPMailer;
8
use Plaisio\C;
9
use Plaisio\CompanyResolver\UniCompanyResolver;
10
use Plaisio\Kernel\Nub;
11
use Psr\Log\LoggerInterface;
12
use SetBased\Exception\FallenException;
13
use SetBased\Exception\RuntimeException;
14
use Symfony\Component\Console\Command\Command;
15
16
/**
17
 * Abstract command for sending mail messages.
18
 */
19
abstract class MailerCommand extends Command
20
{
21
  //--------------------------------------------------------------------------------------------------------------------
22
  /**
23
   * The maximum number of unsent mails processed per batch.
24
   *
25
   * @var int
26
   */
27
  static protected $batchSize = 100;
28
29
  /**
30
   * The basename of the lock file.
31
   *
32
   * @var string
33
   */
34
  protected static $lockFilename = 'mailer.lock';
35
36
  /**
37
   * If true this command will terminate.
38
   *
39
   * @var bool
40
   */
41
  static protected $terminate = false;
42
43
  /**
44
   * The logger.
45
   *
46
   * @var LoggerInterface
47
   */
48
  protected $logger;
49
50
  /**
51
   * Array with domains for which we are authorized to send email.
52
   *
53
   * @var array
54
   */
55
  private $domains;
56
57
  //--------------------------------------------------------------------------------------------------------------------
58
  /**
59
   * Gets the list of domains for which we are authorized to send emails.
60
   */
61
  public function getAuthorizedDomains(): void
62
  {
63
    $domains = Nub::$DL->abcMailBackGetAuthorizedDomains();
64
65
    foreach ($domains as $domain)
66
    {
67
      $this->domains[mb_strtolower($domain['atd_domain_name'])] = true;
68
    }
69
  }
70
71
  //--------------------------------------------------------------------------------------------------------------------
72
  /**
73
   * Returns the path to the lock file.
74
   *
75
   * @return string
76
   *
77
   * @api
78
   * @since 1.0.0
79
   */
80
  public function lockFilePath(): string
81
  {
82
    return Nub::$dirs->lockDir().'/'.static::$lockFilename;
83
  }
84
85
  //--------------------------------------------------------------------------------------------------------------------
86
  /**
87
   * This mailer sends mail messages for all companies in the databases. Changes the company to the company of the
88
   * mail message currently being send.
89
   *
90
   * @param int $cmpId The ID of the new company.
91
   *
92
   * @return void
93
   *
94
   * @api
95
   * @since 1.0.0
96
   */
97
  protected function changeCompany(int $cmpId): void
98
  {
99
    if (Nub::$companyResolver->getCmpId()!=$cmpId)
100
    {
101
      Nub::$companyResolver = new UniCompanyResolver($cmpId);
102
    }
103
  }
104
105
  //--------------------------------------------------------------------------------------------------------------------
106
  /**
107
   * Connects to the MySQL instance.
108
   *
109
   * @return void
110
   *
111
   * @api
112
   * @since 1.0.0
113
   */
114
  abstract protected function connect(): void;
115
116
  //--------------------------------------------------------------------------------------------------------------------
117
  /**
118
   * Creates the lock file.
119
   */
120
  protected function createLockFile(): void
121
  {
122
    file_put_contents($this->lockFilePath(), getmypid());
123
  }
124
125
  //--------------------------------------------------------------------------------------------------------------------
126
  /**
127
   * Creates and initializes a PHPMailer object.
128
   *
129
   * This function might reuse an exiting PHPMailer object (with SMTPKeepAlive and clearing all addresses, headers,
130
   * attachments).
131
   *
132
   * @return PHPMailer
133
   *
134
   * @api
135
   * @since 1.0.0
136
   */
137
  abstract protected function createMailer(): PHPMailer;
138
139
  //--------------------------------------------------------------------------------------------------------------------
140
  /**
141
   * Disconnects from MySQL instance.
142
   *
143
   * @api
144
   * @since 1.0.0
145
   */
146
  protected function disconnect(): void
147
  {
148
    Nub::$DL->disconnect();
149
  }
150
151
  //--------------------------------------------------------------------------------------------------------------------
152
  /**
153
   * Removes the PID file.
154
   */
155
  protected function removeLockFile(): void
156
  {
157
    unlink($this->lockFilePath());
158
  }
159
160
  //--------------------------------------------------------------------------------------------------------------------
161
  /**
162
   * Sends a batch of mail messages.
163
   */
164
  protected function sendBatch(): void
165
  {
166
    $this->connect();
167
    Nub::$DL->begin();
168
169
    $this->getAuthorizedDomains();
170
171
    do
172
    {
173
      $messages = Nub::$DL->abcMailBackGetUnsentMessages(static::$batchSize);
174
      foreach ($messages as $message)
175
      {
176
        $this->sendMail($message);
177
      }
178
    } while (count($messages)==static::$batchSize && !self::$terminate);
179
180
    $this->disconnect();
181
  }
182
183
  //--------------------------------------------------------------------------------------------------------------------
184
  /**
185
   * Sets the message body.
186
   *
187
   * @param PHPMailer $mailer  The PHPMailer object.
188
   * @param array     $message The details of the mail message.
189
   *
190
   * @api
191
   * @since 1.0.0
192
   */
193
  protected function setBody(PHPMailer $mailer, array $message): void
194
  {
195
    $blob = Nub::$nub->getBlobStore()->getBlob($message['blb_id_body']);
196
197
    preg_match('/^([^;]*);\s*charset=(.*)$/', $blob['blb_mime_type'], $matches);
198
    if (sizeof($matches)!=3)
199
    {
200
      throw new RuntimeException("Invalid mime type '%s'", $blob['blb_mime_type']);
201
    }
202
    $type    = trim($matches[1]);
203
    $charset = trim($matches[2]);
204
205
    $mailer->isHTML(($type=='text/html'));
206
    $mailer->CharSet = $charset;
207
    $mailer->Body    = $blob['blb_data'];
208
  }
209
210
  //--------------------------------------------------------------------------------------------------------------------
211
  /**
212
   * Sets the transmitter of this mail message when the transmitter's address is from an unauthorized domain.
213
   *
214
   * @param PHPMailer $mailer  The PHPMailer object.
215
   * @param array     $message The details of the mail message.
216
   *
217
   * @return void
218
   *
219
   * @api
220
   * @since 1.0.0
221
   */
222
  abstract protected function setUnauthorizedFrom(PHPMailer $mailer, array $message): void;
223
224
  //--------------------------------------------------------------------------------------------------------------------
225
  /**
226
   * Adds all headers of a mail message to a PHPMailer object.
227
   *
228
   * @param PHPMailer $mailer  The PHPMailer object.
229
   * @param array     $message The details of the mail message.
230
   *
231
   * @throws Exception
232
   */
233
  private function addHeaders(PHPMailer $mailer, array $message): void
234
  {
235
    $headers = Nub::$DL->abcMailBackMessageGetHeaders($message['cmp_id'], $message['elm_id']);
236
237
    $replyTo = false;
238
    foreach ($headers as $header)
239
    {
240
      switch ($header['ehd_id'])
241
      {
242
        case C::EHD_ID_ATTACHMENT:
243
          $blob = Nub::$nub->getBlobStore()->getBlob($header['blb_id']);
244
          $mailer->addStringAttachment($blob['blb_data'], $blob['blb_filename']);
245
          break;
246
247
        case C::EHD_ID_BCC:
248
          $mailer->addBCC($header['emh_address'], $header['emh_name']);
249
          break;
250
251
        case C::EHD_ID_CC:
252
          $mailer->addCC($header['emh_address'], $header['emh_name']);
253
          break;
254
255
        case C::EHD_ID_CONFIRM_READING_TO:
256
          $mailer->ConfirmReadingTo = $header['emh_address'];
257
          break;
258
259
        case C::EHD_ID_CUSTOM_HEADER:
260
          $mailer->addCustomHeader($header['emh_value']);
261
          break;
262
263
        case C::EHD_ID_MESSAGE_ID:
264
          $mailer->MessageID = $header['emh_value'];
265
          break;
266
267
        case C::EHD_ID_REPLY_TO:
268
          $mailer->addReplyTo($header['emh_address'], $header['emh_name']);
269
          $replyTo = true;
270
          break;
271
272
        case C::EHD_ID_SENDER:
273
          $mailer->Sender = $header['emh_address'];
274
          break;
275
276
        case C::EHD_ID_TO:
277
          $mailer->addAddress($header['emh_address'], $header['emh_name']);
278
          break;
279
280
        default:
281
          throw new FallenException('ehd_id', $header['ehd_id']);
282
      }
283
284
      // Implicitly add ReplyTo header if not set explicitly.
285
      if (!$replyTo)
286
      {
287
        $mailer->addReplyTo($message['elm_address'], $message['elm_name']);
288
      }
289
    }
290
  }
291
292
  //--------------------------------------------------------------------------------------------------------------------
293
  /**
294
   * Sends actually the email message.
295
   *
296
   * @param array $message The details of the mail message.
297
   */
298
  private function sendMail(array $message): void
299
  {
300
    try
301
    {
302
      $this->logger->notice(sprintf('Sending message elm_id=%d', $message['elm_id']));
303
304
      $this->changeCompany($message['cmp_id']);
305
306
      Nub::$DL->abcMailBackMessageMarkAsPickedUp($message['cmp_id'], $message['elm_id']);
307
      Nub::$DL->commit();
308
309
      if ($message['elm_number_from']!=1)
310
      {
311
        throw new RuntimeException('PHPMailer does not support multiple from addresses');
312
      }
313
314
      $mailer          = $this->createMailer();
315
      $mailer->Subject = $message['elm_subject'];
316
317
      $this->setBody($mailer, $message);
318
      $this->setFrom($mailer, $message);
319
      $this->addHeaders($mailer, $message);
320
321
      $success = $mailer->send();
322
      if ($success)
323
      {
324
        Nub::$DL->abcMailBackMessageMarkAsSent($message['cmp_id'], $message['elm_id']);
325
        Nub::$DL->commit();
326
      }
327
      else
328
      {
329
        $this->logger->error(sprintf('Sending message elm_id=%d failed: %s', $message['elm_id'], $mailer->ErrorInfo));
330
      }
331
    }
332
    catch (\Exception $exception)
333
    {
334
      $this->logger->critical($exception);
335
    }
336
  }
337
338
  //--------------------------------------------------------------------------------------------------------------------
339
  /**
340
   * Sets the transmitter of this mail message.
341
   *
342
   * @param PHPMailer $mailer  The PHPMailer object.
343
   * @param array     $message The details of the mail message.
344
   */
345
  private function setFrom(PHPMailer $mailer, array $message): void
346
  {
347
    $domain = mb_strtolower(substr($message['elm_address'], strpos($message['elm_address'], '@') + 1));
348
    if (isset($this->domains[$domain]))
349
    {
350
      // We are authorized to send mail messages from this domain.
351
      $mailer->From     = $message['elm_address'];
352
      $mailer->FromName = $message['elm_name'];
353
    }
354
    else
355
    {
356
      // We are not authorized to send mail messages from this domain.
357
      $this->setUnauthorizedFrom($mailer, $message);
358
    }
359
  }
360
361
  //--------------------------------------------------------------------------------------------------------------------
362
}
363
364
//----------------------------------------------------------------------------------------------------------------------
365