1: <?php
2: namespace TIC\MailBundle\Service;
3:
4: use Doctrine\ORM\EntityManagerInterface;
5: #use Symfony\Component\Mailer\MailerInterface;
6: use Symfony\Component\Mailer\Transport\TransportInterface;
7: use Symfony\Component\Routing\RouterInterface;
8: use Symfony\Contracts\Translation\TranslatorInterface;
9: use Symfony\Component\HttpFoundation\RequestStack;
10:
11: use Twig\Loader\ArrayLoader;
12:
13: use Symfony\Component\Mime\Email;
14: use Symfony\Component\Mime\Address;
15: use Symfony\Component\Mailer\SentMessage;
16: use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
17:
18: use TIC\MailBundle\Entity\Template;
19: use TIC\MailBundle\Entity\Maillog;
20:
21: /**
22: * Service d'envoi de notifications par email.
23: * Utilisation du transport mailer Symfony et Twig d'après les modèles définis dans le bundle.
24: */
25: class MailerService
26: {
27: protected $em;
28: # protected $mailer;
29: protected $transport;
30: protected $router;
31: protected $translator;
32: protected $requestStack;
33: protected $config;
34:
35: protected $locales;
36: protected $locale_orig;
37: public $locale;
38: public $twig;
39: protected $lastError; // conserve le dernier message d'erreur
40: protected $lastEvent; // conserve l'id de la dernière entrée du journal
41:
42: const ConfKeys = ['maillog','smsdomain','fromdomains','fromname','fromaddr','return','admins','locales','formats'];
43:
44: public function __construct(
45: EntityManagerInterface $em,
46: # MailerInterface $mailer,
47: TransportInterface $transport,
48: RouterInterface $router,
49: TranslatorInterface $translator,
50: RequestStack $requestStack,
51: array $config)
52: {
53: $this->em = $em;
54: # $this->mailer = $mailer;
55: $this->transport = $transport;
56: $this->router = $router;
57: $this->translator = $translator;
58: $this->requestStack = $requestStack;
59: $this->config = \array_merge(\array_fill_keys(self::ConfKeys, null), $config);
60: $this->initLocale(); // détection et backup de la locale en cours
61: }
62:
63: /**
64: * Initialise la locale pour la génération des messages (auto ou forcé).
65: *
66: * @param string $locale Spécifie la locale à utiliser (optionnel)
67: * @return string Retourne la valeur de la locale configurée
68: */
69: public function initLocale(string $locale = null): ?string
70: {
71: // récupération de la locale en cours par défaut
72: if ($locale === null) {
73: $request = $this->requestStack->getCurrentRequest();
74: if (\is_object($request)) $locale = $request->getLocale();
75: # if ($locale === null) $locale = $this->container->getParameter('kernel.default_locale');
76: if ($this->locale_orig === null) $this->locale_orig = $locale;
77: }
78:
79: // màj de la locale pour les filtres I18n (trans...)
80: if ($locale !== null) $this->translator->setLocale($locale);
81:
82: return $this->locale = $locale;
83: }
84:
85: /**
86: * Réinitialise l'environnement avec la locale d'origine (auto ou forcé).
87: *
88: * @param string $locale Spécifie la locale à restaurer (optionnel)
89: */
90: public function restoreLocale(string $locale = null): void
91: {
92: if ($locale === null) $locale = $this->locale_orig;
93: if ($locale !== null) {
94: // màj de la locale pour les filtres Intl (localizeddate...)
95: \Symfony\Component\Intl\Locale::setDefault($locale);
96: // màj de la locale pour les filtres I18n (trans...)
97: $this->translator->setLocale($locale);
98: }
99: }
100:
101: /**
102: * Définition de l'environnement Twig (contenus des templates & ajout des extensions utiles).
103: * @TODO transformations/adaptations dans les contenus des templates ?
104: *
105: * @param array $contents Contenus des vues twig pour 'subject', 'bodyText', 'bodyHtml' et 'bodySms'
106: */
107: protected function initTwig(array $contents): void
108: {
109: // vérification que les 4 vues twig attendues existent (vide par défaut)
110: foreach (array('subject', 'bodyText', 'bodyHtml', 'bodySms') as $view) {
111: if (! isset($contents[$view])) $contents[$view] = '';
112: }
113:
114: // création de l'environnement twig avec les contenus
115: $this->twig = new \Twig\Environment(new ArrayLoader($contents));
116:
117: // ajout des extensions twig utilisables
118: $this->twig->addExtension(new \Twig\Extra\Intl\IntlExtension());
119: # $this->twig->addExtension(new \Twig\Extensions\Extension\I18n());
120: # $this->twig->addExtension(new \Twig\Extensions\Extension\Text());
121:
122: $this->twig->addExtension(new \Symfony\Bridge\Twig\Extension\TranslationExtension($this->translator));
123: $this->twig->addExtension(new \Symfony\Bridge\Twig\Extension\RoutingExtension($this->router->getGenerator()));
124: # $this->twig->addExtension(new \Symfony\Bridge\Twig\Extension\AssetExtension($this->container->get('assets.packages')));
125: # $this->twig->addExtension(new \Symfony\Bridge\Twig\Extension\HttpFoundationExtension($this->requestStack));
126:
127: // TIC Core extensions
128: $this->twig->addExtension(new \TIC\TwigBundle\Extension\DatetimeExtension());
129: $this->twig->addExtension(new \TIC\TwigBundle\Extension\FormatExtension());
130: # $this->twig->addExtension(new \TIC\TwigBundle\Extension\RouterExtension());
131:
132: // màj de la locale pour les filtres Intl (localizeddate...)
133: \Symfony\Component\Intl\Locale::setDefault($this->locale);
134: }
135:
136: /**
137: * Préparation d'un message à envoyer (objet Symfony Email & environnement Twig).
138: *
139: * @param string $ref Référence du modèle de notification (cf tic_mail.templates dans services.yaml)
140: * @param string|array $to Adresses mail des destinataires (chaine avec virgule en séparateur acceptée)
141: * @param string|array $pjs Pièces-jointes (chemin ou structure {'name':, 'type':, 'data':}, seul ou en liste)
142: * @param string $locale Spécifie la locale à utiliser (optionnel)
143: * @param boolean $sms Indique s'il s'agit d'un envoi par SMS plutôt que par SMTP
144: * @return Email $message Instance du message (Symfony Email) initialisée selon le modèle de notification
145: * (ou null si aucun modèle de notification correspondant actif ou exception)
146: */
147: public function prepare(string $ref, mixed $to = array(), mixed $pjs = array(), string $locale = null, bool $sms = false): ?Email
148: {
149: $this->lastError = null;
150: $this->lastEvent = null;
151: $message = null;
152: try {
153: // nouvelle instance de message
154: $message = new Email();
155: $message->getHeaders()->addTextHeader('X-TIC-Template', $ref);
156:
157: // recherche du modèle de notification
158: $template = $this->em->getRepository(Template::class)->findOneByRef($ref);
159: if (! \is_object($template)) return null;
160: if (! $template->getEnabled()) return null;
161:
162: // initialisation de l'environnement twig
163: if ($locale !== null) $this->initLocale($locale);
164: $this->initTwig($template->getContents($this->locale));
165:
166: // nom et adresse de l'expéditeur (bdd sinon parameters)
167: $sender = $template->getSender();
168: if (! empty($sender) && \preg_match('/^("?([^"]+)"?\s+)?<?\s*([^\s<@]+@[^@>\s]+)\s*>?$/', $sender, $match)) {
169: $from_name = isset($match[1]) ? $match[2] : null;
170: $from_addr = $match[3];
171: } else {
172: $from_name = $this->config['fromname'];
173: $from_addr = $this->config['fromaddr'];
174: }
175: $message->from(($from_name === null) ? $from_addr : new Address($from_addr, $from_name));
176:
177: // spécification d'une adresse de retour
178: $return = $template->getReturn();
179: if (empty($return)) $return = $this->config['return'];
180: if (! empty($return)) $message->returnPath($return);
181:
182: // ajout des destinataires
183: $this->addDest($message, $to, false, $sms);
184:
185: // ajout des destinataires en copie cachée (sauf envoi sms)
186: if (empty($sms)) {
187: // copie aux administrateurs ?
188: if ($template->getBccAdmins()) $this->addDest($message, $this->config['admins'], true);
189: // copies supplémentaires ?
190: $this->addDest($message, $template->getBccMore(), true);
191: }
192:
193: // attachement de l'éventuelle pièce jointe
194: if (empty($sms)) {
195: if (! \is_array($pjs) || isset($pjs['data'])) $pjs = array($pjs);
196: foreach ($pjs as $pj) $this->attach($message, $pj);
197: }
198:
199: return $message;
200: }
201: catch (\Exception $e) {
202: $this->logger($ref, $message, -3, $e->getMessage(), $sms);
203: return null;
204: }
205: }
206:
207: /**
208: * Indique les informations de l'expéditeur sur un message.
209: * (adresse du "From:" et "Return-Path:" préservée si le domaine du $sender n'est pas dans $config['fromdomains'])
210: *
211: * @param Email $message Instance du message (Symfony Email) auquel ajouter les destinataires
212: * @param string $sender Adresse de l'expéditeur à utiliser (peut contenir une partie "nom" devant)
213: */
214: public function setSender(Email &$message, string $sender): void
215: {
216: if (! \preg_match('/^("?([^"]+)"?\s+)?<?\s*([^\s<@]+@[^@>\s]+)\s*>?$/', $sender, $match)) return;
217: $from_name = isset($match[1]) ? $match[2] : "";
218: $from_addr = $match[3];
219: list($from_user, $from_host) = \explode('@', $from_addr . '@');
220:
221: // domaine connu : utilisation totale de l'expéditeur en remplacement (entêtes "From:" et "Return-Path:")
222: if (\is_array($this->config['fromdomains']) && \in_array(\strtolower($from_host), $this->config['fromdomains'])) {
223: $message->from(new Address($from_addr, $from_name));
224: $message->returnPath($from_addr);
225: }
226: // domaine autre : remplace uniquement la partie "nom" du "From:" (mais pas l'adresse) et ajout sur "Reply-to:"
227: else {
228: $message->replyTo(new Address($from_addr, $from_name));
229: $from_orig = \current($message->getFrom());
230: if ($from_orig) $message->from(new Address($from_orig->getAddress(), empty($from_name) ? $from_user : $from_name));
231: }
232: }
233:
234: /**
235: * Ajout de destinataires sur un message.
236: * @see https://symfony.com/doc/current/mailer.html#email-addresses
237: *
238: * @param Email $message Instance du message (Symfony Email) auquel ajouter les destinataires
239: * @param array|string $dest Liste d'adresses email (si chaine, virgule en séparateur)
240: * @param boolean $bcc Ajout des destinataires en copie cachée (Bcc, sinon To)
241: * @param boolean $sms Destinataires pour SMS (numéros de téléphone avec nom de domaine spécial)
242: */
243: public function addDest(Email &$message, mixed $dest, bool $bcc = false, bool $sms = false): void
244: {
245: if ($dest === null) return;
246: if (! \is_array($dest)) $dest = \explode(',', $dest);
247:
248: if ($sms) $smsdomain = $this->config['smsdomain'];
249:
250: foreach ($dest as $addr) {
251: $addr = \trim($addr);
252: if ($sms) $addr = \preg_replace('/[^\d\w\.\-@]/', '', \preg_replace('/^\+/', 'p', $addr));
253: if ($addr === '') continue;
254: if (isset($smsdomain) && (\strpos($addr, '@') === FALSE)) $addr.= '@' . $smsdomain;
255:
256: if ($bcc)
257: $message->addBcc($addr);
258: else
259: $message->addTo($addr);
260: }
261: }
262:
263: /**
264: * Ajout d'un fichier en pièce-jointe d'un message.
265: * @see https://symfony.com/doc/current/mailer.html#file-attachments
266: *
267: * @param Email $message Instance du message (Symfony Email) à compléter
268: * @param array|string $pj Pièce-jointe (structure {'name':, 'type':, 'data':} ou simple chemin)
269: */
270: public function attach(Email &$message, mixed $pj): void
271: {
272: if (\is_array($pj)) {
273: $message->attach( $pj['data'], $pj['name'], $pj['type'] );
274: } elseif (! empty($pj)) {
275: $message->attachFromPath($pj, null, 'application/octet-stream');
276: }
277: }
278:
279: /**
280: * Détection des images intégrées en base64 dans du contenu HTML pour extraction et conversion en attachements).
281: * @see https://symfony.com/doc/current/mailer.html#embedding-images
282: *
283: * @param Email $message Instance du message (Symfony Email) à compléter
284: * @param string $html Contenu HTML à analyser pour traiter les images à extraire et attacher
285: * @return string Contenu HTML avec les tag <img> modifiés (data base 64 => cid interne)
286: */
287: public function embedBase64HtmlImages(Email &$message, string $html): string
288: {
289: if (empty($html)) return '';
290: $cache_img = array();
291: return preg_replace_callback(
292: '|<img([^>]*) src="data:image/(\w+);base64,([a-zA-Z0-9/+]+=*)"|i',
293: function($matches) use ($message, &$cache_img){
294: $sign = \md5($matches[3]);
295: if (! \array_key_exists($sign, $cache_img)) {
296: $type = $matches[2];
297: $name = \sprintf('internal_img_%03d.%s', \count($cache_img) + 1, $type);
298: $message->embed(\base64_decode($matches[3]), $name, 'image/'.$type);
299: $cache_img[$sign] = $name;
300: }
301: return \sprintf('<img%s src="%s"', $matches[1], $cache_img[$sign]);
302: },
303: $html
304: );
305: }
306:
307: /**
308: * Composition du contenu textuel d'un message (rendu des vues Twig).
309: * @see https://symfony.com/doc/current/mailer.html#message-contents
310: *
311: * @param Email $message Instance du message (Symfony Email) à compléter
312: * @param array $data Liste clé/valeur pour les substitutions des templates
313: * @param string $ref Référence du modèle de notification (pour info dans les logs ; false pour désactiver les logs)
314: * @return boolean Vrai en cas de succès, faux si une exception a été attrapée
315: */
316: public function compose(Email &$message, array $data = array(), string $ref = null): bool
317: {
318: $this->lastError = null;
319: $this->lastEvent = null;
320: try {
321: // génération du sujet
322: $subject = $this->twig->render('subject', $data);
323: $subject = \html_entity_decode($subject, ENT_QUOTES, 'UTF-8');
324: $message->subject($subject);
325:
326: // liste des formats autorisés par la configuration (HTML et TEXT par défaut)
327: $formats = $this->config['formats'];
328: if (empty($formats)) $formats = array('HTML', 'TEXT');
329:
330: // génération du contenu text/plain
331: $bodyText = \in_array('TEXT', $formats) ? $this->twig->render('bodyText', $data) : '';
332: $bodyText = \html_entity_decode($bodyText, ENT_QUOTES, 'UTF-8');
333:
334: // génération du contenu text/html (avec conversion des tags img base64 en attachements)
335: $bodyHtml = \in_array('HTML', $formats) ? $this->twig->render('bodyHtml', $data) : '';
336: $bodyHtml = $this->embedBase64HtmlImages($message, $bodyHtml);
337:
338: // intégration du ou des contenus multipart
339: if (empty($bodyHtml)) {
340: $message->text($bodyText);
341: } else {
342: $message->html($bodyHtml);
343: if (! empty($bodyText)) $message->text($bodyText);
344: }
345: return true;
346: }
347: catch (\Exception $e) {
348: $this->logger($ref, $message, -2, $e->getMessage(), false);
349: return false;
350: }
351: }
352:
353: /**
354: * Composition du contenu SMS d'un message (rendu des vues Twig).
355: *
356: * @param Email $message Instance du message (Symfony Email) à compléter
357: * @param array $data Liste clé/valeur pour les substitutions des templates
358: * @param string $ref Référence du modèle de notification (pour info dans les logs ; false pour désactiver les logs)
359: * @return boolean Vrai en cas de succès, faux si une exception a été attrapée
360: */
361: public function composeSMS(Email &$message, array $data = array(), string $ref = null): bool
362: {
363: $this->lastError = null;
364: $this->lastEvent = null;
365: try {
366: // vérification que le format SMS est autorisé par la configuration
367: $formats = $this->config['formats'];
368: if (! \in_array('SMS', $formats)) throw new \Exception("SMS format not available!");
369:
370: // génération du sujet
371: $subject = $this->twig->render('subject', $data);
372: $subject = \html_entity_decode($subject, ENT_QUOTES, 'UTF-8');
373: $message->subject($subject);
374:
375: // génération du texte
376: $bodySms = $this->twig->render('bodySms', $data);
377: $bodySms = \html_entity_decode($bodySms, ENT_QUOTES, 'UTF-8');
378: $message->text($bodySms);
379:
380: return true;
381: }
382: catch (\Exception $e) {
383: $this->logger($ref, $message, -2, $e->getMessage(), true);
384: return false;
385: }
386: }
387:
388: /**
389: * Envoi un message préparé.
390: * @see https://symfony.com/doc/current/mailer.html#debugging-emails
391: *
392: * @param Email $message Instance du message (Symfony Email) à envoyer
393: * @param mixed $ref Référence du modèle de notification (pour info dans les logs)
394: * Null (défaut) pour la récupérer automatiquement d'après l'entête X-TIC-Template
395: * False pour désactiver les logs en cas de succès (mais toujours sur Exception)
396: * @param boolean $sms Indique s'il s'agit d'un envoi par SMS plutôt que par SMTP
397: * @return integer Nombre de destinataires ou -1 si exception du TransportMailer
398: */
399: public function send(Email &$message, mixed $ref = null, bool $sms = false): int
400: {
401: $this->lastError = null;
402: $this->lastEvent = null;
403: try {
404: # $this->mailer->send($message);
405: $sentMessage = $this->transport->send($message);
406: # dump($sentMessage->getDebug());
407: $message->messageId = $sentMessage->getMessageId();
408: $rc = \count($sentMessage->getEnvelope()->getRecipients());
409: if ($ref !== false) $this->logger($ref, $message, $rc, null, $sms);
410: } catch (TransportExceptionInterface $e) {
411: # dump($e->getDebug());
412: $rc = -1;
413: $this->logger($ref, $message, $rc, $e->getMessage(), $sms);
414: } catch (\Exception $e) {
415: $rc = -1;
416: $this->logger($ref, $message, $rc, $e->getMessage(), $sms);
417: }
418: return $rc;
419: }
420:
421: /**
422: * Préparation, composition puis envoi d'un message PLAIN et/ou HTML (méthode "combo").
423: *
424: * @param string $ref Référence du modèle de notification (cf tic_mail.templates dans services.yaml)
425: * @param string|array $to Adresses mail des destinataires (chaine avec virgule en séparateur acceptée)
426: * @param array $data Liste clé/valeur pour les substitutions des templates (peut contenir 'locale')
427: * @param string|array $pjs Pièces-jointes (chemin ou structure {'name':, 'type':, 'data':}, seul ou en liste)
428: * @param boolean $return Si vrai retournera l'objet Symfony Email au lieu de tenter de l'envoyer
429: * @param String $sender Adresse d'expéditeur (From, Reply-to ou Return-path selon $config['fromdomains'])
430: * @return mixed Nombre de destinaires du message envoyé (peut être 0) ou objet Email généré
431: * Null sur échec aux étapes "prepare" ou "compose"
432: * False sur échec à l'envoi par le Transport Mailer
433: */
434: public function notify(string $ref, mixed $to = array(), array $data = array(), mixed $pjs = array(), bool $return = false, string $sender = null): mixed
435: {
436: $locale = \array_key_exists('locale', $data) ? $data['locale'] : null;
437:
438: // préparation...
439: $message = $this->prepare($ref, $to, $pjs, $locale);
440: if ($message === null) return null;
441:
442: // forcer les informations de l'expéditeur ?
443: if ($sender !== null) $this->setSender($message, $sender);
444:
445: // composition...
446: $rc = $this->compose($message, $data, $ref);
447: if (! $rc) return null;
448:
449: $this->restoreLocale();
450:
451: // retour ou envoi...
452: if ($return) return $message;
453: $rc = $this->send($message, $ref);
454: return ($rc < 0) ? false : $rc;
455: }
456:
457: /**
458: * Préparation, composition puis envoi d'un message SMS (méthode "combo").
459: *
460: * @param string $ref Référence du modèle de notification (cf tic_mail.templates dans services.yaml)
461: * @param string|array $to Adresses mail des destinataires (chaine avec virgule en séparateur acceptée)
462: * @param array $data Liste clé/valeur pour les substitutions des templates (peut contenir 'locale')
463: * @param boolean $return Si vrai retournera l'objet Symfony Email au lieu de tenter de l'envoyer
464: * @return mixed Nombre de destinaires du message envoyé (peut être 0) ou objet Email généré
465: * Null sur échec aux étapes "prepare" ou "compose"
466: * False sur échec à l'envoi par le Transport Mailer
467: */
468: public function notifySMS(string $ref, mixed $to = array(), array $data = array(), bool $return = false): mixed
469: {
470: $locale = \array_key_exists('locale', $data) ? $data['locale'] : null;
471:
472: // préparation...
473: $message = $this->prepare($ref, $to, array(), $locale, true);
474: if ($message === null) return null;
475:
476: // composition...
477: $rc = $this->composeSMS($message, $data, $ref);
478: if (! $rc) return null;
479:
480: $this->restoreLocale();
481:
482: // retour ou envoi...
483: if ($return) return $message;
484: $rc = $this->send($message, $ref, true);
485: return ($rc < 0) ? false : $rc;
486: }
487:
488: /**
489: * Préparation, composition puis envoi de messages pour chaque destinataire (méthode "combo").
490: *
491: * @param string $ref Référence du modèle de notification (cf tic_mail.templates dans services.yaml)
492: * @param string|array $to Adresses mail des destinataires (chaine avec virgule en séparateur acceptée)
493: * @param array $data Liste clé/valeur pour les substitutions des templates
494: * @param string|array $pjs Pièces-jointes (chemin ou structure {'name':, 'type':, 'data':}, seul ou en liste)
495: * @return integer Nombre de messages envoyés (succès sur destinataire)
496: * Null sur échec aux étapes "prepare" ou "compose"
497: */
498: public function batch(string $ref, mixed $to, array $data = array(), mixed $pjs = array()): ?int
499: {
500: $locale = \array_key_exists('locale', $data) ? $data['locale'] : null;
501:
502: // préparation...
503: $message = $this->prepare($ref, null, $pjs, $locale);
504: if ($message === null) return null;
505:
506: // composition...
507: $rc = $this->compose($message, $data, $ref);
508: if (! $rc) return null;
509:
510: $this->restoreLocale();
511:
512: // envois...
513: $counter = 0;
514: if (! \is_array($to)) $to = \explode(',', $to);
515: foreach ($to as $addr) {
516: $addr = \trim($addr);
517: if (empty($addr)) continue;
518: $message->to($addr);
519:
520: $rc = $this->send($message, $ref);
521: if ($rc >= 0) $counter++;
522: }
523: return $counter;
524: }
525:
526: /**
527: * Enregistrement dans le journal des envois (envoyé avec succès ou échec d'une étape).
528: *
529: * @param string $ref Référence du modèle de notification (par défaut récupéré depuis les entêtes)
530: * @param Email $message Instance du message traité (Symfony Email)
531: * @param integer $rc Nombre de destinataires si succès, code d'erreur négatif sur exception
532: * @param string $error Message de l'exception en cas d'erreur
533: * @param boolean $sms Indique s'il s'agit d'un envoi par SMS plutôt que par SMTP
534: * @return integer Identifiant de l'objet Maillog généré
535: */
536: public function logger(?string $ref, Email $message = null, int $rc = null, string $error = null, bool $sms = false): ?int
537: {
538: $this->lastError = $error;
539: $this->lastEvent = 0;
540: try {
541: if (! $this->config['maillog']) return null;
542:
543: if ($message instanceof Email) {
544: // récupération de la ref du template dans les headers
545: if ($ref === null) {
546: $header = $message->getHeaders()->getHeaderBody('X-TIC-Template');
547: if ($header) $ref = $header->getValue();
548: }
549: // récupération du contenu et de son type (plain|html|sms)
550: if ($sms) {
551: $contentBody = $message->getTextBody();
552: $contentType = 'text/sms';
553: } else {
554: $contentBody = $message->getHtmlBody();
555: $contentType = 'text/html';
556: if (empty($contentBody)) {
557: $contentBody = $message->getTextBody();
558: $contentType = 'text/plain';
559: }
560: }
561: $return = $message->getReturnPath();
562:
563: $this->lastEvent = $this->em->getRepository(Maillog::class)->createEventLog(array(
564: 'template' => $ref,
565: 'locale' => $this->locale,
566: 'returnPath' => isset($return) ? $return->getAddress() : null,
567: 'mailFrom' => \array_map(function($a){ return $a->getAddress(); }, $message->getFrom()),
568: 'mailRcpt' => \array_map(function($a){ return $a->getAddress(); }, $message->getTo()),
569: 'mailBcc' => \array_map(function($a){ return $a->getAddress(); }, $message->getBcc()),
570: 'subject' => $message->getSubject(),
571: 'body' => $contentBody,
572: 'contentType'=> $contentType,
573: 'messageId' => isset($message->messageId) ? $message->messageId : null,
574: 'sendCode' => $rc,
575: 'errorMsg' => $error,
576: ));
577: } else {
578: $this->lastEvent = $this->em->getRepository(Maillog::class)->createEventLog(array(
579: 'template' => $ref,
580: 'locale' => $this->locale,
581: 'contentType'=> ($sms) ? 'text/sms' : '',
582: 'sendCode' => $rc,
583: 'errorMsg' => $error,
584: ));
585: }
586: return $this->lastEvent;
587: } catch (\Exception $e) {
588: \printf("MAILER LOGGER FAILED: %s\n", $e->getMessage());
589: return null;
590: }
591: }
592:
593: /**
594: * Retourne le message de la dernière erreur rencontrée (exception interceptée).
595: *
596: * @return string
597: */
598: public function getLastError()
599: {
600: return $this->lastError;
601: }
602:
603: /**
604: * Retourne l'entrée du journal correspondant au dernier envoi (uniquement son id par défaut).
605: *
606: * @param boolean Vrai pour obtenir l'entité complète ; Faux pour avoir une référence Doctrine.
607: * @return integer|Maillog Clé primaire du Maillog, référence du Maillog ou objet Maillog
608: */
609: public function getLastEvent(bool $retrieve_entity = null): mixed
610: {
611: if (empty($this->lastEvent)) return null;
612: if ($retrieve_entity === null) return $this->lastEvent;
613: if (! $retrieve_entity) return $this->em->getReference(Maillog::class, $this->lastEvent);
614: return $this->em->getRepository(Maillog::class)->find($this->lastEvent);
615: }
616:
617: /**
618: * Ajout dans une entité donnée de l'entrée du journal du dernier envoi (via méthode addMaillog ou addNotification).
619: */
620: public function logLastEvent(object &$entity): MailerService
621: {
622: $event = $this->getLastEvent(false);
623: if ($event !== null) {
624: foreach (array('addMaillog', 'addNotification') as $method) {
625: if (! \method_exists($entity, $method)) continue;
626: \call_user_func(array($entity, $method), $event);
627: break;
628: }
629: }
630: return $this;
631: }
632:
633: /**
634: * Retourne la liste des langues définies (gestion des messages multilingues).
635: * Note: les locales possibles sont utilisées dans l'admin, mais pas à l'envoi.
636: *
637: * @return array Liste des locales possibles
638: */
639: public function getLocales(): array
640: {
641: if ($this->locales !== null) return $this->locales;
642:
643: // recherche dans les paramètres du bundle
644: $this->locales = $this->config['locales'];
645: if ($this->locales !== null) return $this->locales;
646:
647: // recherche dans les attributs de la requête
648: $request = $this->requestStack->getCurrentRequest();
649: if ($request) $this->locales = $request->attributes->get('locales');
650: if ($this->locales !== null) return $this->locales;
651:
652: // recherche dans les données de la session
653: # $session = $this->container->get('session');
654: # if ($session) $this->locales = $session->get('locales');
655: # if ($this->locales !== null) return $this->locales;
656:
657: // liste avec la locale par défaut en dernier recours
658: $this->locales = array();
659: return $this->locales;
660: }
661:
662: }
663: