Implement filter handler to apply filters on field values during automation run
[MAILPOET-4946]
This commit is contained in:
39
mailpoet/lib/Automation/Engine/Control/FilterHandler.php
Normal file
39
mailpoet/lib/Automation/Engine/Control/FilterHandler.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Automation\Engine\Control;
|
||||||
|
|
||||||
|
use MailPoet\Automation\Engine\Data\Filter as FilterData;
|
||||||
|
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||||
|
use MailPoet\Automation\Engine\Exceptions;
|
||||||
|
use MailPoet\Automation\Engine\Registry;
|
||||||
|
|
||||||
|
class FilterHandler {
|
||||||
|
/** @var Registry */
|
||||||
|
private $registry;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Registry $registry
|
||||||
|
) {
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@@ -28,6 +28,9 @@ class TriggerHandler {
|
|||||||
/** @var SubjectTransformerHandler */
|
/** @var SubjectTransformerHandler */
|
||||||
private $subjectTransformerHandler;
|
private $subjectTransformerHandler;
|
||||||
|
|
||||||
|
/** @var FilterHandler */
|
||||||
|
private $filterHandler;
|
||||||
|
|
||||||
/** @var WordPress */
|
/** @var WordPress */
|
||||||
private $wordPress;
|
private $wordPress;
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ class TriggerHandler {
|
|||||||
AutomationRunStorage $automationRunStorage,
|
AutomationRunStorage $automationRunStorage,
|
||||||
SubjectLoader $subjectLoader,
|
SubjectLoader $subjectLoader,
|
||||||
SubjectTransformerHandler $subjectTransformerHandler,
|
SubjectTransformerHandler $subjectTransformerHandler,
|
||||||
|
FilterHandler $filterHandler,
|
||||||
WordPress $wordPress
|
WordPress $wordPress
|
||||||
) {
|
) {
|
||||||
$this->actionScheduler = $actionScheduler;
|
$this->actionScheduler = $actionScheduler;
|
||||||
@@ -44,6 +48,7 @@ class TriggerHandler {
|
|||||||
$this->automationRunStorage = $automationRunStorage;
|
$this->automationRunStorage = $automationRunStorage;
|
||||||
$this->subjectLoader = $subjectLoader;
|
$this->subjectLoader = $subjectLoader;
|
||||||
$this->subjectTransformerHandler = $subjectTransformerHandler;
|
$this->subjectTransformerHandler = $subjectTransformerHandler;
|
||||||
|
$this->filterHandler = $filterHandler;
|
||||||
$this->wordPress = $wordPress;
|
$this->wordPress = $wordPress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +75,11 @@ class TriggerHandler {
|
|||||||
|
|
||||||
$automationRun = new AutomationRun($automation->getId(), $automation->getVersionId(), $trigger->getKey(), $subjects);
|
$automationRun = new AutomationRun($automation->getId(), $automation->getVersionId(), $trigger->getKey(), $subjects);
|
||||||
$stepRunArgs = new StepRunArgs($automation, $automationRun, $step, $subjectEntries);
|
$stepRunArgs = new StepRunArgs($automation, $automationRun, $step, $subjectEntries);
|
||||||
|
|
||||||
|
if (!$this->filterHandler->matchesFilters($stepRunArgs)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$createAutomationRun = $trigger->isTriggeredBy($stepRunArgs);
|
$createAutomationRun = $trigger->isTriggeredBy($stepRunArgs);
|
||||||
$createAutomationRun = $this->wordPress->applyFilters(
|
$createAutomationRun = $this->wordPress->applyFilters(
|
||||||
Hooks::AUTOMATION_RUN_CREATE,
|
Hooks::AUTOMATION_RUN_CREATE,
|
||||||
|
@@ -27,6 +27,7 @@ class Exceptions {
|
|||||||
private const MULTIPLE_PAYLOADS_FOUND = 'mailpoet_automation_multiple_payloads_found';
|
private const MULTIPLE_PAYLOADS_FOUND = 'mailpoet_automation_multiple_payloads_found';
|
||||||
private const FIELD_NOT_FOUND = 'mailpoet_automation_field_not_found';
|
private const FIELD_NOT_FOUND = 'mailpoet_automation_field_not_found';
|
||||||
private const FIELD_LOAD_FAILED = 'mailpoet_automation_field_load_failed';
|
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_MODIFICATION_NOT_SUPPORTED = 'mailpoet_automation_structure_modification_not_supported';
|
||||||
private const AUTOMATION_STRUCTURE_NOT_VALID = 'mailpoet_automation_structure_not_valid';
|
private const AUTOMATION_STRUCTURE_NOT_VALID = 'mailpoet_automation_structure_not_valid';
|
||||||
private const AUTOMATION_STEP_MODIFIED_WHEN_UNKNOWN = 'mailpoet_automation_step_modified_when_unknown';
|
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)));
|
->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 {
|
public static function automationStructureModificationNotSupported(): UnexpectedValueException {
|
||||||
return UnexpectedValueException::create()
|
return UnexpectedValueException::create()
|
||||||
->withErrorCode(self::AUTOMATION_STRUCTURE_MODIFICATION_NOT_SUPPORTED)
|
->withErrorCode(self::AUTOMATION_STRUCTURE_MODIFICATION_NOT_SUPPORTED)
|
||||||
|
@@ -122,6 +122,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\Automation\Engine\Builder\UpdateAutomationController::class)->setPublic(true);
|
$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\ActionScheduler::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Control\RootStep::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\StepHandler::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Control\SubjectTransformerHandler::class)->setPublic(true)->setShared(false);
|
$container->autowire(\MailPoet\Automation\Engine\Control\SubjectTransformerHandler::class)->setPublic(true)->setShared(false);
|
||||||
$container->autowire(\MailPoet\Automation\Engine\Control\SubjectLoader::class)->setPublic(true);
|
$container->autowire(\MailPoet\Automation\Engine\Control\SubjectLoader::class)->setPublic(true);
|
||||||
|
@@ -0,0 +1,162 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Test\Automation\Engine\Control;
|
||||||
|
|
||||||
|
use Codeception\Stub;
|
||||||
|
use MailPoet\Automation\Engine\Control\FilterHandler;
|
||||||
|
use MailPoet\Automation\Engine\Data\Automation;
|
||||||
|
use MailPoet\Automation\Engine\Data\AutomationRun;
|
||||||
|
use MailPoet\Automation\Engine\Data\Field;
|
||||||
|
use MailPoet\Automation\Engine\Data\Filter as FilterData;
|
||||||
|
use MailPoet\Automation\Engine\Data\Step;
|
||||||
|
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||||
|
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
|
||||||
|
use MailPoet\Automation\Engine\Data\SubjectEntry;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Filter;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Payload;
|
||||||
|
use MailPoet\Automation\Engine\Integration\Subject;
|
||||||
|
use MailPoet\Automation\Engine\Registry;
|
||||||
|
use MailPoet\Validator\Builder;
|
||||||
|
use MailPoet\Validator\Schema\ObjectSchema;
|
||||||
|
use MailPoetUnitTest;
|
||||||
|
|
||||||
|
class FilterHandlerTest extends MailPoetUnitTest {
|
||||||
|
/** @dataProvider dataForTestItFilters */
|
||||||
|
public function testItFilters(array $stepFilters, bool $expectation): void {
|
||||||
|
$step = new Step('step', Step::TYPE_TRIGGER, 'test:step', [], [], $stepFilters);
|
||||||
|
$subject = $this->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<Payload> */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user