From 80d30884cf1f288ae614bf3baed7e43521def8a3 Mon Sep 17 00:00:00 2001 From: PapsOu Date: Fri, 16 Feb 2018 14:14:05 +0100 Subject: [PATCH 1/7] [Emailing] Add doc --- .../Sil/Component/Mailing/domain/emailing.uxf | 168 +++++++++++++++++ .../domain/emailing_message_states-0.1.png | Bin 0 -> 10566 bytes .../domain/emailing_message_states.uxf | 175 ++++++++++++++++++ doc/fr/Sil/Component/Mailing/domain/index.rst | 163 ++++++++++++++++ doc/fr/Sil/Component/Mailing/index.rst | 9 + doc/fr/Sil/Component/Mailing/installation.rst | 2 + doc/fr/Sil/Component/Mailing/processor.rst | 43 +++++ 7 files changed, 560 insertions(+) create mode 100644 doc/fr/Sil/Component/Mailing/domain/emailing.uxf create mode 100644 doc/fr/Sil/Component/Mailing/domain/emailing_message_states-0.1.png create mode 100644 doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf create mode 100644 doc/fr/Sil/Component/Mailing/domain/index.rst create mode 100644 doc/fr/Sil/Component/Mailing/index.rst create mode 100644 doc/fr/Sil/Component/Mailing/installation.rst create mode 100644 doc/fr/Sil/Component/Mailing/processor.rst diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing.uxf b/doc/fr/Sil/Component/Mailing/domain/emailing.uxf new file mode 100644 index 00000000..be7619aa --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/domain/emailing.uxf @@ -0,0 +1,168 @@ + + + 9 + + UMLFrame + + 171 + 162 + 1017 + 828 + + EmailingComponent + + + + UMLClass + + 531 + 297 + 144 + 54 + + /AbstractMessage/ +-- +#title :string +#content: string +-- + + + + UMLClass + + 423 + 459 + 135 + 27 + + SimpleMessage +-- + + + + UMLClass + + 666 + 459 + 135 + 27 + + GroupedMessage +-- + + + + UMLClass + + 216 + 585 + 153 + 27 + + MessageConfiguration +-- + + + + UMLClass + + 639 + 585 + 135 + 27 + + DiffusionList +-- + + + + UMLClass + + 783 + 657 + 135 + 27 + + Recipient +-- + + + + UMLClass + + 531 + 225 + 144 + 36 + + <<Interface>> +MessageInterface +-- + + + + + UMLClass + + 954 + 297 + 162 + 27 + + Attachment +-- + + + + + UMLClass + + 954 + 225 + 162 + 36 + + <<Interface>> +AttachmentInterface +-- + + + + + Relation + + 666 + 288 + 306 + 45 + + lt=- +m1=0..1 +r1=#message +m2=0..n +r2=#attachments + 10.0;20.0;320.0;20.0 + + + Relation + + 594 + 252 + 27 + 63 + + lt=<<. + 10.0;10.0;10.0;50.0 + + + Relation + + 1026 + 252 + 27 + 63 + + lt=<<. + 10.0;10.0;10.0;50.0 + + diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing_message_states-0.1.png b/doc/fr/Sil/Component/Mailing/domain/emailing_message_states-0.1.png new file mode 100644 index 0000000000000000000000000000000000000000..e81a064050c5bcf35cce362aab1c290d3239eac0 GIT binary patch literal 10566 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 bTND?6P`0w>XP3bJND^Hw!(#;+=dS+`E+>yi literal 0 HcmV?d00001 diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf b/doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf new file mode 100644 index 00000000..01c52e41 --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf @@ -0,0 +1,175 @@ + + + 10 + + UMLSpecialState + + 650 + 210 + 20 + 20 + + type=initial + + + + Relation + + 650 + 220 + 70 + 90 + + lt=-> +create + 10.0;10.0;10.0;70.0 + + + UMLState + + 610 + 290 + 100 + 40 + + DRAFT + + + + UMLState + + 610 + 410 + 100 + 40 + + VALIDATED + + + + Relation + + 650 + 320 + 80 + 110 + + lt=-> +validate + 10.0;10.0;10.0;90.0 + + + UMLState + + 610 + 530 + 100 + 40 + + SENT + + + + Relation + + 650 + 440 + 60 + 110 + + lt=-> +send + 10.0;10.0;10.0;90.0 + + + Relation + + 700 + 410 + 150 + 40 + + lt=- +delete + 10.0;20.0;130.0;20.0 + + + Relation + + 700 + 290 + 150 + 260 + + lt=-> +delete + 10.0;20.0;130.0;20.0;130.0;240.0 + + + UMLSpecialState + + 650 + 630 + 20 + 20 + + type=final + + + + UMLFrame + + 460 + 170 + 460 + 490 + + Mailing State Machine + + + + UMLState + + 780 + 530 + 100 + 40 + + DELETED + + + + Relation + + 650 + 560 + 200 + 60 + + lt=- + 180.0;10.0;180.0;40.0;10.0;40.0 + + + Relation + + 650 + 560 + 30 + 90 + + lt=-> + 10.0;10.0;10.0;70.0 + + + Relation + + 540 + 490 + 110 + 80 + + lt=-> +re-send + 70.0;60.0;10.0;60.0;10.0;10.0;90.0;10.0;90.0;40.0 + + diff --git a/doc/fr/Sil/Component/Mailing/domain/index.rst b/doc/fr/Sil/Component/Mailing/domain/index.rst new file mode 100644 index 00000000..a2f1e65f --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/domain/index.rst @@ -0,0 +1,163 @@ +======== +Emailing +======== + + +---------------------- +Description du domaine +---------------------- + +L'**emailing** consiste à **préparer** des envois d'emails en masse. + +Cela consiste à créer une contenu textuel riche (HTML) et le diffuser à une liste de destinataires appelée **liste de diffusion**. + +L'utilisation du mailing se fait principalement dans un cadre marketing, pour informer sa clientèle ou sa communauté, réaliser des prospections et démarchages, ou communiquer sur des actions commerciales promotionnelles. + +Il peut également servir pour des envois ponctuels (sans passer par des listes de diffusions). + +---------------------- +Fonctionnalités cibles +---------------------- + +- `Gestion du contenu`_ +- `Gestion des listes de diffusion`_ +- `Paramétrage des envois`_ +- `Gestion d'envoi simple`_ +- `Gestion de modèles`_ + +Gestion du contenu +================== + +Saisir le titre du message, son contenu (via éditeur de texte riche (éditeur WYSIWYG)), ses pièces jointes. + +Il faut également gérer son cycle de vie. Selon son état, un mailing ne pourra être envoyé, modifié ou archivé. + +Gestion des listes de diffusion +=============================== + +Le composant doit pouvoir proposer une gestion de liste de diffusion. une liste de diffusion est nommée et est composée d'un ou plusieurs destinataires. + +Paramétrage des envois +====================== + +Pour chaque message, il doit être possible de paramétrer des informations liées à l'envoi : expéditeur, répondre à, copie. + +Gestion d'envoi simple +====================== + +Pouvoir gérer des envois de mail simples : A destination d'une seul contact. Il faut également conserver l'historique des envois par contacts. + +Gestion de modèles +================== + +Une gestion de modèle de contenu doit permettre la création rapide de campagne d'emailing. Il faudra également prévoir un système de remplacement de jeton [1]_ pour permettre de pré-remplir certaines informations. + +.. [1] Un jeton est un emplacement dans un contenu texte qui sera substitué par une valeur lors de la construction du contenu + +------- +Domaine +------- + +Message groupé +============== + +Un **message groupé** est définit de cette manière : + ++-------------+---------------------------------------------------------+--------+ +| Propriété | Description | Oblig. | ++=============+=========================================================+========+ +| title | Le titre du message. Sera également l'objet de l'email. | x | ++-------------+---------------------------------------------------------+--------+ +| content | Le contenu du message. | | ++-------------+---------------------------------------------------------+--------+ +| attachments | Les pièces jointes du message. | | ++-------------+---------------------------------------------------------+--------+ +| lists | Les listes de diffusion qui seront utilisées. | | ++-------------+---------------------------------------------------------+--------+ + +Message simple +============== + +Un **message simple** est définit de cette manière : + ++-------------+---------------------------------------------------------+--------+ +| Propriété | Description | Oblig. | ++=============+=========================================================+========+ +| title | Le titre du message. Sera également l'objet de l'email. | x | ++-------------+---------------------------------------------------------+--------+ +| content | Le contenu du message. | | ++-------------+---------------------------------------------------------+--------+ +| attachments | Les pièces jointes du message. | | ++-------------+---------------------------------------------------------+--------+ +| config | La configuration d'expédition du message. | | ++-------------+---------------------------------------------------------+--------+ + +Configuration de message +======================== + +Une **configuration de message** gère les paramètres suivants : + ++-----------+-----------------------------------------------+ +| Propriété | Description | ++===========+===============================================+ +| from | L'expéditeur qui sera définit pour le message | ++-----------+-----------------------------------------------+ +| to | Le destinataire du message [2]_ | ++-----------+-----------------------------------------------+ +| cc | Une adresse email qui sera mise en copie [2]_ | ++-----------+-----------------------------------------------+ +| bcc | Une adresse email en copie cachée [2]_ | ++-----------+-----------------------------------------------+ + +.. [2] Ce paramètre du message sera utilisé que lors d'envoi de message simple (hors listes de diffusion) + +Liste de diffusion +================== + +Une **liste de diffusion** se définit par un titre et une collection de **destinataires**. + ++-------------+--------------------------------+--------+ +| Propriété | Description | Oblig. | ++=============+================================+========+ +| title | Le titre de la liste | x | ++-------------+--------------------------------+--------+ +| description | Une description optionnelle | | ++-------------+--------------------------------+--------+ +| state | L'état de la liste | | ++-------------+--------------------------------+--------+ +| recipients | Une collection de destinataire | | ++-------------+--------------------------------+--------+ + +Destinataire +============ + +Un **destinataire** est une représentation d'une adresse email. + ++-----------+----------------------------------------------------+--------+ +| Propriété | Description | Oblig. | ++===========+====================================================+========+ +| email | L'adresse email du destinataire | x | ++-----------+----------------------------------------------------+--------+ +| valid | Un indicateur d'état de validité de l'adresse [3]_ | | ++-----------+----------------------------------------------------+--------+ + +.. [3] Cet indicateur sera à mettre à jour en fonction des retours après envoi. (voir https://en.wikipedia.org/wiki/Bounce_message) + +Pièce jointe +============ + +Une pièces jointe représentera un fichier à joindre au message. + ++-----------+-------------------------------------+--------+ +| Propriété | Description | Oblig. | ++===========+=====================================+========+ +| name | Le nom optionnel de la pièce jointe | x | ++-----------+-------------------------------------+--------+ +| file | Le lien vers le fichier réel | x | ++-----------+-------------------------------------+--------+ + +----------------- +Modèle du domaine +----------------- + +.. image:: emailing-0.1.png diff --git a/doc/fr/Sil/Component/Mailing/index.rst b/doc/fr/Sil/Component/Mailing/index.rst new file mode 100644 index 00000000..ff3cdb83 --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/index.rst @@ -0,0 +1,9 @@ +Composants Commande +=================== + +.. toctree:: + :maxdepth: 2 + + installation + domain/index + processor diff --git a/doc/fr/Sil/Component/Mailing/installation.rst b/doc/fr/Sil/Component/Mailing/installation.rst new file mode 100644 index 00000000..11e44375 --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/installation.rst @@ -0,0 +1,2 @@ +Installation +============ diff --git a/doc/fr/Sil/Component/Mailing/processor.rst b/doc/fr/Sil/Component/Mailing/processor.rst new file mode 100644 index 00000000..d2696fd9 --- /dev/null +++ b/doc/fr/Sil/Component/Mailing/processor.rst @@ -0,0 +1,43 @@ +========== +Processeur +========== + +Rôle +==== + +Le processeur est un service permettant de gérer les déclenchements des calculs des éléments d'une commande. + +Méthodes exposées +================= + ++------------------------------------------+---------------------------------------------------------------------------------------------------+ +| Méthode | Description | ++==========================================+===================================================================================================+ +| process(OrderInterface $order): void | Point d'entrée du service, applique les méthodes de calculs sur les éléments de la commande [1]_ | ++------------------------------------------+---------------------------------------------------------------------------------------------------+ +| calculateOrderItemTotals(): void | Se charge de recalculer les totaux des éléments de la commande [2]_ | ++------------------------------------------+---------------------------------------------------------------------------------------------------+ +| calculateOrderItemAdjustedTotals(): void | Appelle la stratégie d'ajustement de chaque élément de la commande qui met à jour le total ajusté | ++------------------------------------------+---------------------------------------------------------------------------------------------------+ +| calculateOrderTotal(): void | Calcule le total de la commande en se basant sur les totaux ajustés des éléments de commande | ++------------------------------------------+---------------------------------------------------------------------------------------------------+ +| calculateOrderAdjustedTotal(): void | Applique le calcul d'ajustement sur la commande (en appelant la stratégie d'ajustement) | ++------------------------------------------+---------------------------------------------------------------------------------------------------+ + +.. [1] C'est la seule méthode qu'il faut utiliser. Les autres méthodes sont exposées pour permettre leur surcharge. +.. [2] Lorsque la quantité d'élément d'une commande est modifié, le pré-calcul effectué lors de l'instanciation de la commande n'est plus juste. Il faut donc recalculer le total de l'élément après coup. + +Processus de calcul +=================== + +Le calcul se fait en 2 grandes étapes : + +- Calculs des éléments de la commande +- Calculs des ajustements de la commande + +Pour le calcul des éléments de la commande, celui-ci se fait également en 2 étapes : + +- Calcul du total de l'élément (sans les ajustements liés à l'élément) +- Calcul du total avec ajustements de l'élément + +Les calculs d'ajustements sont réalisés par les stratégies d'ajustement. Le processeur appelle simplement leur méthode ``adjust``. From 764cd47616104fdc9fb16ac71854bd6f9e427ffd Mon Sep 17 00:00:00 2001 From: PapsOu Date: Fri, 16 Feb 2018 17:32:20 +0100 Subject: [PATCH 2/7] [Emailing] (WIP) Add domain model --- .../Component/Emailing/domain/emailing.uxf | 400 ++++++++++++++++++ .../domain/emailing_message_states-0.1.png | Bin .../domain/emailing_message_states.uxf | 0 .../{Mailing => Emailing}/domain/index.rst | 2 +- .../Component/{Mailing => Emailing}/index.rst | 0 .../{Mailing => Emailing}/installation.rst | 0 .../{Mailing => Emailing}/processor.rst | 0 .../Sil/Component/Mailing/domain/emailing.uxf | 168 -------- 8 files changed, 401 insertions(+), 169 deletions(-) create mode 100644 doc/fr/Sil/Component/Emailing/domain/emailing.uxf rename doc/fr/Sil/Component/{Mailing => Emailing}/domain/emailing_message_states-0.1.png (100%) rename doc/fr/Sil/Component/{Mailing => Emailing}/domain/emailing_message_states.uxf (100%) rename doc/fr/Sil/Component/{Mailing => Emailing}/domain/index.rst (99%) rename doc/fr/Sil/Component/{Mailing => Emailing}/index.rst (100%) rename doc/fr/Sil/Component/{Mailing => Emailing}/installation.rst (100%) rename doc/fr/Sil/Component/{Mailing => Emailing}/processor.rst (100%) delete mode 100644 doc/fr/Sil/Component/Mailing/domain/emailing.uxf diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing.uxf b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf new file mode 100644 index 00000000..d533e6d4 --- /dev/null +++ b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf @@ -0,0 +1,400 @@ + + + 9 + + UMLFrame + + 261 + 117 + 1053 + 621 + + EmailingComponent + + + + UMLClass + + 729 + 234 + 144 + 54 + + /AbstractMessage/ +-- +#title :string +#content: string +-- + + + + UMLClass + + 621 + 369 + 144 + 45 + + template=Resource +SimpleMessage +-- + + + + + UMLClass + + 855 + 369 + 144 + 45 + + template=Resource +GroupedMessage +-- + + + + UMLClass + + 279 + 369 + 153 + 72 + + MessageConfiguration +-- + + + + UMLClass + + 711 + 522 + 180 + 81 + + template=Resource +DiffusionList +-- +#title: string +#description: string +#enabled: bool + + + + UMLClass + + 711 + 666 + 144 + 54 + + Recipient +-- +#valid: bool + + + + UMLClass + + 729 + 162 + 144 + 36 + + <<Interface>> +MessageInterface +-- + + + + + UMLClass + + 306 + 234 + 162 + 54 + + Attachment +-- +#name: string +#file: File + + + + UMLClass + + 306 + 162 + 162 + 36 + + <<Interface>> +AttachmentInterface +-- + + + + + Relation + + 459 + 225 + 288 + 45 + + lt=- +r1=0..1 +m1=#message +r2=0..n +m2=#attachments + 300.0;20.0;10.0;20.0 + + + Relation + + 792 + 189 + 27 + 63 + + lt=<<. + 10.0;10.0;10.0;50.0 + + + Relation + + 378 + 189 + 27 + 63 + + lt=<<. + 10.0;10.0;10.0;50.0 + + + Relation + + 666 + 279 + 153 + 117 + + lt=<<- + 150.0;10.0;150.0;70.0;10.0;70.0;10.0;110.0 + + + Relation + + 792 + 333 + 135 + 63 + + lt=- + 10.0;10.0;130.0;10.0;130.0;50.0 + + + Relation + + 423 + 369 + 216 + 45 + + lt=- +r1=1..1 +m1=#config + 10.0;20.0;220.0;20.0 + + + Relation + + 846 + 387 + 180 + 198 + + lt=- +r1=0..n +m1=#lists + 10.0;190.0;180.0;190.0;180.0;10.0;130.0;10.0 + + + Relation + + 846 + 576 + 180 + 135 + + lt=- +r1=1..n +m1=#lists +r2=1..n +m2=#recipients + 10.0;20.0;180.0;20.0;180.0;120.0;10.0;120.0 + + + UMLClass + + 279 + 522 + 135 + 198 + + EmailAddress +-- +-value: string + + + + Relation + + 405 + 666 + 324 + 45 + + lt=- +m2=email +r2=1..1 + 340.0;20.0;10.0;20.0 + + + Relation + + 405 + 414 + 117 + 261 + + lt=- +m2=#from +r2=1..1 + 30.0;10.0;110.0;10.0;110.0;260.0;10.0;260.0 + + + Relation + + 405 + 594 + 117 + 45 + + lt=- +m2=#to +r2=1..n + 110.0;20.0;10.0;20.0 + + + Relation + + 405 + 558 + 117 + 45 + + lt=- +m2=#cc +r2=0..n + 110.0;20.0;10.0;20.0 + + + Relation + + 405 + 522 + 117 + 45 + + lt=- +m2=#bcc +r2=0..n + 110.0;20.0;10.0;20.0 + + + UMLClass + + 693 + 441 + 180 + 45 + + <<Interface>> +DiffusionListInterface +-- + + + + Relation + + 774 + 477 + 27 + 72 + + lt=<<. + 10.0;10.0;10.0;60.0 + + + UMLClass + + 1080 + 225 + 171 + 63 + + template=Resource +MessageTemplate +-- +#content: string + + + + + UMLClass + + 1062 + 396 + 171 + 63 + + ContentToken +-- +#name: string +#value: ?string + + + + + Relation + + 1206 + 243 + 108 + 198 + + lt=- +r1=0..n +m1=#tokens + 30.0;190.0;100.0;190.0;100.0;10.0;10.0;10.0 + + + Relation + + 864 + 234 + 234 + 45 + + lt=- +r2=0..n +m2=#messages +r1=0..1 +m1=#template + 240.0;20.0;10.0;20.0 + + diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing_message_states-0.1.png b/doc/fr/Sil/Component/Emailing/domain/emailing_message_states-0.1.png similarity index 100% rename from doc/fr/Sil/Component/Mailing/domain/emailing_message_states-0.1.png rename to doc/fr/Sil/Component/Emailing/domain/emailing_message_states-0.1.png diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf b/doc/fr/Sil/Component/Emailing/domain/emailing_message_states.uxf similarity index 100% rename from doc/fr/Sil/Component/Mailing/domain/emailing_message_states.uxf rename to doc/fr/Sil/Component/Emailing/domain/emailing_message_states.uxf diff --git a/doc/fr/Sil/Component/Mailing/domain/index.rst b/doc/fr/Sil/Component/Emailing/domain/index.rst similarity index 99% rename from doc/fr/Sil/Component/Mailing/domain/index.rst rename to doc/fr/Sil/Component/Emailing/domain/index.rst index a2f1e65f..83b14788 100644 --- a/doc/fr/Sil/Component/Mailing/domain/index.rst +++ b/doc/fr/Sil/Component/Emailing/domain/index.rst @@ -123,7 +123,7 @@ Une **liste de diffusion** se définit par un titre et une collection de **desti +-------------+--------------------------------+--------+ | description | Une description optionnelle | | +-------------+--------------------------------+--------+ -| state | L'état de la liste | | +| enabled | La list est utilisable ou non | | +-------------+--------------------------------+--------+ | recipients | Une collection de destinataire | | +-------------+--------------------------------+--------+ diff --git a/doc/fr/Sil/Component/Mailing/index.rst b/doc/fr/Sil/Component/Emailing/index.rst similarity index 100% rename from doc/fr/Sil/Component/Mailing/index.rst rename to doc/fr/Sil/Component/Emailing/index.rst diff --git a/doc/fr/Sil/Component/Mailing/installation.rst b/doc/fr/Sil/Component/Emailing/installation.rst similarity index 100% rename from doc/fr/Sil/Component/Mailing/installation.rst rename to doc/fr/Sil/Component/Emailing/installation.rst diff --git a/doc/fr/Sil/Component/Mailing/processor.rst b/doc/fr/Sil/Component/Emailing/processor.rst similarity index 100% rename from doc/fr/Sil/Component/Mailing/processor.rst rename to doc/fr/Sil/Component/Emailing/processor.rst diff --git a/doc/fr/Sil/Component/Mailing/domain/emailing.uxf b/doc/fr/Sil/Component/Mailing/domain/emailing.uxf deleted file mode 100644 index be7619aa..00000000 --- a/doc/fr/Sil/Component/Mailing/domain/emailing.uxf +++ /dev/null @@ -1,168 +0,0 @@ - - - 9 - - UMLFrame - - 171 - 162 - 1017 - 828 - - EmailingComponent - - - - UMLClass - - 531 - 297 - 144 - 54 - - /AbstractMessage/ --- -#title :string -#content: string --- - - - - UMLClass - - 423 - 459 - 135 - 27 - - SimpleMessage --- - - - - UMLClass - - 666 - 459 - 135 - 27 - - GroupedMessage --- - - - - UMLClass - - 216 - 585 - 153 - 27 - - MessageConfiguration --- - - - - UMLClass - - 639 - 585 - 135 - 27 - - DiffusionList --- - - - - UMLClass - - 783 - 657 - 135 - 27 - - Recipient --- - - - - UMLClass - - 531 - 225 - 144 - 36 - - <<Interface>> -MessageInterface --- - - - - - UMLClass - - 954 - 297 - 162 - 27 - - Attachment --- - - - - - UMLClass - - 954 - 225 - 162 - 36 - - <<Interface>> -AttachmentInterface --- - - - - - Relation - - 666 - 288 - 306 - 45 - - lt=- -m1=0..1 -r1=#message -m2=0..n -r2=#attachments - 10.0;20.0;320.0;20.0 - - - Relation - - 594 - 252 - 27 - 63 - - lt=<<. - 10.0;10.0;10.0;50.0 - - - Relation - - 1026 - 252 - 27 - 63 - - lt=<<. - 10.0;10.0;10.0;50.0 - - From 4ef1eb426f3850debabebbd1d32794e3eaaf8cc8 Mon Sep 17 00:00:00 2001 From: PapsOu Date: Mon, 19 Feb 2018 10:11:58 +0100 Subject: [PATCH 3/7] [Emailing] [Doc] Add template and token description --- .../Emailing/domain/emailing-0.1.png | Bin 0 -> 59670 bytes .../Component/Emailing/domain/emailing.uxf | 226 +++++++++++------- .../Sil/Component/Emailing/domain/index.rst | 50 +++- doc/fr/Sil/Component/Emailing/index.rst | 3 +- doc/fr/Sil/Component/Emailing/processor.rst | 43 ---- doc/fr/Sil/Component/map.rst.inc | 1 + 6 files changed, 181 insertions(+), 142 deletions(-) create mode 100644 doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png delete mode 100644 doc/fr/Sil/Component/Emailing/processor.rst diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png b/doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png new file mode 100644 index 0000000000000000000000000000000000000000..2df69cbbc79f1465fd06774acf800ebec88b9d6f GIT binary patch literal 59670 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 literal 0 HcmV?d00001 diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing.uxf b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf index d533e6d4..07fa756f 100644 --- a/doc/fr/Sil/Component/Emailing/domain/emailing.uxf +++ b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf @@ -4,9 +4,9 @@ UMLFrame - 261 + 153 117 - 1053 + 1098 621 EmailingComponent @@ -15,7 +15,7 @@ UMLClass - 729 + 621 234 144 54 @@ -30,8 +30,8 @@ UMLClass - 621 - 369 + 513 + 405 144 45 @@ -44,8 +44,8 @@ SimpleMessage UMLClass - 855 - 369 + 747 + 405 144 45 @@ -57,8 +57,8 @@ GroupedMessage UMLClass - 279 - 369 + 171 + 405 153 72 @@ -69,8 +69,8 @@ GroupedMessage UMLClass - 711 - 522 + 603 + 549 180 81 @@ -85,7 +85,7 @@ DiffusionList UMLClass - 711 + 603 666 144 54 @@ -98,7 +98,7 @@ DiffusionList UMLClass - 729 + 621 162 144 36 @@ -112,23 +112,9 @@ MessageInterface UMLClass - 306 + 198 234 162 - 54 - - Attachment --- -#name: string -#file: File - - - - UMLClass - - 306 - 162 - 162 36 <<Interface>> @@ -140,7 +126,7 @@ AttachmentInterface Relation - 459 + 351 225 288 45 @@ -155,18 +141,7 @@ m2=#attachments Relation - 792 - 189 - 27 - 63 - - lt=<<. - 10.0;10.0;10.0;50.0 - - - Relation - - 378 + 684 189 27 63 @@ -177,30 +152,30 @@ m2=#attachments Relation - 666 + 558 279 153 - 117 + 153 lt=<<- - 150.0;10.0;150.0;70.0;10.0;70.0;10.0;110.0 + 150.0;10.0;150.0;70.0;10.0;70.0;10.0;150.0 Relation - 792 + 684 333 135 - 63 + 99 lt=- - 10.0;10.0;130.0;10.0;130.0;50.0 + 10.0;10.0;130.0;10.0;130.0;90.0 Relation - 423 - 369 + 315 + 405 216 45 @@ -212,35 +187,35 @@ m1=#config Relation - 846 - 387 + 738 + 423 180 - 198 + 189 lt=- r1=0..n m1=#lists - 10.0;190.0;180.0;190.0;180.0;10.0;130.0;10.0 + 10.0;180.0;180.0;180.0;180.0;10.0;130.0;10.0 Relation - 846 - 576 + 738 + 603 180 - 135 + 108 lt=- r1=1..n m1=#lists r2=1..n m2=#recipients - 10.0;20.0;180.0;20.0;180.0;120.0;10.0;120.0 + 10.0;20.0;180.0;20.0;180.0;90.0;10.0;90.0 UMLClass - 279 + 171 522 135 198 @@ -253,7 +228,7 @@ m2=#recipients Relation - 405 + 297 666 324 45 @@ -266,20 +241,20 @@ r2=1..1 Relation - 405 - 414 + 297 + 450 117 - 261 + 225 lt=- m2=#from r2=1..1 - 30.0;10.0;110.0;10.0;110.0;260.0;10.0;260.0 + 30.0;10.0;110.0;10.0;110.0;220.0;10.0;220.0 Relation - 405 + 297 594 117 45 @@ -292,7 +267,7 @@ r2=1..n Relation - 405 + 297 558 117 45 @@ -305,7 +280,7 @@ r2=0..n Relation - 405 + 297 522 117 45 @@ -318,8 +293,8 @@ r2=0..n UMLClass - 693 - 441 + 585 + 477 180 45 @@ -331,19 +306,19 @@ DiffusionListInterface Relation - 774 - 477 + 666 + 513 27 - 72 + 63 lt=<<. - 10.0;10.0;10.0;60.0 + 10.0;10.0;10.0;50.0 UMLClass - 1080 - 225 + 972 + 144 171 63 @@ -357,44 +332,121 @@ MessageTemplate UMLClass - 1062 - 396 + 972 + 360 171 63 - ContentToken + template=Resource +ContentToken -- -#name: string -#value: ?string +#value: mixed Relation - 1206 - 243 + 1098 + 162 108 - 198 + 126 lt=- r1=0..n m1=#tokens - 30.0;190.0;100.0;190.0;100.0;10.0;10.0;10.0 + 10.0;110.0;100.0;110.0;100.0;10.0;10.0;10.0 Relation - 864 - 234 + 756 + 153 234 - 45 + 117 lt=- r2=0..n m2=#messages r1=0..1 m1=#template - 240.0;20.0;10.0;20.0 + 240.0;20.0;140.0;20.0;140.0;100.0;10.0;100.0 + + + Relation + + 756 + 261 + 234 + 144 + + lt=- +r2=1..1 +m2=#message +r1=0..n +m1=#tokens + 240.0;130.0;140.0;130.0;140.0;20.0;10.0;20.0 + + + UMLClass + + 972 + 225 + 171 + 99 + + template=Resource +ContentTokenType +-- +#name: string + + + + + Relation + + 1098 + 297 + 108 + 108 + + lt=- +r2=1..1 +m2=#type + 10.0;100.0;100.0;100.0;100.0;20.0;10.0;20.0 + + + UMLClass + + 999 + 477 + 90 + 126 + + <<enum>> +DataType +-- +BOOLEAN +STRING +INTEGER +FLOAT +PERCENT +DATE +DATETIME + + + + + Relation + + 1080 + 279 + 171 + 243 + + lt=- +r1=1..1 +m1=#dataType + 10.0;240.0;170.0;240.0;170.0;10.0;30.0;10.0 diff --git a/doc/fr/Sil/Component/Emailing/domain/index.rst b/doc/fr/Sil/Component/Emailing/domain/index.rst index 83b14788..5a462469 100644 --- a/doc/fr/Sil/Component/Emailing/domain/index.rst +++ b/doc/fr/Sil/Component/Emailing/domain/index.rst @@ -2,7 +2,6 @@ Emailing ======== - ---------------------- Description du domaine ---------------------- @@ -40,7 +39,7 @@ Le composant doit pouvoir proposer une gestion de liste de diffusion. une liste Paramétrage des envois ====================== -Pour chaque message, il doit être possible de paramétrer des informations liées à l'envoi : expéditeur, répondre à, copie. +Pour chaque message simple, il doit être possible de paramétrer des informations liées à l'envoi : expéditeur, répondre à, copie. Gestion d'envoi simple ====================== @@ -146,15 +145,46 @@ Un **destinataire** est une représentation d'une adresse email. Pièce jointe ============ -Une pièces jointe représentera un fichier à joindre au message. +Une pièce jointe représentera un fichier à joindre au message. + +Modèle de message +================= + +Un **modèle de message** permet de définir une mise en page de base pour les messages ainsi que la définition de jetons de substitution pour faciliter la saisie des messages utilisant un modèle. + ++-----------+-----------------------------------------------+--------+ +| Propriété | Description | Oblig. | ++===========+===============================================+========+ +| content | Contenu du modèle | | ++-----------+-----------------------------------------------+--------+ +| tokens | Collection de types de jetons de substitution | | ++-----------+-----------------------------------------------+--------+ + +Type de jeton de substitution +============================= + +Un **type de jeton** permet de définir quelle donnée sera affichée dans un modèle de message. + ++-----------+-----------------------+--------+ +| Propriété | Description | Oblig. | ++===========+=======================+========+ +| name | Nom du type de donnée | | ++-----------+-----------------------+--------+ +| dataType | Type de donnée cible | | ++-----------+-----------------------+--------+ + +Jeton de substitution +===================== + +Un **jeton de substitution** permet de remplacer des emplacements définis depuis un modèle par des valeurs de substitution. -+-----------+-------------------------------------+--------+ -| Propriété | Description | Oblig. | -+===========+=====================================+========+ -| name | Le nom optionnel de la pièce jointe | x | -+-----------+-------------------------------------+--------+ -| file | Le lien vers le fichier réel | x | -+-----------+-------------------------------------+--------+ ++-----------+---------------------------------+--------+ +| Propriété | Description | Oblig. | ++===========+=================================+========+ +| value | Valeur du jeton de substitution | | ++-----------+---------------------------------+--------+ +| type | Type de jeton | x | ++-----------+---------------------------------+--------+ ----------------- Modèle du domaine diff --git a/doc/fr/Sil/Component/Emailing/index.rst b/doc/fr/Sil/Component/Emailing/index.rst index ff3cdb83..1ec1d93c 100644 --- a/doc/fr/Sil/Component/Emailing/index.rst +++ b/doc/fr/Sil/Component/Emailing/index.rst @@ -1,4 +1,4 @@ -Composants Commande +Composants Emailing =================== .. toctree:: @@ -6,4 +6,3 @@ Composants Commande installation domain/index - processor diff --git a/doc/fr/Sil/Component/Emailing/processor.rst b/doc/fr/Sil/Component/Emailing/processor.rst deleted file mode 100644 index d2696fd9..00000000 --- a/doc/fr/Sil/Component/Emailing/processor.rst +++ /dev/null @@ -1,43 +0,0 @@ -========== -Processeur -========== - -Rôle -==== - -Le processeur est un service permettant de gérer les déclenchements des calculs des éléments d'une commande. - -Méthodes exposées -================= - -+------------------------------------------+---------------------------------------------------------------------------------------------------+ -| Méthode | Description | -+==========================================+===================================================================================================+ -| process(OrderInterface $order): void | Point d'entrée du service, applique les méthodes de calculs sur les éléments de la commande [1]_ | -+------------------------------------------+---------------------------------------------------------------------------------------------------+ -| calculateOrderItemTotals(): void | Se charge de recalculer les totaux des éléments de la commande [2]_ | -+------------------------------------------+---------------------------------------------------------------------------------------------------+ -| calculateOrderItemAdjustedTotals(): void | Appelle la stratégie d'ajustement de chaque élément de la commande qui met à jour le total ajusté | -+------------------------------------------+---------------------------------------------------------------------------------------------------+ -| calculateOrderTotal(): void | Calcule le total de la commande en se basant sur les totaux ajustés des éléments de commande | -+------------------------------------------+---------------------------------------------------------------------------------------------------+ -| calculateOrderAdjustedTotal(): void | Applique le calcul d'ajustement sur la commande (en appelant la stratégie d'ajustement) | -+------------------------------------------+---------------------------------------------------------------------------------------------------+ - -.. [1] C'est la seule méthode qu'il faut utiliser. Les autres méthodes sont exposées pour permettre leur surcharge. -.. [2] Lorsque la quantité d'élément d'une commande est modifié, le pré-calcul effectué lors de l'instanciation de la commande n'est plus juste. Il faut donc recalculer le total de l'élément après coup. - -Processus de calcul -=================== - -Le calcul se fait en 2 grandes étapes : - -- Calculs des éléments de la commande -- Calculs des ajustements de la commande - -Pour le calcul des éléments de la commande, celui-ci se fait également en 2 étapes : - -- Calcul du total de l'élément (sans les ajustements liés à l'élément) -- Calcul du total avec ajustements de l'élément - -Les calculs d'ajustements sont réalisés par les stratégies d'ajustement. Le processeur appelle simplement leur méthode ``adjust``. diff --git a/doc/fr/Sil/Component/map.rst.inc b/doc/fr/Sil/Component/map.rst.inc index 08c2f171..4226949d 100644 --- a/doc/fr/Sil/Component/map.rst.inc +++ b/doc/fr/Sil/Component/map.rst.inc @@ -5,3 +5,4 @@ * :doc:`/Sil/Component/Product/index` * :doc:`/Sil/Component/Stock/index` * :doc:`/Sil/Component/Uom/index` +* :doc:`/Sil/Component/Emailing/index` From a7c5b11625e4da3cfdfef96f531f53c73b4210a6 Mon Sep 17 00:00:00 2001 From: PapsOu Date: Mon, 19 Feb 2018 10:46:40 +0100 Subject: [PATCH 4/7] [Emailing] [Doc] Improve doc --- .../Sil/Component/Emailing/domain/index.rst | 27 ++++++++++++++++--- doc/fr/conf.py | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/doc/fr/Sil/Component/Emailing/domain/index.rst b/doc/fr/Sil/Component/Emailing/domain/index.rst index 5a462469..57793ea2 100644 --- a/doc/fr/Sil/Component/Emailing/domain/index.rst +++ b/doc/fr/Sil/Component/Emailing/domain/index.rst @@ -51,7 +51,9 @@ Gestion de modèles Une gestion de modèle de contenu doit permettre la création rapide de campagne d'emailing. Il faudra également prévoir un système de remplacement de jeton [1]_ pour permettre de pré-remplir certaines informations. -.. [1] Un jeton est un emplacement dans un contenu texte qui sera substitué par une valeur lors de la construction du contenu +.. note:: + + .. [1] Un jeton est un emplacement dans un contenu texte qui sera substitué par une valeur lors de la construction du contenu ------- Domaine @@ -108,7 +110,9 @@ Une **configuration de message** gère les paramètres suivants : | bcc | Une adresse email en copie cachée [2]_ | +-----------+-----------------------------------------------+ -.. [2] Ce paramètre du message sera utilisé que lors d'envoi de message simple (hors listes de diffusion) +.. note:: + + .. [2] Ce paramètre du message sera utilisé que lors d'envoi de message simple (hors listes de diffusion) Liste de diffusion ================== @@ -140,7 +144,9 @@ Un **destinataire** est une représentation d'une adresse email. | valid | Un indicateur d'état de validité de l'adresse [3]_ | | +-----------+----------------------------------------------------+--------+ -.. [3] Cet indicateur sera à mettre à jour en fonction des retours après envoi. (voir https://en.wikipedia.org/wiki/Bounce_message) +.. note:: + + .. [3] Cet indicateur sera à mettre à jour en fonction des retours après envoi. (voir https://en.wikipedia.org/wiki/Bounce_message) Pièce jointe ============ @@ -191,3 +197,18 @@ Modèle du domaine ----------------- .. image:: emailing-0.1.png + +------------ +Cycle de vie +------------ + +Message +======= + +Un message, qu'il soit simple ou groupé, suivra le même cycle de vie suivant : + +.. image:: emailing_message_states-0.1.png + +.. note:: + + Un message envoyé pourra être envoyé à nouveau, ceci sans limite. Il sera à la charge des implémentations de limiter ou non cette particularité. diff --git a/doc/fr/conf.py b/doc/fr/conf.py index a99b1797..dbba8534 100644 --- a/doc/fr/conf.py +++ b/doc/fr/conf.py @@ -20,7 +20,7 @@ master_doc = 'index' project = u'Sil & Blast Projects' -copyright = u'2017, Libre-Informatique' +copyright = u'2018, Libre-Informatique' version = '' release = '' From 79a2940ef033fe8610035f0fe2e8732672d25a0e Mon Sep 17 00:00:00 2001 From: PapsOu Date: Mon, 19 Feb 2018 17:43:46 +0100 Subject: [PATCH 5/7] [Emailing] Add component and basic tests --- .../Emailing/domain/emailing-0.1.png | Bin 59670 -> 0 bytes .../Emailing/domain/emailing-0.2.png | Bin 0 -> 60130 bytes .../Component/Emailing/domain/emailing.uxf | 372 +++++++++--------- .../Sil/Component/Emailing/domain/index.rst | 2 +- src/Sil/Component/Emailing/.editorconfig | 11 + src/Sil/Component/Emailing/.env.jenkins | 32 ++ src/Sil/Component/Emailing/.env.travis | 23 ++ .../Emailing/.github/ISSUE_TEMPLATE.md | 11 + .../Emailing/.github/PULL_REQUEST_TEMPLATE.md | 15 + src/Sil/Component/Emailing/.gitignore | 11 + src/Sil/Component/Emailing/.gitrepo | 12 + src/Sil/Component/Emailing/.php_cs | 47 +++ src/Sil/Component/Emailing/.scrutinizer.yml | 16 + src/Sil/Component/Emailing/.styleci.yml | 11 + src/Sil/Component/Emailing/.travis.yml | 54 +++ src/Sil/Component/Emailing/LICENCE.md | 157 ++++++++ .../Emailing/Model/AbstractMessage.php | 168 ++++++++ .../Emailing/Model/AttachmentInterface.php | 17 + .../Component/Emailing/Model/ContentToken.php | 147 +++++++ .../Emailing/Model/ContentTokenInterface.php | 62 +++ .../Emailing/Model/ContentTokenType.php | 54 +++ .../Model/ContentTokenTypeInterface.php | 30 ++ .../Component/Emailing/Model/EmailAddress.php | 69 ++++ .../Emailing/Model/EmailAddressInterface.php | 37 ++ .../Emailing/Model/GroupedMessage.php | 69 ++++ .../Model/GroupedMessageInterface.php | 37 ++ .../Component/Emailing/Model/MailingList.php | 129 ++++++ .../Emailing/Model/MailingListInterface.php | 80 ++++ .../Emailing/Model/MessageInterface.php | 96 +++++ .../Component/Emailing/Model/MessageState.php | 180 +++++++++ .../Model/MessageStateAwareInterface.php | 66 ++++ .../Emailing/Model/MessageStateAwareTrait.php | 100 +++++ .../Emailing/Model/MessageStateInterface.php | 107 +++++ .../Emailing/Model/MessageTemplate.php | 138 +++++++ .../Model/MessageTemplateInterface.php | 57 +++ .../Component/Emailing/Model/Recipient.php | 74 ++++ .../Emailing/Model/RecipientInterface.php | 46 +++ .../Emailing/Model/SimpleMessage.php | 168 ++++++++ .../Emailing/Model/SimpleMessageInterface.php | 98 +++++ .../ContentTokenRepositoryInterface.php | 19 + .../ContentTokenTypeRepositoryInterface.php | 19 + .../EmailAddressRepositoryInterface.php | 19 + .../GroupedMessageRepositoryInterface.php | 19 + .../MailingListRepositoryInterface.php | 19 + .../MessageTemplateRepositoryInterface.php | 19 + .../RecipientRepositoryInterface.php | 19 + .../SimpleMessageRepositoryInterface.php | 19 + .../StateMachine/MessageStateMachine.php | 49 +++ src/Sil/Component/Emailing/Tests/.gitkeep | 0 .../Component/Emailing/Tests/Unit.suite.yml | 9 + .../Component/Emailing/Tests/Unit/.gitkeep | 0 .../Emailing/Tests/Unit/EmailAddressTest.php | 41 ++ .../Emailing/Tests/Unit/Fixtures/Fixtures.php | 241 ++++++++++++ .../Emailing/Tests/Unit/MailingListTest.php | 55 +++ .../Emailing/Tests/Unit/MessageTest.php | 49 +++ .../Emailing/Tests/Unit/RecipientTest.php | 51 +++ .../bin/ci-scripts/after_success_test.sh | 4 + .../bin/ci-scripts/before_install_test.sh | 10 + .../ci-scripts/before_install_travis_test.sh | 11 + .../bin/ci-scripts/create_database_test.sh | 58 +++ .../Emailing/bin/ci-scripts/do_run.sh | 11 + .../Emailing/bin/ci-scripts/install_docs.sh | 4 + .../Emailing/bin/ci-scripts/install_lint.sh | 5 + .../Emailing/bin/ci-scripts/install_test.sh | 9 + .../Emailing/bin/ci-scripts/run_docs.sh | 4 + .../Emailing/bin/ci-scripts/run_lint.sh | 3 + .../Emailing/bin/ci-scripts/run_test.sh | 23 ++ .../bin/ci-scripts/set_db_host_test.sh | 44 +++ src/Sil/Component/Emailing/composer.json | 40 ++ .../Component/Emailing/etc/dockerfile.jenkins | 73 ++++ .../Component/Emailing/etc/pipeline.jenkins | 153 +++++++ src/Sil/Component/Emailing/phpmd.xml.dist | 15 + src/Sil/Component/Emailing/phpunit.xml.dist | 50 +++ 73 files changed, 3783 insertions(+), 184 deletions(-) delete mode 100644 doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png create mode 100644 doc/fr/Sil/Component/Emailing/domain/emailing-0.2.png create mode 100644 src/Sil/Component/Emailing/.editorconfig create mode 100644 src/Sil/Component/Emailing/.env.jenkins create mode 100644 src/Sil/Component/Emailing/.env.travis create mode 100644 src/Sil/Component/Emailing/.github/ISSUE_TEMPLATE.md create mode 100644 src/Sil/Component/Emailing/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 src/Sil/Component/Emailing/.gitignore create mode 100644 src/Sil/Component/Emailing/.gitrepo create mode 100644 src/Sil/Component/Emailing/.php_cs create mode 100644 src/Sil/Component/Emailing/.scrutinizer.yml create mode 100644 src/Sil/Component/Emailing/.styleci.yml create mode 100644 src/Sil/Component/Emailing/.travis.yml create mode 100644 src/Sil/Component/Emailing/LICENCE.md create mode 100644 src/Sil/Component/Emailing/Model/AbstractMessage.php create mode 100644 src/Sil/Component/Emailing/Model/AttachmentInterface.php create mode 100644 src/Sil/Component/Emailing/Model/ContentToken.php create mode 100644 src/Sil/Component/Emailing/Model/ContentTokenInterface.php create mode 100644 src/Sil/Component/Emailing/Model/ContentTokenType.php create mode 100644 src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php create mode 100644 src/Sil/Component/Emailing/Model/EmailAddress.php create mode 100644 src/Sil/Component/Emailing/Model/EmailAddressInterface.php create mode 100644 src/Sil/Component/Emailing/Model/GroupedMessage.php create mode 100644 src/Sil/Component/Emailing/Model/GroupedMessageInterface.php create mode 100644 src/Sil/Component/Emailing/Model/MailingList.php create mode 100644 src/Sil/Component/Emailing/Model/MailingListInterface.php create mode 100644 src/Sil/Component/Emailing/Model/MessageInterface.php create mode 100644 src/Sil/Component/Emailing/Model/MessageState.php create mode 100644 src/Sil/Component/Emailing/Model/MessageStateAwareInterface.php create mode 100644 src/Sil/Component/Emailing/Model/MessageStateAwareTrait.php create mode 100644 src/Sil/Component/Emailing/Model/MessageStateInterface.php create mode 100644 src/Sil/Component/Emailing/Model/MessageTemplate.php create mode 100644 src/Sil/Component/Emailing/Model/MessageTemplateInterface.php create mode 100644 src/Sil/Component/Emailing/Model/Recipient.php create mode 100644 src/Sil/Component/Emailing/Model/RecipientInterface.php create mode 100644 src/Sil/Component/Emailing/Model/SimpleMessage.php create mode 100644 src/Sil/Component/Emailing/Model/SimpleMessageInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/ContentTokenRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/ContentTokenTypeRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/EmailAddressRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/GroupedMessageRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/MailingListRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/MessageTemplateRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/RecipientRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/Repository/SimpleMessageRepositoryInterface.php create mode 100644 src/Sil/Component/Emailing/StateMachine/MessageStateMachine.php create mode 100644 src/Sil/Component/Emailing/Tests/.gitkeep create mode 100644 src/Sil/Component/Emailing/Tests/Unit.suite.yml create mode 100644 src/Sil/Component/Emailing/Tests/Unit/.gitkeep create mode 100644 src/Sil/Component/Emailing/Tests/Unit/EmailAddressTest.php create mode 100644 src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php create mode 100644 src/Sil/Component/Emailing/Tests/Unit/MailingListTest.php create mode 100644 src/Sil/Component/Emailing/Tests/Unit/MessageTest.php create mode 100644 src/Sil/Component/Emailing/Tests/Unit/RecipientTest.php create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/after_success_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/before_install_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/before_install_travis_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/create_database_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/do_run.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/install_docs.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/install_lint.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/install_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/run_docs.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/run_lint.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/run_test.sh create mode 100644 src/Sil/Component/Emailing/bin/ci-scripts/set_db_host_test.sh create mode 100644 src/Sil/Component/Emailing/composer.json create mode 100644 src/Sil/Component/Emailing/etc/dockerfile.jenkins create mode 100644 src/Sil/Component/Emailing/etc/pipeline.jenkins create mode 100644 src/Sil/Component/Emailing/phpmd.xml.dist create mode 100644 src/Sil/Component/Emailing/phpunit.xml.dist diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png b/doc/fr/Sil/Component/Emailing/domain/emailing-0.1.png deleted file mode 100644 index 2df69cbbc79f1465fd06774acf800ebec88b9d6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59670 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 diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing-0.2.png b/doc/fr/Sil/Component/Emailing/domain/emailing-0.2.png new file mode 100644 index 0000000000000000000000000000000000000000..b246cab3cc8bfef12a5502aae67dc9292de11349 GIT binary patch literal 60130 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 literal 0 HcmV?d00001 diff --git a/doc/fr/Sil/Component/Emailing/domain/emailing.uxf b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf index 07fa756f..1e503515 100644 --- a/doc/fr/Sil/Component/Emailing/domain/emailing.uxf +++ b/doc/fr/Sil/Component/Emailing/domain/emailing.uxf @@ -1,13 +1,13 @@ - 9 + 10 UMLFrame - 153 - 117 - 1098 - 621 + 70 + 20 + 1210 + 650 EmailingComponent @@ -15,10 +15,10 @@ UMLClass - 621 - 234 - 144 - 54 + 580 + 150 + 160 + 60 /AbstractMessage/ -- @@ -30,10 +30,10 @@ UMLClass - 513 - 405 - 144 - 45 + 460 + 340 + 160 + 50 template=Resource SimpleMessage @@ -44,10 +44,10 @@ SimpleMessage UMLClass - 747 - 405 - 144 - 45 + 720 + 340 + 160 + 50 template=Resource GroupedMessage @@ -57,27 +57,15 @@ GroupedMessage UMLClass - 171 - 405 - 153 - 72 - - MessageConfiguration --- - - - - UMLClass - - 603 - 549 - 180 - 81 + 560 + 430 + 200 + 90 template=Resource -DiffusionList +MailingList -- -#title: string +#name: string #description: string #enabled: bool @@ -85,12 +73,13 @@ DiffusionList UMLClass - 603 - 666 - 144 - 54 + 560 + 590 + 200 + 60 - Recipient + template=Resource +Recipient -- #valid: bool @@ -98,10 +87,10 @@ DiffusionList UMLClass - 621 - 162 - 144 - 36 + 580 + 70 + 160 + 40 <<Interface>> MessageInterface @@ -112,10 +101,10 @@ MessageInterface UMLClass - 198 - 234 - 162 - 36 + 90 + 70 + 180 + 40 <<Interface>> AttachmentInterface @@ -126,25 +115,25 @@ AttachmentInterface Relation - 351 - 225 - 288 - 45 + 260 + 70 + 340 + 120 lt=- r1=0..1 m1=#message r2=0..n m2=#attachments - 300.0;20.0;10.0;20.0 + 320.0;90.0;130.0;90.0;130.0;20.0;10.0;20.0 Relation - 684 - 189 - 27 - 63 + 650 + 100 + 30 + 70 lt=<<. 10.0;10.0;10.0;50.0 @@ -152,10 +141,10 @@ m2=#attachments Relation - 558 - 279 - 153 - 153 + 510 + 200 + 170 + 170 lt=<<- 150.0;10.0;150.0;70.0;10.0;70.0;10.0;150.0 @@ -163,10 +152,10 @@ m2=#attachments Relation - 684 - 333 - 135 - 99 + 650 + 260 + 150 + 110 lt=- 10.0;10.0;130.0;10.0;130.0;90.0 @@ -174,53 +163,41 @@ m2=#attachments Relation - 315 - 405 - 216 - 45 - - lt=- -r1=1..1 -m1=#config - 10.0;20.0;220.0;20.0 - - - Relation - - 738 - 423 - 180 - 189 + 710 + 360 + 200 + 140 lt=- r1=0..n m1=#lists - 10.0;180.0;180.0;180.0;180.0;10.0;130.0;10.0 + 10.0;110.0;180.0;110.0;180.0;10.0;130.0;10.0 Relation - 738 - 603 - 180 - 108 + 710 + 490 + 200 + 170 lt=- r1=1..n m1=#lists r2=1..n m2=#recipients - 10.0;20.0;180.0;20.0;180.0;90.0;10.0;90.0 + 10.0;20.0;180.0;20.0;180.0;140.0;10.0;140.0 UMLClass - 171 - 522 - 135 - 198 + 90 + 330 + 190 + 220 - EmailAddress + template=Resource +EmailAddress -- -value: string @@ -228,103 +205,80 @@ m2=#recipients Relation - 297 - 666 - 324 - 45 + 230 + 510 + 350 + 140 lt=- m2=email r2=1..1 - 340.0;20.0;10.0;20.0 + 330.0;120.0;100.0;120.0;100.0;20.0;10.0;20.0 Relation - 297 - 450 - 117 - 225 + 230 + 360 + 250 + 160 lt=- m2=#from r2=1..1 - 30.0;10.0;110.0;10.0;110.0;220.0;10.0;220.0 + 230.0;10.0;100.0;10.0;100.0;130.0;10.0;130.0 Relation - 297 - 594 - 117 - 45 + 230 + 430 + 120 + 50 lt=- m2=#to r2=1..n - 110.0;20.0;10.0;20.0 + 100.0;20.0;10.0;20.0 Relation - 297 - 558 - 117 - 45 + 230 + 390 + 120 + 50 lt=- m2=#cc r2=0..n - 110.0;20.0;10.0;20.0 + 100.0;20.0;10.0;20.0 Relation - 297 - 522 - 117 - 45 + 230 + 350 + 120 + 50 lt=- m2=#bcc r2=0..n - 110.0;20.0;10.0;20.0 + 100.0;20.0;10.0;20.0 UMLClass - 585 - 477 - 180 - 45 - - <<Interface>> -DiffusionListInterface --- - - - - Relation - - 666 - 513 - 27 - 63 - - lt=<<. - 10.0;10.0;10.0;50.0 - - - UMLClass - - 972 - 144 - 171 - 63 + 970 + 50 + 190 + 70 template=Resource MessageTemplate -- +#name: string #content: string @@ -332,10 +286,10 @@ MessageTemplate UMLClass - 972 - 360 - 171 - 63 + 970 + 290 + 190 + 70 template=Resource ContentToken @@ -347,23 +301,25 @@ ContentToken Relation - 1098 - 162 - 108 - 126 + 1110 + 60 + 170 + 150 lt=- r1=0..n -m1=#tokens - 10.0;110.0;100.0;110.0;100.0;10.0;10.0;10.0 +m1=#tokenTypes +r2=1..1 +m2=#template + 10.0;120.0;150.0;120.0;150.0;20.0;10.0;20.0 Relation - 756 - 153 - 234 - 117 + 730 + 60 + 260 + 130 lt=- r2=0..n @@ -375,10 +331,10 @@ m1=#template Relation - 756 - 261 - 234 - 144 + 730 + 180 + 260 + 160 lt=- r2=1..1 @@ -390,10 +346,10 @@ m1=#tokens UMLClass - 972 - 225 - 171 - 99 + 970 + 140 + 190 + 110 template=Resource ContentTokenType @@ -405,10 +361,10 @@ ContentTokenType Relation - 1098 - 297 - 108 - 108 + 1110 + 220 + 120 + 120 lt=- r2=1..1 @@ -418,19 +374,18 @@ m2=#type UMLClass - 999 - 477 - 90 - 126 + 960 + 420 + 170 + 130 <<enum>> -DataType +ContentTokenDataType -- BOOLEAN STRING INTEGER FLOAT -PERCENT DATE DATETIME @@ -439,14 +394,65 @@ DATETIME Relation - 1080 - 279 - 171 - 243 + 1110 + 200 + 170 + 270 lt=- r1=1..1 m1=#dataType - 10.0;240.0;170.0;240.0;170.0;10.0;30.0;10.0 + 20.0;240.0;150.0;240.0;150.0;10.0;10.0;10.0 + + + UMLClass + + 90 + 180 + 180 + 40 + + <<Interface>> +MessageStateInterface +-- + + + + + UMLClass + + 90 + 250 + 180 + 30 + + MessageState +-- + + + + + Relation + + 170 + 210 + 30 + 60 + + lt=<<. + 10.0;10.0;10.0;40.0 + + + Relation + + 260 + 180 + 340 + 50 + + lt=- +r2=1..1 +m2=#state + 320.0;20.0;10.0;20.0 diff --git a/doc/fr/Sil/Component/Emailing/domain/index.rst b/doc/fr/Sil/Component/Emailing/domain/index.rst index 57793ea2..44764d3a 100644 --- a/doc/fr/Sil/Component/Emailing/domain/index.rst +++ b/doc/fr/Sil/Component/Emailing/domain/index.rst @@ -196,7 +196,7 @@ Un **jeton de substitution** permet de remplacer des emplacements définis depui Modèle du domaine ----------------- -.. image:: emailing-0.1.png +.. image:: emailing-0.2.png ------------ Cycle de vie diff --git a/src/Sil/Component/Emailing/.editorconfig b/src/Sil/Component/Emailing/.editorconfig new file mode 100644 index 00000000..43148672 --- /dev/null +++ b/src/Sil/Component/Emailing/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,json,scss,css,yml,twig,php,xml,sh}] +indent_size = 4 diff --git a/src/Sil/Component/Emailing/.env.jenkins b/src/Sil/Component/Emailing/.env.jenkins new file mode 100644 index 00000000..7c9729eb --- /dev/null +++ b/src/Sil/Component/Emailing/.env.jenkins @@ -0,0 +1,32 @@ + + +if [ ! -f shuf.nbr ] +then + shuf -i 200-600 -n 1 > shuf.nbr +fi + +#RND contain branch name which may contain '-' which may not work as database name for postgre + +RND=$(echo $RND|sed -e s/-/_/g|tr '[:upper:]' '[:lower:]')$(echo -n $(cat shuf.nbr )) + +SILURL="/sil" +SERVERADDR="127.0.0.1:8042" +SERVERENV="test" + +DBHOST=postgres.host +DBROOTUSER=postgres +DBROOTPASSWORD=postgres24 +DBAPPNAME=sil_db_${RND} +DBAPPUSER=sil_user_${RND} +DBAPPPASSWORD=sil_password + +ELHOST=elk.host +ELALIAS=sil_${RND} + +PHPUNITCMD="bin/phpunit --verbose --debug -c phpunit.xml.dist --coverage-html build/coverage --coverage-clover build/clover.xml --coverage-crap4j build/crap4j.xml --log-junit build/junit.xml" #--testdox +#CODECEPTCMD="bin/codecept run -vvv --debug --steps --fail-fast --no-interaction --xml --html" +CODECEPTENV="firefox" #chrome firefox,lisem ... +CODECEPTOUTPUT="src/Tests/_output/" + + +DISPLAY=:99 diff --git a/src/Sil/Component/Emailing/.env.travis b/src/Sil/Component/Emailing/.env.travis new file mode 100644 index 00000000..0b042b50 --- /dev/null +++ b/src/Sil/Component/Emailing/.env.travis @@ -0,0 +1,23 @@ + + +SILURL="/sil" +SERVERADDR="127.0.0.1:8042" +SERVERENV="test" + +DBHOST=localhost +DBROOTUSER=postgres +DBROOTPASSWORD=postgres24 +DBAPPNAME=test_sil_db +DBAPPUSER=test_sil_user +DBAPPPASSWORD=test_sil_password + +ELHOST=localhost +ELALIAS=Sil + +PHPUNITCMD="bin/phpunit --verbose --debug -c phpunit.xml.dist --coverage-clover build/logs/clover.xml" #--testdox +#CODECEPTCMD="bin/codecept run -vvv --debug --steps --fail-fast --no-interaction --xml --html" +CODECEPTENV="firefox" #chrome firefox,lisem ... +CODECEPTOUTPUT="src/Tests/_output/" + + +DISPLAY=:99 diff --git a/src/Sil/Component/Emailing/.github/ISSUE_TEMPLATE.md b/src/Sil/Component/Emailing/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..7209c517 --- /dev/null +++ b/src/Sil/Component/Emailing/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +| Q | A +| ---------------- | ----- +| Bug report? | yes/no +| Feature request? | yes/no +| BC Break report? | yes/no +| Version | x.y.z + + diff --git a/src/Sil/Component/Emailing/.github/PULL_REQUEST_TEMPLATE.md b/src/Sil/Component/Emailing/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..b92f1610 --- /dev/null +++ b/src/Sil/Component/Emailing/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +| Q | A +| ------------- | --- +| Branch? | master +| Bug fix? | yes/no +| New feature? | yes/no +| BC breaks? | yes/no +| Tests pass? | yes/no +| Fixed tickets | fixes #X, partially #Y, mentioned in #Z +| License | LGPL + + + diff --git a/src/Sil/Component/Emailing/.gitignore b/src/Sil/Component/Emailing/.gitignore new file mode 100644 index 00000000..6be7c114 --- /dev/null +++ b/src/Sil/Component/Emailing/.gitignore @@ -0,0 +1,11 @@ +/nbproject +/.php_cs.cache +/composer.phar +/composer.lock +/vendor/ +/Resources/doc/_build +*~ +/bin/* +!/bin/console +!/bin/ci-scripts +!/bin/git-script \ No newline at end of file diff --git a/src/Sil/Component/Emailing/.gitrepo b/src/Sil/Component/Emailing/.gitrepo new file mode 100644 index 00000000..98603c72 --- /dev/null +++ b/src/Sil/Component/Emailing/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = git@github.com:sil-project/Order.git + branch = wip-platform + commit = 4e6c89333ba3b70c866553de3777a438fd8401a0 + method = merge + cmdver = 0.4.0 + parent = 50e93e7427a5053c19cebb6beb86860b5e318920 diff --git a/src/Sil/Component/Emailing/.php_cs b/src/Sil/Component/Emailing/.php_cs new file mode 100644 index 00000000..e41052d3 --- /dev/null +++ b/src/Sil/Component/Emailing/.php_cs @@ -0,0 +1,47 @@ +in(__DIR__) +; + +$config = PhpCsFixer\Config::create() + ->setRules(array( + '@Symfony' => true, + 'binary_operator_spaces' => ['align_double_arrow' => true], + 'concat_space' => ['spacing'=>'one'], + 'yoda_style' => null, + 'increment_style' => ['style' => 'post'], + )) + ->setFinder($finder); + +// PHP-CS-Fixer 2.x +if (method_exists($config, 'setRules')) { + $config->setRules(array_merge($config->getRules(), array( + 'header_comment' => array('header' => $header), + ))); +} + +return $config; diff --git a/src/Sil/Component/Emailing/.scrutinizer.yml b/src/Sil/Component/Emailing/.scrutinizer.yml new file mode 100644 index 00000000..08151b1a --- /dev/null +++ b/src/Sil/Component/Emailing/.scrutinizer.yml @@ -0,0 +1,16 @@ +filter: + excluded_paths: + - 'vendor/*' + - 'bin/*' + - '*.min.js' + - 'Tests/*' +checks: + php: + code_rating: true + duplication: true + javascript: true +coding_style: + php: + spaces: + around_operators: + concatenation: true diff --git a/src/Sil/Component/Emailing/.styleci.yml b/src/Sil/Component/Emailing/.styleci.yml new file mode 100644 index 00000000..977c5a87 --- /dev/null +++ b/src/Sil/Component/Emailing/.styleci.yml @@ -0,0 +1,11 @@ +# Package `sllh/php-cs-fixer-styleci-bridge` is required to get it working. + +preset: psr2 + +#disabled: +# - concat_without_spaces +# - single_quote + +enabled: + - no_php4_constructor + - concat_with_spaces diff --git a/src/Sil/Component/Emailing/.travis.yml b/src/Sil/Component/Emailing/.travis.yml new file mode 100644 index 00000000..bd1d3462 --- /dev/null +++ b/src/Sil/Component/Emailing/.travis.yml @@ -0,0 +1,54 @@ +language: php + +php: + - '7.1' + - nightly + +services: + - mysql + - postgresql + +cache: + directories: + - $HOME/.composer/cache/files + +env: + global: + - PATH="$HOME/.local/bin:$PATH" + - SCRIPTS_FOLDER=bin/ci-scripts + - SYMFONY_DEPRECATIONS_HELPER=weak + - TARGET=test + - XMLLINT_INDENT=" " + +matrix: + fast_finish: true + include: + - php: '7.1' + env: TARGET=docs + - php: '7.1' + env: TARGET=lint + allow_failures: + - php: nightly + - env: SYMFONY_DEPRECATIONS_HELPER=0 + - env: SYMFONY=dev-master@dev + - env: SONATA_CORE=dev-master@dev + - env: SONATA_BLOCK=dev-master@dev + +before_install: + - if [ -x ${SCRIPTS_FOLDER}/before_install_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/before_install_${TARGET}.sh; fi; + - if [ -x ${SCRIPTS_FOLDER}/create_database_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/create_database_${TARGET}.sh; fi; + +install: + - if [ -x ${SCRIPTS_FOLDER}/install_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/install_${TARGET}.sh; fi; + +before_script: + - if [ -x ${SCRIPTS_FOLDER}/before_script_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/before_script_${TARGET}.sh; fi; + +script: + - if [ -x ${SCRIPTS_FOLDER}/run_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/run_${TARGET}.sh; fi; + +after_success: + - if [ -x ${SCRIPTS_FOLDER}/after_success_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/after_success_${TARGET}.sh; fi; + +after_failure: + - if [ -x ${SCRIPTS_FOLDER}/after_failure_${TARGET}.sh ]; then ${SCRIPTS_FOLDER}/after_failure_${TARGET}.sh; fi; diff --git a/src/Sil/Component/Emailing/LICENCE.md b/src/Sil/Component/Emailing/LICENCE.md new file mode 100644 index 00000000..408c98d4 --- /dev/null +++ b/src/Sil/Component/Emailing/LICENCE.md @@ -0,0 +1,157 @@ +### GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +#### 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +#### 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +#### 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +#### 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +#### 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +#### 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +#### 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/src/Sil/Component/Emailing/Model/AbstractMessage.php b/src/Sil/Component/Emailing/Model/AbstractMessage.php new file mode 100644 index 00000000..bb1cdb23 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/AbstractMessage.php @@ -0,0 +1,168 @@ +title = $title; + $this->content = $content; + $this->template = $template; + + $this->state = MessageState::draft(); + + $this->attachments = new ArrayCollection(); + $this->tokens = new ArrayCollection(); + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @return string + */ + public function getContent(): string + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function getAttachments(): array + { + return $this->attachments->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addAttachment(AttachmentInterface $attachment): void + { + if ($this->attachments->contains($attachment)) { + throw new InvalidArgumentException(sprintf('Attachment « %s » is already attached to message « %s »', $attachment->getName(), $this->getTitle())); + } + $this->attachments->add($attachment); + } + + /** + * {@inheritdoc} + */ + public function removeAttachment(AttachmentInterface $attachment): void + { + if (!$this->attachments->contains($attachment)) { + throw new InvalidArgumentException(sprintf('Attachment « %s » is not attached to message « %s »', $attachment->getName(), $this->getTitle())); + } + $this->attachments->removeElement($attachment); + } + + /** + * {@inheritdoc} + */ + public function getTemplate(): ?MessageTemplateInterface + { + return $this->template; + } + + /** + * {@inheritdoc} + */ + public function setTemplate(?MessageTemplateInterface $template): void + { + $this->template = $template; + } + + /** + * {@inheritdoc} + */ + public function getTokens(): array + { + return $this->tokens->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addToken(ContentTokenInterface $token): void + { + if ($this->tokens->contains($token)) { + throw new InvalidArgumentException(sprintf('Token « %s : %s » is already assigned to the message « %s »', $token->getName(), $token->getValue(), $this->getTitle())); + } + $this->tokens->add($token); + } + + /** + * {@inheritdoc} + */ + public function removeToken(ContentTokenInterface $token): void + { + if (!$this->tokens->contains($token)) { + throw new InvalidArgumentException(sprintf('Token « %s : %s » is not assigned to the message « %s »', $token->getName(), $token->getValue(), $this->getTitle())); + } + $this->tokens->removeElement($token); + } +} diff --git a/src/Sil/Component/Emailing/Model/AttachmentInterface.php b/src/Sil/Component/Emailing/Model/AttachmentInterface.php new file mode 100644 index 00000000..4851fd5b --- /dev/null +++ b/src/Sil/Component/Emailing/Model/AttachmentInterface.php @@ -0,0 +1,17 @@ +tokenType = $tokenType; + $this->$message = $message; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->getTokenType()->getName(); + } + + /** + * {@inheritdoc} + */ + public function getMessage(): MessageInterface + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function setValue($value): void + { + $typeIsValid = false; + + switch ($this->getTokenType()->getDataType()) { + case ContentTokenDataType::BOOLEAN: + $typeIsValid = is_bool($value); + break; + + case ContentTokenDataType::DATE: + case ContentTokenDataType::DATETIME: + $typeIsValid = get_class($value) === 'DateTime'; + break; + + case ContentTokenDataType::STRING: + $typeIsValid = is_string($value); + break; + + case ContentTokenDataType::INTEGER: + $typeIsValid = is_int($value); + break; + + case ContentTokenDataType::FLOAT: + $typeIsValid = is_float($value); + break; + + default: + throw new InvalidArgumentException(sprintf('The type of the value (%s) of ContentToken « %s » is not managed', $this->getName(), gettype($value))); + } + + if (!$typeIsValid) { + throw new InvalidArgumentException(sprintf('Value of ContentToken « %s » must be of type « %s », « %s » given', $this->getName(), $this->getTokenType()->getDataType(), gettype($value))); + } + + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function getTokenType(): TokentTypeInterface + { + return $this->tokenType; + } + + /** + * {@inheritdoc} + */ + public function getValueAsString(): string + { + $stringValue = ''; + + switch ($this->getTokenType()->getDataType()) { + case ContentTokenDataType::BOOLEAN: + $stringValue = $this->value === true ? 'Yes' : 'No'; + break; + + case ContentTokenDataType::DATE: + $stringValue = $this->value->format('Y-m-d'); + break; + + case ContentTokenDataType::DATETIME: + $stringValue = $this->value->format('Y-m-d H:i:s'); + break; + + case ContentTokenDataType::STRING: + case ContentTokenDataType::INTEGER: + case ContentTokenDataType::FLOAT: + default: + $stringValue = (string) $this->value; + } + + return $stringValue; + } +} diff --git a/src/Sil/Component/Emailing/Model/ContentTokenInterface.php b/src/Sil/Component/Emailing/Model/ContentTokenInterface.php new file mode 100644 index 00000000..c0114e35 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/ContentTokenInterface.php @@ -0,0 +1,62 @@ +name = $name; + $this->dataType = $dataType; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getDataType(): ContentTokenDataType + { + return $this->dataType; + } +} diff --git a/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php b/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php new file mode 100644 index 00000000..8c8f3e5d --- /dev/null +++ b/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php @@ -0,0 +1,30 @@ +isValid($value)) { + throw new InvalidArgumentException(sprintf('The string « %s » is not a valid email address', $value)); + } + + $this->value = $value; + } + + /** + * Validate if given string is an email. + * + * @param string $value + */ + public function isValid(?string $value = null): bool + { + if ($value === null) { + $value = $this->value; + } + + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * Gets the email as string. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + public function __toString(): string + { + return $this->getValue(); + } +} diff --git a/src/Sil/Component/Emailing/Model/EmailAddressInterface.php b/src/Sil/Component/Emailing/Model/EmailAddressInterface.php new file mode 100644 index 00000000..8159d93e --- /dev/null +++ b/src/Sil/Component/Emailing/Model/EmailAddressInterface.php @@ -0,0 +1,37 @@ +lists = new ArrayCollection(); + } + + /** + * @return array|MailingListInterface[] + */ + public function getLists(): array + { + return $this->lists->getValues(); + } + + /** + * @param MailingListInterface $list + * + * @throws InvalidArgumentException + */ + public function addList(MailingListInterface $list): void + { + if ($this->lists->contains($list)) { + throw new InvalidArgumentException(sprintf('List « %s » is already used by message « %s »', $list->getName(), $this->getTitle())); + } + $this->lists->add($list); + } + + /** + * @param MailingListInterface $list + * + * @throws InvalidArgumentException + */ + public function removeList(MailingListInterface $list): void + { + if (!$this->lists->contains($list)) { + throw new InvalidArgumentException(sprintf('List « %s » is not used by message « %s »', $list->getName(), $this->getTitle())); + } + $this->lists->removeElement($list); + } +} diff --git a/src/Sil/Component/Emailing/Model/GroupedMessageInterface.php b/src/Sil/Component/Emailing/Model/GroupedMessageInterface.php new file mode 100644 index 00000000..21637b10 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/GroupedMessageInterface.php @@ -0,0 +1,37 @@ +name = $name; + $this->description = $description; + + $this->recipients = new ArrayCollection(); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function setDescription(?string $description): void + { + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public function setEnabled(bool $enabled): void + { + if ($this->recipients->count() === 0) { + throw new DomainException(sprintf('MailingList « %s » cannot be enabled because it has no recipients', $this->getName())); + } + $this->enabled = $enabled; + } + + /** + * {@inheritdoc} + */ + public function isEnabled(): bool + { + return $this->enabled; + } + + /** + * {@inheritdoc} + */ + public function getRecipients(): array + { + return $this->recipients->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addRecipient(RecipientInterface $recipient): void + { + if ($this->recipients->contains($recipient)) { + throw new InvalidArgumentException(sprintf('Recipient « %s » already belong to list « %s »', $recipient->getEmail(), $this->getName())); + } + $this->recipients->add($recipient); + } + + /** + * {@inheritdoc} + */ + public function removeRecipient(RecipientInterface $recipient): void + { + if (!$this->recipients->contains($recipient)) { + throw new InvalidArgumentException(sprintf('Recipient « %s » does not belongs to list « %s »', $recipient->getEmail(), $this->getName())); + } + $this->recipients->removeElement($recipient); + } +} diff --git a/src/Sil/Component/Emailing/Model/MailingListInterface.php b/src/Sil/Component/Emailing/Model/MailingListInterface.php new file mode 100644 index 00000000..40ce42f8 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/MailingListInterface.php @@ -0,0 +1,80 @@ +setValue($value); + $this->stateMachine = new MessageStateMachine($this); + } + + /** + * {@inheritdoc} + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @internal + * + * @param string $value + */ + public function setValue(string $value): void + { + if (!in_array($value, static::getStates())) { + throw new InvalidArgumentException(sprintf('The state %s is not a valid state, valids are : %s', $value, implode(', ', static::getStates()))); + } + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public static function getStates(): array + { + return [ + 'DRAFT' => static::DRAFT, + 'VALIDATED' => static::VALIDATED, + 'SENT' => static::SENT, + 'DELETED' => static::DELETED, + ]; + } + + /** + * {@inheritdoc} + */ + public static function draft(): MessageStateInterface + { + return new self(static::DRAFT); + } + + /** + * {@inheritdoc} + */ + public static function validated(): MessageStateInterface + { + return new self(static::VALIDATED); + } + + /** + * {@inheritdoc} + */ + public static function sent(): MessageStateInterface + { + return new self(static::SENT); + } + + /** + * {@inheritdoc} + */ + public static function deleted(): MessageStateInterface + { + return new self(static::DELETED); + } + + /** + * {@inheritdoc} + */ + public function isDraft(): bool + { + return $this->value === static::DRAFT; + } + + /** + * {@inheritdoc} + */ + public function isValidated(): bool + { + return $this->value === static::VALIDATED; + } + + /** + * {@inheritdoc} + */ + public function isSent(): bool + { + return $this->value === static::SENT; + } + + /** + * {@inheritdoc} + */ + public function isDeleted(): bool + { + return $this->value === static::DELETED; + } + + /** + * {@inheritdoc} + */ + public function toDelete(): MessageStateInterface + { + $this->stateMachine->apply('delete'); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function toValidate(): MessageStateInterface + { + $this->stateMachine->apply('validate'); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function toSent(): MessageStateInterface + { + $this->stateMachine->apply('sent'); + + return $this; + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/Sil/Component/Emailing/Model/MessageStateAwareInterface.php b/src/Sil/Component/Emailing/Model/MessageStateAwareInterface.php new file mode 100644 index 00000000..e22cb867 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/MessageStateAwareInterface.php @@ -0,0 +1,66 @@ +state; + } + + /** + * @internal + * + * @param MessageStateInterface $state + */ + public function setState(MessageStateInterface $state): void + { + $this->state = $state; + } + + /** + * Sets current state to validated. + */ + public function beValidated(): void + { + $this->getState()->toValidate(); + } + + /** + * Sets current state to sent. + */ + public function beSent(): void + { + $this->getState()->toSent(); + } + + /** + * Sets current state to deleted. + */ + public function beDeleted(): void + { + $this->getState()->toDelete(); + } + + /** + * Checks if current state is draft. + * + * @return bool + */ + public function isDraft(): bool + { + return $this->getState()->isDraft(); + } + + /** + * Checks if current state is validated. + * + * @return bool + */ + public function isValidated(): bool + { + return $this->getState()->isValidated(); + } + + /** + * Checks if current state is sent. + * + * @return bool + */ + public function isSent(): bool + { + return $this->getState()->isSent(); + } + + /** + * Checks if current state is deleted. + * + * @return bool + */ + public function isDeleted(): bool + { + return $this->getState()->isDeleted(); + } +} diff --git a/src/Sil/Component/Emailing/Model/MessageStateInterface.php b/src/Sil/Component/Emailing/Model/MessageStateInterface.php new file mode 100644 index 00000000..6891ddc6 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/MessageStateInterface.php @@ -0,0 +1,107 @@ + static::STATE, + * [...] + * ]. + * + * @return array + */ + public static function getStates(): array; + + /** + * Gets current state value. + * + * @return string + */ + public function getValue(): string; + + /** + * Draft state constructor. + * + * @return MessageStateInterface + */ + public static function draft(): self; + + /** + * Validated state constructor. + * + * @return MessageStateInterface + */ + public static function validated(): self; + + /** + * Sent state constructor. + * + * @return MessageStateInterface + */ + public static function sent(): self; + + /** + * Deleted state constructor. + * + * @return MessageStateInterface + */ + public static function deleted(): self; + + /** + * Current state is Draft. + * + * @return bool + */ + public function isDraft(): bool; + + /** + * Current state is Validated. + * + * @return bool + */ + public function isValidated(): bool; + + /** + * Current state is Sent. + * + * @return bool + */ + public function isSent(): bool; + + /** + * Current state is Deleted. + * + * @return bool + */ + public function isDeleted(): bool; + + /** + * Apply transition delete. + */ + public function toDelete(): self; + + /** + * Apply transition validate. + */ + public function toValidate(): self; + + /** + * Apply transition cancel. + */ + public function toSent(): self; +} diff --git a/src/Sil/Component/Emailing/Model/MessageTemplate.php b/src/Sil/Component/Emailing/Model/MessageTemplate.php new file mode 100644 index 00000000..33311f48 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/MessageTemplate.php @@ -0,0 +1,138 @@ +name = $name; + $this->content = $content; + + $this->tokenTypes = new ArrayCollection(); + $this->messages = new ArrayCollection(); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getContent(): string + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function getTokenTypes(): array + { + return $this->tokenTypes->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addTokenType(ContentTokenTypeInterface $token): void + { + if ($this->tokenTypes->contains($token)) { + throw new InvalidArgumentException(sprintf('TokenType « %s » is already associated to template « %s »', $token->getName(), $this->getName())); + } + $this->tokenTypes->add($token); + } + + /** + * {@inheritdoc} + */ + public function removeTokenType(ContentTokenTypeInterface $token): void + { + if (!$this->tokenTypes->contains($token)) { + throw new InvalidArgumentException(sprintf('TokenType « %s » is not associated to template « %s »', $token->getName(), $this->getName())); + } + $this->tokenTypes->removeElement($token); + } + + /** + * @return array|MessageInterface[] + */ + public function getMessages(): array + { + return $this->messages->getValues(); + } + + /** + * @param MessageInterface $message + * + * @throws InvalidArgumentException + */ + public function addMessage(MessageInterface $message): void + { + if ($this->messages->contains($message)) { + throw new InvalidArgumentException(sprintf('Message « %s » is already using template « %s »', $message->getTitle(), $this->getName())); + } + $this->messages->add($message); + } + + /** + * @param MessageInterface $message + * + * @throws InvalidArgumentException + */ + public function removeMessage(MessageInterface $message): void + { + if (!$this->messages->contains($message)) { + throw new InvalidArgumentException(sprintf('Message « %s » is not using template « %s »', $message->getTitle(), $this->getName())); + } + $this->messages->removeElement($message); + } +} diff --git a/src/Sil/Component/Emailing/Model/MessageTemplateInterface.php b/src/Sil/Component/Emailing/Model/MessageTemplateInterface.php new file mode 100644 index 00000000..2c79abfb --- /dev/null +++ b/src/Sil/Component/Emailing/Model/MessageTemplateInterface.php @@ -0,0 +1,57 @@ +email = $email; + } + + /** + * {@inheritdoc} + */ + public static function createFromEmailAsString(string $email): RecipientInterface + { + $email = new EmailAddress($email); + + return new self($email); + } + + /** + * {@inheritdoc} + */ + public function isValid(): bool + { + return $this->valid; + } + + /** + * {@inheritdoc} + */ + public function setValid(bool $valid): void + { + $this->valid = $valid; + } + + /** + * {@inheritdoc} + */ + public function getEmail(): EmailAddressInterface + { + return $this->email; + } +} diff --git a/src/Sil/Component/Emailing/Model/RecipientInterface.php b/src/Sil/Component/Emailing/Model/RecipientInterface.php new file mode 100644 index 00000000..c885a2c7 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/RecipientInterface.php @@ -0,0 +1,46 @@ +to = new ArrayCollection(); + $this->cc = new ArrayCollection(); + $this->bcc = new ArrayCollection(); + + $this->from = $from; + $this->addTo($to); + } + + /** + * {@inheritdoc} + */ + public function getFrom(): EmailAddressInterface + { + return $this->from; + } + + /** + * {@inheritdoc} + */ + public function getTo(): array + { + return $this->to->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addTo(EmailAddressInterface $to): void + { + if ($this->to->contains($to)) { + throw new InvalidArgumentException(sprintf('To « %s » is already set for message « %s »', $to, $this->getTitle())); + } + $this->to->add($to); + } + + /** + * {@inheritdoc} + */ + public function removeTo(EmailAddressInterface $to): void + { + if (!$this->to->contains($to)) { + throw new InvalidArgumentException(sprintf('To « %s » is not set for message « %s »', $to, $this->getTitle())); + } + if ($this->to->count() === 1) { + throw new DomainException( + sprintf( + 'Message « %s » must have at least one recipient in To field. + Removing « %s » will result in an empty to field, wich is not a valid state for a message', + $this->getTitle(), + $to + ) + ); + } + $this->to->removeElement($to); + } + + /** + * {@inheritdoc} + */ + public function getCc(): array + { + return $this->cc->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addCc(EmailAddressInterface $cc): void + { + if ($this->cc->contains($cc)) { + throw new InvalidArgumentException(sprintf('Cc address « %s » is already set for message « %s »', $cc, $this->getTitle())); + } + $this->cc->add($cc); + } + + /** + * {@inheritdoc} + */ + public function removeCc(EmailAddressInterface $cc): void + { + if (!$this->cc->contains($cc)) { + throw new InvalidArgumentException(sprintf('Cc address « %s » does not exists for message « %s »', $cc, $this->getTitle())); + } + $this->cc->removeElement($cc); + } + + /** + * {@inheritdoc} + */ + public function getBcc(): array + { + return $this->bcc->getValues(); + } + + /** + * {@inheritdoc} + */ + public function addBcc(EmailAddressInterface $bcc): void + { + if ($this->bcc->contains($bcc)) { + throw new InvalidArgumentException(sprintf('Bcc address « %s » is already set for message « %s »', $bcc, $this->getTitle())); + } + $this->bcc->add($bcc); + } + + /** + * {@inheritdoc} + */ + public function removeBcc(EmailAddressInterface $bcc): void + { + if (!$this->bcc->contains($bcc)) { + throw new InvalidArgumentException(sprintf('Bcc address « %s » does not exists for message « %s »', $bcc, $this->getTitle())); + } + $this->bcc->removeElement($bcc); + } +} diff --git a/src/Sil/Component/Emailing/Model/SimpleMessageInterface.php b/src/Sil/Component/Emailing/Model/SimpleMessageInterface.php new file mode 100644 index 00000000..7641ab3d --- /dev/null +++ b/src/Sil/Component/Emailing/Model/SimpleMessageInterface.php @@ -0,0 +1,98 @@ + 'message_state', + 'property_path' => 'value', + 'states' => [ + MessageState::DRAFT, + MessageState::VALIDATED, + MessageState::SENT, + MessageState::DELETED, + ], + 'transitions' => [ + 'delete' => [ + 'from' => [MessageState::DRAFT, MessageState::VALIDATED], + 'to' => MessageState::DELETED, + ], + 'validate' => [ + 'from' => [MessageState::DRAFT], + 'to' => MessageState::VALIDATED, + ], + 'sent' => [ + 'from' => [MessageState::VALIDATED, MessageState::SENT], + 'to' => MessageState::SENT, + ], + ], + ]; + + public function __construct($object) + { + parent::__construct($object, $this->config); + } +} diff --git a/src/Sil/Component/Emailing/Tests/.gitkeep b/src/Sil/Component/Emailing/Tests/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/Sil/Component/Emailing/Tests/Unit.suite.yml b/src/Sil/Component/Emailing/Tests/Unit.suite.yml new file mode 100644 index 00000000..026c91db --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit.suite.yml @@ -0,0 +1,9 @@ +# Codeception Test Suite Configuration +# +# Suite for unit or integration tests. + +actor: UnitTester +modules: + enabled: + - Asserts + - \Helper\Unit \ No newline at end of file diff --git a/src/Sil/Component/Emailing/Tests/Unit/.gitkeep b/src/Sil/Component/Emailing/Tests/Unit/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/Sil/Component/Emailing/Tests/Unit/EmailAddressTest.php b/src/Sil/Component/Emailing/Tests/Unit/EmailAddressTest.php new file mode 100644 index 00000000..c3a5188e --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/EmailAddressTest.php @@ -0,0 +1,41 @@ +fixtures = new Fixtures(); + } + + public function test_valid_email_address_creation() + { + $emailAddress = new EmailAddress('valid@sil.eu'); + + $this->assertTrue($emailAddress->isValid()); + } + + public function test_invalid_email_address_creation() + { + $this->expectException(\InvalidArgumentException::class); + + $emailAddress = new EmailAddress('not.a.valid.address'); + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php new file mode 100644 index 00000000..d2de64bc --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php @@ -0,0 +1,241 @@ +rawData = [ + 'Mailing lists' => [ + 'ListOne' => [ + 'name' => 'ListOne', + 'description' => null, + 'recipients' => [ + 'recipient1@sil.eu', + 'recipient2@sil.eu', + 'recipient3@sil.eu', + 'recipient4@sil.eu', + 'recipient5@sil.eu', + ], + ], + 'ListTwo' => [ + 'name' => 'ListTwo', + 'description' => 'A small description of list', + 'recipients' => [ + 'recipient6@sil.eu', + 'recipient7@sil.eu', + 'recipient8@sil.eu', + 'recipient9@sil.eu', + 'recipient10@sil.eu', + ], + ], + ], + 'Simple messages' => [ + [ + 'title' => 'Simple message One', + 'content' => 'A rich content without lorem.', + 'from' => 'sender1@sil.eu', + 'to' => ['recipient1@sil.eu', 'recipient2@sil.eu'], + 'cc' => ['cc1@sil.eu', 'cc2@sil.eu', 'cc3@sil.eu', 'cc4@sil.eu'], + 'bcc' => ['bcc1@sil.eu'], + ], + [ + 'title' => 'Simple message Two', + 'content' => 'A text content without lorem.', + 'from' => 'sender2@sil.eu', + 'to' => ['recipient3@sil.eu', 'recipient4@sil.eu'], + 'cc' => [], + 'bcc' => [], + ], + ], + 'Grouped messages' => [ + [ + 'title' => 'Grouped message One', + 'content' => 'A rich content without lorem.', + 'lists' => [ + 'ListOne', + ], + ], + [ + 'title' => 'Grouped message Two', + 'content' => 'A text content without lorem.', + 'lists' => [ + 'ListOne', + 'ListTwo', + ], + ], + ], + ]; + + $this->simpleMessageRepository = new InMemoryRepository(SimpleMessageInterface::class); + $this->groupedMessageRepository = new InMemoryRepository(GroupedMessageInterface::class); + $this->mailingListRepository = new InMemoryRepository(MailingListInterface::class); + $this->recipientRepository = new InMemoryRepository(RecipientInterface::class); + $this->emailAddressRepository = new InMemoryRepository(EmailAddressInterface::class); + + $this->generateFixtures(); + } + + private function generateFixtures(): void + { + $this->loadMailingLists(); + $this->loadSimpleMessages(); + $this->loadGroupedMessages(); + } + + private function loadMailingLists() + { + foreach ($this->rawData['Mailing lists'] as $mailingListData) { + $mailingList = new MailingList($mailingListData['name']); + + foreach ($mailingListData['recipients'] as $recipientEmail) { + $recipient = Recipient::createFromEmailAsString($recipientEmail); + $this->recipientRepository->add($recipient); + $mailingList->addRecipient($recipient); + } + + $this->mailingListRepository->add($mailingList); + } + } + + private function loadSimpleMessages() + { + foreach ($this->rawData['Simple messages'] as $messageData) { + $from = new EmailAddress($messageData['from']); + $to = new EmailAddress($messageData['to'][0]); + + $this->emailAddressRepository->add($from); + $this->emailAddressRepository->add($to); + + $simpleMessage = new SimpleMessage($messageData['title'], $messageData['content'], null, $from, $to); + + foreach ($messageData['to'] as $tos) { + $toAddress = new EmailAddress($tos); + if (!in_array($toAddress, $simpleMessage->getTo())) { + $simpleMessage->addTo($toAddress); + $this->emailAddressRepository->add($toAddress); + } + } + + foreach ($messageData['cc'] as $ccs) { + $ccAddress = new EmailAddress($ccs); + if (!in_array($ccAddress, $simpleMessage->getCc())) { + $simpleMessage->addCc($ccAddress); + $this->emailAddressRepository->add($ccAddress); + } + } + + foreach ($messageData['bcc'] as $bccs) { + $bccAddress = new EmailAddress($bccs); + if (!in_array($bccAddress, $simpleMessage->getBcc())) { + $simpleMessage->addBcc($bccAddress); + $this->emailAddressRepository->add($bccAddress); + } + } + + $this->simpleMessageRepository->add($simpleMessage); + } + } + + private function loadGroupedMessages() + { + foreach ($this->rawData['Grouped messages'] as $messageData) { + $message = new GroupedMessage($messageData['title'], $messageData['content']); + + foreach ($messageData['lists'] as $listName) { + $list = $this->mailingListRepository->findOneBy(['name' => $listName]); + $message->addList($list); + } + + $this->groupedMessageRepository->add($message); + } + } + + /** + * @return SimpleMessageRepositoryInterface + */ + public function getSimpleMessageRepository(): SimpleMessageRepositoryInterface + { + return $this->simpleMessageRepository; + } + + /** + * @return GroupedMessageRepositoryInterface + */ + public function getGroupedMessageRepository(): GroupedMessageRepositoryInterface + { + return $this->groupedMessageRepository; + } + + /** + * @return MailingListRepositoryInterface + */ + public function getMailingListRepository(): MailingListRepositoryInterface + { + return $this->mailingListRepository; + } + + /** + * @return RecipientRepositoryInterface + */ + public function getRecipientRepository(): RecipientRepositoryInterface + { + return $this->recipientRepository; + } + + /** + * @return array + */ + public function getRawData(): array + { + return $this->rawData; + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/MailingListTest.php b/src/Sil/Component/Emailing/Tests/Unit/MailingListTest.php new file mode 100644 index 00000000..b92de679 --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/MailingListTest.php @@ -0,0 +1,55 @@ +fixtures = new Fixtures(); + } + + public function test_mailing_list_creation() + { + $mailingList = new MailingList('Test list', 'a test mailing list'); + + $this->assertEquals('Test list', $mailingList->getName()); + $this->assertEquals('a test mailing list', $mailingList->getDescription()); + $this->assertFalse($mailingList->isEnabled()); + } + + public function test_mailing_list_enabling_without_recipients() + { + $mailingList = new MailingList('Test list', 'a test mailing list'); + + $this->expectException(\DomainException::class); + + $mailingList->setEnabled(true); + } + + public function test_mailing_list_enabling_with_recipients() + { + $mailingList = new MailingList('Test list', 'a test mailing list'); + + $mailingList->addRecipient(Recipient::createFromEmailAsString('recipient@sil.eu')); + + $mailingList->setEnabled(true); + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php b/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php new file mode 100644 index 00000000..05cb426e --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php @@ -0,0 +1,49 @@ +fixtures = new Fixtures(); + } + + public function test_simple_email_creation() + { + $from = new EmailAddress('from@sil.eu'); + $to = new EmailAddress('to@sil.eu'); + $message = new SimpleMessage('Simple message', 'a simple message test', null, $from, $to); + + $this->assertEquals('Simple message', $message->getTitle()); + $this->assertEquals('a simple message test', $message->getContent()); + $this->assertEquals($from->getValue(), $message->getFrom()->getValue()); + $this->assertContains($to, $message->getTo()); + } + + public function test_grouped_message_creation() + { + $message = new GroupedMessage('Grouped message', 'a grouped message test'); + + $this->assertEquals('Grouped message', $message->getTitle()); + $this->assertEquals('a grouped message test', $message->getContent()); + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/RecipientTest.php b/src/Sil/Component/Emailing/Tests/Unit/RecipientTest.php new file mode 100644 index 00000000..8f1f3e73 --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/RecipientTest.php @@ -0,0 +1,51 @@ +fixtures = new Fixtures(); + } + + public function test_recipient_creation() + { + $emailAddress = new EmailAddress('recipient@sil.eu'); + + $recipient = new Recipient($emailAddress); + + $this->assertEquals($emailAddress, $recipient->getEmail()); + } + + public function test_recipient_creation_from_valid_string() + { + $recipient = Recipient::createFromEmailAsString('valid.recipient@sil.eu'); + + $this->assertEquals('valid.recipient@sil.eu', $recipient->getEmail()->getValue()); + } + + public function test_recipient_creation_from_invalid_string() + { + $this->expectException(\InvalidArgumentException::class); + + $recipient = Recipient::createFromEmailAsString('invalid.recipient'); + } +} diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/after_success_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/after_success_test.sh new file mode 100644 index 00000000..87158518 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/after_success_test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +set -ev + +coveralls -v diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/before_install_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/before_install_test.sh new file mode 100644 index 00000000..e4fc977b --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/before_install_test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +set -ev + +composer self-update --stable + +mkdir -p ${HOME}/bin + +# Coveralls client install +wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar --output-document="${HOME}/bin/coveralls" +chmod u+x "${HOME}/bin/coveralls" diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/before_install_travis_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/before_install_travis_test.sh new file mode 100644 index 00000000..5e838f77 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/before_install_travis_test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -ev + +mkdir --parents "${HOME}/bin" + +if [ "${WHORUN}" = travis ] +then + # Ugly hack + echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + +fi diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/create_database_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/create_database_test.sh new file mode 100644 index 00000000..4415f6af --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/create_database_test.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -v + +# TODO share this between script (in an include) +if [ -f .env ] +then + source .env +else + echo "Please run this script from project root, and check .env file as it is mandatory" + echo "If it is missing a quick solution is :" + echo "ln -s .env.travis .env" + exit 42 +fi + +if [ -z "${DBHOST}" ] +then + echo "Please add DBHOST in .env file as it is mandatory" + exit 42 +fi + + +# Database creation + +### +### mysql +### + +# (mysql service is started by default by travis for each build instance +# (mysql travis user is created by travis for each build instance +# mysql -u travis -e 'CREATE DATABASE travis;' -v + +### +### postgresql +### + +#psql auto password +#echo localhost:5432:*:postgres:postgres24 >> ~/.pgpass + +# needed in .travis.yml +#services: +# - postgresql +# or here : sudo /etc/init.d/postgresql start + + +psql -w -h ${DBHOST} -c "DROP DATABASE IF EXISTS ${DBAPPNAME};" -U ${DBROOTUSER} +psql -w -h ${DBHOST} -c "DROP ROLE IF EXISTS ${DBAPPUSER};" -U ${DBROOTUSER} + + +# (we try to create a travis user) +psql -w -h ${DBHOST} -c "CREATE USER ${DBAPPUSER} WITH PASSWORD '${DBAPPPASSWORD}';" -U ${DBROOTUSER} +psql -w -h ${DBHOST} -c "ALTER ROLE ${DBAPPUSER} WITH CREATEDB;" -U ${DBROOTUSER} + +psql -w -h ${DBHOST} -c "CREATE DATABASE ${DBAPPNAME};" -U ${DBROOTUSER} +psql -w -h ${DBHOST} -c "ALTER DATABASE ${DBAPPNAME} OWNER TO ${DBAPPUSER};" -U ${DBROOTUSER} + + +psql -w -h ${DBHOST} -c 'CREATE EXTENSION "uuid-ossp";' -U ${DBROOTUSER} -d ${DBAPPNAME} + diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/do_run.sh b/src/Sil/Component/Emailing/bin/ci-scripts/do_run.sh new file mode 100644 index 00000000..0411937d --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/do_run.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +for name in $@ +do + script_name=$(dirname $0)/$name + if [ -x $script_name ] + then + $script_name + fi +done diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/install_docs.sh b/src/Sil/Component/Emailing/bin/ci-scripts/install_docs.sh new file mode 100644 index 00000000..362f6449 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/install_docs.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +set -ev + +pip install -r Resources/doc/requirements.txt --user diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/install_lint.sh b/src/Sil/Component/Emailing/bin/ci-scripts/install_lint.sh new file mode 100644 index 00000000..42a58dca --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/install_lint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +set -ev + +composer global require sllh/composer-lint:@stable --prefer-dist --no-interaction + diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/install_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/install_test.sh new file mode 100644 index 00000000..483ed38e --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/install_test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +set -ev + +composer install --no-interaction --prefer-dist + + + + + diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/run_docs.sh b/src/Sil/Component/Emailing/bin/ci-scripts/run_docs.sh new file mode 100644 index 00000000..31fae91f --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/run_docs.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh + +cd Resources/doc +sphinx-build -b html -d _build/doctrees . _build/html diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/run_lint.sh b/src/Sil/Component/Emailing/bin/ci-scripts/run_lint.sh new file mode 100644 index 00000000..732ea402 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/run_lint.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +composer validate diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/run_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/run_test.sh new file mode 100644 index 00000000..625ff2a5 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/run_test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -ev + +# TODO share this between script (in an include) +if [ -f .env ] +then + source .env +else + echo "Please run this script from project root, and check .env file as it is mandatory" + echo "If it is missing a quick solution is :" + echo "ln -s .env.travis .env" + exit 42 +fi + +export SILURL + + +if [ -n "$PHPUNITCMD" ] +then + $PHPUNITCMD +fi + +#bin/ci-scripts/do_it_for_bundle.sh run test diff --git a/src/Sil/Component/Emailing/bin/ci-scripts/set_db_host_test.sh b/src/Sil/Component/Emailing/bin/ci-scripts/set_db_host_test.sh new file mode 100644 index 00000000..f9d82167 --- /dev/null +++ b/src/Sil/Component/Emailing/bin/ci-scripts/set_db_host_test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -ev + +# TODO share this between script (in an include) +if [ -f .env ] +then + source .env +else + echo "Please run this script from project root, and check .env file as it is mandatory" + echo "If it is missing a quick solution is :" + echo "ln -s .env.travis .env" + exit 42 +fi + + + + +################## +#### POSTGRES #### + + +# TODO +# should remove db info from app/config/config_test.yml +# and use sed or etcd or confd or both to update parameters.yml.dist or create parameters.yml +# sed -e s/'database_host: 127.0.0.1'/'database_host: ${DBHOST}'/g -i app/config/parameters.yml.dist +if [ -n "${DBHOST}" ] +then + + if [ -f Tests/Resources/App/config/parameters.yml ] + then + sed -e s/'127.0.0.1'/${DBHOST}/g -i Tests/Resources/App/config/parameters.yml + sed -e s/'blast_test_user'/${DBAPPUSER}/g -i Tests/Resources/App/config/parameters.yml + sed -e s/'blast_test_password'/${DBAPPPASSWORD}/g -i Tests/Resources/App/config/parameters.yml + sed -e s/'blast_test_db'/${DBAPPNAME}/g -i Tests/Resources/App/config/parameters.yml + cat Tests/Resources/App/config/parameters.yml + fi + + #TODO + # should use env var from etcd (for password) + echo ${DBHOST}:5432:*:${DBROOTUSER}:${DBROOTPASSWORD} >> $HOME/.pgpass + echo ${DBHOST}:5432:*:${DBROOTUSER}:${DBROOTPASSWORD} >> $HOME/.pgpass + chmod 600 $HOME/.pgpass + cat $HOME/.pgpass +fi diff --git a/src/Sil/Component/Emailing/composer.json b/src/Sil/Component/Emailing/composer.json new file mode 100644 index 00000000..eb711009 --- /dev/null +++ b/src/Sil/Component/Emailing/composer.json @@ -0,0 +1,40 @@ +{ + "name": "sil-project/emailing", + "type": "library", + "description": "Emailing Management", + "require": { + "php": "^7.1", + "doctrine/collections": "^1.3", + "blast-project/resource": "self.version", + "winzou/state-machine": "^0.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "license": "LGPL-3.0", + "keywords": [ + "emailing", + "emailing management" + ], + "homepage": "https://github.com/sil-project/Emailing", + "authors": [ + { + "name": "Vivien FRASCA", + "email": "vivien.frasca@libre-informatique.fr" + }, + { + "name": "Libre Informatique", + "homepage": "http://www.libre-informatique.fr/" + } + ], + + "autoload": { + "psr-4": { + "Sil\\Component\\Emailing\\": "." + } + }, + "config": { + "bin-dir": "bin/" + }, + "version": "dev-wip-platform" +} diff --git a/src/Sil/Component/Emailing/etc/dockerfile.jenkins b/src/Sil/Component/Emailing/etc/dockerfile.jenkins new file mode 100644 index 00000000..5acb9820 --- /dev/null +++ b/src/Sil/Component/Emailing/etc/dockerfile.jenkins @@ -0,0 +1,73 @@ +FROM debian:latest +# https://issues.jenkins-ci.org/browse/JENKINS-44609 +# https://issues.jenkins-ci.org/browse/JENKINS-31507 +#as phpcli_for_platform + +RUN apt-get update + +RUN apt-get install -y apt-transport-https lsb-release ca-certificates git curl wget build-essential g++ unzip net-tools lsof dnsutils vim python-pip + +RUN echo "deb http://ftp.debian.org/debian $(lsb_release -sc)-backports main" >> /etc/apt/sources.list && apt-get update +RUN wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg && echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list && apt-get update + + +#RUN apt-get install -y libpq-dev libcurl4-openssl-dev libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng12-dev zlib1g-dev libicu-dev unixodbc-dev libxml2-dev libaio-dev libmemcached-dev freetds-dev + +RUN apt-get install -y nodejs python python3 +RUN apt-get install -y openjdk-8-jdk openjdk-8-jre && update-alternatives --config java +RUN apt-get install -y xvfb chromium chromium-l10n firefox-esr postgresql-client + + +ARG PHPVER=7.1 +RUN apt-get install -y php${PHPVER} php${PHPVER}-cli php${PHPVER}-pgsql php${PHPVER}-mysql php${PHPVER}-curl php${PHPVER}-json php${PHPVER}-gd php${PHPVER}-mcrypt php${PHPVER}-intl php${PHPVER}-sqlite3 php${PHPVER}-gmp php${PHPVER}-geoip php${PHPVER}-mbstring php${PHPVER}-redis php${PHPVER}-xml php${PHPVER}-zip php${PHPVER}-xdebug + +RUN echo "phar.readonly = Off" >> /etc/php/${PHPVER}/cli/conf.d/42-phar-readonly.ini +RUN echo "memory_limit=-1" >> /etc/php/${PHPVER}/cli/conf.d/42-memory-limit.ini + +RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer && chmod 755 /usr/local/bin/composer + +ENV HOME=/home/jenkins +ENV USER=jenkins +ENV GROUP=users + + + +# -u $(id -u) +# because jenkins docker pipeline force a -u option on run +# https://github.com/jenkinsci/docker-workflow-plugin/pull/25/files +# see whoAmI function here: +# https://github.com/ndeloof/docker-workflow-plugin/blob/master/src/main/java/org/jenkinsci/plugins/docker/workflow/client/DockerClient.java +# it should be fixed in next plugins version >= 1.14 + +# jenkins user on sandboxrd has the 1004 id and it is forced by pipeline plugin on docker log on + + +ARG UID=1004 +RUN useradd -d $HOME -g $GROUP -u ${UID} -m $USER + + +# --no-create-home +# if we don't want skeleton file so we create home by hand (or by RUN :) ) +# RUN mkdir -p $HOME && mkdir -p $HOME/bin && chown -R $USER:$GROUP $HOME + +RUN mkdir -p $HOME/bin && chown -R $USER:$GROUP $HOME + +USER $USER:$GROUP +WORKDIR $HOME + +#should not be good, where does the current $PATH came from ? +ENV PATH=$PATH:$HOME/bin:$HOME/.local/bin/ + +# Add some before install step to run test faster +RUN composer self-update --no-progress --stable && composer global config bin-dir ${HOME}/bin +RUN pip install --upgrade pip + +RUN for i in "sllh/composer-lint" "pdepend/pdepend" "squizlabs/php_codesniffer" "phploc/phploc" "phpmd/phpmd" "sebastian/phpcpd" "theseer/phpdox" "phpmetrics/phpmetrics"; do composer global require $i:@stable --prefer-dist --no-interaction; done + +ENV NVM_DIR="$HOME/.nvm" +RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm install 8.9 && npm config set color false + +#Try to cache various tools +RUN wget -q https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz && gunzip -f geckodriver-v0.19.1-linux64.tar.gz && tar -xvf geckodriver-v0.19.1-linux64.tar && mv geckodriver ${HOME}/bin/ +RUN wget -q https://selenium-release.storage.googleapis.com/3.7/selenium-server-standalone-3.7.0.jar && mv selenium-server-standalone-3.7.0.jar ${HOME}/bin/selenium-server-standalone.jar +RUN wget -q https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar --output-document="${HOME}/bin/coveralls" && chmod u+x "${HOME}/bin/coveralls" diff --git a/src/Sil/Component/Emailing/etc/pipeline.jenkins b/src/Sil/Component/Emailing/etc/pipeline.jenkins new file mode 100644 index 00000000..6e39fe76 --- /dev/null +++ b/src/Sil/Component/Emailing/etc/pipeline.jenkins @@ -0,0 +1,153 @@ +pipeline { + agent { + dockerfile { + filename "etc/dockerfile.jenkins" + args '--network=ci.network --volume /home/jenkins/cache/composer:/home/jenkins/.composer/cache' + } + } + + environment { + RND = "${BUILD_NUMBER}_${BRANCH_NAME}" + } + + options { + timeout(time: 1, unit: 'HOURS') + timestamps() + disableConcurrentBuilds() + } + + stages { + stage ('Where Am I') { + steps { + sh "uname -a" + sh "php -v" + sh "composer -V" + } + } + stage ('Set Env') { + steps { + sh "ln -fs ./.env.jenkins ./.env" + sh "cat ./.env" + sh "mkdir -p build" + } + } + + + stage ('Prepare') { + steps { + sh "./bin/ci-scripts/do_run.sh before_install_test.sh" + } + } + + stage ('Create Database') { + steps { + sh "./bin/ci-scripts/do_run.sh set_db_host_test.sh # needed before create as it set .pgpass" + sh "./bin/ci-scripts/do_run.sh create_database_test.sh" + sh "./bin/ci-scripts/do_run.sh create_table_test.sh" + + } + } + + stage('Parallel Install') { + parallel { + stage ('Install Test') { + steps { + sh "./bin/ci-scripts/do_run.sh install_test.sh" + } + } + + stage ('Install Doc') { + steps { + sh "./bin/ci-scripts/do_run.sh install_doc.sh" + } + } + } + } + stage('Parallel Start') { + parallel { + stage ('Start Project') { + steps { + sh "./bin/ci-scripts/do_run.sh before_script_test.sh" + } + } + + stage ('Start Selenium') { + steps { + sh "./bin/ci-scripts/do_run.sh launch_selenium_test.sh" + } + } + } + } + + stage('Parallel Report') { + parallel { + stage ('Run Test') { + steps { + sh "./bin/ci-scripts/do_run.sh run_test.sh" + step([ + $class: 'XUnitBuilder', + thresholds: [[$class: 'FailedThreshold', unstableThreshold: '1']], + tools: [[$class: 'JUnitType', pattern: 'build/junit.xml']] + ]) + publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'build/coverage', reportFiles: 'index.html', reportName: 'Coverage Report', reportTitles: '']) + } + } + + stage ('Run Doc') { + steps { + sh "./bin/ci-scripts/do_run.sh run_doc.sh" + } + } + + + stage('Check Style') { + steps { + sh 'phpcs -q --report=checkstyle --report-file=build/checkstyle.xml --standard=PSR2 --extensions=php --ignore=vendor ./ || exit 0' + checkstyle pattern: 'build/checkstyle.xml' + } + } + + stage('Copy Paste Detection') { + steps { + sh 'phpcpd -q --exclude=vendor --log-pmd build/pmd-cpd.xml ./ || exit 0' + dry canRunOnFailed: true, pattern: 'build/pmd-cpd.xml' + } + } + + stage('Mess Detection') { + steps { + sh 'phpmd ./ xml phpmd.xml.dist --exclude vendor --reportfile build/pmd.xml || exit 0' + pmd canRunOnFailed: true, pattern: 'build/pmd.xml' + } + } + + stage('Collect Metrics') { + steps { + sh "phpmetrics --quiet --excluded-dirs=vendor --report-html=build/metrics.html ./" + publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'build/', reportFiles: 'metrics.html', reportName: 'Metrics Report', reportTitles: '']) + } + } + } + } + /* + stage ('Archive Gzip') { + steps { + sh 'tar -czf Platform_${BRANCH_NAME}.tgz ./*' + archiveArtifacts artifacts: "Platform_${BRANCH_NAME}.tgz", fingerprint: true + } + } + */ + + + } + + + + post { + always { + cleanWs() + + } + } + +} diff --git a/src/Sil/Component/Emailing/phpmd.xml.dist b/src/Sil/Component/Emailing/phpmd.xml.dist new file mode 100644 index 00000000..766e122e --- /dev/null +++ b/src/Sil/Component/Emailing/phpmd.xml.dist @@ -0,0 +1,15 @@ + + Standard Sil Coding Standard + + + + + + + + \ No newline at end of file diff --git a/src/Sil/Component/Emailing/phpunit.xml.dist b/src/Sil/Component/Emailing/phpunit.xml.dist new file mode 100644 index 00000000..231b362a --- /dev/null +++ b/src/Sil/Component/Emailing/phpunit.xml.dist @@ -0,0 +1,50 @@ + + + + + + + + ./Tests/Unit + + + + + + + ./ + + ./Tests/ + ./Resources/ + ./vendor/ + ./coverage/ + + + + + + + + + + + + + + + + From 34ce890869a374bc76a878e7a991510ee174b8b3 Mon Sep 17 00:00:00 2001 From: PapsOu Date: Mon, 19 Feb 2018 18:21:54 +0100 Subject: [PATCH 6/7] [Emailing] (WIP) Add templating tests --- .../Emailing/Model/ContentTokenDataType.php | 60 ++++++++++ .../Emailing/Model/ContentTokenType.php | 18 ++- .../Model/ContentTokenTypeInterface.php | 7 ++ .../Emailing/Tests/Unit/Fixtures/Fixtures.php | 110 +++++++++++++++++- 4 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 src/Sil/Component/Emailing/Model/ContentTokenDataType.php diff --git a/src/Sil/Component/Emailing/Model/ContentTokenDataType.php b/src/Sil/Component/Emailing/Model/ContentTokenDataType.php new file mode 100644 index 00000000..60c1bc10 --- /dev/null +++ b/src/Sil/Component/Emailing/Model/ContentTokenDataType.php @@ -0,0 +1,60 @@ +getTypes())) { + throw new InvalidArgumentException(sprintf('Type « %s » is not managed. Managed types are : %s', $value, implode(', ', $this->getTypes()))); + } + $this->value = $value; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + public function getTypes(): array + { + return [ + 'TYPE_BOOLEAN' => static::TYPE_BOOLEAN, + 'TYPE_STRING' => static::TYPE_STRING, + 'TYPE_INTEGER' => static::TYPE_INTEGER, + 'TYPE_FLOAT' => static::TYPE_FLOAT, + 'TYPE_DATE' => static::TYPE_DATE, + 'TYPE_DATETIME' => static::TYPE_DATETIME, + ]; + } +} diff --git a/src/Sil/Component/Emailing/Model/ContentTokenType.php b/src/Sil/Component/Emailing/Model/ContentTokenType.php index 87cb97b2..25f3ff95 100644 --- a/src/Sil/Component/Emailing/Model/ContentTokenType.php +++ b/src/Sil/Component/Emailing/Model/ContentTokenType.php @@ -30,10 +30,18 @@ class ContentTokenType implements ContentTokenTypeInterface, ResourceInterface */ protected $dataType; - public function __construct(string $name, ContentTokenDataType $dataType) + /** + * The temple wich token type belongs. + * + * @var MessageTemplateInterface + */ + protected $template; + + public function __construct(MessageTemplateInterface $template, string $name, ContentTokenDataType $dataType) { $this->name = $name; $this->dataType = $dataType; + $this->template = $template; } /** @@ -51,4 +59,12 @@ public function getDataType(): ContentTokenDataType { return $this->dataType; } + + /** + * {@inheritdoc} + */ + public function getTemplate(): MessageTemplateInterface + { + return $this->template; + } } diff --git a/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php b/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php index 8c8f3e5d..a9914f09 100644 --- a/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php +++ b/src/Sil/Component/Emailing/Model/ContentTokenTypeInterface.php @@ -27,4 +27,11 @@ public function getName(): string; * @return ContentTokenDataType */ public function getDataType(): ContentTokenDataType; + + /** + * Gets the associated template. + * + * @return MessageTemplateInterface + */ + public function getTemplate(): MessageTemplateInterface; } diff --git a/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php index d2de64bc..71bf1556 100644 --- a/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php +++ b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php @@ -23,6 +23,13 @@ use Sil\Component\Emailing\Model\RecipientInterface; use Sil\Component\Emailing\Model\SimpleMessage; use Sil\Component\Emailing\Model\SimpleMessageInterface; +use Sil\Component\Emailing\Model\MessageTemplate; +use Sil\Component\Emailing\Model\MessageTemplateInterface; +// use Sil\Component\Emailing\Model\ContentToken; +use Sil\Component\Emailing\Model\ContentTokenInterface; +use Sil\Component\Emailing\Model\ContentTokenDataType; +use Sil\Component\Emailing\Model\ContentTokenType; +use Sil\Component\Emailing\Model\ContentTokenTypeInterface; use Sil\Component\Emailing\Repository\MailingListRepositoryInterface; use Sil\Component\Emailing\Repository\RecipientRepositoryInterface; use Sil\Component\Emailing\Repository\SimpleMessageRepositoryInterface; @@ -49,11 +56,70 @@ class Fixtures */ private $recipientRepository; + /** + * @var MessageTemplateRepositoryInterface + */ + private $messageTemplateRepository; + + /** + * @var ContentTokenTypeRepositoryInterface + */ + private $contentTokenTypeRepository; + + /** + * @var ContentTokenRepositoryInterface + */ + private $contentTokenRepository; + private $rawData = []; public function __construct() { $this->rawData = [ + 'Templates' => [ + 'basic' => [ + 'name' => 'basic', + 'content' => 'A template without token placeholder', + 'tokens' => [], + ], + 'rich' => [ + 'name' => 'rich', + 'content' => 'A template with token placeholders : + - %%A_BOOLEAN_TOKEN%% + - %%A_STRING_TOKEN%% + - %%A_INTEGER_TOKEN%% + - %%A_FLOAT_TOKEN%% + - %%A_DATE_TOKEN%% + - %%A_DATETIME_TOKEN%% + ', + 'tokens' => [ + [ + 'name' => 'A_BOOLEAN_TOKEN', + 'type' => 'BOOLEAN', + ], + [ + 'name' => 'A_STRING_TOKEN', + 'type' => 'STRING', + ], + [ + 'name' => 'A_INTEGER_TOKEN', + 'type' => 'INTEGER', + ], + [ + 'name' => 'A_FLOAT_TOKEN', + 'type' => 'FLOAT', + ], + [ + 'name' => 'A_DATE_TOKEN', + 'type' => 'DATE', + ], + [ + 'name' => 'A_DATETIME_TOKEN', + 'type' => 'DATETIME', + ], + ], + ], + ], 'Mailing lists' => [ 'ListOne' => [ 'name' => 'ListOne', @@ -82,6 +148,7 @@ public function __construct() [ 'title' => 'Simple message One', 'content' => 'A rich content without lorem.', + 'template' => 'rich', 'from' => 'sender1@sil.eu', 'to' => ['recipient1@sil.eu', 'recipient2@sil.eu'], 'cc' => ['cc1@sil.eu', 'cc2@sil.eu', 'cc3@sil.eu', 'cc4@sil.eu'], @@ -90,6 +157,7 @@ public function __construct() [ 'title' => 'Simple message Two', 'content' => 'A text content without lorem.', + 'template' => null, 'from' => 'sender2@sil.eu', 'to' => ['recipient3@sil.eu', 'recipient4@sil.eu'], 'cc' => [], @@ -98,16 +166,18 @@ public function __construct() ], 'Grouped messages' => [ [ - 'title' => 'Grouped message One', - 'content' => 'A rich content without lorem.', - 'lists' => [ + 'title' => 'Grouped message One', + 'content' => 'A rich content without lorem.', + 'template' => 'basic', + 'lists' => [ 'ListOne', ], ], [ - 'title' => 'Grouped message Two', - 'content' => 'A text content without lorem.', - 'lists' => [ + 'title' => 'Grouped message Two', + 'content' => 'A text content without lorem.', + 'template' => null, + 'lists' => [ 'ListOne', 'ListTwo', ], @@ -120,17 +190,35 @@ public function __construct() $this->mailingListRepository = new InMemoryRepository(MailingListInterface::class); $this->recipientRepository = new InMemoryRepository(RecipientInterface::class); $this->emailAddressRepository = new InMemoryRepository(EmailAddressInterface::class); + $this->messageTemplateRepository = new InMemoryRepository(MessageTemplateInterface::class); + $this->contentTokenTypeRepository = new InMemoryRepository(ContentTokenTypeInterface::class); + $this->contentTokenRepository = new InMemoryRepository(ContentTokenInterface::class); $this->generateFixtures(); } private function generateFixtures(): void { + $this->loadTemplates(); $this->loadMailingLists(); $this->loadSimpleMessages(); $this->loadGroupedMessages(); } + private function loadtemplates() + { + foreach ($this->rawData['Templates'] as $templateData) { + $template = new MessageTemplate($templateData['name'], $templateData['content']); + + foreach ($templateData['tokens'] as $tokenData) { + $tokenType = new ContentTokenType($template, $tokenData['name'], new ContentTokenDataType(constant(ContentTokenDataType::class . '::TYPE_' . $tokenData['type']))); + $this->contentTokenTypeRepository->add($tokenType); + } + + $this->messageTemplateRepository->add($template); + } + } + private function loadMailingLists() { foreach ($this->rawData['Mailing lists'] as $mailingListData) { @@ -157,6 +245,11 @@ private function loadSimpleMessages() $simpleMessage = new SimpleMessage($messageData['title'], $messageData['content'], null, $from, $to); + if ($messageData['template'] !== null) { + $template = $this->messageTemplateRepository->findOneBy(['name' => $messageData['template']]); + $simpleMessage->setTemplate($template); + } + foreach ($messageData['to'] as $tos) { $toAddress = new EmailAddress($tos); if (!in_array($toAddress, $simpleMessage->getTo())) { @@ -190,6 +283,11 @@ private function loadGroupedMessages() foreach ($this->rawData['Grouped messages'] as $messageData) { $message = new GroupedMessage($messageData['title'], $messageData['content']); + if ($messageData['template'] !== null) { + $template = $this->messageTemplateRepository->findOneBy(['name' => $messageData['template']]); + $message->setTemplate($template); + } + foreach ($messageData['lists'] as $listName) { $list = $this->mailingListRepository->findOneBy(['name' => $listName]); $message->addList($list); From 7cc0235d82aac0981bdd98c8935226009bf94569 Mon Sep 17 00:00:00 2001 From: PapsOu Date: Tue, 20 Feb 2018 10:23:46 +0100 Subject: [PATCH 7/7] [Emailing] Add template tests --- .../Emailing/Model/AbstractMessage.php | 33 ++++++- .../Component/Emailing/Model/ContentToken.php | 36 +++---- .../Emailing/Model/ContentTokenInterface.php | 4 +- .../Emailing/Model/GroupedMessage.php | 4 +- .../Emailing/Model/MessageInterface.php | 19 ++++ .../Emailing/Model/SimpleMessage.php | 4 +- .../Emailing/Service/TemplateHandler.php | 60 +++++++++++ .../Emailing/Tests/Unit/Fixtures/Fixtures.php | 75 +++++++++----- .../Tests/Unit/MessageTemplateTest.php | 99 +++++++++++++++++++ .../Emailing/Tests/Unit/MessageTest.php | 2 +- 10 files changed, 282 insertions(+), 54 deletions(-) create mode 100644 src/Sil/Component/Emailing/Service/TemplateHandler.php create mode 100644 src/Sil/Component/Emailing/Tests/Unit/MessageTemplateTest.php diff --git a/src/Sil/Component/Emailing/Model/AbstractMessage.php b/src/Sil/Component/Emailing/Model/AbstractMessage.php index bb1cdb23..fa073124 100644 --- a/src/Sil/Component/Emailing/Model/AbstractMessage.php +++ b/src/Sil/Component/Emailing/Model/AbstractMessage.php @@ -62,11 +62,10 @@ abstract class AbstractMessage implements MessageInterface, MessageStateAwareInt */ protected $tokens; - public function __construct(string $title, string $content, MessageTemplateInterface $template = null) + public function __construct(string $title, string $content) { $this->title = $title; - $this->content = $content; - $this->template = $template; + $this->content = trim($content); $this->state = MessageState::draft(); @@ -75,7 +74,7 @@ public function __construct(string $title, string $content, MessageTemplateInter } /** - * @return string + * {@inheritdoc} */ public function getTitle(): string { @@ -83,13 +82,29 @@ public function getTitle(): string } /** - * @return string + * {@inheritdoc} + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * {@inheritdoc} */ public function getContent(): string { return $this->content; } + /** + * {@inheritdoc} + */ + public function setContent(string $content): void + { + $this->content = trim($content); + } + /** * {@inheritdoc} */ @@ -165,4 +180,12 @@ public function removeToken(ContentTokenInterface $token): void } $this->tokens->removeElement($token); } + + /** + * {@inheritdoc} + */ + public function clearTokens(): void + { + $this->tokens->clear(); + } } diff --git a/src/Sil/Component/Emailing/Model/ContentToken.php b/src/Sil/Component/Emailing/Model/ContentToken.php index 85aaefa1..48825f65 100644 --- a/src/Sil/Component/Emailing/Model/ContentToken.php +++ b/src/Sil/Component/Emailing/Model/ContentToken.php @@ -26,7 +26,7 @@ class ContentToken implements ContentTokenInterface /** * Type of token. * - * @var TokenTypeInterface + * @var ContentTokenTypeInterface */ protected $tokenType; @@ -37,10 +37,10 @@ class ContentToken implements ContentTokenInterface */ protected $message; - public function __construct(MessageInterface $message, TokenTypeInterface $tokenType) + public function __construct(MessageInterface $message, ContentTokenTypeInterface $tokenType) { $this->tokenType = $tokenType; - $this->$message = $message; + $this->message = $message; } /** @@ -74,25 +74,25 @@ public function setValue($value): void { $typeIsValid = false; - switch ($this->getTokenType()->getDataType()) { - case ContentTokenDataType::BOOLEAN: + switch ($this->getTokenType()->getDataType()->getValue()) { + case ContentTokenDataType::TYPE_BOOLEAN: $typeIsValid = is_bool($value); break; - case ContentTokenDataType::DATE: - case ContentTokenDataType::DATETIME: + case ContentTokenDataType::TYPE_DATE: + case ContentTokenDataType::TYPE_DATETIME: $typeIsValid = get_class($value) === 'DateTime'; break; - case ContentTokenDataType::STRING: + case ContentTokenDataType::TYPE_STRING: $typeIsValid = is_string($value); break; - case ContentTokenDataType::INTEGER: + case ContentTokenDataType::TYPE_INTEGER: $typeIsValid = is_int($value); break; - case ContentTokenDataType::FLOAT: + case ContentTokenDataType::TYPE_FLOAT: $typeIsValid = is_float($value); break; @@ -110,7 +110,7 @@ public function setValue($value): void /** * {@inheritdoc} */ - public function getTokenType(): TokentTypeInterface + public function getTokenType(): ContentTokenTypeInterface { return $this->tokenType; } @@ -122,22 +122,22 @@ public function getValueAsString(): string { $stringValue = ''; - switch ($this->getTokenType()->getDataType()) { - case ContentTokenDataType::BOOLEAN: + switch ($this->getTokenType()->getDataType()->getValue()) { + case ContentTokenDataType::TYPE_BOOLEAN: $stringValue = $this->value === true ? 'Yes' : 'No'; break; - case ContentTokenDataType::DATE: + case ContentTokenDataType::TYPE_DATE: $stringValue = $this->value->format('Y-m-d'); break; - case ContentTokenDataType::DATETIME: + case ContentTokenDataType::TYPE_DATETIME: $stringValue = $this->value->format('Y-m-d H:i:s'); break; - case ContentTokenDataType::STRING: - case ContentTokenDataType::INTEGER: - case ContentTokenDataType::FLOAT: + case ContentTokenDataType::TYPE_STRING: + case ContentTokenDataType::TYPE_INTEGER: + case ContentTokenDataType::TYPE_FLOAT: default: $stringValue = (string) $this->value; } diff --git a/src/Sil/Component/Emailing/Model/ContentTokenInterface.php b/src/Sil/Component/Emailing/Model/ContentTokenInterface.php index c0114e35..13db8a36 100644 --- a/src/Sil/Component/Emailing/Model/ContentTokenInterface.php +++ b/src/Sil/Component/Emailing/Model/ContentTokenInterface.php @@ -49,9 +49,9 @@ public function setValue($value): void; /** * Gets the token type. * - * @return TokenTypeInterface + * @return ContentTokenTypeInterface */ - public function getTokenType(): TokenTypeInterface; + public function getTokenType(): ContentTokenTypeInterface; /** * Gets the value as a string. diff --git a/src/Sil/Component/Emailing/Model/GroupedMessage.php b/src/Sil/Component/Emailing/Model/GroupedMessage.php index 8cb8c7cd..9632c956 100644 --- a/src/Sil/Component/Emailing/Model/GroupedMessage.php +++ b/src/Sil/Component/Emailing/Model/GroupedMessage.php @@ -26,9 +26,9 @@ class GroupedMessage extends AbstractMessage implements GroupedMessageInterface, */ protected $lists; - public function __construct(string $title, string $content, MessageTemplateInterface $template = null) + public function __construct(string $title, string $content) { - parent::__construct($title, $content, $template); + parent::__construct($title, $content); $this->lists = new ArrayCollection(); } diff --git a/src/Sil/Component/Emailing/Model/MessageInterface.php b/src/Sil/Component/Emailing/Model/MessageInterface.php index 01cc280f..a6c267a9 100644 --- a/src/Sil/Component/Emailing/Model/MessageInterface.php +++ b/src/Sil/Component/Emailing/Model/MessageInterface.php @@ -23,6 +23,13 @@ interface MessageInterface */ public function getTitle(): string; + /** + * Sets the message title. + * + * @param string $title + */ + public function setTitle(string $title): void; + /** * Gets the content of the message. * @@ -30,6 +37,13 @@ public function getTitle(): string; */ public function getContent(): string; + /** + * Sets the message content. + * + * @param string $content + */ + public function setContent(string $content): void; + /** * Retreives the message template. * @@ -69,6 +83,11 @@ public function addToken(ContentTokenInterface $token): void; */ public function removeToken(ContentTokenInterface $token): void; + /** + * Removes all tokens for current message. + */ + public function clearTokens(): void; + /** * Gets a list of attachments. * diff --git a/src/Sil/Component/Emailing/Model/SimpleMessage.php b/src/Sil/Component/Emailing/Model/SimpleMessage.php index 949e1e74..db6d4168 100644 --- a/src/Sil/Component/Emailing/Model/SimpleMessage.php +++ b/src/Sil/Component/Emailing/Model/SimpleMessage.php @@ -46,9 +46,9 @@ class SimpleMessage extends AbstractMessage implements SimpleMessageInterface, R */ protected $bcc; - public function __construct(string $title, string $content, MessageTemplateInterface $template = null, EmailAddressInterface $from, EmailAddressInterface $to) + public function __construct(string $title, string $content, EmailAddressInterface $from, EmailAddressInterface $to) { - parent::__construct($title, $content, $template); + parent::__construct($title, $content); $this->to = new ArrayCollection(); $this->cc = new ArrayCollection(); diff --git a/src/Sil/Component/Emailing/Service/TemplateHandler.php b/src/Sil/Component/Emailing/Service/TemplateHandler.php new file mode 100644 index 00000000..54d7ad90 --- /dev/null +++ b/src/Sil/Component/Emailing/Service/TemplateHandler.php @@ -0,0 +1,60 @@ +clearTokens(); + $message->setTemplate($template); + $message->setContent($template->getContent()); + + foreach ($template->getTokenTypes() as $tokenType) { + $token = new ContentToken($message, $tokenType); + $message->addToken($token); + } + } + + /** + * Replaces all token placeholder with values (if they are set) in message content. + * + * @param MessageInterface $message + */ + public function replaceTokensOfMessage(MessageInterface $message): void + { + foreach ($message->getTokens() as $token) { + if ($token->getValue() === null) { + continue; + } + + $rawContent = $message->getContent(); + + $regexExpression = sprintf('/%s/', preg_quote('%%' . $token->getName() . '%%')); + + $rawContent = preg_replace($regexExpression, $token->getValueAsString(), $rawContent); + + $message->setContent($rawContent); + } + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php index 71bf1556..6095483f 100644 --- a/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php +++ b/src/Sil/Component/Emailing/Tests/Unit/Fixtures/Fixtures.php @@ -30,44 +30,42 @@ use Sil\Component\Emailing\Model\ContentTokenDataType; use Sil\Component\Emailing\Model\ContentTokenType; use Sil\Component\Emailing\Model\ContentTokenTypeInterface; -use Sil\Component\Emailing\Repository\MailingListRepositoryInterface; -use Sil\Component\Emailing\Repository\RecipientRepositoryInterface; -use Sil\Component\Emailing\Repository\SimpleMessageRepositoryInterface; +use Sil\Component\Emailing\Service\TemplateHandler; class Fixtures { /** - * @var SimpleMessageRepositoryInterface + * @var InMemoryRepository */ private $simpleMessageRepository; /** - * @var GroupedMessageRepositoryInterface + * @var InMemoryRepository */ private $groupedMessageRepository; /** - * @var MailingListRepositoryInterface + * @var InMemoryRepository */ private $mailingListRepository; /** - * @var RecipientRepositoryInterface + * @var InMemoryRepository */ private $recipientRepository; /** - * @var MessageTemplateRepositoryInterface + * @var InMemoryRepository */ private $messageTemplateRepository; /** - * @var ContentTokenTypeRepositoryInterface + * @var InMemoryRepository */ private $contentTokenTypeRepository; /** - * @var ContentTokenRepositoryInterface + * @var InMemoryRepository */ private $contentTokenRepository; @@ -212,6 +210,7 @@ private function loadtemplates() foreach ($templateData['tokens'] as $tokenData) { $tokenType = new ContentTokenType($template, $tokenData['name'], new ContentTokenDataType(constant(ContentTokenDataType::class . '::TYPE_' . $tokenData['type']))); + $template->addTokenType($tokenType); $this->contentTokenTypeRepository->add($tokenType); } @@ -243,11 +242,13 @@ private function loadSimpleMessages() $this->emailAddressRepository->add($from); $this->emailAddressRepository->add($to); - $simpleMessage = new SimpleMessage($messageData['title'], $messageData['content'], null, $from, $to); + $simpleMessage = new SimpleMessage($messageData['title'], $messageData['content'], $from, $to); if ($messageData['template'] !== null) { $template = $this->messageTemplateRepository->findOneBy(['name' => $messageData['template']]); - $simpleMessage->setTemplate($template); + + $templateHandler = new TemplateHandler(); + $templateHandler->applyTemplateToMessage($template, $simpleMessage); } foreach ($messageData['to'] as $tos) { @@ -281,54 +282,80 @@ private function loadSimpleMessages() private function loadGroupedMessages() { foreach ($this->rawData['Grouped messages'] as $messageData) { - $message = new GroupedMessage($messageData['title'], $messageData['content']); + $groupedMessage = new GroupedMessage($messageData['title'], $messageData['content']); if ($messageData['template'] !== null) { $template = $this->messageTemplateRepository->findOneBy(['name' => $messageData['template']]); - $message->setTemplate($template); + + $templateHandler = new TemplateHandler(); + $templateHandler->applyTemplateToMessage($template, $groupedMessage); } foreach ($messageData['lists'] as $listName) { $list = $this->mailingListRepository->findOneBy(['name' => $listName]); - $message->addList($list); + $groupedMessage->addList($list); } - $this->groupedMessageRepository->add($message); + $this->groupedMessageRepository->add($groupedMessage); } } /** - * @return SimpleMessageRepositoryInterface + * @return InMemoryRepository */ - public function getSimpleMessageRepository(): SimpleMessageRepositoryInterface + public function getSimpleMessageRepository(): InMemoryRepository { return $this->simpleMessageRepository; } /** - * @return GroupedMessageRepositoryInterface + * @return InMemoryRepository */ - public function getGroupedMessageRepository(): GroupedMessageRepositoryInterface + public function getGroupedMessageRepository(): InMemoryRepository { return $this->groupedMessageRepository; } /** - * @return MailingListRepositoryInterface + * @return InMemoryRepository */ - public function getMailingListRepository(): MailingListRepositoryInterface + public function getMailingListRepository(): InMemoryRepository { return $this->mailingListRepository; } /** - * @return RecipientRepositoryInterface + * @return InMemoryRepository */ - public function getRecipientRepository(): RecipientRepositoryInterface + public function getRecipientRepository(): InMemoryRepository { return $this->recipientRepository; } + /** + * @return InMemoryRepository + */ + public function getMessageTemplateRepository(): InMemoryRepository + { + return $this->messageTemplateRepository; + } + + /** + * @return InMemoryRepository + */ + public function getContentTokenTypeRepository(): InMemoryRepository + { + return $this->contentTokenTypeRepository; + } + + /** + * @return InMemoryRepository + */ + public function getContentTokenRepository(): InMemoryRepository + { + return $this->contentTokenRepository; + } + /** * @return array */ diff --git a/src/Sil/Component/Emailing/Tests/Unit/MessageTemplateTest.php b/src/Sil/Component/Emailing/Tests/Unit/MessageTemplateTest.php new file mode 100644 index 00000000..f3d216f2 --- /dev/null +++ b/src/Sil/Component/Emailing/Tests/Unit/MessageTemplateTest.php @@ -0,0 +1,99 @@ +fixtures = new Fixtures(); + } + + public function test_applying_template_with_template_handler_to_a_new_message() + { + $from = new EmailAddress('from@sil.eu'); + $to = new EmailAddress('to@sil.eu'); + + $templateData = $this->fixtures->getRawData()['Templates']['rich']; + $template = $this->fixtures->getMessageTemplateRepository()->findOneBy(['name' => $templateData['name']]); + + $message = new SimpleMessage('Simple message', 'a simple message test', $from, $to); + + $templateHandler = new TemplateHandler(); + $templateHandler->applyTemplateToMessage($template, $message); + + $this->assertEquals(count($template->getTokenTypes()), count($message->getTokens())); + + foreach ($message->getTokens() as $token) { + $this->assertContains($token->getTokenType(), $template->getTokenTypes()); + } + } + + public function test_clearing_message_tokens() + { + $messageData = $this->fixtures->getRawData()['Simple messages'][0]; + $message = $this->fixtures->getSimpleMessageRepository()->findOneBy(['title' => $messageData['title']]); + + $this->assertEquals(6, count($message->getTokens())); + + $message->clearTokens(); + + $this->assertEquals(0, count($message->getTokens())); + } + + public function test_replacing_tokens_on_message() + { + $messageData = $this->fixtures->getRawData()['Simple messages'][0]; + $templateData = $this->fixtures->getRawData()['Templates'][$messageData['template']]; + $message = $this->fixtures->getSimpleMessageRepository()->findOneBy(['title' => $messageData['title']]); + + $tokenValues = [ + ContentTokenDataType::TYPE_BOOLEAN => true, + ContentTokenDataType::TYPE_DATE => new DateTime('2020-05-01'), + ContentTokenDataType::TYPE_DATETIME => new DateTime('2020-05-01 12:12:42'), + ContentTokenDataType::TYPE_STRING => 'a replaced string', + ContentTokenDataType::TYPE_INTEGER => 42, + ContentTokenDataType::TYPE_FLOAT => 512.42, + ]; + + foreach ($message->getTokens() as $token) { + $token->setValue($tokenValues[$token->getTokenType()->getDataType()->getValue()]); + } + + $this->assertEquals(trim($templateData['content']), $message->getContent()); + + $templateHandler = new TemplateHandler(); + $templateHandler->replaceTokensOfMessage($message); + + $expectedContent = trim('A template with token placeholders : + - Yes + - a replaced string + - 42 + - 512.42 + - 2020-05-01 + - 2020-05-01 12:12:42 + '); + + $this->assertEquals($expectedContent, $message->getContent()); + } +} diff --git a/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php b/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php index 05cb426e..ae4cef63 100644 --- a/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php +++ b/src/Sil/Component/Emailing/Tests/Unit/MessageTest.php @@ -31,7 +31,7 @@ public function test_simple_email_creation() { $from = new EmailAddress('from@sil.eu'); $to = new EmailAddress('to@sil.eu'); - $message = new SimpleMessage('Simple message', 'a simple message test', null, $from, $to); + $message = new SimpleMessage('Simple message', 'a simple message test', $from, $to); $this->assertEquals('Simple message', $message->getTitle()); $this->assertEquals('a simple message test', $message->getContent());