PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Automation / Engine / Control / StepHandler.php
mailpoet / lib / Automation / Engine / Control Last commit date
ActionScheduler.php 3 weeks ago AutomationController.php 2 years ago FilterHandler.php 1 month ago RootStep.php 2 years ago StepHandler.php 2 months ago StepRunController.php 1 year ago StepRunControllerFactory.php 1 year ago StepRunLogger.php 2 months ago StepRunLoggerFactory.php 2 years ago StepScheduler.php 1 year ago SubjectLoader.php 3 years ago SubjectTransformerHandler.php 1 year ago TriggerHandler.php 2 years ago index.php 3 years ago
StepHandler.php
228 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Automation\Engine\Control;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use Exception;
9 use MailPoet\Automation\Engine\Data\Automation;
10 use MailPoet\Automation\Engine\Data\AutomationRun;
11 use MailPoet\Automation\Engine\Data\AutomationRunLog;
12 use MailPoet\Automation\Engine\Data\StepRunArgs;
13 use MailPoet\Automation\Engine\Data\StepValidationArgs;
14 use MailPoet\Automation\Engine\Data\SubjectEntry;
15 use MailPoet\Automation\Engine\Exceptions;
16 use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
17 use MailPoet\Automation\Engine\Hooks;
18 use MailPoet\Automation\Engine\Integration\Action;
19 use MailPoet\Automation\Engine\Integration\Payload;
20 use MailPoet\Automation\Engine\Integration\Subject;
21 use MailPoet\Automation\Engine\Registry;
22 use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
23 use MailPoet\Automation\Engine\Storage\AutomationStorage;
24 use MailPoet\Automation\Engine\WordPress;
25 use Throwable;
26
27 class StepHandler {
28 /** @var SubjectLoader */
29 private $subjectLoader;
30
31 /** @var WordPress */
32 private $wordPress;
33
34 /** @var AutomationRunStorage */
35 private $automationRunStorage;
36
37 /** @var AutomationStorage */
38 private $automationStorage;
39
40 /** @var Registry */
41 private $registry;
42
43 /** @var StepRunControllerFactory */
44 private $stepRunControllerFactory;
45
46 /** @var StepRunLoggerFactory */
47 private $stepRunLoggerFactory;
48
49 /** @var StepScheduler */
50 private $stepScheduler;
51
52 public function __construct(
53 SubjectLoader $subjectLoader,
54 WordPress $wordPress,
55 AutomationRunStorage $automationRunStorage,
56 AutomationStorage $automationStorage,
57 Registry $registry,
58 StepRunControllerFactory $stepRunControllerFactory,
59 StepRunLoggerFactory $stepRunLoggerFactory,
60 StepScheduler $stepScheduler
61 ) {
62 $this->subjectLoader = $subjectLoader;
63 $this->wordPress = $wordPress;
64 $this->automationRunStorage = $automationRunStorage;
65 $this->automationStorage = $automationStorage;
66 $this->registry = $registry;
67 $this->stepRunControllerFactory = $stepRunControllerFactory;
68 $this->stepRunLoggerFactory = $stepRunLoggerFactory;
69 $this->stepScheduler = $stepScheduler;
70 }
71
72 public function initialize(): void {
73 $this->wordPress->addAction(Hooks::AUTOMATION_STEP, [$this, 'handle']);
74 }
75
76 /** @param mixed $args */
77 public function handle($args): void {
78 // TODO: better args validation
79 if (
80 !is_array($args)
81 || !isset($args['automation_run_id'])
82 || !is_numeric($args['automation_run_id'])
83 || !array_key_exists('step_id', $args)
84 || !is_scalar($args['step_id'])
85 ) {
86 throw new InvalidStateException();
87 }
88
89 $runId = (int)$args['automation_run_id'];
90 $stepId = (string)$args['step_id'];
91 $runNumber = is_numeric($args['run_number'] ?? null) ? (int)$args['run_number'] : 1;
92
93 // BC — complete automation run if "step_id" is empty (was nullable in the past)
94 if (!$stepId) {
95 $this->automationRunStorage->updateStatus($runId, AutomationRun::STATUS_COMPLETE);
96 return;
97 }
98
99 $logger = $this->stepRunLoggerFactory->createLogger($runId, $stepId, AutomationRunLog::TYPE_ACTION, $runNumber);
100 $logger->logStart();
101 try {
102 $this->handleStep($runId, $stepId, $runNumber, $logger);
103 } catch (Throwable $e) {
104 $status = $e instanceof InvalidStateException && $e->getErrorCode() === 'mailpoet_automation_not_active'
105 ? AutomationRun::STATUS_CANCELLED
106 : AutomationRun::STATUS_FAILED;
107 $this->automationRunStorage->updateStatus($runId, $status);
108 $logger->logFailure($e);
109
110 // Action Scheduler catches only Exception instances, not other errors.
111 // We need to convert them to exceptions to be processed and logged.
112 if (!$e instanceof Exception) {
113 throw new Exception($e->getMessage(), intval($e->getCode()), $e);
114 }
115 throw $e;
116 } finally {
117 $this->postProcessAutomationRun($runId);
118 }
119 }
120
121 private function handleStep(int $runId, string $stepId, int $runNumber, StepRunLogger $logger): void {
122 $automationRun = $this->automationRunStorage->getAutomationRun($runId);
123 if (!$automationRun) {
124 throw Exceptions::automationRunNotFound($runId);
125 }
126
127 if ($automationRun->getStatus() !== AutomationRun::STATUS_RUNNING) {
128 throw Exceptions::automationRunNotRunning($runId, $automationRun->getStatus());
129 }
130
131 $automation = $this->automationStorage->getAutomation($automationRun->getAutomationId(), $automationRun->getVersionId());
132 if (!$automation) {
133 throw Exceptions::automationVersionNotFound($automationRun->getAutomationId(), $automationRun->getVersionId());
134 }
135
136 if (!in_array($automation->getStatus(), [Automation::STATUS_ACTIVE, Automation::STATUS_DEACTIVATING], true)) {
137 throw Exceptions::automationNotActive($automationRun->getAutomationId());
138 }
139
140 $stepData = $automation->getStep($stepId);
141 if (!$stepData) {
142 throw Exceptions::automationStepNotFound($stepId);
143 }
144
145 $logger->logStepData($stepData);
146
147 $step = $this->registry->getStep($stepData->getKey());
148 if (!$step instanceof Action) {
149 throw new InvalidStateException();
150 }
151
152 $requiredSubjects = $step->getSubjectKeys();
153 $subjectEntries = $this->getSubjectEntries($automationRun, $requiredSubjects);
154 $args = new StepRunArgs($automation, $automationRun, $stepData, $subjectEntries, $runNumber);
155 $validationArgs = new StepValidationArgs($automation, $stepData, array_map(function (SubjectEntry $entry) {
156 return $entry->getSubject();
157 }, $subjectEntries));
158
159 $step->validate($validationArgs);
160 $step->run($args, $this->stepRunControllerFactory->createController($args, $logger));
161
162 // check if run is not completed by now (e.g., one of if/else branches is empty)
163 $automationRun = $this->automationRunStorage->getAutomationRun($runId);
164 if ($automationRun && $automationRun->getStatus() !== AutomationRun::STATUS_RUNNING) {
165 $logger->logSuccess();
166 return;
167 }
168
169 // schedule next step if not scheduled by action
170 if (!$this->stepScheduler->hasScheduledStep($args)) {
171 $this->stepScheduler->scheduleNextStep($args);
172 }
173
174 // logging
175 if ($this->stepScheduler->hasScheduledProgress($args)) {
176 $logger->logProgress();
177 } else {
178 $logger->logSuccess();
179 }
180 }
181
182 /** @return SubjectEntry<Subject<Payload>>[] */
183 private function getSubjectEntries(AutomationRun $automationRun, array $requiredSubjectKeys): array {
184 $subjectDataMap = [];
185 foreach ($automationRun->getSubjects() as $data) {
186 $subjectDataMap[$data->getKey()] = array_merge($subjectDataMap[$data->getKey()] ?? [], [$data]);
187 }
188
189 $subjectEntries = [];
190 foreach ($requiredSubjectKeys as $key) {
191 $subjectData = $subjectDataMap[$key] ?? null;
192 if (!$subjectData) {
193 throw Exceptions::subjectDataNotFound($key, $automationRun->getId());
194 }
195 }
196 foreach ($subjectDataMap as $subjectData) {
197 foreach ($subjectData as $data) {
198 $subjectEntries[] = $this->subjectLoader->getSubjectEntry($data);
199 }
200 }
201 return $subjectEntries;
202 }
203
204 private function postProcessAutomationRun(int $automationRunId): void {
205 $automationRun = $this->automationRunStorage->getAutomationRun($automationRunId);
206 if (!$automationRun) {
207 return;
208 }
209 $automation = $this->automationStorage->getAutomation($automationRun->getAutomationId());
210 if (!$automation) {
211 return;
212 }
213 $this->postProcessAutomation($automation);
214 }
215
216 private function postProcessAutomation(Automation $automation): void {
217 if ($automation->getStatus() === Automation::STATUS_DEACTIVATING) {
218 $activeRuns = $this->automationRunStorage->getCountForAutomation($automation, AutomationRun::STATUS_RUNNING);
219
220 // Set a deactivating Automation to draft once all automation runs are finished.
221 if ($activeRuns === 0) {
222 $automation->setStatus(Automation::STATUS_DRAFT);
223 $this->automationStorage->updateAutomation($automation);
224 }
225 }
226 }
227 }
228