This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,259 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use DateTimeImmutable;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Utils\Json;
class Automation {
public const STATUS_ACTIVE = 'active';
public const STATUS_DEACTIVATING = 'deactivating';
public const STATUS_DRAFT = 'draft';
public const STATUS_TRASH = 'trash';
public const STATUS_ALL = [
self::STATUS_ACTIVE,
self::STATUS_DEACTIVATING,
self::STATUS_DRAFT,
self::STATUS_TRASH,
];
/** @var int|null */
private $id;
/** @var int|null */
private $versionId;
/** @var string */
private $name;
/** @var \WP_User */
private $author;
/** @var string */
private $status = self::STATUS_DRAFT;
/** @var DateTimeImmutable */
private $createdAt;
/** @var DateTimeImmutable */
private $updatedAt;
/** @var ?DateTimeImmutable */
private $activatedAt = null;
/** @var array<string|int, Step> */
private $steps;
/** @var array<string, mixed> */
private $meta = [];
/** @param array<string, Step> $steps */
public function __construct(
string $name,
array $steps,
\WP_User $author,
int $id = null,
int $versionId = null
) {
$this->name = $name;
$this->steps = $steps;
$this->author = $author;
$this->id = $id;
$this->versionId = $versionId;
$now = new DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
}
public function getId(): int {
if ($this->id === null) {
throw InvalidStateException::create()->withMessage('No automation ID was set');
}
return $this->id;
}
public function setId(int $id): void {
$this->id = $id;
}
public function getVersionId(): int {
if (!$this->versionId) {
throw InvalidStateException::create()->withMessage('No automation version ID was set');
}
return $this->versionId;
}
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
$this->setUpdatedAt();
}
public function getStatus(): string {
return $this->status;
}
public function setStatus(string $status): void {
if ($status === self::STATUS_ACTIVE && $this->status !== self::STATUS_ACTIVE) {
$this->activatedAt = new DateTimeImmutable();
}
$this->status = $status;
$this->setUpdatedAt();
}
public function getCreatedAt(): DateTimeImmutable {
return $this->createdAt;
}
public function setCreatedAt(DateTimeImmutable $createdAt): void {
$this->createdAt = $createdAt;
}
public function getAuthor(): \WP_User {
return $this->author;
}
public function getUpdatedAt(): DateTimeImmutable {
return $this->updatedAt;
}
public function getActivatedAt(): ?DateTimeImmutable {
return $this->activatedAt;
}
/** @return array<string|int, Step> */
public function getSteps(): array {
return $this->steps;
}
/**
* @return array<string|int, Step>
*/
public function getTriggers(): array {
return array_filter(
$this->steps,
function (Step $step) {
return $step->getType() === Step::TYPE_TRIGGER;
}
);
}
/** @param array<string|int, Step> $steps */
public function setSteps(array $steps): void {
$this->steps = $steps;
$this->setUpdatedAt();
}
public function getStep(string $id): ?Step {
return $this->steps[$id] ?? null;
}
public function getTrigger(string $key): ?Step {
foreach ($this->steps as $step) {
if ($step->getType() === Step::TYPE_TRIGGER && $step->getKey() === $key) {
return $step;
}
}
return null;
}
public function equals(Automation $compare): bool {
$compareArray = $compare->toArray();
$currentArray = $this->toArray();
$ignoreValues = [
'created_at',
'updated_at',
];
foreach ($ignoreValues as $ignore) {
unset($compareArray[$ignore]);
unset($currentArray[$ignore]);
}
return $compareArray === $currentArray;
}
public function needsFullValidation(): bool {
return in_array($this->status, [Automation::STATUS_ACTIVE, Automation::STATUS_DEACTIVATING], true);
}
public function toArray(): array {
return [
'id' => $this->id,
'name' => $this->name,
'status' => $this->status,
'author' => $this->author->ID,
'created_at' => $this->createdAt->format(DateTimeImmutable::W3C),
'updated_at' => $this->updatedAt->format(DateTimeImmutable::W3C),
'activated_at' => $this->activatedAt ? $this->activatedAt->format(DateTimeImmutable::W3C) : null,
'steps' => Json::encode(
array_map(function (Step $step) {
return $step->toArray();
}, $this->steps)
),
'meta' => Json::encode($this->meta),
];
}
private function setUpdatedAt(): void {
$this->updatedAt = new DateTimeImmutable();
}
/**
* @param string $key
* @return mixed|null
*/
public function getMeta(string $key) {
return $this->meta[$key] ?? null;
}
public function getAllMetas(): array {
return $this->meta;
}
/**
* @param string $key
* @param mixed $value
* @return void
*/
public function setMeta(string $key, $value): void {
$this->meta[$key] = $value;
$this->setUpdatedAt();
}
public function deleteMeta(string $key): void {
unset($this->meta[$key]);
$this->setUpdatedAt();
}
public function deleteAllMetas(): void {
$this->meta = [];
$this->setUpdatedAt();
}
public static function fromArray(array $data): self {
// TODO: validation
$automation = new self(
$data['name'],
array_map(function (array $stepData): Step {
return Step::fromArray($stepData);
}, Json::decode($data['steps'])),
new \WP_User((int)$data['author'])
);
$automation->id = (int)$data['id'];
$automation->versionId = (int)$data['version_id'];
$automation->status = $data['status'];
$automation->createdAt = new DateTimeImmutable($data['created_at']);
$automation->updatedAt = new DateTimeImmutable($data['updated_at']);
$automation->activatedAt = $data['activated_at'] !== null ? new DateTimeImmutable($data['activated_at']) : null;
$automation->meta = $data['meta'] ? Json::decode($data['meta']) : [];
return $automation;
}
}
@@ -0,0 +1,137 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use DateTimeImmutable;
class AutomationRun {
public const STATUS_RUNNING = 'running';
public const STATUS_COMPLETE = 'complete';
public const STATUS_CANCELLED = 'cancelled';
public const STATUS_FAILED = 'failed';
/** @var int */
private $id;
/** @var int */
private $automationId;
/** @var int */
private $versionId;
/** @var string */
private $triggerKey;
/** @var string */
private $status = self::STATUS_RUNNING;
/** @var DateTimeImmutable */
private $createdAt;
/** @var DateTimeImmutable */
private $updatedAt;
/** @var Subject[] */
private $subjects;
/**
* @param Subject[] $subjects
*/
public function __construct(
int $automationId,
int $versionId,
string $triggerKey,
array $subjects,
int $id = null
) {
$this->automationId = $automationId;
$this->versionId = $versionId;
$this->triggerKey = $triggerKey;
$this->subjects = $subjects;
if ($id) {
$this->id = $id;
}
$now = new DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
}
public function getId(): int {
return $this->id;
}
public function setId(int $id): void {
$this->id = $id;
}
public function getAutomationId(): int {
return $this->automationId;
}
public function getVersionId(): int {
return $this->versionId;
}
public function getTriggerKey(): string {
return $this->triggerKey;
}
public function getStatus(): string {
return $this->status;
}
public function getCreatedAt(): DateTimeImmutable {
return $this->createdAt;
}
public function getUpdatedAt(): DateTimeImmutable {
return $this->updatedAt;
}
/** @return Subject[] */
public function getSubjects(string $key = null): array {
if ($key) {
return array_values(
array_filter($this->subjects, function (Subject $subject) use ($key) {
return $subject->getKey() === $key;
})
);
}
return $this->subjects;
}
public function toArray(): array {
return [
'automation_id' => $this->automationId,
'version_id' => $this->versionId,
'trigger_key' => $this->triggerKey,
'status' => $this->status,
'created_at' => $this->createdAt->format(DateTimeImmutable::W3C),
'updated_at' => $this->updatedAt->format(DateTimeImmutable::W3C),
'subjects' => array_map(function (Subject $subject): array {
return $subject->toArray();
}, $this->subjects),
];
}
public static function fromArray(array $data): self {
$automationRun = new AutomationRun(
(int)$data['automation_id'],
(int)$data['version_id'],
$data['trigger_key'],
array_map(function (array $subject) {
return Subject::fromArray($subject);
}, $data['subjects'])
);
$automationRun->id = (int)$data['id'];
$automationRun->status = $data['status'];
$automationRun->createdAt = new DateTimeImmutable($data['created_at']);
$automationRun->updatedAt = new DateTimeImmutable($data['updated_at']);
return $automationRun;
}
}
@@ -0,0 +1,216 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use DateTimeImmutable;
use InvalidArgumentException;
use MailPoet\Automation\Engine\Utils\Json;
use Throwable;
class AutomationRunLog {
public const STATUS_RUNNING = 'running';
public const STATUS_COMPLETE = 'complete';
public const STATUS_FAILED = 'failed';
public const STATUS_ALL = [
self::STATUS_RUNNING,
self::STATUS_COMPLETE,
self::STATUS_FAILED,
];
public const TYPE_ACTION = 'action';
public const TYPE_TRIGGER = 'trigger';
public const KEY_UNKNOWN = 'unknown';
/** @var int */
private $id;
/** @var int */
private $automationRunId;
/** @var string */
private $stepId;
/** @var string */
private $stepType;
/** @var string */
private $stepKey;
/** @var string */
private $status;
/** @var DateTimeImmutable */
private $startedAt;
/** @var DateTimeImmutable */
private $updatedAt;
/** @var int */
private $runNumber = 1;
/** @var array */
private $data = [];
/** @var array|null */
private $error;
public function __construct(
int $automationRunId,
string $stepId,
string $stepType,
int $id = null
) {
$this->automationRunId = $automationRunId;
$this->stepId = $stepId;
$this->stepType = $stepType;
$this->stepKey = self::KEY_UNKNOWN;
$this->status = self::STATUS_RUNNING;
$now = new DateTimeImmutable();
$this->startedAt = $now;
$this->updatedAt = $now;
if ($id) {
$this->id = $id;
}
}
public function getId(): ?int {
return $this->id;
}
public function getAutomationRunId(): int {
return $this->automationRunId;
}
public function getStepId(): string {
return $this->stepId;
}
public function getStepType(): string {
return $this->stepType;
}
public function getStepKey(): string {
return $this->stepKey;
}
public function setStepKey(string $stepKey): void {
$this->stepKey = $stepKey;
$this->updatedAt = new DateTimeImmutable();
}
public function getStatus(): string {
return $this->status;
}
public function setStatus(string $status): void {
if (!in_array($status, self::STATUS_ALL, true)) {
throw new InvalidArgumentException("Invalid status '$status'.");
}
$this->status = $status;
$this->updatedAt = new DateTimeImmutable();
}
public function getStartedAt(): DateTimeImmutable {
return $this->startedAt;
}
public function getUpdatedAt(): DateTimeImmutable {
return $this->updatedAt;
}
public function getRunNumber(): int {
return $this->runNumber;
}
public function setRunNumber(int $runNumber): void {
$this->runNumber = $runNumber;
}
public function setUpdatedAt(DateTimeImmutable $updatedAt): void {
$this->updatedAt = $updatedAt;
}
public function getData(): array {
return $this->data;
}
/** @param mixed $value */
public function setData(string $key, $value): void {
if (!$this->isDataStorable($value)) {
throw new InvalidArgumentException("Invalid data provided for key '$key'. Only scalar values and arrays of scalar values are allowed.");
}
$this->data[$key] = $value;
$this->updatedAt = new DateTimeImmutable();
}
public function getError(): ?array {
return $this->error;
}
public function toArray(): array {
return [
'id' => $this->id,
'automation_run_id' => $this->automationRunId,
'step_id' => $this->stepId,
'step_type' => $this->stepType,
'step_key' => $this->stepKey,
'status' => $this->status,
'started_at' => $this->startedAt->format(DateTimeImmutable::W3C),
'updated_at' => $this->updatedAt->format(DateTimeImmutable::W3C),
'run_number' => $this->runNumber,
'data' => Json::encode($this->data),
'error' => $this->error ? Json::encode($this->error) : null,
];
}
public function setError(Throwable $error): void {
// Normalize all nested objects in error trace to associative arrays.
// Empty objects would then get decoded to "[]" instead of "{}".
$trace = Json::decode(Json::encode($error->getTrace()));
$this->error = [
'message' => $error->getMessage(),
'errorClass' => get_class($error),
'code' => $error->getCode(),
'trace' => $trace,
];
$this->updatedAt = new DateTimeImmutable();
}
public static function fromArray(array $data): self {
$log = new AutomationRunLog((int)$data['automation_run_id'], $data['step_id'], $data['step_type']);
$log->id = (int)$data['id'];
$log->stepKey = $data['step_key'];
$log->status = $data['status'];
$log->startedAt = new DateTimeImmutable($data['started_at']);
$log->updatedAt = new DateTimeImmutable($data['updated_at']);
$log->runNumber = (int)$data['run_number'];
$log->data = Json::decode($data['data']);
$log->error = isset($data['error']) ? Json::decode($data['error']) : null;
return $log;
}
/** @param mixed $data */
private function isDataStorable($data): bool {
if (is_scalar($data)) {
return true;
}
if (!is_array($data)) {
return false;
}
foreach ($data as $value) {
if (!$this->isDataStorable($value)) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,57 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class AutomationStatistics {
private $automationId;
private $versionId;
private $entered;
private $inProgress;
public function __construct(
int $automationId,
int $entered = 0,
int $inProcess = 0,
?int $versionId = null
) {
$this->automationId = $automationId;
$this->entered = $entered;
$this->inProgress = $inProcess;
$this->versionId = $versionId;
}
public function getAutomationId(): int {
return $this->automationId;
}
public function getVersionId(): ?int {
return $this->versionId;
}
public function getEntered(): int {
return $this->entered;
}
public function getInProgress(): int {
return $this->inProgress;
}
public function getExited(): int {
return $this->getEntered() - $this->getInProgress();
}
public function toArray(): array {
return [
'automation_id' => $this->getAutomationId(),
'totals' => [
'entered' => $this->getEntered(),
'in_progress' => $this->getInProgress(),
'exited' => $this->getExited(),
],
];
}
}
@@ -0,0 +1,95 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class AutomationTemplate {
public const TYPE_DEFAULT = 'default';
public const TYPE_PREMIUM = 'premium';
public const TYPE_COMING_SOON = 'coming-soon';
/** @var string */
private $slug;
/** @var string */
private $category;
/** @var string */
private $name;
/** @var string */
private $description;
/** @var callable(): Automation */
private $automationFactory;
/** @var array<string, int|bool> */
private $requiredCapabilities;
/** @var string */
private $type;
/**
* @param callable(): Automation $automationFactory
* @param array<string, int|bool> $requiredCapabilities
*/
public function __construct(
string $slug,
string $category,
string $name,
string $description,
callable $automationFactory,
array $requiredCapabilities = [],
string $type = self::TYPE_DEFAULT
) {
$this->slug = $slug;
$this->category = $category;
$this->name = $name;
$this->description = $description;
$this->automationFactory = $automationFactory;
$this->requiredCapabilities = $requiredCapabilities;
$this->type = $type;
}
public function getSlug(): string {
return $this->slug;
}
public function getName(): string {
return $this->name;
}
public function getCategory(): string {
return $this->category;
}
public function getType(): string {
return $this->type;
}
public function getDescription(): string {
return $this->description;
}
/** @return array<string, int|bool> */
public function getRequiredCapabilities(): array {
return $this->requiredCapabilities;
}
public function createAutomation(): Automation {
return ($this->automationFactory)();
}
public function toArray(): array {
return [
'slug' => $this->getSlug(),
'name' => $this->getName(),
'category' => $this->getCategory(),
'type' => $this->getType(),
'required_capabilities' => $this->getRequiredCapabilities(),
'description' => $this->getDescription(),
];
}
}
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class AutomationTemplateCategory {
/** @var string */
private $slug;
/** @var string */
private $name;
public function __construct(
string $slug,
string $name
) {
$this->slug = $slug;
$this->name = $name;
}
public function getSlug(): string {
return $this->slug;
}
public function getName(): string {
return $this->name;
}
}
@@ -0,0 +1,73 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Integration\Payload;
class Field {
public const TYPE_BOOLEAN = 'boolean';
public const TYPE_INTEGER = 'integer';
public const TYPE_NUMBER = 'number';
public const TYPE_STRING = 'string';
public const TYPE_ENUM = 'enum';
public const TYPE_ENUM_ARRAY = 'enum_array';
public const TYPE_DATETIME = 'datetime';
/** @var string */
private $key;
/** @var string */
private $type;
/** @var string */
private $name;
/** @var callable */
private $factory;
/** @var array */
private $args;
public function __construct(
string $key,
string $type,
string $name,
callable $factory,
array $args = []
) {
$this->key = $key;
$this->type = $type;
$this->name = $name;
$this->factory = $factory;
$this->args = $args;
}
public function getKey(): string {
return $this->key;
}
public function getType(): string {
return $this->type;
}
public function getName(): string {
return $this->name;
}
public function getFactory(): callable {
return $this->factory;
}
/** @return mixed */
public function getValue(Payload $payload, array $params = []) {
return $this->getFactory()($payload, $params);
}
public function getArgs(): array {
return $this->args;
}
}
@@ -0,0 +1,77 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class Filter {
/** @var string */
private $id;
/** @var string */
private $fieldType;
/** @var string */
private $fieldKey;
/** @var string */
private $condition;
/** @var array */
private $args;
public function __construct(
string $id,
string $fieldType,
string $fieldKey,
string $condition,
array $args
) {
$this->id = $id;
$this->fieldType = $fieldType;
$this->fieldKey = $fieldKey;
$this->condition = $condition;
$this->args = $args;
}
public function getId(): string {
return $this->id;
}
public function getFieldType(): string {
return $this->fieldType;
}
public function getFieldKey(): string {
return $this->fieldKey;
}
public function getCondition(): string {
return $this->condition;
}
public function getArgs(): array {
return $this->args;
}
public function toArray(): array {
return [
'id' => $this->id,
'field_type' => $this->fieldType,
'field_key' => $this->fieldKey,
'condition' => $this->condition,
'args' => $this->args,
];
}
public static function fromArray(array $data): self {
return new self(
$data['id'],
$data['field_type'],
$data['field_key'],
$data['condition'],
$data['args']
);
}
}
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class FilterGroup {
public const OPERATOR_AND = 'and';
public const OPERATOR_OR = 'or';
/** @var string */
private $id;
/** @var string */
private $operator;
/** @var Filter[] */
private $filters;
public function __construct(
string $id,
string $operator,
array $filters
) {
$this->id = $id;
$this->operator = $operator;
$this->filters = $filters;
}
public function getId(): string {
return $this->id;
}
public function getOperator(): string {
return $this->operator;
}
public function getFilters(): array {
return $this->filters;
}
public function toArray(): array {
return [
'id' => $this->id,
'operator' => $this->operator,
'filters' => array_map(function (Filter $filter): array {
return $filter->toArray();
}, $this->filters),
];
}
public static function fromArray(array $data): self {
return new self(
$data['id'],
$data['operator'],
array_map(function (array $filter) {
return Filter::fromArray($filter);
}, $data['filters'])
);
}
}
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class Filters {
public const OPERATOR_AND = 'and';
public const OPERATOR_OR = 'or';
/** @var string */
private $operator;
/** @var FilterGroup[] */
private $groups;
public function __construct(
string $operator,
array $groups
) {
$this->operator = $operator;
$this->groups = $groups;
}
public function getOperator(): string {
return $this->operator;
}
public function getGroups(): array {
return $this->groups;
}
public function toArray(): array {
return [
'operator' => $this->operator,
'groups' => array_map(function (FilterGroup $group): array {
return $group->toArray();
}, $this->groups),
];
}
public static function fromArray(array $data): self {
return new self(
$data['operator'],
array_map(function (array $group) {
return FilterGroup::fromArray($group);
}, $data['groups'])
);
}
}
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class NextStep {
/** @var string|null */
protected $id;
public function __construct(
?string $id
) {
$this->id = $id;
}
public function getId(): ?string {
return $this->id;
}
public function toArray(): array {
return [
'id' => $this->id,
];
}
public static function fromArray(array $data): self {
return new self($data['id']);
}
}
@@ -0,0 +1,117 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
class Step {
public const TYPE_ROOT = 'root';
public const TYPE_TRIGGER = 'trigger';
public const TYPE_ACTION = 'action';
/** @var string */
private $id;
/** @var string */
private $type;
/** @var string */
private $key;
/** @var array */
protected $args;
/** @var NextStep[] */
protected $nextSteps;
/** @var Filters|null */
private $filters;
/**
* @param array<string, mixed> $args
* @param NextStep[] $nextSteps
*/
public function __construct(
string $id,
string $type,
string $key,
array $args,
array $nextSteps,
Filters $filters = null
) {
$this->id = $id;
$this->type = $type;
$this->key = $key;
$this->args = $args;
$this->nextSteps = $nextSteps;
$this->filters = $filters;
}
public function getId(): string {
return $this->id;
}
public function getType(): string {
return $this->type;
}
public function getKey(): string {
return $this->key;
}
/** @return NextStep[] */
public function getNextSteps(): array {
return $this->nextSteps;
}
public function getNextStepIds(): array {
$ids = [];
foreach ($this->nextSteps as $nextStep) {
$nextStepId = $nextStep->getId();
if ($nextStepId) {
$ids[] = $nextStep->getId();
}
}
return $ids;
}
/** @param NextStep[] $nextSteps */
public function setNextSteps(array $nextSteps): void {
$this->nextSteps = $nextSteps;
}
public function getArgs(): array {
return $this->args;
}
public function getFilters(): ?Filters {
return $this->filters;
}
public function toArray(): array {
return [
'id' => $this->id,
'type' => $this->type,
'key' => $this->key,
'args' => $this->args,
'next_steps' => array_map(function (NextStep $nextStep) {
return $nextStep->toArray();
}, $this->nextSteps),
'filters' => $this->filters ? $this->filters->toArray() : null,
];
}
public static function fromArray(array $data): self {
return new self(
$data['id'],
$data['type'],
$data['key'],
$data['args'],
array_map(function (array $nextStep) {
return NextStep::fromArray($nextStep);
}, $data['next_steps']),
isset($data['filters']) ? Filters::fromArray($data['filters']) : null
);
}
}
@@ -0,0 +1,166 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject;
use Throwable;
class StepRunArgs {
/** @var Automation */
private $automation;
/** @var AutomationRun */
private $automationRun;
/** @var Step */
private $step;
/** @var array<string, SubjectEntry<Subject<Payload>>[]> */
private $subjectEntries = [];
/** @var array<class-string, string> */
private $subjectKeyClassMap = [];
/** @var array<string, Field> */
private $fields = [];
/** @var array<string, string> */
private $fieldToSubjectMap = [];
/** @var int */
private $runNumber;
/** @param SubjectEntry<Subject<Payload>>[] $subjectsEntries */
public function __construct(
Automation $automation,
AutomationRun $automationRun,
Step $step,
array $subjectsEntries,
int $runNumber
) {
$this->automation = $automation;
$this->step = $step;
$this->automationRun = $automationRun;
$this->runNumber = $runNumber;
foreach ($subjectsEntries as $entry) {
$subject = $entry->getSubject();
$key = $subject->getKey();
$this->subjectEntries[$key] = array_merge($this->subjectEntries[$key] ?? [], [$entry]);
$this->subjectKeyClassMap[get_class($subject)] = $key;
foreach ($subject->getFields() as $field) {
$this->fields[$field->getKey()] = $field;
$this->fieldToSubjectMap[$field->getKey()] = $key;
}
}
}
public function getAutomation(): Automation {
return $this->automation;
}
public function getAutomationRun(): AutomationRun {
return $this->automationRun;
}
public function getStep(): Step {
return $this->step;
}
/** @return array<string, SubjectEntry<Subject<Payload>>[]> */
public function getSubjectEntries(): array {
return $this->subjectEntries;
}
/** @return SubjectEntry<Subject<Payload>> */
public function getSingleSubjectEntry(string $key): SubjectEntry {
$subjects = $this->subjectEntries[$key] ?? [];
if (count($subjects) === 0) {
throw Exceptions::subjectDataNotFound($key, $this->automationRun->getId());
}
if (count($subjects) > 1) {
throw Exceptions::multipleSubjectsFound($key, $this->automationRun->getId());
}
return $subjects[0];
}
/**
* @template P of Payload
* @template S of Subject<P>
* @param class-string<S> $class
* @return SubjectEntry<S<P>>
*/
public function getSingleSubjectEntryByClass(string $class): SubjectEntry {
$key = $this->subjectKeyClassMap[$class] ?? null;
if (!$key) {
throw Exceptions::subjectClassNotFound($class);
}
/** @var SubjectEntry<S<P>> $entry -- for PHPStan */
$entry = $this->getSingleSubjectEntry($key);
return $entry;
}
/**
* @template P of Payload
* @param class-string<P> $class
* @return P
*/
public function getSinglePayloadByClass(string $class): Payload {
$payloads = [];
foreach ($this->subjectEntries as $entries) {
foreach ($entries as $entry) {
$payload = $entry->getPayload();
if (get_class($payload) === $class) {
$payloads[] = $payload;
}
}
}
if (count($payloads) === 0) {
throw Exceptions::payloadNotFound($class, $this->automationRun->getId());
}
if (count($payloads) > 1) {
throw Exceptions::multiplePayloadsFound($class, $this->automationRun->getId());
}
// ensure PHPStan we're indeed returning an instance of $class
$payload = $payloads[0];
if (!$payload instanceof $class) {
throw InvalidStateException::create();
}
return $payload;
}
/** @return mixed */
public function getFieldValue(string $key, array $params = []) {
$field = $this->fields[$key] ?? null;
$subjectKey = $this->fieldToSubjectMap[$key] ?? null;
if (!$field || !$subjectKey) {
throw Exceptions::fieldNotFound($key);
}
$entry = $this->getSingleSubjectEntry($subjectKey);
try {
$value = $field->getValue($entry->getPayload(), $params);
} catch (Throwable $e) {
throw Exceptions::fieldLoadFailed($field->getKey(), $field->getArgs());
}
return $value;
}
public function getRunNumber(): int {
return $this->runNumber;
}
public function isFirstRun(): bool {
return $this->runNumber === 1;
}
}
@@ -0,0 +1,79 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject;
class StepValidationArgs {
/** @var Automation */
private $automation;
/** @var Step */
private $step;
/** @var array<string, Subject<Payload>> */
private $subjects = [];
/** @var array<class-string, string> */
private $subjectKeyClassMap = [];
/** @param Subject<Payload>[] $subjects */
public function __construct(
Automation $automation,
Step $step,
array $subjects
) {
$this->automation = $automation;
$this->step = $step;
foreach ($subjects as $subject) {
$key = $subject->getKey();
$this->subjects[$key] = $subject;
$this->subjectKeyClassMap[get_class($subject)] = $key;
}
}
public function getAutomation(): Automation {
return $this->automation;
}
public function getStep(): Step {
return $this->step;
}
/** @return Subject<Payload>[] */
public function getSubjects(): array {
return array_values($this->subjects);
}
/** @return Subject<Payload> */
public function getSingleSubject(string $key): Subject {
$subject = $this->subjects[$key] ?? null;
if (!$subject) {
throw Exceptions::subjectNotFound($key);
}
return $subject;
}
/**
* @template P of Payload
* @template S of Subject<P>
* @param class-string<S> $class
* @return S<P>
*/
public function getSingleSubjectByClass(string $class): Subject {
$key = $this->subjectKeyClassMap[$class] ?? null;
if (!$key) {
throw Exceptions::subjectClassNotFound($class);
}
/** @var S<P> $subject -- for PHPStan */
$subject = $this->getSingleSubject($key);
return $subject;
}
}
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Utils\Json;
class Subject {
/** @var string */
private $key;
/** @var array */
private $args;
public function __construct(
string $key,
array $args
) {
$this->key = $key;
$this->args = $args;
}
public function getKey(): string {
return $this->key;
}
public function getArgs(): array {
return $this->args;
}
public function getHash(): string {
return md5($this->getKey() . serialize($this->getArgs()));
}
public function toArray(): array {
return [
'key' => $this->getKey(),
'args' => Json::encode($this->getArgs()),
'hash' => $this->getHash(),
];
}
public static function fromArray(array $data): self {
return new self($data['key'], Json::decode($data['args']));
}
}
@@ -0,0 +1,56 @@
<?php declare(strict_types = 1);
namespace MailPoet\Automation\Engine\Data;
if (!defined('ABSPATH')) exit;
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
use MailPoet\Automation\Engine\Exceptions;
use MailPoet\Automation\Engine\Integration\Payload;
use MailPoet\Automation\Engine\Integration\Subject;
use Throwable;
/**
* @template-covariant S of Subject<Payload>
*/
class SubjectEntry {
/** @var S */
private $subject;
/** @var SubjectData */
private $subjectData;
/** @var Payload|null */
private $payloadCache;
/** @param S $subject */
public function __construct(
Subject $subject,
SubjectData $subjectData
) {
$this->subject = $subject;
$this->subjectData = $subjectData;
}
/** @return S */
public function getSubject(): Subject {
return $this->subject;
}
public function getSubjectData(): SubjectData {
return $this->subjectData;
}
/** @return Payload */
public function getPayload() {
if ($this->payloadCache === null) {
try {
$this->payloadCache = $this->subject->getPayload($this->subjectData);
} catch (Throwable $e) {
throw Exceptions::subjectLoadFailed($this->subject->getKey(), $this->subjectData->getArgs());
}
}
return $this->payloadCache;
}
}
@@ -0,0 +1 @@
<?php