diff --git a/mailpoet/lib/Automation/Engine/Control/FilterHandler.php b/mailpoet/lib/Automation/Engine/Control/FilterHandler.php new file mode 100644 index 0000000000..33d9bbcaae --- /dev/null +++ b/mailpoet/lib/Automation/Engine/Control/FilterHandler.php @@ -0,0 +1,39 @@ +registry = $registry; + } + + public function matchesFilters(StepRunArgs $args): bool { + $filters = $args->getStep()->getFilters(); + foreach ($filters as $filter) { + $value = $args->getFieldValue($filter->getFieldKey()); + if (!$this->matchesFilter($filter, $value)) { + return false; + } + } + return true; + } + + /** @param mixed $value */ + private function matchesFilter(FilterData $data, $value): bool { + $filter = $this->registry->getFilter($data->getFieldType()); + if (!$filter) { + throw Exceptions::filterNotFound($data->getFieldType()); + } + return $filter->matches($data, $value); + } +} diff --git a/mailpoet/lib/Automation/Engine/Control/TriggerHandler.php b/mailpoet/lib/Automation/Engine/Control/TriggerHandler.php index 1e7477e13f..ea611da3fa 100644 --- a/mailpoet/lib/Automation/Engine/Control/TriggerHandler.php +++ b/mailpoet/lib/Automation/Engine/Control/TriggerHandler.php @@ -28,6 +28,9 @@ class TriggerHandler { /** @var SubjectTransformerHandler */ private $subjectTransformerHandler; + /** @var FilterHandler */ + private $filterHandler; + /** @var WordPress */ private $wordPress; @@ -37,6 +40,7 @@ class TriggerHandler { AutomationRunStorage $automationRunStorage, SubjectLoader $subjectLoader, SubjectTransformerHandler $subjectTransformerHandler, + FilterHandler $filterHandler, WordPress $wordPress ) { $this->actionScheduler = $actionScheduler; @@ -44,6 +48,7 @@ class TriggerHandler { $this->automationRunStorage = $automationRunStorage; $this->subjectLoader = $subjectLoader; $this->subjectTransformerHandler = $subjectTransformerHandler; + $this->filterHandler = $filterHandler; $this->wordPress = $wordPress; } @@ -70,6 +75,11 @@ class TriggerHandler { $automationRun = new AutomationRun($automation->getId(), $automation->getVersionId(), $trigger->getKey(), $subjects); $stepRunArgs = new StepRunArgs($automation, $automationRun, $step, $subjectEntries); + + if (!$this->filterHandler->matchesFilters($stepRunArgs)) { + continue; + } + $createAutomationRun = $trigger->isTriggeredBy($stepRunArgs); $createAutomationRun = $this->wordPress->applyFilters( Hooks::AUTOMATION_RUN_CREATE, diff --git a/mailpoet/lib/Automation/Engine/Exceptions.php b/mailpoet/lib/Automation/Engine/Exceptions.php index 5d1617c8a9..9cb23999a4 100644 --- a/mailpoet/lib/Automation/Engine/Exceptions.php +++ b/mailpoet/lib/Automation/Engine/Exceptions.php @@ -27,6 +27,7 @@ class Exceptions { private const MULTIPLE_PAYLOADS_FOUND = 'mailpoet_automation_multiple_payloads_found'; private const FIELD_NOT_FOUND = 'mailpoet_automation_field_not_found'; private const FIELD_LOAD_FAILED = 'mailpoet_automation_field_load_failed'; + private const FILTER_NOT_FOUND = 'mailpoet_automation_filter_not_found'; private const AUTOMATION_STRUCTURE_MODIFICATION_NOT_SUPPORTED = 'mailpoet_automation_structure_modification_not_supported'; private const AUTOMATION_STRUCTURE_NOT_VALID = 'mailpoet_automation_structure_not_valid'; private const AUTOMATION_STEP_MODIFIED_WHEN_UNKNOWN = 'mailpoet_automation_step_modified_when_unknown'; @@ -183,6 +184,13 @@ class Exceptions { ->withMessage(sprintf(__('Field with key "%1$s" and args "%2$s" failed to load.', 'mailpoet'), $key, Json::encode($args))); } + public static function filterNotFound(string $fieldType): NotFoundException { + return NotFoundException::create() + ->withErrorCode(self::FILTER_NOT_FOUND) + // translators: %s is the type of the field for which a filter was not found. + ->withMessage(sprintf(__("Filter for field of type '%s' not found.", 'mailpoet'), $fieldType)); + } + public static function automationStructureModificationNotSupported(): UnexpectedValueException { return UnexpectedValueException::create() ->withErrorCode(self::AUTOMATION_STRUCTURE_MODIFICATION_NOT_SUPPORTED) diff --git a/mailpoet/lib/DI/ContainerConfigurator.php b/mailpoet/lib/DI/ContainerConfigurator.php index c7ec88c16d..c107170a0c 100644 --- a/mailpoet/lib/DI/ContainerConfigurator.php +++ b/mailpoet/lib/DI/ContainerConfigurator.php @@ -122,6 +122,7 @@ class ContainerConfigurator implements IContainerConfigurator { $container->autowire(\MailPoet\Automation\Engine\Builder\UpdateAutomationController::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\ActionScheduler::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\RootStep::class)->setPublic(true); + $container->autowire(\MailPoet\Automation\Engine\Control\FilterHandler::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\StepHandler::class)->setPublic(true); $container->autowire(\MailPoet\Automation\Engine\Control\SubjectTransformerHandler::class)->setPublic(true)->setShared(false); $container->autowire(\MailPoet\Automation\Engine\Control\SubjectLoader::class)->setPublic(true); diff --git a/mailpoet/tests/unit/Automation/Engine/Control/FilterHandlerTest.php b/mailpoet/tests/unit/Automation/Engine/Control/FilterHandlerTest.php new file mode 100644 index 0000000000..3f63a031aa --- /dev/null +++ b/mailpoet/tests/unit/Automation/Engine/Control/FilterHandlerTest.php @@ -0,0 +1,162 @@ +createSubject('subject', [ + new Field('test:field-string', Field::TYPE_STRING, 'Test field string', function () { + return 'abc'; + }), + new Field('test:field-integer', Field::TYPE_INTEGER, 'Test field integer', function () { + return 123; + }), + new Field('test:field-boolean', Field::TYPE_BOOLEAN, 'Test field boolean', function () { + return true; + }), + ]); + + $stepRunArgs = new StepRunArgs( + $this->createMock(Automation::class), + $this->createMock(AutomationRun::class), + $step, + [new SubjectEntry($subject, new SubjectData($subject->getKey(), []))] + ); + + $registry = Stub::make(Registry::class, [ + 'filters' => [ + Field::TYPE_STRING => $this->createFilter(Field::TYPE_STRING), + Field::TYPE_INTEGER => $this->createFilter(Field::TYPE_INTEGER), + Field::TYPE_BOOLEAN => $this->createFilter(Field::TYPE_BOOLEAN), + ], + ]); + + $handler = new FilterHandler($registry); + $result = $handler->matchesFilters($stepRunArgs); + $this->assertSame($expectation, $result); + } + + public function dataForTestItFilters(): array { + return [ + // no filters + [ + [], + true, + ], + + // matching + [ + [ + new FilterData(Field::TYPE_STRING, 'test:field-string', '', ['value' => 'abc']), + new FilterData(Field::TYPE_INTEGER, 'test:field-integer', '', ['value' => 123]), + ], + true, + ], + + // not matching + [ + [ + new FilterData(Field::TYPE_INTEGER, 'test:field-integer', '', ['value' => 999]), + ], + false, + ], + [ + [ + new FilterData(Field::TYPE_STRING, 'test:field-string', '', ['value' => 'abc']), + new FilterData(Field::TYPE_INTEGER, 'test:field-integer', '', ['value' => 999]), + new FilterData(Field::TYPE_BOOLEAN, 'test:field-boolean', '', ['value' => true]), + ], + false, + ], + ]; + } + + /** @return Subject */ + private function createSubject(string $key, array $fields): Subject { + return new class($key, $fields) implements Subject { + /** @var string */ + private $key; + + /** @var array */ + private $fields; + + public function __construct( + string $key, + array $fields + ) { + $this->key = $key; + $this->fields = $fields; + } + + public function getKey(): string { + return 'test:' . $this->key; + } + + public function getName(): string { + return 'Test subject ' . $this->key; + } + + public function getArgsSchema(): ObjectSchema { + return Builder::object(); + } + + public function getFields(): array { + return $this->fields; + } + + public function getPayload(SubjectData $subjectData): Payload { + return new class implements Payload { + }; + } + }; + } + + private function createFilter(string $fieldType): Filter { + return new class($fieldType) implements Filter { + /** @var string */ + private $fieldType; + + public function __construct( + string $fieldType + ) { + $this->fieldType = $fieldType; + } + + public function getFieldType(): string { + return $this->fieldType; + } + + public function getConditions(): array { + return []; + } + + public function getArgsSchema(): ObjectSchema { + return Builder::object(); + } + + public function matches(FilterData $data, $value): bool { + return $data->getArgs()['value'] === $value; + } + }; + } +}