Skip to content
Snippets Groups Projects
Select Git revision
  • dc82da3d8caccbb97f96c00f54f8fb1b4c714e6b
  • master default protected
  • main
  • update_github_actions
  • 144_rocky8_support
  • 195-update-pdk-to-300
  • 144-rocky8
  • add_test_github_test_workflow
  • pdk_2.4.0
  • fix_unclosed_let_block_in_defines_client_spec
  • validation_fixes
  • freeradius_3_0_21_config_updates
  • data_types
  • PrepareBuster
  • travis
  • 4.0.1
  • 4.0.0
  • 3.9.2
  • 3.9.1
  • 3.9.0
  • 3.8.2
  • 3.8.1
  • 3.8.0
  • 3.7.0
  • 3.6.0
  • 3.5.0
  • 3.4.3
  • 3.4.2
  • 3.4.1
  • 3.4.0
  • 3.3.0
  • 3.2.0
  • 3.1.0
  • 3.0.0
  • 2.3.1
35 results

dictionary.header

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    SignatureService.php 41.95 KiB
    <?php
    
    namespace UnicaenSignature\Service;
    
    use DoctrineModule\Persistence\ProvidesObjectManager;
    use Exception;
    use JetBrains\PhpStorm\NoReturn;
    use Laminas\EventManager\EventManager;
    use UnicaenSignature\Entity\Db\Process;
    use UnicaenSignature\Entity\Db\Signature;
    use UnicaenSignature\Entity\Db\SignatureFlow;
    use UnicaenSignature\Entity\Db\SignatureFlowStep;
    use UnicaenSignature\Entity\Db\SignatureRecipient;
    use UnicaenSignature\Entity\Repository\SignatureFlowRepository;
    use UnicaenSignature\Entity\Repository\SignatureRecipientRepository;
    use UnicaenSignature\Entity\Repository\SignatureRepository;
    use UnicaenSignature\Event\SignatureEvent;
    use UnicaenSignature\Formatter\ObjectFormatter;
    use UnicaenSignature\Utils\SignatureConstants;
    use UnicaenSignature\Exception\SignatureException;
    
    
    class SignatureService
    {
        use
            LetterfileServiceAwareTrait,
            LoggerServiceAwareTrait,
            ProvidesObjectManager,
            SharedEventManagerAwareTrait,
            ServiceContainerAwareTrait,
            SignatureConfigurationServiceAwareTrait;
    
        const ERR_SIGNATURETYPE_LIST = "Impossible de charger la liste des types de signature";
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///// GET/READ
    
        public function getSignatureRepository(): SignatureRepository
        {
            return $this->getObjectManager()->getRepository(Signature::class);
        }
    
        protected function getSignatureFlowRepository(): SignatureFlowRepository
        {
            return $this->getObjectManager()->getRepository(SignatureFlow::class);
        }
    
        protected function getProcessRepository()
        {
            return $this->getObjectManager()->getRepository(Process::class);
        }
    
        /**
         * @return SignatureRecipientRepository
         */
        protected function getSignatureRecipientRepository()
        {
            return $this->getObjectManager()
                ->getRepository(SignatureRecipient::class);
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///// LISTES
    
        /**
         * @return Signature[]
         * @throws Exception
         */
        public function getSignatures(): array
        {
            try {
                return $this->getSignatureRepository()->findAll();
            } catch (Exception $e) {
                $this->getLoggerService()->critical("Get signature ERROR : " . $e->getMessage());
                throw new SignatureException("Impossible de charger les signatures");
            }
        }
    
        /**
         * @return Signature[]
         * @throws SignatureException
         */
        public function getSignaturesSimples(): array
        {
            try {
                return $this->getSignatureRepository()->getSignaturesSimples();
            } catch (Exception $e) {
                $this->getLoggerService()->critical("Get signature ERROR : " . $e->getMessage());
                throw new SignatureException("Impossible de charger les signatures");
            }
        }
    
        /**
         * @return Signature[]
         * @throws SignatureException
         */
        public function getSignaturesUpdatable(): array
        {
            try {
                return $this->getSignatureRepository()->getSignaturesUpdatable();
            } catch (Exception $e) {
                $this->getLoggerService()->error($e->getMessage());
                throw new SignatureException("Impossible de charger les signatures éligibles à une mise à jour");
            }
        }
    
        /**
         * @param int $id
         * @return Signature
         * @throws SignatureException
         */
        public function getSignature(int $id): Signature
        {
            try {
                /** @var Signature $signature */
                $s = $this->getSignatureRepository()->find($id);
                if ($s === null) {
                    throw new Exception("Signature avec l'ID '$id' non-trouvée");
                }
                return $s;
            } catch (Exception $e) {
                $msg = "Impossible de trouver la demande de signature '$id'";
                $this->getLoggerService()->error("$msg : " . $e->getMessage());
                throw new SignatureException($msg);
            }
        }
    
        /**
         * @param string|null $email
         * @return SignatureRecipient[]
         * @throws SignatureException
         */
        public function getSignaturesRecipientsByEmail(?string $email): array
        {
            if ($email) {
                return $this->getSignatureRecipientRepository()->getRecipientSignatures(
                    $email,
                    [
                        Signature::STATUS_SIGNATURE_WAIT,
                        Signature::STATUS_SIGNATURE_SIGNED,
                        Signature::STATUS_SIGNATURE_REJECT
                    ]
                );
            }
            throw new SignatureException("Impossible de charger vos documents (Vous n'avez pas d'email)");
        }
    
        /**
         * @param string|null $email
         * @param string|null $letterfile
         * @return SignatureRecipient[]
         * @throws SignatureException
         */
        public function getSignaturesRecipientsByEmailWaiting(?string $email, string|null $letterfile = null): array
        {
            if ($email) {
                return $this->getSignatureRecipientRepository()->getRecipientSignatures(
                    $email,
                    [
                        Signature::STATUS_SIGNATURE_WAIT
                    ],
                    $letterfile
                );
            }
            throw new SignatureException("Impossible de charger vos documents (Vous n'avez pas d'email)");
        }
    
        /**
         * Affichage des informations du document.
         *
         * @param Signature $signature
         * @return array
         * @throws SignatureException
         */
        public function getDocumentData(Signature $signature): array
        {
            $docDir = $this->getSignatureConfigurationService()->getDocumentsLocation();
            $docName = $signature->getDocumentPath();
            $docPath = $docDir . DIRECTORY_SEPARATOR . $docName;
            $docMime = mime_content_type($docPath);
            $docDatas = file_get_contents($docPath);
    
            return [
                'name'  => $docName,
                'mime'  => $docMime,
                'path'  => $docPath,
                'datas' => $docDatas,
            ];
        }
    
        /**
         * @param string $letterFileKey
         * @return array
         * @throws SignatureException
         */
        public function getMarksSelectFromLetterFile(string $letterFileKey): array
        {
            $letterFile = $this->getLetterfileService()->getLetterFileStrategy($letterFileKey);
            return $this->getMarksSelectFrom(array_keys($letterFile->getLevels()));
        }
    
        /**
         * @param array $marksKeys
         * @return array
         * @throws SignatureException
         */
        public function getMarksSelectFrom(array $marksKeys): array
        {
            $select = [];
            foreach ($marksKeys as $mark) {
                $levelInfos = $this->getSignatureConfigurationService()->getLevelByName($mark);
                $select[$mark] = $levelInfos->getLabel();
            }
            return $select;
        }
    
        /**
         * Construction des données pour déclencher une procédure de signature.
         *
         * @param string $urlDocPreview
         * @param int $signatureFlowId
         * @param array $extrasOptions
         * @return array
         */
        public function createSignatureFlowDatasById(
            string $urlDocPreview,
            int $signatureFlowId,
            array $extrasOptions
        ): array {
            $out = [];
    
            /** @var SignatureFlow $signatureFlow */
            $signatureFlow = $this->getSignatureFlowRepository()->find($signatureFlowId);
    
            $out['signatureflow'] = [
                'id'          => $signatureFlow->getId(),
                'label'       => $signatureFlow->getLabel(),
                'description' => $signatureFlow->getDescription(),
                //'missing_recipients' => false,
                'doc_url'     => $urlDocPreview,
                'steps'       => []
            ];
    
            $context_subject = "";
            $context_body = "";
    
            // Extration du contexte depuis les options
            if (array_key_exists("context_subject", $extrasOptions)) {
                $context_subject = $extrasOptions['context_subject'];
            }
    
            // Extration du contexte depuis les options
            if (array_key_exists("context_body", $extrasOptions)) {
                $context_body = $extrasOptions['context_body'];
            }
    
            $missing_recipients = false;
    
            $letterfiles = $this->getSignatureConfigurationService()->getLetterFiles();
    
            /** @var SignatureFlowStep $step */
            foreach ($signatureFlow->getSteps() as $step) {
                $letterfile = $letterfiles[$step->getLetterfileName()];
                $levels = $letterfile['levels_infos'];
    
                $method = $this->getSignatureConfigurationService()->getMethodByKey($step->getRecipientsMethod());
    
                $currentStep = [];
                $currentStep['id'] = $step->getId();
                $currentStep['label'] = $step->getLabel();
                $currentStep['context_subject'] = $context_subject;
                $currentStep['context_body'] = $context_body;
                $currentStep['description'] = "";
                $currentStep['order'] = $step->getOrder();
                $currentStep['letterfilename'] = $step->getLetterfileName();
                $currentStep['letterfile_label'] = $letterfile['label'];
                $currentStep['level'] = $step->getLevel();
                $currentStep['level_label'] = $levels[$step->getLevel()]['label'];
                $currentStep['level_in_letterfile'] = $levels[$step->getLevel()]['key_in_letterfile'];
                $currentStep['allSignToComplete'] = $step->isAllRecipientsSign();
                $currentStep['editable'] = $step->isEditableRecipients();
                $currentStep['missing_recipients'] = false;
                $currentStep['recipient_method'] = $step->getRecipientsMethod();
                $currentStep['options'] = $step->getOptions();
                $currentStep['dynamicRecipients'] = (!empty($method))?is_callable($method['getRecipients']):false;
                $currentStep['recipients'] = [];
                $currentStep['observers'] = [];
    
                if ($step->getObserversMethod()) {
                    $methodObservers = $this->getSignatureConfigurationService()->getMethodByKey(
                        $step->getObserversMethod()
                    );
                    if (is_callable($methodObservers['getRecipients'])) {
                        $options = array_merge_recursive($extrasOptions, $step->getObserversOptions());
                        $observersDatas = $methodObservers['getRecipients']($this->getServiceContainer(), $options);
                        $observers = array_values($observersDatas);
                    }
                    else {
                        $observers = $step->getOptions()['observers'];
                    }
                }
                else {
                    $observers = [];
                }
                //Uniquement si on a définit des méthodes dynamiques de récupération dans la config unicaen-signature
                if (is_callable($method['getRecipients'])) {
                    $options = array_merge_recursive($extrasOptions, $step->getOptions());
                    $recipientsDatas = $method['getRecipients']($this->getServiceContainer(), $options);
                    $recipients = array_values($recipientsDatas);
                }
                else {
                    $recipients = $step->getOptions()['recipients'];
                }
    
                if ($recipients == null) {
                    $recipients = [];
                }
    
                if ($observers == null) {
                    $observers = [];
                }
    
                if ($step->isEditableRecipients()) {
                    foreach ($recipients as &$recipient) {
                        $recipient['selected'] = true;
                    }
                    foreach ($observers as &$observer) {
                        $observer['selected'] = true;
                    }
                }
    
                $currentStep['recipients'] = $recipients;
                $currentStep['observers'] = $observers;
    
                if (count($currentStep['recipients']) == 0) {
                    $missing_recipients = true;
                    $currentStep['missing_recipients'] = true;
                }
    
                $out['signatureflow']['steps'][] = $currentStep;
            }
    
            $out['signatureflow']['missing_recipients'] = $missing_recipients;
    
            return $out;
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// API
        ///
        public function saveSignatureFlowFromJSON(array $jsonSend): void
        {
            $id = $jsonSend['id'];
            if ($id) {
                try {
                    $signatureFlow = $this->getSignatureFlowRepository()->find($id);
                } catch (Exception $e) {
                    $this->getLoggerService()->errorLogAndThrow($e, "Impossible de charger le processus '$id'");
                }
            }
            else {
                $signatureFlow = new SignatureFlow();
                $this->getObjectManager()->persist($signatureFlow);
            }
            try {
                $signatureFlow->setLabel($jsonSend['label'])
                    ->setEnabled($jsonSend['enabled'])
                    ->setDescription($jsonSend['description']);
    
                $existStep = $signatureFlow->getStepsIndexed();
                $remove = array_keys($existStep);
    
                $stepsOrder = [];
                foreach ($jsonSend['steps'] as $stepData) {
                    if ($stepData['id']) {
                        $step = $existStep[$stepData['id']];
                        $stepsOrder[] = $step;
                        $k = array_search($stepData['id'], $remove);
                        unset($remove[$k]);
                    }
                    else {
                        $step = new SignatureFlowStep();
                        $stepsOrder[] = $step;
                        $this->getObjectManager()->persist($step);
                        $signatureFlow->getSteps()->add($step);
                        $step->setSignatureFlow($signatureFlow);
                    }
                    $step->setLabel($stepData['label'])
                        ->setAllRecipientsSign($stepData['allRecipientsSign'] && $stepData['allRecipientsSign'] == true)
                        ->setNotificationsRecipients(
                            $stepData['notificationsRecipients'] && $stepData['notificationsRecipients'] == true
                        )
                        ->setEditableRecipients($stepData['editable'] && $stepData['editable'] == true)
                        ->setOptions($stepData['options'] ? $stepData['options'] : [])
                        ->setLetterfileName($stepData['letterfile'])
                        ->setObserversMethod($stepData['observers_method'])
                        ->setObserversOptions($stepData['observers_options'])
                        ->setRecipientsMethod($stepData['method'])
                        ->setLevel($stepData['level']);
                }
                $order = 1;
                foreach ($stepsOrder as $step) {
                    $step->setOrder($order);
                    $order++;
                }
                foreach ($remove as $id) {
                    $this->getObjectManager()->remove($existStep[$id]);
                }
                try {
                    $this->getObjectManager()->flush();
                } catch (Exception $e) {
                    $this->getLoggerService()->errorLogAndThrow($e, "Impossible d'enregistrer la procédure");
                }
            } catch (Exception $e) {
                $this->getLoggerService()->errorLogAndThrow($e, "Impossible d'enregistrer le processus de signature");
            }
        }
    
        public function deleteSignatureFlowFromJSON(array $jsonSend): void
        {
            try {
                $id = $jsonSend['id'];
                $signatureFlow = $this->getSignatureFlowRepository()->find($id);
            } catch (Exception $e) {
                $this->getLoggerService()->errorLogAndThrow($e, "Impossible de charger le processus '$id'");
            }
            try {
                $this->getObjectManager()->remove($signatureFlow);
                $this->getObjectManager()->flush();
            } catch (Exception $e) {
                $this->getLoggerService()->errorLogAndThrow($e, "Impossible de supprimer le processus '$id'");
            }
        }
    
        public function getSignatureFlows(
            string $format = SignatureConstants::FORMAT_DEFAULT,
            bool $justActives = false
        ): array {
            try {
                $objectFormatter = new ObjectFormatter();
                if ($justActives) {
                    $signatureFlows = $this->getObjectManager()->getRepository(SignatureFlow::class)->findBy(
                        [
                            'enabled' => true
                        ]
                    );
                }
                else {
                    $signatureFlows = $this->getObjectManager()->getRepository(SignatureFlow::class)->findAll();
                }
                return $objectFormatter->formatCollection($signatureFlows, ObjectFormatter::FORMAT_JSON);
            } catch (Exception $e) {
                $this->getLoggerService()->errorLogAndThrow($e, "Impossible de charger la liste des processus");
            }
        }
    
        public function getSignatureFlowById(int $id): SignatureFlow
        {
            try {
                $signatureFlow = $this->getObjectManager()->getRepository(SignatureFlow::class)->find($id);
                if (!$signatureFlow) {
                    throw new SignatureException("Absent de la base de données");
                }
                return $signatureFlow;
            } catch (Exception $e) {
                $this->getLoggerService()->errorLogAndThrow($e, "Impossible de charger le modèle de processus");
            }
        }
    
        public function getLetterFiles(string $format = SignatureConstants::FORMAT_DEFAULT): array
        {
            $config = $this->getSignatureConfigurationService()->getLetterfileConfiguration();
            $levels = $this->getSignatureConfigurationService()->getLevels();
            $out = [];
            foreach ($config as $c) {
                $levels = [];
                foreach ($c['levels'] as $level => $useName) {
                    $levels[] = $this->getSignatureConfigurationService()->getLevelByName($level)->toArray();
                }
                $out[] = [
                    'name'        => $c['name'],
                    'label'       => $c['label'],
                    'description' => $c['description'],
                    'default'     => $c['default'],
                    'levels'      => $levels,
                ];
            }
            return $out;
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///
    
        /**
         * Création d'une signature.
         *
         * @param Signature $signature
         * @param bool $pushToLetterFile
         * @return void
         * @throws Exception
         */
        public function saveNewSignature(Signature $signature, bool $pushToLetterFile = false): void
        {
            $this->getLoggerService()->info("Enregistrement de la demande de signature");
            try {
                $signature->setStatus(Signature::STATUS_SIGNATURE_DRAFT);
                $this->getObjectManager()->persist($signature);
                $this->getObjectManager()->flush($signature);
                if ($pushToLetterFile) {
                    $this->getObjectManager()->refresh($signature);
                    $this->sendSignature($signature);
                }
            } catch (Exception $e) {
                $msg = "Impossible d'enregistrer la demande de signature";
                $this->getLoggerService()->critical("$msg : " . $e->getMessage());
                throw new Exception($msg);
            }
        }
    
        /**
         * @param int $id
         * @return void
         * @throws SignatureException
         */
        public function updateStatusSignatureById(int $id): void
        {
            $this->updateStatusSignature($this->getSignature($id));
        }
    
        /**
         * @param Signature $signature
         * @return void
         * @throws SignatureException
         */
        public function updateStatusSignature(
            Signature $signature,
            bool $callProcessStep = false,
            bool $writeDocument = true
        ): bool {
            $this->getLoggerService()->debug("### UPDATE STATUS '$signature'");
            try {
                if (!$signature->isFinished()) {
                    $documentSrc = $this->getSignatureConfigurationService()->getDocumentsLocation()
                        . DIRECTORY_SEPARATOR
                        . $signature->getDocumentPath();
    
                    if ($writeDocument && !is_writable($documentSrc)) {
                        $err = "L'emplacement de dépôt du document n'est pas accessible en écriture";
                        $this->getLoggerService()->critical("[update] $err : '$documentSrc'");
                        throw new SignatureException($err);
                    }
    
    
                    $letterFileStrategy = $this->getLetterfileService()->getLetterFileStrategy(
                        $signature->getLetterfileKey()
                    );
    
                    try {
                        $infos = $letterFileStrategy->getSignatureInfos($signature);
                        $status = $infos->getStatus();
                    } catch (Exception $e) {
                        $this->getLoggerService()->critical(
                            "[update] Impossible de mettre à jour le statut depuis le parapheur : "
                            . $e->getMessage()
                        );
                        throw new SignatureException("Impossible de mettre à jour le statut depuis le parapheur");
                    }
    
                    $statusChanged = $signature->getStatus() !== $status;
    
                    $signature->setStatus($status);
                    if ($statusChanged) {
                        $this->getLoggerService()->info(
                            "[update] Le statut a changé"
                        );
                        $signature->setDateUpdate(new \DateTime());
                    } else {
                        $this->getLoggerService()->info(
                            "[update] Le statut N'A PAS changé"
                        );
                    }
    
                    $events = [];
    
                    $infosObservers = [];
                    $subject = "[suivi] ";
                    $message = "";
    
                    foreach ($signature->getRecipients() as $recipient) {
    
                        $updateRequired = $recipient->isForceUpdate() || !$recipient->isDone();
    
                        if ($infos->isAccepted($recipient->getEmail()) && $updateRequired) {
                            $this->getLoggerService()->debug("$recipient a signé");
                            $recipient->setStatus(Signature::STATUS_SIGNATURE_SIGNED, true);
                            $recipient->setDateFinished($infos->getDateDone($recipient->getEmail()));
                            $events[] = [
                                'spot' => 'recipient',
                                'type' => SignatureEvent::EVENT_TYPE_RECIPIENT_SIGNED,
                                'id'   => $recipient->getId()
                            ];
                            $message .= " - $recipient a " . $signature->getParticiple() . " le document.\n\r";
                        }
                        if ($infos->isRefused($recipient->getEmail()) && $updateRequired) {
                            $recipient->setStatus(Signature::STATUS_SIGNATURE_REJECT, true);
                            $this->getLoggerService()->debug("$recipient a REJETé");
                            $events[] = [
                                'spot' => 'recipient',
                                'type' => SignatureEvent::EVENT_TYPE_SIGNED,
                                'id'   => $recipient->getId()
                            ];
                            $message .= " - $recipient a refusé le document.\n\r";
                            // TODO vérifier si on a la date de refus
                        }
                        if ($signature->isFinished() && $recipient->getStatus() == Signature::STATUS_SIGNATURE_WAIT) {
                            $this->getLoggerService()->debug("$recipient passé à ANNULé");
                            $recipient
                                ->setStatus(Signature::STATUS_SIGNATURE_CANCEL, true)
                                ->setDateUpdate(new \DateTime());
                        }
                    }
                    $this->getObjectManager()->flush();
    
                    if ($signature->getStatus() == Signature::STATUS_SIGNATURE_SIGNED) {
                        if ($writeDocument) {
                            $this->getLoggerService()->info("[update] Enregistrement du document");
    
                            $documentSrc = $this->getSignatureConfigurationService()->getDocumentsLocation()
                                . DIRECTORY_SEPARATOR
                                . $signature->getDocumentPath();
    
                            if (!file_put_contents(
                                $documentSrc,
                                $this->getDocumentSignedSignature($signature)
                            )) {
                                $this->getLoggerService()->errorLogAndThrow(
                                    new SignatureException("Impossible de récupérer le document signé"),
                                    "Impossible d'écrire le document"
                                );
                            }
                        }
    
                        if( $signature->getProcessStep() ){
                            $step = $signature->getProcessStep()->getOrder();
                            $steps = count($signature->getProcessStep()->getProcess()->getSteps());
                            $subject = "[suivi] étape $step/$steps du Document {DOCUMENT} terminée";
                        }
    
                        $events[] = [
                            'spot' => 'signature',
                            'type' => SignatureConstants::EVENT_SIGNATURE_SIGNED,
                            'id'   => $signature->getId()
                        ];
                    }
    
                    if ($signature->getStatus() == Signature::STATUS_SIGNATURE_REJECT) {
    
                        $subject = "[suivi] étape $step/$steps du Document {DOCUMENT} rejetée";
    
                        $events[] = [
                            'spot' => 'signature',
                            'type' => SignatureConstants::EVENT_SIGNATURE_REJECTED,
                            'id'   => $signature->getId()
                        ];
                    }
    
                    if( $message ){
                        foreach ($signature->getObservers() as $observer) {
                            $this->sendNotificationTo(
                                $observer->getEmail(),
                                $subject,
                                $message,
                                $signature
                            );
                        }
                    }
    
                    if (count($events)) {
                        $this->getLoggerService()->debug("Envoi des événements");
                        $em = new EventManager($this->getSharedEventManager());
                        foreach ($events as $event) {
                            $this->getLoggerService()->info(
                                sprintf(
                                    '[EVENT %s] - %s (id = %s)',
                                    $event['spot'],
                                    $event['type'],
                                    $event['id']
                                )
                            );
                            $em->trigger($event['type'], null, ['id' => $event['id']]);
                        }
                    }
    
                    if ($callProcessStep && $signature->getProcessStep()) {
                        /** @var ProcessService $processService */
                        $processService = $this->getServiceContainer()->get(ProcessService::class);
                        $processService->trigger($signature->getProcessStep()->getProcess());
                    }
    
                    return true;
                }
            } catch (Exception $e) {
                throw new SignatureException("Impossible de mettre à jour le statut de la signature : " . $e->getMessage());
            }
            return false;
        }
    
        public function observersNotifications(array $events): void
        {
        }
    
        /**
         * Notification des destinataires.
         *
         * @return void
         */
        #[NoReturn] public function recipientsNotifications(): void
        {
            $template = [
                'subject' => 'Vous avez des documents à {action}',
                'body'    => "Bonjour,\n\rVous avez des documents à {action},\n\rVous pouvez vous rendre à l'URL {url}\n\r",
            ];
            $recipients = $this->getSignatureRecipientRepository()->getRecipientsNotifiable();
            foreach ($recipients as $recipient) {
                echo "$recipient (" . $recipient->getSignature() . ")\n";
            }
            die("todo");
        }
    
        public function testTrigger()
        {
            $em = new EventManager($this->getSharedEventManager());
            $em->trigger(SignatureConstants::EVENT_SIGNATURE_SIGNED, null, ['id' => 666]);
        }
    
        /**
         * Mise à jour des signatures.
         *
         * @return void
         * @throws SignatureException
         */
        public function updateAll(): void
        {
            $signatures = $this->getSignatureRepository()->getSignaturesUpdatable();
            foreach ($signatures as $signature) {
                $this->updateStatusSignature($signature);
            }
        }
    
        /**
         * @param int $id
         * @return void
         * @throws SignatureException
         */
        public function deleteSignatureById(int $id): void
        {
            $this->deleteSignature($this->getSignature($id));
        }
    
        /**
         * Suppression d'une demande signature.
         *
         * @param Signature $signature
         * @param bool $flush Enregistre les changements en BDD
         * @param bool $removedoc Suppression du document source
         * @return void
         * @throws SignatureException
         */
        public function deleteSignature(Signature $signature, bool $flush = true, bool $removedoc = true): void
        {
            $docName = $signature->getDocumentPath();
    
            if ($signature->getStatus() == Signature::STATUS_SIGNATURE_DRAFT) {
                try {
                    foreach ($signature->getRecipients() as $recipient) {
                        $this->getObjectManager()->remove($recipient);
                    }
                    foreach ($signature->getObservers() as $observer) {
                        $this->getObjectManager()->remove($observer);
                    }
                    $this->getObjectManager()->remove($signature);
                    if ($flush) {
                        $this->getObjectManager()->flush();
                    }
                } catch (Exception $exception) {
                    $this->getLoggerService()->error("Error DB : " . $exception->getMessage());
                    throw new SignatureException("Impossible de supprimer la demande de la base de donnée");
                }
            }
            else {
                // Suppression dans le parapheur
                try {
                    $letterFile = $this->getLetterfileService()->getLetterFileStrategy($signature->getLetterfileKey());
                    $letterFile->deleteSignature($signature);
                } catch (Exception $e) {
                    $this->getLoggerService()->error("Erreur de suppression dans le parapheur : " . $e->getMessage());
                }
                try {
                    foreach ($signature->getRecipients() as $recipient) {
                        $this->getObjectManager()->remove($recipient);
                    }
                    $this->getObjectManager()->remove($signature);
                    if ($flush) {
                        $this->getObjectManager()->flush();
                    }
                } catch (Exception $exception) {
                    $this->getLoggerService()->error("Error DB : " . $exception->getMessage());
                    throw new SignatureException("Impossible de supprimer la demande de la base de donnée");
                }
            }
    
            if ($removedoc) {
                $doc = $this->getSignatureConfigurationService()->getDocumentsLocation()
                    . DIRECTORY_SEPARATOR
                    . $docName;
                if (!unlink($doc)) {
                    $error = error_get_last();
                    $err = 'Erreur inconnue';
                    if (is_array($error) && array_key_exists('message', $error)) {
                        $err = $error['message'];
                    }
                    $this->getLoggerService()->error(
                        "Impossible de supprimer le fichier '$doc' associé à la signature supprimée : $err"
                    );
                }
            }
        }
    
        /**
         * @param int $id
         * @return string
         * @throws SignatureException
         */
        public function getDocumentSignedSignatureById(int $id): string
        {
            return $this->getDocumentSignedSignature($this->getSignature($id));
        }
    
        /**
         * Retourne le document (Flux de données).
         *
         * @param Signature $signature
         * @return string
         * @throws SignatureException
         */
        public function getDocumentSignedSignature(Signature $signature): string
        {
            if ($signature->isDownloadable()) {
                try {
                    return $this->getLetterfileService()
                        ->getLetterFileStrategy($signature->getLetterfileKey())
                        ->getLastDocumentSignature($signature);
                } catch (Exception $e) {
                    $msg = "Impossible de charger le document depuis la parapheur";
                    $this->getLoggerService()->critical($msg . " : " . $e->getMessage());
                    throw new SignatureException($msg);
                }
            }
            else {
                throw new SignatureException(
                    "Le document n'est pas téléchargeable pour la demande"
                );
            }
        }
    
        /**
         * Permet, si la signature le permet, de réinitialiser l'état d'un document.
         *
         * @param Signature $signature
         * @return void
         */
        public function resetSignatureDocument(Signature $signature): void
        {
        }
    
        /**
         * @param int $id
         * @return void
         * @throws SignatureException
         */
        public function sendSignatureById(int $id): void
        {
            $this->sendSignature($this->getSignature($id));
        }
    
        /**
         * Envoi de la signature au parafeur.
         *
         * @param Signature $signature
         * @return void
         * @throws SignatureException
         */
        public function sendSignature(Signature $signature): void
        {
            if ($signature->isSendable()) {
    
                // Envoi au parafeur
                try {
                    $this->getLetterfileService()->getLetterFileStrategy(
                        $signature->getLetterfileKey()
                    )->sendSignature($signature);
                } catch (Exception $e) {
                    $msg = "Un problème est survenu lors de l'envoi au parapheur";
                    $this->getLoggerService()->critical($msg . " : " . $e->getMessage());
                    throw new SignatureException($msg);
                }
    
                // Enregistrement des informations d'envoi en local
                try {
                    $signature
                        ->setDateSend(new \DateTime())
                        ->setDateUpdate(new \DateTime())
                        ->setStatus(Signature::STATUS_SIGNATURE_WAIT);
    
                    foreach ($signature->getRecipients() as $recipient) {
                        $recipient
                            ->setStatus(Signature::STATUS_SIGNATURE_WAIT)
                            ->setDateUpdate(new \DateTime());
                    }
    
                    $this->getObjectManager()->flush();
    
                    // Event
                    $this->triggerSignatureEvent($signature, SignatureEvent::EVENT_TYPE_SEND);
    
                    // Notifications
                    if( $signature->isNotifiable() ){
                        foreach ($signature->getRecipients() as $recipient) {
                            $this->notificationRecipientSend($recipient);
                        }
                    }
                    if( $signature->isNotifiableObserver() ){
                        $this->notificationObserversSend($signature);
                    }
    
                } catch (Exception $exception) {
                    $this->getLoggerService()->critical(
                        "Can't save Signature, DB error : " . $exception->getMessage(
                        ) . " - " . $exception->getTraceAsString()
                    );
                    throw new SignatureException(
                        "La demande a été envoyé mais un problème est survenu lors de l'enregistrement."
                    );
                }
            }
            else {
                throw new SignatureException("La demande '" . $signature->getId() . "' ne peut pas être envoyée");
            }
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        /**
         * @param string $name
         * @param array|null $params
         * @param bool $absolute
         * @return string
         * @throws \Psr\Container\ContainerExceptionInterface
         * @throws \Psr\Container\NotFoundExceptionInterface
         */
        protected function forgeLink(string $name, ?array $params, bool $absolute = true ): string {
            static $urlBuilder;
    
            try {
                if( !$urlBuilder ){
                    $urlBuilder = $this->getServiceContainer()
                        ->get('ViewHelperManager')
                        ->get('url');
                }
                return ($absolute ? $this->getSignatureConfigurationService()->getNotificationsBaseUrl() : '')
                    .$urlBuilder($name, $params);
            } catch (Exception $e) {
                $this->getLoggerService()->error("URL ERROR : " . $e->getMessage());
                return "URL_EXCEPTION";
            }
        }
    
        /// NOTIFICATION des SIGNATAIRES
    
        /**
         * Envoi d'un avis de signature au destinataire.
         *
         * @param SignatureRecipient $signatureRecipient
         * @return void
         */
        public function notificationRecipientSend( SignatureRecipient $signatureRecipient ):void {
    
            // Obtenir le lien
            $link = $signatureRecipient->getUrlDocument();
    
            $subject = "[signature] Vous avez un document à {SIGN_VERB} - {DOCUMENT}";
            $message = "Bonjour, vous avez un document '{DOCUMENT}' à {SIGN_VERB} : $link\n";
            if( $signatureRecipient->getSignature()->getProcessStep() ){
                $subject .= " (étape {ETAPE}/{TOTAL_ETAPES})";
    
                $previousSteps = $signatureRecipient->getSignature()->getProcessStep()->getProcess()->previousSteps();
                if( count($previousSteps) ){
                    $message .= "étapes précédentes : \n";
                    // Calcule des étapes
                    foreach ($signatureRecipient->getSignature()->getProcessStep()->getProcess()->previousSteps() as $step) {
                        $message .= " - ". $step->getMessage() . "\n";
                    }
                }
            }
            $message .= "Merci\n(Ce message est automatique, ne pas répondre)";
    
            $this->sendNotificationTo(
                $signatureRecipient->getEmail(),
                $subject,
                $message,
                $signatureRecipient->getSignature()
            );
        }
    
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        /// NOTIFICATIONS des OBSERVATEURS
        public function notificationObserversSend( Signature $signature ):void {
            if( $signature->isNotifiableObserver() ) {
                $nbrRecipients = count($signature->getRecipients());
    
                $subject = "[suivi signature] Document '{DOCUMENT} envoyé pour {SIGN_NOUN}";
                if( $signature->getProcessStep() ){
                    $this->getLoggerService()->debug("SUBJECT -> Ajout étape");
                    $subject .= " (étape {ETAPE}/{TOTAL_ETAPES})";
                }
    
                //$url = $this->forgeLink('unicaen-signature/my-documents', []);
                foreach ($signature->getObservers() as $observer) {
                    $this->sendNotificationTo(
                        $observer->getEmail(),
                        $subject,
                        "Bonjour,\n le document '{DOCUMENT}' a été envoyé pour {SIGN_NOUN} à $nbrRecipients destinataire(s)"
                        ,
                        $signature
                    );
                }
            }
        }
    
        protected function sendNotificationTo(
            string $email,
            string $subject,
            string $message,
            Signature $signature
        ): void {
            $datas = [
                '{RECIPIENT}' => 'NO-RECIPIENT',
            ];
    
            $datas['{DOCUMENT}'] = $signature->getContextShort() ?: 'Document';
            $datas['{SIGN_NOUN}'] = $signature->getNoun();
            $datas['{SIGN_VERB}'] = $signature->getVerb();
            $datas['{SIGN_PARTICIPLE}'] = $signature->getParticiple();
            $datas['{LINK}'] = "LINK TO BUILD";
    
            if ($signature->getProcessStep()) {
                $datas['{ETAPE}'] = $signature->getProcessStep()->getOrder();
                $datas['{TOTAL_ETAPES}'] = count($signature->getProcessStep()->getProcess()->getSteps());
            }
    
            $find = array_keys($datas);
            $replace = array_values($datas);
    
            $body = str_ireplace($find, $replace, $message);
            $subject = str_ireplace($find, $replace, $subject);;
    
    
            $strategy = $this->getSignatureConfigurationService()->getNotificationStrategy();
            foreach ($strategy as $str) {
                try {
                    if (is_string($str)) {
                        $this->getServiceContainer()->get($str)->sendNotification($email, $subject, $body);
                    }
                    elseif (is_callable($str)) {
                        $str($this->getServiceContainer(), $email, $subject, $body);
                    }
                } catch (Exception $e) {
                    $this->getLoggerService()->error(
                        "Problème lors de l'envoi des notifications : " . $e->getMessage()
                    );
                }
            }
        }
    
        ///// CREATE
        public function createSignature(
            string $documentDatas,
            string $levelKey,
            array $recipientsDatas,
            array $letterFileDatas = []
        ): void {
            $level = $this->getSignatureConfigurationService()->getLevelByName($levelKey);
            $signature = new Signature();
            $signature->setStatus(Signature::STATUS_SIGNATURE_DRAFT);
            $signature->setType($level->getKey());
            foreach ($recipientsDatas as $recipientData) {
                $signatureRecipient = new SignatureRecipient();
                $signatureRecipient->setStatus(Signature::STATUS_SIGNATURE_DRAFT);
                $signatureRecipient->setSignature($signature);
                $signatureRecipient->setFirstname($recipientData['firstname']);
                $signatureRecipient->setLastname($recipientData['lastname']);
                $signatureRecipient->setEmail($recipientData['email']);
                $signatureRecipient->setPhone($recipientData['phone']);
            }
        }
    
        /**
         * Diffusion d'événement.
         *
         * @param Signature $signature
         * @return void
         */
        protected function triggerSignatureEvent(Signature $signature, string $eventType): void
        {
            $em = new EventManager($this->getSharedEventManager());
            $em->trigger($eventType, null, ['id' => $signature->getId()]);
        }
    
        protected function triggerRecipientEvent(SignatureRecipient $signatureRecipient, string $eventType): void
        {
            $em = new EventManager($this->getSharedEventManager());
            $em->trigger($eventType, null, ['id' => $signatureRecipient->getId()]);
        }
    }