PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.2
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.2
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Helpers / ScenarioHelper.php

ScenarioHelper.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.2, at back/Helpers/ScenarioHelper.php

371 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Helpers;
4
5 use AcyMailing\Classes\HistoryClass;
6 use AcyMailing\Classes\ScenarioClass;
7 use AcyMailing\Classes\ScenarioHistoryLineClass;
8 use AcyMailing\Classes\ScenarioProcessClass;
9 use AcyMailing\Classes\ScenarioStepClass;
10 use AcyMailing\Classes\ScenarioQueueClass;
11 use AcyMailing\Classes\UserClass;
12 use AcyMailing\Core\AcymObject;
13
14 class ScenarioHelper extends AcymObject
15 {
16 private ScenarioClass $scenarioClass;
17 private ScenarioStepClass $scenarioStepClass;
18 private ScenarioQueueClass $scenarioQueueClass;
19 private ScenarioHistoryLineClass $scenarioHistoryLineClass;
20 private ScenarioProcessClass $scenarioProcessClass;
21
22 public function __construct()
23 {
24 parent::__construct();
25 $this->scenarioClass = new ScenarioClass();
26 $this->scenarioStepClass = new ScenarioStepClass();
27 $this->scenarioQueueClass = new ScenarioQueueClass();
28 $this->scenarioHistoryLineClass = new ScenarioHistoryLineClass();
29 $this->scenarioProcessClass = new ScenarioProcessClass();
30 }
31
32 public function trigger(string $trigger, array $options): void
33 {
34 if (!acym_level(ACYM_ENTERPRISE)) {
35 return;
36 }
37
38 if (empty($trigger)) {
39 acym_logError('ScenarioHelper::trigger - Missing trigger for options: '.json_encode($options));
40
41 return;
42 }
43
44 if (empty($options['userId'])) {
45 acym_logError('ScenarioHelper::trigger - Missing userId, trigger: '.$trigger);
46
47 return;
48 }
49
50 $userId = intval($options['userId']);
51
52 $userClass = new UserClass();
53 if (empty($userClass->getOneById($userId))) {
54 acym_logError('ScenarioHelper::trigger - User not found, userId: '.$userId);
55
56 return;
57 }
58
59 $scenarios = $this->scenarioClass->getAllActiveByTriggers([$trigger]);
60
61 foreach ($scenarios as $scenario) {
62 if ($scenario->trigger_once) {
63 if ($this->scenarioProcessClass->hasUserProcess($scenario->id, $userId)) {
64 continue;
65 }
66 }
67
68 // For the trigger TimeAutomationTriggers, this way we know if we are triggering a scenario or not
69 $scenario->is_scenario = true;
70 $scenario->triggers = [$scenario->trigger => 1];
71 $execute = false;
72 $options = $this->formatOptions($scenario->trigger_params, 'trigger');
73 $options['userId'] = $userId;
74 $options['time'] = time();
75 acym_trigger('onAcymExecuteTrigger', [&$scenario, &$execute, &$options]);
76
77 if (!$execute) {
78 continue;
79 }
80
81 $firstStep = $this->scenarioStepClass->getFirstStepByScenarioId($scenario->id);
82
83 if (empty($firstStep)) {
84 acym_logError('ScenarioHelper::trigger - No first step found for scenario '.$scenario->id);
85 continue;
86 }
87
88 $scenarioProcess = new \stdClass();
89 $scenarioProcess->scenario_id = $scenario->id;
90 $scenarioProcess->user_id = $userId;
91 $scenarioProcess->start_at = acym_date('now', 'Y-m-d H:i:s', false);
92 $scenarioProcess->id = $this->scenarioProcessClass->save($scenarioProcess);
93
94 $this->createHistory($scenarioProcess->id, 'trigger', 'success');
95
96 $this->handleStep($firstStep, $userId, $scenarioProcess->id);
97 }
98 }
99
100 private function handleStep(object $step, int $userId, int $scenarioProcessId): void
101 {
102 switch ($step->type) {
103 case ScenarioClass::TYPE_DELAY:
104 $this->handleDelay($step, $userId, $scenarioProcessId);
105 break;
106 case ScenarioClass::TYPE_CONDITION:
107 $this->handleCondition($step, $userId, $scenarioProcessId);
108 break;
109 case ScenarioClass::TYPE_ACTION:
110 $this->handleAction($step, $userId, $scenarioProcessId);
111 break;
112 }
113 }
114
115 private function handleDelay(object $step, int $userId, int $scenarioProcessId): void
116 {
117 $nextStep = $this->scenarioStepClass->getStepByPreviousStepId($step->id);
118
119 if (empty($nextStep)) {
120 $this->ending($scenarioProcessId);
121
122 return;
123 }
124
125 $scenarioQueue = new \stdClass();
126 $scenarioQueue->scenario_process_id = $scenarioProcessId;
127 $scenarioQueue->step_id = $nextStep->id;
128 $timestampNextAction = time() + $this->calculateDelaySecondsToAdd($step->params);
129 $scenarioQueue->execution_date = acym_date($timestampNextAction, 'Y-m-d H:i:s', false);
130
131 $this->scenarioQueueClass->save($scenarioQueue);
132 $this->scenarioQueueClass->deleteByStepIds([$step->id]);
133
134 $this->createHistory($scenarioProcessId, ScenarioClass::TYPE_DELAY, 'success', $step->id, $step->params);
135 }
136
137 private function calculateDelaySecondsToAdd(array $stepDelayParams): int
138 {
139 if (empty($stepDelayParams['delay']) || empty($stepDelayParams['unit'])) {
140 return 0;
141 }
142
143 return $stepDelayParams['delay'] * $stepDelayParams['unit'];
144 }
145
146 private function handleCondition(object $step, int $userId, int $scenarioProcessId): void
147 {
148 $conditionNotValidCount = 0;
149
150 $query = new AutomationHelper();
151 $query->where = ['user.id = '.$userId];
152
153 $conditionOptions = $this->formatOptions($step->params['option'], $step->type);
154 // Unused variable
155 $number = 0;
156 acym_trigger('onAcymProcessCondition_'.$step->params['condition'], [&$query, &$conditionOptions, &$number, &$conditionNotValidCount]);
157 $this->scenarioQueueClass->deleteByStepIds([$step->id]);
158
159 $conditionValid = $conditionNotValidCount === 0;
160
161 $historyOptions = array_merge($conditionOptions, ['scenario_condition' => $step->params['condition']]);
162 $this->createHistory($scenarioProcessId, ScenarioClass::TYPE_CONDITION, 'Condition is '.($conditionValid ? 'valid' : 'not valid'), $step->id, $historyOptions);
163
164 $nextStep = $this->scenarioStepClass->getStepByPreviousConditionId($step->id, $conditionValid);
165
166 if (empty($nextStep)) {
167 $this->ending($scenarioProcessId);
168
169 return;
170 }
171
172 $this->handleStep($nextStep, $userId, $scenarioProcessId);
173 }
174
175 private function formatOptions(array $options, string $type): array
176 {
177 $formattedOptions = [];
178
179 switch ($type) {
180 case ScenarioClass::TYPE_CONDITION:
181 $regex = '/acym_condition\[conditions\]\[__numor__\]\[__numand__\]\[(.+)\]\[(.+)\]/';
182 break;
183 case ScenarioClass::TYPE_ACTION:
184 $regex = '/acym_action\[actions\]\[__and__\]\[(.+)\]\[(.+)\]/';
185 break;
186 case 'trigger':
187 $regex = '/\[triggers\]\[user\]\[(.+)\]\[(.+)\]/';
188 break;
189 default:
190 $regex = '';
191 }
192
193 if (empty($regex)) {
194 return $formattedOptions;
195 }
196
197 foreach ($options as $key => $option) {
198 $formattedKey = preg_replace($regex, '$2', $key);
199 if ($formattedKey === $key) {
200 continue;
201 }
202 $formattedOptions[$formattedKey] = $option;
203 }
204
205 return $formattedOptions;
206 }
207
208 private function handleAction(object $step, int $userId, int $scenarioProcessId): void
209 {
210 $scenarioClass = new ScenarioClass();
211 $markUnsubscribed = $scenarioClass->checkUserMarkedUnsubscribed($userId, $scenarioProcessId);
212 if ($markUnsubscribed && $step->params['action'] === 'acy_send_email') {
213 $historyClass = new HistoryClass();
214 $historyClass->insert($userId, 'unsubscribed', [acym_translation('ACYM_SCENARIO_USER_UNSUBSCRIBED')]);
215
216 return;
217 }
218
219 $query = new AutomationHelper();
220 $query->where = ['user.id = '.$userId];
221
222 $actionOptions = $this->formatOptions($step->params['option'], $step->type);
223 $log = '';
224 $report = '';
225 try {
226 $report = acym_trigger('onAcymProcessAction_'.$step->params['action'], [&$query, &$actionOptions]);
227 $report = is_array($report) ? implode(', ', $report) : $report;
228 } catch (\Exception $e) {
229 $log = $e->getMessage();
230 }
231
232 $this->scenarioQueueClass->deleteByStepIds([$step->id]);
233
234 $historyOptions = array_merge($actionOptions, ['scenario_action' => $step->params['action']]);
235 $this->createHistory($scenarioProcessId, ScenarioClass::TYPE_CONDITION, 'Action completed: '.$report, $step->id, $historyOptions, $log);
236
237 $nextStep = $this->scenarioStepClass->getStepByPreviousStepId($step->id);
238
239 if (empty($nextStep)) {
240 $this->ending($scenarioProcessId);
241
242 return;
243 }
244
245 $this->handleStep($nextStep, $userId, $scenarioProcessId);
246 }
247
248 private function ending(int $scenarioProcessId): void
249 {
250 $this->createHistory($scenarioProcessId, 'ending', acym_translation('ACYM_SCENARIO_ENDED'));
251
252 $this->scenarioProcessClass->endProcess($scenarioProcessId);
253 }
254
255 public function executeAvailableSteps(): void
256 {
257 $dateNowUtc = acym_date('now', 'Y-m-d H:i:s', false);
258
259 $steps = $this->scenarioStepClass->getAvailableStepsByDate($dateNowUtc);
260
261 if (empty($steps)) {
262 return;
263 }
264
265 foreach ($steps as $step) {
266 $this->handleStep($step, $step->user_id, $step->scenario_process_id);
267 }
268 }
269
270 public function triggerTimeScenarios(): void
271 {
272 $userStatusTriggers = [];
273 acym_trigger('onAcymDefineUserStatusCheckTriggers', [&$userStatusTriggers]);
274
275 if (empty($userStatusTriggers)) {
276 return;
277 }
278
279 $scenarioClass = new ScenarioClass();
280 $scenarios = $scenarioClass->getAllActiveByTriggers($userStatusTriggers);
281
282 if (empty($scenarios)) {
283 return;
284 }
285
286 foreach ($scenarios as $scenario) {
287 $nextExecution = $scenario->trigger_params['next_execution'] ?? null;
288 $scenario->next_execution = $nextExecution;
289
290 if (!empty($nextExecution) && $nextExecution > time()) {
291 continue;
292 }
293
294 $firstStep = $this->scenarioStepClass->getFirstStepByScenarioId($scenario->id);
295 if (empty($firstStep)) {
296 acym_logError('ScenarioHelper::triggerTimeScenarios - No first step found for scenario '.$scenario->id);
297 continue;
298 }
299
300 $execute = false;
301 $options = $this->formatOptions($scenario->trigger_params['option'] ?? [], 'trigger');
302 $options['time'] = time();
303
304 $scenario->is_scenario = true;
305 $scenario->triggers = [$scenario->trigger => $options];
306
307 // Get the users matching the trigger
308 acym_trigger('onAcymExecuteTrigger', [&$scenario, &$execute, &$options]);
309
310 if ($nextExecution !== $scenario->next_execution) {
311 $scenario->trigger_params['next_execution'] = $scenario->next_execution;
312 $scenarioClass->save($scenario);
313 }
314
315 if (empty($options['userIds'])) {
316 continue;
317 }
318
319 foreach ($options['userIds'] as $userId) {
320 if ($scenario->trigger_once && $this->scenarioProcessClass->hasUserProcess($scenario->id, $userId)) {
321 continue;
322 }
323
324 $scenarioProcess = new \stdClass();
325 $scenarioProcess->scenario_id = $scenario->id;
326 $scenarioProcess->user_id = $userId;
327 $scenarioProcess->start_at = acym_date('now', 'Y-m-d H:i:s', false);
328 $scenarioProcess->id = $this->scenarioProcessClass->save($scenarioProcess);
329
330 $this->createHistory($scenarioProcess->id, 'trigger', 'success');
331
332 $this->handleStep($firstStep, $userId, $scenarioProcess->id);
333 }
334 }
335 }
336
337 private function createHistory(int $scenarioProcessId, string $type, string $result, string $scenarioStepId = '', array $params = [], string $log = ''): void
338 {
339 $scenarioHistoryLine = new \stdClass();
340 $scenarioHistoryLine->scenario_process_id = $scenarioProcessId;
341 $scenarioHistoryLine->date = acym_date('now', 'Y-m-d H:i:s', false);
342 $scenarioHistoryLine->type = $type;
343 //TODO maybe add something more specific
344 $scenarioHistoryLine->result = $result;
345
346 if (!empty($scenarioStepId)) {
347 $scenarioHistoryLine->scenario_step_id = $scenarioStepId;
348 }
349
350 if (!empty($params)) {
351 $scenarioHistoryLine->params = $params;
352 }
353
354 if (!empty($log)) {
355 $scenarioHistoryLine->log = $log;
356 }
357
358 $this->scenarioHistoryLineClass->save($scenarioHistoryLine);
359 }
360
361 public function getConditionValue(object $step): string
362 {
363 return $step->params['condition'];
364 }
365
366 public function getActionValue(object $step): string
367 {
368 return $step->params['action'];
369 }
370 }
371