1: <?php
2: namespace TIC\MailBundle\Entity;
3:
4: use TIC\DormBundle\Base\TICEntity as BaseEntity;
5: use TIC\MailBundle\Repository\TemplateRepository as EntityRepo;
6:
7: use Doctrine\ORM\Mapping as ORM;
8: use Symfony\Component\Validator\Constraints as Assert;
9:
10: /**
11: * Entité représentant un modèle de notification par email.
12: * @ORM\Table(name="tic_mail_template")
13: * @ORM\Entity(repositoryClass=EntityRepo::class)
14: * @ORM\HasLifecycleCallbacks
15: */
16: class Template extends BaseEntity
17: {
18: use \TIC\DormBundle\Traits\EntityTimestampable; // require @ORM\HasLifecycleCallbacks
19:
20:
21: // --------------------------------------------------------------------- Properties
22:
23: /**
24: * @ORM\Column(type="string", length=100, unique=true)
25: * @ORM\Id
26: */
27: private $ref;
28:
29: /**
30: * @ORM\Column(type="string", length=250)
31: */
32: private $label;
33:
34: /**
35: * @ORM\Column(type="string", length=100, nullable=true)
36: */
37: private $target;
38:
39: /**
40: * @ORM\Column(type="string", length=250, nullable=true)
41: */
42: private $sender;
43:
44: /**
45: * @ORM\Column(type="string", length=250, nullable=true, name="return_path")
46: */
47: private $return;
48:
49: /**
50: * @ORM\Column(type="boolean", options={"default"=true})
51: */
52: private $bccAdmins;
53:
54: /**
55: * @ORM\Column(type="text", nullable=true)
56: */
57: private $bccMore;
58:
59: /**
60: * @ORM\Column(type="string", length=250)
61: */
62: private $subject;
63:
64: /**
65: * @ORM\Column(type="text", nullable=true)
66: */
67: private $bodyText;
68:
69: /**
70: * @ORM\Column(type="text", nullable=true)
71: */
72: private $bodyHtml;
73:
74: /**
75: * @ORM\Column(type="text", nullable=true)
76: */
77: private $bodySms;
78:
79: /**
80: * @ORM\Column(type="boolean", options={"default"=true})
81: */
82: private $enabled;
83:
84: /**
85: * @ORM\OneToMany(targetEntity="TemplateTranslation", mappedBy="master", cascade={"persist", "remove"}, orphanRemoval=true)
86: */
87: private $translations;
88:
89:
90: // --------------------------------------------------------------------- Custom methods
91:
92: public function __construct(?string $ref = null)
93: {
94: $this->ref = $ref;
95: $this->bccAdmins = true;
96: $this->enabled = true;
97: $this->translations = new \Doctrine\Common\Collections\ArrayCollection();
98: }
99:
100: public function __toString()
101: {
102: return $this->label;
103: }
104:
105: /**
106: * Import des données de l'entité depuis un tableau associatif.
107: */
108: public function import(array $messages): void
109: {
110: \krsort($messages); // pour que la locale _DEFAULT_ passe en dernier (données prioritaires)
111: foreach ($messages as $locale => $data) {
112: if (! \is_array($data)) continue;
113: $lang = ($locale == '_DEFAULT_') ? '' : '_' . $locale;
114: foreach ($data as $key => $val) switch (\strtolower($key)) {
115: # case 'x-tic-reference': $this->ref = \trim($val); break;
116: case 'x-tic-enabled' : $this->enabled = ($val !== 'false'); break;
117: case 'comments' : $this->label = \trim($val); break;
118: case 'keywords' : $this->target = \trim($val); break;
119: case 'return-path' : $this->return = \trim($val); break;
120: case 'from' : $this->sender = \trim($val); break;
121: # case 'to' : $this-> = \trim($val); break;
122: case 'bcc' :
123: $this->bccAdmins = \preg_match('/^Admins(,.*)?\s*$/', $val);
124: $this->bccMore = \trim(\preg_replace('/^Admins(,(.*))?$/', '$2', $val));
125: break;
126: case 'subject' : $this->{'subject' . $lang} = \trim($val); break;
127: case 'body_plain' : $this->{'bodyText' . $lang} = \trim($val); break;
128: case 'body_html' : $this->{'bodyHtml' . $lang} = \trim($val); break;
129: case 'body_sms' : $this->{'bodySms' . $lang} = \trim($val); break;
130: case 'date' : $this->{'createdAt' . $lang} = \DateTime::createFromFormat('j M Y H:i:s e', \trim($val)); break;
131: case 'resent-date' : $this->{'updatedAt' . $lang} = \DateTime::createFromFormat('j M Y H:i:s e', \trim($val)); break;
132: }
133: }
134: // désactivation du comportement timestampable automatique pour l'import
135: foreach ($this->translations as $translation) $translation->doTimestampable(false);
136: $this->doTimestampable(false);
137: }
138:
139: /**
140: * Export des données de l'entité au format Mail/Mbox.
141: * (messages multipart MIME pour contenus text+html avec concaténation Mbox pour différentes locales)
142: */
143: public function exportMail(string $locale = null, array $formats = null): string
144: {
145: $translation = ($locale === null) ? false : $this->getTranslation($locale);
146: if (empty($formats)) $formats = array('TEXT','HTML','SMS');
147:
148: $headers = array(
149: 'Message-ID' => 'template_' . $this->ref,
150: 'Return-Path' => $this->return,
151: 'From' => $this->sender,
152: 'To' => null,
153: 'Bcc' => \trim((($this->bccAdmins) ? 'Admins, ' : '') . $this->bccMore, " \t\r\n,"),
154: 'Subject' => $this->subject,
155: 'Content-Language' => null,
156: 'Date' => ($this->createdAt) ? $this->createdAt->format('j M Y H:i:s e') : null,
157: 'Resent-Date' => ($this->updatedAt) ? $this->updatedAt->format('j M Y H:i:s e') : null,
158: 'Comments' => $this->label,
159: 'Keywords' => $this->target,
160: 'X-TIC-Reference' => $this->ref,
161: 'X-TIC-Enabled' => $this->enabled ? 'true' : 'false',
162: );
163: $mailsep = 'From tic.mail@bundle.teicee.fr ' . \date('D M j H:i:s Y') . "\n";
164: $bounds = '----TIC_TemplatePart_' . $this->ref;
165: $messages= ($locale === null) ? $mailsep : '';
166:
167: if (! $translation) {
168: $boundary = $bounds . '_default';
169: foreach ($headers as $key => $val) if ($val !== null) $messages.= $key . ': ' . $val . "\n";
170: $messages.= "Content-Type: multipart/alternative; boundary=\"" . $boundary . "\"\n\nThis is a multi-part template in MIME format.\n";
171: if (\in_array('TEXT', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/plain; charset=\"UTF8\"\n\n" . $this->bodyText;
172: if (\in_array('HTML', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/html; charset=\"UTF8\"\n\n" . $this->bodyHtml;
173: if (\in_array('SMS', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/sms; charset=\"UTF8\"\n\n" . $this->bodySms;
174: $messages.= "\n--" . $boundary . "--\n\n";
175: } else {
176: $boundary = $bounds . '_' . $translation->getLocale();
177: $headers['Subject'] = $translation->getSubject();
178: $headers['Content-Language'] = $translation->getLocale();
179: foreach ($headers as $key => $val) if ($val !== null) $messages.= $key . ': ' . $val . "\n";
180: $messages.= "Content-Type: multipart/alternative; boundary=\"" . $boundary . "\"\n\nThis is a multi-part template in MIME format.\n";
181: if (\in_array('TEXT', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/plain; charset=\"UTF8\"\n\n" . $translation->getBodyText();
182: if (\in_array('HTML', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/html; charset=\"UTF8\"\n\n" . $translation->getBodyHtml();
183: if (\in_array('SMS', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/sms; charset=\"UTF8\"\n\n" . $translation->getBodySms();
184: $messages.= "\n--" . $boundary . "--\n\n";
185: }
186: if ($locale === null) foreach ($this->translations as $translation) {
187: $messages.= $mailsep;
188: $boundary = $bounds . '_' . $translation->getLocale();
189: $headers['Subject'] = $translation->getSubject();
190: $headers['Content-Language'] = $translation->getLocale();
191: foreach ($headers as $key => $val) if ($val !== null) $messages.= $key . ': ' . $val . "\n";
192: $messages.= "Content-Type: multipart/alternative; boundary=\"" . $boundary . "\"\n\nThis is a multi-part template in MIME format.\n";
193: if (\in_array('TEXT', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/plain; charset=\"UTF8\"\n\n" . $translation->getBodyText();
194: if (\in_array('HTML', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/html; charset=\"UTF8\"\n\n" . $translation->getBodyHtml();
195: if (\in_array('SMS', $formats)) $messages.= "\n--" . $boundary . "\nContent-Type: text/sms; charset=\"UTF8\"\n\n" . $translation->getBodySms();
196: $messages.= "\n--" . $boundary . "--\n\n";
197: }
198: return $messages;
199: }
200:
201:
202: // --------------------------------------------------------------------- Shortcut methods
203:
204: /**
205: * Test si le contenu text/html ET text/plain ET sms sont vides.
206: * @Assert\IsFalse(message = "ticmail.alert.emptyBody")
207: */
208: public function isEmpty(): bool
209: {
210: if ($this->bodyHtml !== NULL) return false;
211: if ($this->bodyText !== NULL) return false;
212: if ($this->bodySms !== NULL) return false;
213: return true;
214: }
215:
216: /**
217: * Indique les formats (text/plain, text/html, sms) utilisés par ce modèle.
218: *
219: * @return array Liste pouvant contenir les indications : 'HTML', 'TEXT', 'SMS'
220: */
221: public function listFormats(): array
222: {
223: $formats = array();
224: if ($this->bodyHtml !== NULL) $formats[] = 'HTML';
225: if ($this->bodyText !== NULL) $formats[] = 'TEXT';
226: if ($this->bodySms !== NULL) $formats[] = 'SMS';
227: return $formats;
228: }
229:
230: /**
231: * Retourne la liste des contenus pour les 3 vues.
232: * (utilisé pour initialiser un environnement Twig dans le service tic_mailer)
233: *
234: * @param string $locale Langue souhaitée pour les modèles (optionnel)
235: * @return array Contenus pour les clés 'subject', 'bodyText', 'bodyHtml' et 'bodySms'
236: */
237: public function getContents(string $locale = null): array
238: {
239: $contents = array(
240: 'subject' => $this->subject,
241: 'bodyText' => $this->bodyText,
242: 'bodyHtml' => $this->bodyHtml,
243: 'bodySms' => $this->bodySms,
244: );
245:
246: if (($locale !== null) && ($translation = $this->getTranslation($locale))) {
247: if (($data = $translation->getSubject() ) !== null) $contents['subject'] = $data;
248: if (($data = $translation->getBodyText()) !== null) $contents['bodyText'] = $data;
249: if (($data = $translation->getBodyHtml()) !== null) $contents['bodyHtml'] = $data;
250: if (($data = $translation->getBodySms() ) !== null) $contents['bodySms'] = $data;
251: }
252:
253: return $contents;
254: }
255:
256:
257: // --------------------------------------------------------------------- Tweaked methods
258:
259: /**
260: * Get id
261: * @return string
262: */
263: public function getId(): ?string
264: {
265: return $this->ref;
266: }
267:
268: /**
269: * Recherche l'instance TemplateTranslation associée pour la locale spécifiée.
270: * @param string $locale
271: * @return TemplateTranslation ou null si aucune correspondance
272: */
273: public function getTranslation(string $locale): ?TemplateTranslation
274: {
275: foreach ($this->translations as $translation) {
276: if ($translation->getLocale() === $locale) return $translation;
277: }
278: return null;
279: }
280:
281:
282: // --------------------------------------------------------------------- Magic methods
283:
284: /**
285: * @param string $property
286: * @return boolean
287: */
288: public function __isset(string $property)
289: {
290: return \preg_match('/^(subject|bodyText|bodyHtml|bodySms|createdAt|updatedAt)_(.+)$/', $property);
291: }
292:
293: /**
294: * @param string $property
295: * @return mixed
296: * @throws \LogicException If no accessor/property exists by that name
297: */
298: public function __get(string $property)
299: {
300: if (\preg_match('/^(subject|bodyText|bodyHtml|bodySms|createdAt|updatedAt)_(.+)$/', $property, $match)) {
301: $accessor = 'get' . \ucfirst($match[1]);
302: $locale = \strtolower($match[2]);
303:
304: // récupération de la traduction existante sur cette locale ou résultat vide
305: $translation = $this->getTranslation($locale);
306: if ($translation) return \call_user_func(array($translation, $accessor));
307: return null;
308: }
309: throw new \LogicException(\sprintf('No property named `%s` exists', $property));
310: }
311:
312: /**
313: * @param string $property
314: * @param mixed $value
315: * @return void
316: * @throws \LogicException If no mutator/property exists by that name
317: */
318: public function __set(string $property, mixed $value)
319: {
320: if (\preg_match('/^(subject|bodyText|bodyHtml|bodySms|createdAt|updatedAt)_(.+)$/', $property, $match)) {
321: $mutator = 'set' . \ucfirst($match[1]);
322: $locale = \strtolower($match[2]);
323:
324: // récupération de la traduction existante sur cette locale ou création
325: $translation = $this->getTranslation($locale);
326: if ($translation === null) {
327: $translation = new TemplateTranslation();
328: $translation->setMaster($this);
329: $translation->setLocale($locale);
330: $this->translations[] = $translation;
331: }
332: // mise à jour de la traduction
333: \call_user_func(array($translation, $mutator), $value);
334: return $this;
335: }
336: throw new \LogicException(\sprintf('No property named `%s` exists', $property));
337: }
338:
339:
340: // --------------------------------------------------------------------- Auto-generated
341:
342: /**
343: * Set ref
344: *
345: * @param string $ref
346: *
347: * @return Template
348: */
349: public function setRef($ref)
350: {
351: $this->ref = $ref;
352:
353: return $this;
354: }
355:
356: /**
357: * Get ref
358: *
359: * @return string
360: */
361: public function getRef()
362: {
363: return $this->ref;
364: }
365:
366: /**
367: * Set label
368: *
369: * @param string $label
370: *
371: * @return Template
372: */
373: public function setLabel($label)
374: {
375: $this->label = $label;
376:
377: return $this;
378: }
379:
380: /**
381: * Get label
382: *
383: * @return string
384: */
385: public function getLabel()
386: {
387: return $this->label;
388: }
389:
390: /**
391: * Set target
392: *
393: * @param string $target
394: *
395: * @return Template
396: */
397: public function setTarget($target)
398: {
399: $this->target = $target;
400:
401: return $this;
402: }
403:
404: /**
405: * Get target
406: *
407: * @return string
408: */
409: public function getTarget()
410: {
411: return $this->target;
412: }
413:
414: /**
415: * Set sender
416: *
417: * @param string $sender
418: *
419: * @return Template
420: */
421: public function setSender($sender)
422: {
423: $this->sender = $sender;
424:
425: return $this;
426: }
427:
428: /**
429: * Get sender
430: *
431: * @return string
432: */
433: public function getSender()
434: {
435: return $this->sender;
436: }
437:
438: /**
439: * Set return
440: *
441: * @param string $return
442: *
443: * @return Template
444: */
445: public function setReturn($return)
446: {
447: $this->return = $return;
448:
449: return $this;
450: }
451:
452: /**
453: * Get return
454: *
455: * @return string
456: */
457: public function getReturn()
458: {
459: return $this->return;
460: }
461:
462: /**
463: * Set bccAdmins
464: *
465: * @param boolean $bccAdmins
466: *
467: * @return Template
468: */
469: public function setBccAdmins($bccAdmins)
470: {
471: $this->bccAdmins = $bccAdmins;
472:
473: return $this;
474: }
475:
476: /**
477: * Get bccAdmins
478: *
479: * @return boolean
480: */
481: public function getBccAdmins()
482: {
483: return $this->bccAdmins;
484: }
485:
486: /**
487: * Set bccMore
488: *
489: * @param string $bccMore
490: *
491: * @return Template
492: */
493: public function setBccMore($bccMore)
494: {
495: $this->bccMore = $bccMore;
496:
497: return $this;
498: }
499:
500: /**
501: * Get bccMore
502: *
503: * @return string
504: */
505: public function getBccMore()
506: {
507: return $this->bccMore;
508: }
509:
510: /**
511: * Set subject
512: *
513: * @param string $subject
514: *
515: * @return Template
516: */
517: public function setSubject($subject)
518: {
519: $this->subject = $subject;
520:
521: return $this;
522: }
523:
524: /**
525: * Get subject
526: *
527: * @return string
528: */
529: public function getSubject()
530: {
531: return $this->subject;
532: }
533:
534: /**
535: * Set bodyText
536: *
537: * @param string $bodyText
538: *
539: * @return Template
540: */
541: public function setBodyText($bodyText)
542: {
543: $this->bodyText = $bodyText;
544:
545: return $this;
546: }
547:
548: /**
549: * Get bodyText
550: *
551: * @return string
552: */
553: public function getBodyText()
554: {
555: return $this->bodyText;
556: }
557:
558: /**
559: * Set bodyHtml
560: *
561: * @param string $bodyHtml
562: *
563: * @return Template
564: */
565: public function setBodyHtml($bodyHtml)
566: {
567: $this->bodyHtml = $bodyHtml;
568:
569: return $this;
570: }
571:
572: /**
573: * Get bodyHtml
574: *
575: * @return string
576: */
577: public function getBodyHtml()
578: {
579: return $this->bodyHtml;
580: }
581:
582: /**
583: * Set bodySms
584: *
585: * @param string $bodySms
586: *
587: * @return Template
588: */
589: public function setBodySms($bodySms)
590: {
591: $this->bodySms = $bodySms;
592:
593: return $this;
594: }
595:
596: /**
597: * Get bodySms
598: *
599: * @return string
600: */
601: public function getBodySms()
602: {
603: return $this->bodySms;
604: }
605:
606: /**
607: * Set enabled
608: *
609: * @param boolean $enabled
610: *
611: * @return Template
612: */
613: public function setEnabled($enabled)
614: {
615: $this->enabled = $enabled;
616:
617: return $this;
618: }
619:
620: /**
621: * Get enabled
622: *
623: * @return boolean
624: */
625: public function getEnabled()
626: {
627: return $this->enabled;
628: }
629:
630: /**
631: * Add translation
632: *
633: * @param TemplateTranslation $translation
634: *
635: * @return Template
636: */
637: public function addTranslation(TemplateTranslation $translation)
638: {
639: $this->translations[] = $translation;
640:
641: return $this;
642: }
643:
644: /**
645: * Remove translation
646: *
647: * @param TemplateTranslation $translation
648: */
649: public function removeTranslation(TemplateTranslation $translation)
650: {
651: $this->translations->removeElement($translation);
652: }
653:
654: /**
655: * Get translations
656: *
657: * @return \Doctrine\Common\Collections\Collection
658: */
659: public function getTranslations()
660: {
661: return $this->translations;
662: }
663: }
664: