Given the trigger provides two subjects but only one subject was given via parameter and no transformer was able to generate the second from the first, this method runs into an infinite loop. This commit prevents this infinite loop. [MAILPOET-4935]
202 lines
5.9 KiB
PHP
202 lines
5.9 KiB
PHP
<?php declare(strict_types = 1);
|
|
|
|
namespace MailPoet\Automation\Engine\Control;
|
|
|
|
use MailPoet\Automation\Engine\Data\Automation;
|
|
use MailPoet\Automation\Engine\Data\Step as StepData;
|
|
use MailPoet\Automation\Engine\Data\Subject;
|
|
use MailPoet\Automation\Engine\Integration\Step;
|
|
use MailPoet\Automation\Engine\Integration\SubjectTransformer;
|
|
use MailPoet\Automation\Engine\Integration\Trigger;
|
|
use MailPoet\Automation\Engine\Registry;
|
|
|
|
class SubjectTransformerHandler {
|
|
|
|
/* @var Registry */
|
|
private $registry;
|
|
|
|
public function __construct(
|
|
Registry $registry
|
|
) {
|
|
$this->registry = $registry;
|
|
}
|
|
|
|
public function subjectKeysForTrigger(Trigger $trigger): array {
|
|
$subjectKeys = $trigger->getSubjectKeys();
|
|
$possibleKeys = [];
|
|
foreach ($subjectKeys as $key) {
|
|
$possibleKeys = array_merge($possibleKeys, $this->getPossibleTransformations($key));
|
|
}
|
|
return array_unique(array_values(array_merge($subjectKeys, $possibleKeys)));
|
|
}
|
|
|
|
/** @return string[] */
|
|
public function subjectKeysForAutomation(Automation $automation): array {
|
|
$configuredTriggers = array_filter(
|
|
$automation->getSteps(),
|
|
function (StepData $step): bool {
|
|
return $step->getType() === StepData::TYPE_TRIGGER;
|
|
}
|
|
);
|
|
|
|
$triggers = array_filter(array_map(
|
|
function(StepData $step): ?Step {
|
|
return $this->registry->getStep($step->getKey());
|
|
},
|
|
$configuredTriggers
|
|
));
|
|
|
|
$triggerKeys = [];
|
|
foreach ($triggers as $trigger) {
|
|
$triggerKeys[$trigger->getKey()] = $trigger->getSubjectKeys();
|
|
}
|
|
if (!$triggerKeys) {
|
|
return [];
|
|
}
|
|
|
|
$triggerKeys = count($triggerKeys) === 1 ? current($triggerKeys) : array_intersect(...array_values($triggerKeys));
|
|
$possibleKeys = [];
|
|
foreach ($triggerKeys as $key) {
|
|
$possibleKeys = array_merge($possibleKeys, $this->getPossibleTransformations($key));
|
|
}
|
|
return array_unique(array_values(array_merge($triggerKeys, $possibleKeys)));
|
|
}
|
|
|
|
private function getPossibleTransformations(string $key, string $stopKey = null): array {
|
|
if (!$stopKey) {
|
|
$stopKey = $key;
|
|
}
|
|
$allTransformer = $this->registry->getSubjectTransformer();
|
|
$possibleTransformer = array_filter(
|
|
$allTransformer,
|
|
function (SubjectTransformer $transformer) use ($key): bool {
|
|
return $transformer->accepts() === $key;
|
|
}
|
|
);
|
|
|
|
$possibleKeys = [];
|
|
foreach ($possibleTransformer as $transformer) {
|
|
$possibleKey = $transformer->returns();
|
|
if ($possibleKey === $stopKey) {
|
|
continue;
|
|
}
|
|
$possibleKeys[$possibleKey] = $possibleKey;
|
|
$possibleKeys = array_merge(
|
|
$possibleKeys,
|
|
$this->getPossibleTransformations($possibleKey, $stopKey)
|
|
);
|
|
}
|
|
return $possibleKeys;
|
|
}
|
|
|
|
/**
|
|
* @param Trigger $trigger
|
|
* @param Subject ...$subjects
|
|
* @return Subject[]
|
|
*/
|
|
public function provideAllSubjects(Trigger $trigger, Subject ...$subjects): array {
|
|
$allSubjectsKeys = $this->subjectKeysForTrigger($trigger);
|
|
$allSubjectKeyTargets = array_filter(
|
|
array_diff($allSubjectsKeys, array_map(
|
|
function(Subject $subject): string {
|
|
return $subject->getKey();
|
|
},
|
|
$subjects
|
|
)),
|
|
function(string $target) use ($subjects): bool {
|
|
return $this->canSubjectsTransformTo($target, ...$subjects);
|
|
}
|
|
);
|
|
|
|
$allSubjects = [];
|
|
foreach ($subjects as $existingSubject) {
|
|
$allSubjects[$existingSubject->getKey()] = $existingSubject;
|
|
}
|
|
foreach ($allSubjectKeyTargets as $target) {
|
|
if (isset($allSubjects[$target])) {
|
|
continue;
|
|
}
|
|
foreach ($subjects as $subject) {
|
|
$transformedSubject = $this->transformSubjectTo($subject, $target);
|
|
if ($transformedSubject) {
|
|
$allSubjects[$transformedSubject->getKey()] = $transformedSubject;
|
|
}
|
|
}
|
|
}
|
|
|
|
$allSubjectsTransformed = true;
|
|
do {
|
|
foreach ($allSubjectKeyTargets as $key) {
|
|
if (!isset($allSubjects[$key])) {
|
|
$allSubjectsTransformed = false;
|
|
break;
|
|
}
|
|
}
|
|
$allSubjects = $allSubjectsTransformed ? $allSubjects : $this->provideAllSubjects($trigger, ...array_values($allSubjects));
|
|
} while (!$allSubjectsTransformed);
|
|
return array_values($allSubjects);
|
|
}
|
|
|
|
private function transformSubjectTo(Subject $subject, string $target): ?Subject {
|
|
$transformerChain = $this->getTransformerChain($subject->getKey(), $target);
|
|
if (!$transformerChain) {
|
|
return null;
|
|
}
|
|
foreach ($transformerChain as $transformer) {
|
|
$subject = $transformer->transform($subject);
|
|
}
|
|
return $subject;
|
|
}
|
|
|
|
/**
|
|
* @return SubjectTransformer[]
|
|
*/
|
|
private function getTransformerChain(string $from, string $to): array {
|
|
$transformers = $this->registry->getSubjectTransformer();
|
|
$transformerChain = [];
|
|
|
|
//walk the graph of transformers to find the shortest path
|
|
$queue = [];
|
|
$queue[] = [$from, []];
|
|
while (count($queue) > 0) {
|
|
$current = array_shift($queue);
|
|
$currentKey = $current[0];
|
|
$currentPath = $current[1];
|
|
if ($currentKey === $to) {
|
|
$transformerChain = $currentPath;
|
|
break;
|
|
}
|
|
foreach ($transformers as $transformer) {
|
|
if ($transformer->accepts() === $currentKey) {
|
|
$newPath = $currentPath;
|
|
$newPath[] = $transformer;
|
|
$queue[] = [$transformer->returns(), $newPath];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $transformerChain;
|
|
}
|
|
|
|
private function canSubjectsTransformTo(string $target, Subject ...$subjects): bool {
|
|
if (
|
|
in_array($target, array_map(
|
|
function(Subject $subject): string {
|
|
return $subject->getKey();
|
|
},
|
|
$subjects
|
|
))
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($subjects as $subject) {
|
|
$possibleTransformations = $this->getPossibleTransformations($subject->getKey());
|
|
if (in_array($target, $possibleTransformations)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|