diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php index e87e8e36d..c6094ef46 100644 --- a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php @@ -21,7 +21,6 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter; use Chill\MainBundle\Workflow\EntityWorkflowManager; -use Chill\MainBundle\Workflow\Validator\StepDestValid; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -152,9 +151,6 @@ class WorkflowController extends AbstractController throw new AccessDeniedException('Not a valid user'); } - dump($accessKey); - dump($entityWorkflowStep->getAccessKey()); - if ($entityWorkflowStep->getAccessKey() !== $accessKey) { throw new AccessDeniedException('Access key is invalid'); } @@ -264,6 +260,7 @@ class WorkflowController extends AbstractController } $entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData(); + $entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData(); $workflow->apply($entityWorkflow, $transition); @@ -271,18 +268,13 @@ class WorkflowController extends AbstractController $entityWorkflow->getCurrentStep()->addDestUser($user); } - $errors = $this->validator->validate( - $entityWorkflow->getCurrentStep(), - new StepDestValid() - ); - - if (count($errors) === 0) { - $this->entityManager->flush(); - - return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); + foreach ($transitionForm['future_dest_emails']->getData() as $email) { + $entityWorkflow->getCurrentStep()->addDestEmail($email); } - return new Response((string) $errors, Response::HTTP_UNPROCESSABLE_ENTITY); + $this->entityManager->flush(); + + return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]); } if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) { diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index 84a75fc6d..bb0fc0e93 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -41,6 +41,17 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface use TrackUpdateTrait; + /** + * a list of future dest emails for the next steps. + * + * This is in used in order to let controller inform who will be the future emails which will validate + * the next step. This is necessary to perform some computation about the next emails, before they are + * associated to the entity EntityWorkflowStep. + * + * @var array|string[] + */ + public array $futureDestEmails = []; + /** * a list of future dest users for the next steps. * diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php index 590380749..9de76e039 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php @@ -142,7 +142,7 @@ class EntityWorkflowStep public function addDestUserByAccessKey(User $user): self { - if (!$this->destUserByAccessKey->contains($user)) { + if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) { $this->destUserByAccessKey[] = $user; $this->getEntityWorkflow() ->addSubscriberToFinal($user) diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php index eaca35812..9a2fd1b6c 100644 --- a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php +++ b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php @@ -13,6 +13,7 @@ namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; +use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -21,8 +22,14 @@ use LogicException; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition; use function array_key_exists; @@ -146,6 +153,22 @@ class WorkflowStepType extends AbstractType 'label' => 'workflow.dest for next steps', 'multiple' => true, 'mapped' => false, + ]) + ->add('future_dest_emails', ChillCollectionType::class, [ + 'label' => 'workflow.dest by email', + 'help' => 'workflow.dest by email help', + 'mapped' => false, + 'allow_add' => true, + 'entry_type' => EmailType::class, + 'button_add_label' => 'workflow.Add an email', + 'button_remove_label' => 'workflow.Remove an email', + 'empty_collection_explain' => 'workflow.Any email', + 'entry_options' => [ + 'constraints' => [ + new NotNull(), new NotBlank(), new Email(['checkMX' => true]), + ], + 'label' => 'Email', + ], ]); } @@ -175,6 +198,37 @@ class WorkflowStepType extends AbstractType ->setRequired('transition') ->setAllowedTypes('transition', 'bool') ->setRequired('entity_workflow') - ->setAllowedTypes('entity_workflow', EntityWorkflow::class); + ->setAllowedTypes('entity_workflow', EntityWorkflow::class) + ->setDefault('constraints', [ + new Callback( + function ($step, ExecutionContextInterface $context, $payload) { + /** @var EntityWorkflowStep $step */ + $form = $context->getObject(); + $workflow = $this->registry->get($step->getEntityWorkflow(), $step->getEntityWorkflow()->getWorkflowName()); + $transition = $form['transition']->getData(); + $toFinal = true; + + foreach ($transition->getTos() as $to) { + $meta = $workflow->getMetadataStore()->getPlaceMetadata($to); + + if ( + !array_key_exists('isFinal', $meta) || false === $meta['isFinal'] + ) { + $toFinal = false; + } + } + + $destUsers = $form['future_dest_users']->getData(); + $destEmails = $form['future_dest_emails']->getData(); + + if (!$toFinal && [] === $destUsers && [] === $destEmails) { + $context + ->buildViolation('workflow.You must add at least one dest user or email') + ->atPath('future_dest_users') + ->addViolation(); + } + } + ), + ]); } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js b/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js index bd05a8aaa..2f91c7777 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/page/workflow-show/index.js @@ -3,7 +3,7 @@ import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js'; window.addEventListener('DOMContentLoaded', function() { let divTransitions = document.querySelector('#transitions'), - futureDestUsersContainer = document.querySelector('#futureDestUsers') + futureDestUsersContainer = document.querySelector('#futureDests') ; if (null !== divTransitions) { @@ -67,24 +67,4 @@ window.addEventListener('DOMContentLoaded', function() { }); } - // validate form - let form = document.querySelector('form[name="workflow_step"]'); - - if (form === null) { - console.error('form to validate not found'); - } - - form.addEventListener('submit', function (event) { - const datas = new FormData(event.target); - - if (datas.has('workflow_step[future_dest_users]')) { - const dests = JSON.parse(datas.get('workflow_step[future_dest_users]')); - if (dests === null || (dests instanceof Array && dests.length === 0)) { - event.preventDefault(); - console.log('no users!'); - window.alert('Indiquez un utilisateur pour traiter la prochaine étape.'); - } - } - }); - }); diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig index 5ae454359..bb0411371 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig @@ -3,6 +3,8 @@ {% if transition_form is not null %} {{ form_start(transition_form) }} + {{ form_errors(transition_form) }} + {% set step = entity_workflow.currentStepChained %} {% set labels = workflow_metadata(entity_workflow, 'label', step.currentStep) %} {% set label = labels is null ? step.currentStep : labels|localize_translatable_string %} @@ -60,8 +62,10 @@ {{ form_row(transition_form.freezeAfter) }} {% endif %} -
+
{{ form_row(transition_form.future_dest_users) }} + + {{ form_row(transition_form.future_dest_emails) }}

