From dceb9918fbbfe3d3992a51dcfe67248e61637bc7 Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Mon, 13 Jan 2025 09:50:49 +0100 Subject: [PATCH] introduce channel context, resolves #486 (#504) --- README.md | 1 + UPGRADE.md | 8 +++- docs/OutputWorkflow/09_ApiChannel.md | 5 ++- docs/OutputWorkflow/12_CustomChannel.md | 4 +- docs/OutputWorkflow/13_ChannelContext.md | 34 +++++++++++++++ docs/OutputWorkflow/30_Events.md | 32 ++++++++++++--- .../ChannelSubjectGuardEvent.php | 18 +++++++- src/OutputWorkflow/Channel/Api/ApiData.php | 41 ++++++++++--------- .../Channel/Api/ApiOutputChannel.php | 18 +++++++- .../Channel/Api/ApiOutputChannelWorker.php | 39 ++++++++++++------ src/OutputWorkflow/Channel/ChannelContext.php | 28 +++++++++++++ .../Channel/ChannelContextAwareInterface.php | 10 +++++ .../Channel/Email/EmailOutputChannel.php | 22 +++++----- .../Email/EmailOutputChannelWorker.php | 26 ++++++++++-- .../Channel/Object/AbstractObjectResolver.php | 34 +++++++++++++-- .../Channel/Object/ObjectOutputChannel.php | 20 ++++----- .../Channel/Trait/ChannelContextTrait.php | 20 +++++++++ .../OutputWorkflowDispatcher.php | 27 +++++++++--- src/Tool/LocaleDataMapper.php | 4 +- 19 files changed, 310 insertions(+), 81 deletions(-) create mode 100644 docs/OutputWorkflow/13_ChannelContext.md create mode 100644 src/OutputWorkflow/Channel/ChannelContext.php create mode 100644 src/OutputWorkflow/Channel/ChannelContextAwareInterface.php create mode 100644 src/OutputWorkflow/Channel/Trait/ChannelContextTrait.php diff --git a/README.md b/README.md index d0d4f196..ada55b7e 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Nothing to tell here, it's just [Symfony](https://symfony.com/doc/current/templa - [Email Channel](docs/OutputWorkflow/10_EmailChannel.md) - [Object Channel](docs/OutputWorkflow/11_ObjectChannel.md) - [Custom Channel](docs/OutputWorkflow/12_CustomChannel.md) + - [Channel Context](docs/OutputWorkflow/13_ChannelContext.md) - [Output Transformer](docs/OutputWorkflow/15_OutputTransformer.md) - [Field Transformer](docs/OutputWorkflow/16_FieldTransformer.md) - [Success Management](docs/OutputWorkflow/20_SuccessManagement.md) diff --git a/UPGRADE.md b/UPGRADE.md index b635ee07..fe47b0ea 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,7 +1,11 @@ # Upgrade Notes -## 5.1.5 -- **[BUGFIX]** Update custom channel docs [#493](https://github.com/dachcom-digital/pimcore-formbuilder/issues/493) +## 5.2.0 +- **[NEW FEATURE]** Bootstrap 5 Layout Support [@mackrais](https://github.com/dachcom-digital/pimcore-formbuilder/pull/500) +- **[NEW FEATURE]** Introduce Channel Context [#486](https://github.com/dachcom-digital/pimcore-formbuilder/issues/486) +- **[IMPROVEMENT]** Doctrine ORM 3.0 Support [#503](https://github.com/dachcom-digital/pimcore-formbuilder/pull/503) +- **[BUGFIX]** API Channel: Keep array index when merging child nodes [@simon-matt-oetztal](https://github.com/dachcom-digital/pimcore-formbuilder/pull/496) +- **[BUGFIX]** Update Custom Channel Documentation [#493](https://github.com/dachcom-digital/pimcore-formbuilder/issues/493) ## 5.1.4 - **[BUGFIX]** Allow using double-opt-in variables in placeholder processor diff --git a/docs/OutputWorkflow/09_ApiChannel.md b/docs/OutputWorkflow/09_ApiChannel.md index 389bc2bf..d4577077 100644 --- a/docs/OutputWorkflow/09_ApiChannel.md +++ b/docs/OutputWorkflow/09_ApiChannel.md @@ -157,7 +157,7 @@ class MailChimpApiProvider implements ApiProviderInterface } } - protected function getClient() + protected function getClient(): MailchimpMarketing\ApiClient { $mailchimp = new MailchimpMarketing\ApiClient(); @@ -213,12 +213,13 @@ class OutputWorkflowEventListener implements EventSubscriberInterface // different fail scenarios can be applied: $event->shouldFail('My invalid message for a specific channel! Allow further channels to pass!', true); - + // OR $event->shouldFail('My invalid message! If this happens, no further channel will be executed!', false); // silently skip channel if ($subject->getProviderConfigurationNode('myConfig') === 'a special value') { $event->shouldSuspend(); + return; } } diff --git a/docs/OutputWorkflow/12_CustomChannel.md b/docs/OutputWorkflow/12_CustomChannel.md index e660331d..5549af0e 100644 --- a/docs/OutputWorkflow/12_CustomChannel.md +++ b/docs/OutputWorkflow/12_CustomChannel.md @@ -13,6 +13,9 @@ services: ## Output Transformer Read [here](./15_OutputTransformer.md#custom-output-transformer) how to add a single output transformer to your new custom channel. +## Channel Context +Read [here](./13_ChannelContext.md) how to support channel context within your new custom channel. + ## PHP Configuration Form Type Class ```php getContextItem('channelContext)`). +This can be helpful, if you need to clean up data after an exception occurs or the workflow has been completely processed. + +## Channel Context in Custom Channel +To receive the channel context in a custom channel, you need to implement the `ChannelContextAwareInterface`: + +> [!IMPORTANT] +> If your channel also dispatches a `ChannelSubjectGuardEvent`, +> don't forget to pass the channel context to it to pass the context to upcoming channels! + +If your channel implements the `ChannelContextAwareInterface`, the `OutputWorkflowDispatcher` automatically will inject the active `ChannelContext` object. + +```php + 'checkSubject', + FormBuilderEvents::OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH => ['addChannelContextData', 'checkSubject'], ]; } - public function checkSubject(ChannelSubjectGuardEvent $event) + public function addChannelContextData(ChannelSubjectGuardEvent $event): void { // this could be a data object but also a field collection $subject = $event->getSubject(); - if($event->getWorkflowName() === 'my_weird_workflow') { + if ( + // if it's a special channel, add some context data to fetch it again in the next channel! + $event->getWorkflowName() === 'my_workflow' && + $event->getChannelType() === 'mail' && + $event->hasChannelContext() + ) { + $event + ->getChannelContext() + ->addContextData('special_key', 'my_special_data'); + } + } + + public function checkSubject(ChannelSubjectGuardEvent $event): void + { + // this could be a data object but also a field collection + $subject = $event->getSubject(); + + if ($event->getWorkflowName() === 'my_workflow') { $event->shouldFail('My invalid message for a specific channel! Allow further channels to pass!', true); + return; } - if($event->getWorkflowName() === 'my_second_weird_workflow') { + if ($event->getWorkflowName() === 'my_second_workflow') { $event->shouldFail('My invalid message! If this happens, no further channel will be executed!', false); + return; } - if($event->getChannelType() === 'object') { + if ($event->getChannelType() === 'object') { // silently skip channel $event->shouldSuspend(); + return; } } diff --git a/src/Event/OutputWorkflow/ChannelSubjectGuardEvent.php b/src/Event/OutputWorkflow/ChannelSubjectGuardEvent.php index aaa9f3bd..2425425d 100644 --- a/src/Event/OutputWorkflow/ChannelSubjectGuardEvent.php +++ b/src/Event/OutputWorkflow/ChannelSubjectGuardEvent.php @@ -3,6 +3,7 @@ namespace FormBuilderBundle\Event\OutputWorkflow; use FormBuilderBundle\Form\Data\FormDataInterface; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use Symfony\Contracts\EventDispatcher\Event; class ChannelSubjectGuardEvent extends Event @@ -17,7 +18,8 @@ public function __construct( protected mixed $subject, protected string $workflowName, protected string $channelType, - protected array $formRuntimeData + protected array $formRuntimeData, + protected ?ChannelContext $channelContext = null ) { } @@ -51,6 +53,20 @@ public function getChannelType(): string return $this->channelType; } + public function hasChannelContext(): bool + { + return $this->channelContext instanceof ChannelContext; + } + + public function getChannelContext(): ChannelContext + { + if (!$this->hasChannelContext()) { + throw new \RuntimeException('ChannelContext not available'); + } + + return $this->channelContext; + } + /** * Silently suspend current process without any notices. */ diff --git a/src/OutputWorkflow/Channel/Api/ApiData.php b/src/OutputWorkflow/Channel/Api/ApiData.php index 17b3795c..3a549836 100644 --- a/src/OutputWorkflow/Channel/Api/ApiData.php +++ b/src/OutputWorkflow/Channel/Api/ApiData.php @@ -2,31 +2,20 @@ namespace FormBuilderBundle\OutputWorkflow\Channel\Api; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use Symfony\Component\Form\FormInterface; class ApiData { - protected string $apiProviderName; - protected array $apiNodes; - protected ?array $providerConfiguration; - protected string $locale; - protected array $formRuntimeData; - protected FormInterface $form; - public function __construct( - string $apiProviderName, - array $apiNodes, - ?array $providerConfiguration, - string $locale, - array $formRuntimeData, - FormInterface $form + protected string $apiProviderName, + protected array $apiNodes, + protected ?array $providerConfiguration, + protected string $locale, + protected array $formRuntimeData, + protected FormInterface $form, + protected ?ChannelContext $channelContext ) { - $this->apiProviderName = $apiProviderName; - $this->apiNodes = $apiNodes; - $this->providerConfiguration = $providerConfiguration; - $this->locale = $locale; - $this->formRuntimeData = $formRuntimeData; - $this->form = $form; } public function getApiProviderName(): string @@ -39,6 +28,20 @@ public function getForm(): FormInterface return $this->form; } + public function hasChannelContext(): bool + { + return $this->channelContext instanceof ChannelContext; + } + + public function getChannelContext(): ChannelContext + { + if (!$this->hasChannelContext()) { + throw new \RuntimeException('ChannelContext not available'); + } + + return $this->channelContext; + } + public function getFormRuntimeData(): array { return $this->formRuntimeData; diff --git a/src/OutputWorkflow/Channel/Api/ApiOutputChannel.php b/src/OutputWorkflow/Channel/Api/ApiOutputChannel.php index 9d20498f..8eee9380 100644 --- a/src/OutputWorkflow/Channel/Api/ApiOutputChannel.php +++ b/src/OutputWorkflow/Channel/Api/ApiOutputChannel.php @@ -4,10 +4,14 @@ use FormBuilderBundle\Event\SubmissionEvent; use FormBuilderBundle\Form\Admin\Type\OutputWorkflow\Channel\ApiChannelType; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContextAwareInterface; use FormBuilderBundle\OutputWorkflow\Channel\ChannelInterface; +use FormBuilderBundle\OutputWorkflow\Channel\Trait\ChannelContextTrait; -class ApiOutputChannel implements ChannelInterface +class ApiOutputChannel implements ChannelInterface, ChannelContextAwareInterface { + use ChannelContextTrait; + public function __construct(protected ApiOutputChannelWorker $apiOutputChannelWorker) { } @@ -33,7 +37,17 @@ public function getUsedFormFieldNames(array $channelConfiguration): array public function dispatchOutputProcessing(SubmissionEvent $submissionEvent, string $workflowName, array $channelConfiguration): void { - $this->apiOutputChannelWorker->process($submissionEvent, $workflowName, $channelConfiguration); + $locale = $submissionEvent->getLocale() ?? $submissionEvent->getRequest()->getLocale(); + $form = $submissionEvent->getForm(); + $formRuntimeData = $submissionEvent->getFormRuntimeData(); + + $context = [ + 'locale' => $locale, + 'doubleOptInSession' => $submissionEvent->getDoubleOptInSession(), + 'channelContext' => $this->getChannelContext(), + ]; + + $this->apiOutputChannelWorker->process($form, $channelConfiguration, $formRuntimeData, $workflowName, $context); } protected function findUsedFormFieldsInConfiguration(array $definitionFields, array $fieldNames = []): array diff --git a/src/OutputWorkflow/Channel/Api/ApiOutputChannelWorker.php b/src/OutputWorkflow/Channel/Api/ApiOutputChannelWorker.php index fc69a694..4a784281 100644 --- a/src/OutputWorkflow/Channel/Api/ApiOutputChannelWorker.php +++ b/src/OutputWorkflow/Channel/Api/ApiOutputChannelWorker.php @@ -3,11 +3,11 @@ namespace FormBuilderBundle\OutputWorkflow\Channel\Api; use FormBuilderBundle\Event\OutputWorkflow\ChannelSubjectGuardEvent; -use FormBuilderBundle\Event\SubmissionEvent; use FormBuilderBundle\Exception\OutputWorkflow\GuardChannelException; use FormBuilderBundle\Exception\OutputWorkflow\GuardOutputWorkflowException; use FormBuilderBundle\Form\FormValuesOutputApplierInterface; use FormBuilderBundle\FormBuilderEvents; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use FormBuilderBundle\Registry\ApiProviderRegistry; use FormBuilderBundle\Registry\FieldTransformerRegistry; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -23,16 +23,15 @@ public function __construct( ) { } - public function process(SubmissionEvent $submissionEvent, string $workflowName, array $channelConfiguration): void + public function process(FormInterface $form, array $channelConfiguration, array $formRuntimeData, string $workflowName, array $context = []): void { - $formRuntimeData = $submissionEvent->getFormRuntimeData(); - $locale = $submissionEvent->getRequest()->getLocale(); - $form = $submissionEvent->getForm(); - $apiProviderName = $channelConfiguration['apiProvider']; $apiMappingData = $channelConfiguration['apiMappingData']; $providerConfiguration = $channelConfiguration['apiConfiguration']; + $channelContext = $context['channelContext'] ?? null; + $locale = $context['locale'] ?? null; + // no data, no gain. if (!is_array($apiMappingData)) { return; @@ -50,9 +49,9 @@ public function process(SubmissionEvent $submissionEvent, string $workflowName, return; } - $apiData = new ApiData($apiProviderName, $nodes, $providerConfiguration, $locale, $formRuntimeData, $form); + $apiData = new ApiData($apiProviderName, $nodes, $providerConfiguration, $locale, $formRuntimeData, $form, $channelContext); - if (null === $apiData = $this->dispatchGuardEvent($apiData, $form, $workflowName, $formRuntimeData)) { + if (null === $apiData = $this->dispatchGuardEvent($apiData, $form, $workflowName, $formRuntimeData, $channelContext)) { return; } @@ -248,9 +247,23 @@ protected function findFormDataField(string $requestedFieldName, array $data): ? * @throws GuardChannelException * @throws GuardOutputWorkflowException */ - protected function dispatchGuardEvent($subject, FormInterface $form, string $workflowName, array $formRuntimeData): mixed - { - $channelSubjectGuardEvent = new ChannelSubjectGuardEvent($form->getData(), $subject, $workflowName, 'api', $formRuntimeData); + protected function dispatchGuardEvent( + $subject, + FormInterface $form, + string $workflowName, + array $formRuntimeData, + ?ChannelContext $channelContext + ): mixed { + + $channelSubjectGuardEvent = new ChannelSubjectGuardEvent( + $form->getData(), + $subject, + $workflowName, + 'api', + $formRuntimeData, + $channelContext + ); + $this->eventDispatcher->dispatch($channelSubjectGuardEvent, FormBuilderEvents::OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH); if ($channelSubjectGuardEvent->isSuspended()) { @@ -259,7 +272,9 @@ protected function dispatchGuardEvent($subject, FormInterface $form, string $wor if ($channelSubjectGuardEvent->shouldStopChannel()) { throw new GuardChannelException($channelSubjectGuardEvent->getFailMessage()); - } elseif ($channelSubjectGuardEvent->shouldStopOutputWorkflow()) { + } + + if ($channelSubjectGuardEvent->shouldStopOutputWorkflow()) { throw new GuardOutputWorkflowException($channelSubjectGuardEvent->getFailMessage()); } diff --git a/src/OutputWorkflow/Channel/ChannelContext.php b/src/OutputWorkflow/Channel/ChannelContext.php new file mode 100644 index 00000000..2cf07ed7 --- /dev/null +++ b/src/OutputWorkflow/Channel/ChannelContext.php @@ -0,0 +1,28 @@ +contextData; + } + + public function hasContextData(string $key): bool + { + return array_key_exists($key, $this->contextData); + } + + public function getContextData(string $key): mixed + { + return $this->contextData[$key] ?? []; + } + + public function addContextData(string $key, mixed $data): void + { + $this->contextData[$key] = $data; + } +} diff --git a/src/OutputWorkflow/Channel/ChannelContextAwareInterface.php b/src/OutputWorkflow/Channel/ChannelContextAwareInterface.php new file mode 100644 index 00000000..c0a61a01 --- /dev/null +++ b/src/OutputWorkflow/Channel/ChannelContextAwareInterface.php @@ -0,0 +1,10 @@ +getLocale() ?? $submissionEvent->getRequest()->getLocale(); $form = $submissionEvent->getForm(); $formRuntimeData = $submissionEvent->getFormRuntimeData(); - $localizedConfig = $this->validateOutputConfig($channelConfiguration, $locale); + $localizedChannelConfiguration = $this->validateOutputConfig($channelConfiguration, $locale); $context = [ 'locale' => $locale, 'doubleOptInSession' => $submissionEvent->getDoubleOptInSession(), + 'channelContext' => $this->getChannelContext(), ]; - $this->channelWorker->process($form, $localizedConfig, $formRuntimeData, $workflowName, $context); + $this->channelWorker->process($form, $localizedChannelConfiguration, $formRuntimeData, $workflowName, $context); } /** @@ -56,17 +58,17 @@ public function dispatchOutputProcessing(SubmissionEvent $submissionEvent, strin */ protected function validateOutputConfig(array $channelConfiguration, string $locale): array { - $localizedConfig = $this->localeDataMapper->mapMultiDimensional($locale, 'mailTemplate', true, $channelConfiguration); + $localizedChannelConfiguration = $this->localeDataMapper->mapMultiDimensional($locale, 'mailTemplate', true, $channelConfiguration); $message = null; - if (!isset($localizedConfig['mailTemplate'])) { + if (!isset($localizedChannelConfiguration['mailTemplate'])) { $message = 'No mail template definition available.'; - } elseif ($localizedConfig['mailTemplate']['id'] === null) { + } elseif ($localizedChannelConfiguration['mailTemplate']['id'] === null) { $message = 'No mail template id available.'; } if ($message === null) { - return $localizedConfig; + return $localizedChannelConfiguration; } throw new \Exception($message); diff --git a/src/OutputWorkflow/Channel/Email/EmailOutputChannelWorker.php b/src/OutputWorkflow/Channel/Email/EmailOutputChannelWorker.php index 0360058d..39d5810b 100644 --- a/src/OutputWorkflow/Channel/Email/EmailOutputChannelWorker.php +++ b/src/OutputWorkflow/Channel/Email/EmailOutputChannelWorker.php @@ -8,6 +8,7 @@ use FormBuilderBundle\Exception\OutputWorkflow\GuardException; use FormBuilderBundle\Exception\OutputWorkflow\GuardOutputWorkflowException; use FormBuilderBundle\Model\DoubleOptInSessionInterface; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use Pimcore\Mail; use Pimcore\Model\Document; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -37,7 +38,9 @@ public function process(FormInterface $form, array $channelConfiguration, array $forcePlainText = $channelConfiguration['forcePlainText']; $disableDefaultMailBody = $channelConfiguration['disableDefaultMailBody']; $disableMailLogging = $channelConfiguration['disableMailLogging'] ?? false; + $doubleOptInSession = $context['doubleOptInSession'] ?? null; + $channelContext = $context['channelContext'] ?? null; $mailTemplateId = $mailTemplate['id']; $mailTemplate = is_numeric($mailTemplateId) ? Document\Email::getById($mailTemplateId) : null; @@ -67,7 +70,7 @@ public function process(FormInterface $form, array $channelConfiguration, array } // dispatch subject guard event - if (null === $mail = $this->dispatchGuardEvent($form->getData(), $mail, $workflowName, $formRuntimeData)) { + if (null === $mail = $this->dispatchGuardEvent($form->getData(), $mail, $workflowName, $formRuntimeData, $channelContext)) { return; } @@ -106,10 +109,25 @@ protected function sendDefault(Mail $mail): void /** * @throws GuardException + * @throws GuardOutputWorkflowException */ - protected function dispatchGuardEvent(FormDataInterface $formData, Mail $subject, string $workflowName, array $formRuntimeData): ?Mail - { - $channelSubjectGuardEvent = new ChannelSubjectGuardEvent($formData, $subject, $workflowName, 'email', $formRuntimeData); + protected function dispatchGuardEvent( + FormDataInterface $formData, + Mail $subject, + string $workflowName, + array $formRuntimeData, + ?ChannelContext $channelContext + ): ?Mail { + + $channelSubjectGuardEvent = new ChannelSubjectGuardEvent( + $formData, + $subject, + $workflowName, + 'email', + $formRuntimeData, + $channelContext + ); + $this->eventDispatcher->dispatch($channelSubjectGuardEvent, FormBuilderEvents::OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH); if ($channelSubjectGuardEvent->isSuspended()) { diff --git a/src/OutputWorkflow/Channel/Object/AbstractObjectResolver.php b/src/OutputWorkflow/Channel/Object/AbstractObjectResolver.php index 7bed9d71..5a7e3dad 100644 --- a/src/OutputWorkflow/Channel/Object/AbstractObjectResolver.php +++ b/src/OutputWorkflow/Channel/Object/AbstractObjectResolver.php @@ -2,6 +2,7 @@ namespace FormBuilderBundle\OutputWorkflow\Channel\Object; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use FormBuilderBundle\Registry\DynamicObjectResolverRegistry; use Pimcore\Model\DataObject; use Pimcore\Model\Element\ElementInterface; @@ -25,6 +26,8 @@ abstract class AbstractObjectResolver public const OBJECT_RESOLVER_UPDATE = 'existingObject'; protected FormInterface $form; + protected ?ChannelContext $channelContext; + protected array $formRuntimeData; protected string $locale; protected string $workflowName; @@ -68,6 +71,16 @@ public function getFormRuntimeData(): array return $this->formRuntimeData; } + public function setChannelContext(?ChannelContext $channelContext): void + { + $this->channelContext = $channelContext; + } + + public function getChannelContext(): ?ChannelContext + { + return $this->channelContext; + } + public function setLocale(string $locale): void { $this->locale = $locale; @@ -366,7 +379,7 @@ protected function findMapDefinition(array $definitionFields, string $formFieldN protected function processFieldWorkerValue(string $workerName, array $workerConfig, ?DataObject\ClassDefinition\Data $fieldDefinition, mixed $value) { return match ($workerName) { - 'relationWorker' => call_user_func(function (?DataObject\ClassDefinition\Data $fieldDefinition, array $workerConfig, mixed $value) { + 'relationWorker' => call_user_func(static function (?DataObject\ClassDefinition\Data $fieldDefinition, array $workerConfig, mixed $value) { $relationType = $workerConfig['relationType'] ?? null; @@ -386,7 +399,9 @@ protected function processFieldWorkerValue(string $workerName, array $workerConf if ($fieldDefinition instanceof DataObject\ClassDefinition\Data\ManyToOneRelation) { return $element; - } elseif ($fieldDefinition instanceof DataObject\ClassDefinition\Data\ManyToManyRelation) { + } + + if ($fieldDefinition instanceof DataObject\ClassDefinition\Data\ManyToManyRelation) { return [$element]; } @@ -399,10 +414,19 @@ protected function processFieldWorkerValue(string $workerName, array $workerConf /** * @throws GuardException + * @throws GuardOutputWorkflowException */ protected function dispatchGuardEvent(mixed $subject): DataObject\Fieldcollection\Data\AbstractData|DataObject\Concrete|null { - $channelSubjectGuardEvent = new ChannelSubjectGuardEvent($this->getForm()->getData(), $subject, $this->getWorkflowName(), 'object', $this->getFormRuntimeData()); + $channelSubjectGuardEvent = new ChannelSubjectGuardEvent( + $this->getForm()->getData(), + $subject, + $this->getWorkflowName(), + 'object', + $this->getFormRuntimeData(), + $this->getChannelContext() + ); + $this->eventDispatcher->dispatch($channelSubjectGuardEvent, FormBuilderEvents::OUTPUT_WORKFLOW_GUARD_SUBJECT_PRE_DISPATCH); if ($channelSubjectGuardEvent->isSuspended()) { @@ -411,7 +435,9 @@ protected function dispatchGuardEvent(mixed $subject): DataObject\Fieldcollectio if ($channelSubjectGuardEvent->shouldStopChannel()) { throw new GuardChannelException($channelSubjectGuardEvent->getFailMessage()); - } elseif ($channelSubjectGuardEvent->shouldStopOutputWorkflow()) { + } + + if ($channelSubjectGuardEvent->shouldStopOutputWorkflow()) { throw new GuardOutputWorkflowException($channelSubjectGuardEvent->getFailMessage()); } diff --git a/src/OutputWorkflow/Channel/Object/ObjectOutputChannel.php b/src/OutputWorkflow/Channel/Object/ObjectOutputChannel.php index d84edbb3..6a78176b 100644 --- a/src/OutputWorkflow/Channel/Object/ObjectOutputChannel.php +++ b/src/OutputWorkflow/Channel/Object/ObjectOutputChannel.php @@ -5,16 +5,16 @@ use FormBuilderBundle\Event\SubmissionEvent; use FormBuilderBundle\Factory\ObjectResolverFactoryInterface; use FormBuilderBundle\Form\Admin\Type\OutputWorkflow\Channel\ObjectChannelType; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContextAwareInterface; use FormBuilderBundle\OutputWorkflow\Channel\ChannelInterface; -use Pimcore\Model\DataObject; +use FormBuilderBundle\OutputWorkflow\Channel\Trait\ChannelContextTrait; -class ObjectOutputChannel implements ChannelInterface +class ObjectOutputChannel implements ChannelInterface, ChannelContextAwareInterface { - protected ObjectResolverFactoryInterface $objectResolverFactory; + use ChannelContextTrait; - public function __construct(ObjectResolverFactoryInterface $objectResolverFactory) + public function __construct(protected ObjectResolverFactoryInterface $objectResolverFactory) { - $this->objectResolverFactory = $objectResolverFactory; } public function getFormType(): string @@ -36,14 +36,11 @@ public function getUsedFormFieldNames(array $channelConfiguration): array return $this->findUsedFormFieldsInConfiguration($channelConfiguration['objectMappingData']); } - /** - * {@inheritdoc} - */ public function dispatchOutputProcessing(SubmissionEvent $submissionEvent, string $workflowName, array $channelConfiguration): void { - $formRuntimeData = $submissionEvent->getFormRuntimeData(); - $locale = $submissionEvent->getRequest()->getLocale(); + $locale = $submissionEvent->getLocale() ?? $submissionEvent->getRequest()->getLocale(); $form = $submissionEvent->getForm(); + $formRuntimeData = $submissionEvent->getFormRuntimeData(); $objectMappingData = $channelConfiguration['objectMappingData']; @@ -71,6 +68,7 @@ public function dispatchOutputProcessing(SubmissionEvent $submissionEvent, strin $objectResolver->setLocale($locale); $objectResolver->setWorkflowName($workflowName); $objectResolver->setFormRuntimeData($formRuntimeData); + $objectResolver->setChannelContext($this->getChannelContext()); $objectResolver->resolve(); } @@ -79,7 +77,7 @@ protected function findUsedFormFieldsInConfiguration(array $definitionFields, ar { foreach ($definitionFields as $definitionField) { $hasChildren = isset($definitionField['childs']) && is_array($definitionField['childs']) && count($definitionField['childs']) > 0; - $hasWorkerFieldMapping = isset($definitionField['config']['workerData']) && isset($definitionField['config']['workerData']['fieldMapping']); + $hasWorkerFieldMapping = isset($definitionField['config']['workerData']['fieldMapping']); if ($definitionField['type'] === 'form_field' && $hasChildren) { $fieldNames[] = $definitionField['config']['name']; diff --git a/src/OutputWorkflow/Channel/Trait/ChannelContextTrait.php b/src/OutputWorkflow/Channel/Trait/ChannelContextTrait.php new file mode 100644 index 00000000..f55236ad --- /dev/null +++ b/src/OutputWorkflow/Channel/Trait/ChannelContextTrait.php @@ -0,0 +1,20 @@ +channelContext; + } + + public function setChannelContext(ChannelContext $channelContext): void + { + $this->channelContext = $channelContext; + } +} diff --git a/src/OutputWorkflow/OutputWorkflowDispatcher.php b/src/OutputWorkflow/OutputWorkflowDispatcher.php index 334fca72..32743373 100644 --- a/src/OutputWorkflow/OutputWorkflowDispatcher.php +++ b/src/OutputWorkflow/OutputWorkflowDispatcher.php @@ -8,7 +8,9 @@ use FormBuilderBundle\Exception\OutputWorkflow\GuardOutputWorkflowException; use FormBuilderBundle\Exception\OutputWorkflow\GuardStackedException; use FormBuilderBundle\Model\OutputWorkflowInterface; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContext; use FormBuilderBundle\Registry\OutputWorkflowChannelRegistry; +use FormBuilderBundle\OutputWorkflow\Channel\ChannelContextAwareInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -35,21 +37,34 @@ protected function dispatchOutputWorkflowInitiating(OutputWorkflowInterface $out $this->signalSubscribeHandler->listen(SignalSubscribeHandler::CHANNEL_OUTPUT_WORKFLOW); $exceptionStack = []; + $channelContext = new ChannelContext(); + foreach ($outputWorkflow->getChannels() as $index => $channel) { try { $channelProcessor = $this->channelRegistry->get($channel->getType()); + + if ($channelProcessor instanceof ChannelContextAwareInterface) { + $channelProcessor->setChannelContext($channelContext); + } + $channelProcessor->dispatchOutputProcessing($submissionEvent, $outputWorkflow->getName(), $channel->getConfiguration()); } catch (GuardChannelException $e) { $exceptionStack[] = $e; } catch (GuardOutputWorkflowException $e) { - $this->signalSubscribeHandler->broadcast(['exception' => $e]); + $this->signalSubscribeHandler->broadcast([ + 'exception' => $e, + 'channelContext' => $channelContext + ]); throw $e; } catch (\Throwable $e) { - $this->signalSubscribeHandler->broadcast(['exception' => $e]); + $this->signalSubscribeHandler->broadcast([ + 'exception' => $e, + 'channelContext' => $channelContext + ]); throw new \Exception( sprintf( @@ -66,12 +81,15 @@ protected function dispatchOutputWorkflowInitiating(OutputWorkflowInterface $out if (count($exceptionStack) > 0) { $exception = new GuardStackedException($exceptionStack); - $this->signalSubscribeHandler->broadcast(['exception' => $exception]); + $this->signalSubscribeHandler->broadcast([ + 'exception' => $exception, + 'channelContext' => $channelContext + ]); throw $exception; } - $this->signalSubscribeHandler->broadcast(); + $this->signalSubscribeHandler->broadcast(['channelContext' => $channelContext]); } protected function dispatchOutputWorkflowFunnelInitiating(OutputWorkflowInterface $outputWorkflow, SubmissionEvent $submissionEvent): void @@ -115,5 +133,4 @@ public function dispatchOutputWorkflowFunnelProcessing(OutputWorkflowInterface $ return $response; } - } diff --git a/src/Tool/LocaleDataMapper.php b/src/Tool/LocaleDataMapper.php index 3d0904ec..46729d27 100644 --- a/src/Tool/LocaleDataMapper.php +++ b/src/Tool/LocaleDataMapper.php @@ -35,10 +35,10 @@ public function mapMultiDimensional(string $requestedLocale, string $identifier, { $blockGenerator = static function ($locale) use ($isHref, $data, $identifier) { if ($isHref === true) { - return isset($data[$locale][$identifier]['id']) && $data[$locale][$identifier]['id'] !== null; + return isset($data[$locale][$identifier]['id']); } - return isset($data[$locale][$identifier]) && $data[$locale][$identifier] !== null; + return isset($data[$locale][$identifier]); }; if ($blockGenerator($requestedLocale) === true) {