{{ form_label(transition_form.comment) }}

diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key.fr.txt.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key.fr.txt.twig new file mode 100644 index 000000000..6237cb68a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key.fr.txt.twig @@ -0,0 +1,15 @@ +Madame, Monsieur, + +Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }}. + +Titre du workflow: "{{ entityTitle }}". + +Vous êtes invité·e à valider cette étape. Pour obtenir un accès, vous pouvez cliquer sur le lien suivant: + +{{ absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) }} + +Dès que vous aurez cliqué une fois sur le lien, vous serez autorisé à valider cette étape. + +Notez que vous devez disposer d'un compte utilisateur valide dans Chill. + +Cordialement, diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key_title.fr.txt.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key_title.fr.txt.twig new file mode 100644 index 000000000..b505a97ed --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_access_key_title.fr.txt.twig @@ -0,0 +1 @@ +Un suivi {{ workflow.text }} demande votre attention: {{ entityTitle }} diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php index 6d5a54d45..f7854c93f 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationOnTransition.php @@ -52,11 +52,11 @@ class NotificationOnTransition implements EventSubscriberInterface public static function getSubscribedEvents(): array { return [ - 'workflow.completed' => 'onCompleted', + 'workflow.completed' => 'onCompletedSendNotification', ]; } - public function onCompleted(Event $event): void + public function onCompletedSendNotification(Event $event): void { if (!$event->getSubject() instanceof EntityWorkflow) { return; diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/SendAccessKeyEventSubscriber.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/SendAccessKeyEventSubscriber.php new file mode 100644 index 000000000..d91926dc5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/SendAccessKeyEventSubscriber.php @@ -0,0 +1,71 @@ +engine = $engine; + $this->metadataExtractor = $metadataExtractor; + $this->registry = $registry; + $this->entityWorkflowManager = $entityWorkflowManager; + $this->mailer = $mailer; + } + + public function postPersist(EntityWorkflowStep $step): void + { + $entityWorkflow = $step->getEntityWorkflow(); + + $place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow); + $workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow( + $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()) + ); + $handler = $this->entityWorkflowManager->getHandler($entityWorkflow); + + foreach ($entityWorkflow->futureDestEmails as $emailAddress) { + $context = [ + 'entity_workflow' => $entityWorkflow, + 'dest' => $emailAddress, + 'place' => $place, + 'workflow' => $workflow, + 'entityTitle' => $handler->getEntityTitle($entityWorkflow), + ]; + + $email = new Email(); + $email + ->addTo($emailAddress) + ->subject($this->engine->render('@ChillMain/Workflow/workflow_send_access_key_title.fr.txt.twig', $context)) + ->text($this->engine->render('@ChillMain/Workflow/workflow_send_access_key.fr.txt.twig', $context)); + + $this->mailer->send($email); + } + } +} diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index dd2407d2b..57e18ce86 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -38,6 +38,17 @@ services: arguments: $handlers: !tagged_iterator chill_main.workflow_handler + Chill\MainBundle\Workflow\EventSubscriber\SendAccessKeyEventSubscriber: + autoconfigure: true + autowire: true + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'Chill\MainBundle\Entity\Workflow\EntityWorkflowStep' + # set the 'lazy' option to TRUE to only instantiate listeners when they are used + lazy: true + # other stuffes chill.main.helper.translatable_string: diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 86f906413..d8186bd6a 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -403,6 +403,12 @@ workflow: Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ? You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow. Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès + dest by email: Liens d'autorisation par email + dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape. + Add an email: Ajouter une adresse email + Remove an email: Enlever cette adresse email + Any email: Aucune adresse email + Subscribe final: Recevoir une notification à l'étape finale Subscribe all steps: Recevoir une notification à chaque étape diff --git a/src/Bundle/ChillMainBundle/translations/validators.fr.yml b/src/Bundle/ChillMainBundle/translations/validators.fr.yml index 33973f9d7..da433ba0f 100644 --- a/src/Bundle/ChillMainBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/validators.fr.yml @@ -28,3 +28,6 @@ notification: At least one addressee: Indiquez au moins un destinataire Title must be defined: Un titre doit être indiqué Comment content might not be blank: Le commentaire ne peut pas être vide + +workflow: + You must add at least one dest user or email: Indiquez au moins un destinataire ou une adresse email