PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.4
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 / Classes / ScenarioClass.php

ScenarioClass.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.4, at back/Classes/ScenarioClass.php

326 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Classes;
4
5 use AcyMailing\Helpers\PaginationHelper;
6 use AcyMailing\Core\AcymClass;
7
8 class ScenarioClass extends AcymClass
9 {
10 const TYPE_DELAY = 'delay';
11 const TYPE_CONDITION = 'condition';
12 const TYPE_ACTION = 'action';
13
14 private ScenarioStepClass $scenarioStepClass;
15
16 // This is a list of all the step ids we have to save when saving a scenario, this way we can delete all the steps that are not in this list
17 private array $stepIdsToKeep = [];
18
19 public function __construct()
20 {
21 parent::__construct();
22
23 $this->table = 'scenario';
24 $this->pkey = 'id';
25 $this->scenarioStepClass = new ScenarioStepClass();
26 }
27
28 public function getMatchingElements(array $settings = []): array
29 {
30 $query = 'SELECT DISTINCT scenario.* FROM #__acym_scenario AS `scenario`';
31 $queryCount = 'SELECT COUNT(DISTINCT scenario.id) AS total FROM #__acym_scenario AS scenario';
32 $queryStatus = 'SELECT COUNT(DISTINCT scenario.id) AS `all`, SUM(`scenario`.`active`) AS active, SUM(IF(`scenario`.`active` = 0, 1, 0)) AS inactive FROM #__acym_scenario AS `scenario`';
33 $filters = [];
34
35 if (!empty($settings['search'])) {
36 $searchValue = acym_escapeDB('%'.$settings['search'].'%');
37 $filters[] = 'scenario.name LIKE '.$searchValue.' OR scenario.id = '.intval($settings['search']);
38 }
39
40 if (!empty($filters)) {
41 $queryStatus .= ' WHERE ('.implode(') AND (', $filters).')';
42 }
43
44 if (!empty($settings['status'])) {
45 switch ($settings['status']) {
46 case 'active':
47 $filters[] = 'scenario.active = 1';
48 break;
49 case 'inactive':
50 $filters[] = 'scenario.active = 0';
51 }
52 }
53
54 // We don't filter by status on the status query this way we can select other statuses
55 if (!empty($filters)) {
56 $query .= ' WHERE ('.implode(') AND (', $filters).')';
57 $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
58 }
59
60 if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
61 $query .= ' ORDER BY scenario.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
62 }
63
64 if (empty($settings['offset']) || $settings['offset'] < 0) {
65 $settings['offset'] = 0;
66 }
67
68 if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
69 $pagination = new PaginationHelper();
70 $settings['elementsPerPage'] = $pagination->getListLimit();
71 }
72
73 return [
74 'elements' => acym_loadObjectList($query, 'id', $settings['offset'], $settings['elementsPerPage']),
75 'total' => acym_loadObject($queryCount),
76 'totalOverall' => acym_loadResult('SELECT COUNT(id) FROM #__acym_scenario'),
77 'scenariosNumberStatus' => acym_loadObjectList($queryStatus)[0],
78 ];
79 }
80
81 public function save(object $element): ?int
82 {
83 $flow = [];
84 if (!empty($element->flow)) {
85 $flow = json_decode($element->flow, true);
86
87 // The first step should the trigger
88 if (empty($flow[0]['params'])) {
89 return 0;
90 }
91
92 $element->trigger = $flow[0]['params']['trigger'];
93
94 if (!empty($element->id)) {
95 $scenario = $this->getOneById($element->id);
96 if (!empty($scenario->trigger_params['next_execution'])) {
97 $flow[0]['params']['next_execution'] = $scenario->trigger_params['next_execution'];
98 }
99 }
100
101 $element->trigger_params = json_encode($flow[0]['params']);
102 } elseif (!empty($element->trigger_params) && is_array($element->trigger_params)) {
103 $element->trigger_params = json_encode($element->trigger_params);
104 }
105
106 unset($element->flow);
107
108 $element->id = parent::save($element);
109
110 if (!empty($flow) && !empty($element->id)) {
111 $this->saveFlow($flow, $element->id);
112 }
113
114 return $element->id;
115 }
116
117 public function getOneById(int $id): ?object
118 {
119 $scenario = parent::getOneById($id);
120
121 if (empty($scenario->trigger_params)) {
122 return $scenario;
123 }
124
125 $scenario->trigger_params = json_decode($scenario->trigger_params, true);
126
127 return $scenario;
128 }
129
130 private function saveFlow(array $flow, int $scenarioId): void
131 {
132 if (!empty($flow[0]['children'][0])) {
133 // We save all the step
134 $this->saveStep($flow[0]['children'][0], $scenarioId);
135 }
136 $this->cleanSteps($scenarioId);
137 }
138
139 private function saveStep(array $currentStep, int $scenarioId, ?string $previousStepId = null, ?int $conditionValid = null): void
140 {
141 // If this is a direct child of a condition we pass it, it's a hidden object for the javascript
142 if (isset($currentStep['conditionEnd'])) {
143 if ($currentStep['conditionEnd'] && !empty($currentStep['children'])) {
144 $isConditionValid = empty($currentStep['conditionValid']) ? 0 : 1;
145 $this->saveStep($currentStep['children'][0], $scenarioId, $previousStepId, $isConditionValid);
146 }
147
148 return;
149 }
150
151 switch ($currentStep['params']['type']) {
152 case self::TYPE_DELAY:
153 $type = self::TYPE_DELAY;
154 break;
155 case self::TYPE_CONDITION:
156 $type = self::TYPE_CONDITION;
157 break;
158 case self::TYPE_ACTION:
159 $type = self::TYPE_ACTION;
160 break;
161 default:
162 acym_enqueueMessage(acym_translation('ACYM_INVALID_STEP_TYPE'), 'error');
163
164 return;
165 }
166
167 $step = new \stdClass();
168 $step->id = $currentStep['slug'];
169 $step->previous_id = $previousStepId;
170 $step->type = $type;
171 $step->params = $currentStep['params'];
172 $step->scenario_id = $scenarioId;
173 $step->condition_valid = $conditionValid;
174
175 $this->scenarioStepClass->save($step);
176
177 $this->stepIdsToKeep[] = $step->id;
178
179 if (!empty($currentStep['children'])) {
180 foreach ($currentStep['children'] as $child) {
181 $this->saveStep($child, $scenarioId, $currentStep['slug']);
182 }
183 }
184 }
185
186 private function cleanSteps(int $scenarioId): void
187 {
188 $stepIdsToDelete = $this->scenarioStepClass->getAllStepsToDelete($scenarioId, $this->stepIdsToKeep);
189
190 if (empty($stepIdsToDelete)) {
191 return;
192 }
193
194 $scenarioQueueClass = new ScenarioQueueClass();
195 $scenarioQueueClass->deleteByStepIds($stepIdsToDelete);
196 $this->scenarioStepClass->delete($stepIdsToDelete);
197 }
198
199 private function formatScenario(object $scenario): ?object
200 {
201 if (empty($scenario)) {
202 return null;
203 }
204
205 $scenario->trigger_params = json_decode($scenario->trigger_params, true);
206
207 return $scenario;
208 }
209
210 public function getAllActiveByTriggers(array $triggerTypes): array
211 {
212 if (empty($triggerTypes)) {
213 return [];
214 }
215
216 $triggerTypes = array_map('acym_escapeDB', $triggerTypes);
217 $query = 'SELECT * FROM #__acym_scenario WHERE active = 1 AND `trigger` IN ('.implode(', ', $triggerTypes).') ORDER BY `ordering` ASC';
218
219 return array_map([$this, 'formatScenario'], acym_loadObjectList($query));
220 }
221
222 public function delete(array $elements): int
223 {
224 if (empty($elements)) {
225 return 0;
226 }
227
228 $escapedElements = array_map('acym_escapeDB', $elements);
229
230 $scenarioHistoryLineQuery = 'DELETE FROM #__acym_scenario_history_line WHERE scenario_process_id IN (SELECT id FROM #__acym_scenario_process WHERE scenario_id IN ('.implode(
231 ',',
232 $escapedElements
233 ).'))';
234 $scenarioQueueQuery = 'DELETE FROM #__acym_scenario_queue WHERE scenario_process_id IN (SELECT id FROM #__acym_scenario_process WHERE scenario_id IN ('.implode(
235 ',',
236 $escapedElements
237 ).'))';
238 $scenarioProcessQuery = 'DELETE FROM #__acym_scenario_process WHERE scenario_id IN ('.implode(',', $escapedElements).')';
239 $scenarioStepQuery = 'DELETE FROM #__acym_scenario_step WHERE scenario_id IN ('.implode(',', $escapedElements).')';
240 acym_query($scenarioHistoryLineQuery);
241 acym_query($scenarioQueueQuery);
242 acym_query($scenarioProcessQuery);
243 acym_query($scenarioStepQuery);
244
245 return parent::delete($elements);
246 }
247
248 public function duplicate(int $scenarioId): void
249 {
250 $scenarioStepClass = new ScenarioStepClass();
251
252 $scenario = $this->getOneById($scenarioId);
253
254 if (empty($scenario)) {
255 return;
256 }
257
258 unset($scenario->id);
259 $scenario->name = $scenario->name.' - '.acym_translation('ACYM_COPY');
260
261 $scenario->id = $this->save($scenario);
262
263 $scenarioSteps = $scenarioStepClass->getAllByScenarioId($scenarioId);
264
265 if (empty($scenarioSteps)) {
266 return;
267 }
268
269 $allStepIds = $scenarioStepClass->getAllStepIds();
270
271 $stepCorrespondence = [];
272
273 foreach ($scenarioSteps as $scenarioStep) {
274 $stepCorrespondence[$scenarioStep->id] = $scenarioStepClass->generateNewStepId($allStepIds);
275 $allStepIds[] = $stepCorrespondence[$scenarioStep->id];
276 }
277
278 foreach ($scenarioSteps as $scenarioStep) {
279 $scenarioStep->id = $stepCorrespondence[$scenarioStep->id];
280 $scenarioStep->previous_id = $stepCorrespondence[$scenarioStep->previous_id] ?? null;
281 $scenarioStep->scenario_id = $scenario->id;
282 $scenarioStepClass->save($scenarioStep);
283 }
284 }
285
286 public function markUserUnsubscribeFromScenario(int $userId, int $scenarioId): void
287 {
288 $query = 'UPDATE #__acym_scenario_process SET unsubscribed = 1 WHERE scenario_id = '.intval($scenarioId).' AND user_id = '.intval($userId);
289
290
291 acym_query($query);
292 }
293
294 public function getScenarioIdByMailId(int $mailId): ?int
295 {
296 $query = 'SELECT scenario_id
297 FROM #__acym_scenario_step
298 WHERE type = '.acym_escapeDB(self::TYPE_ACTION).'
299 AND params LIKE '.acym_escapeDB('%[mail_id]":"'.$mailId.'"%').'
300 LIMIT 1';
301
302 return acym_loadResult($query);
303 }
304
305 public function checkUserMarkedUnsubscribed(int $userId, int $scenarioProcessId): bool
306 {
307 $queryScenId = 'SELECT scenario_id
308 FROM #__acym_scenario_process
309 WHERE id = '.intval($scenarioProcessId).'
310 AND user_id = '.intval($userId).'
311 LIMIT 1';
312
313 $scenarioId = acym_loadResult($queryScenId);
314
315 $queryUnsubscribed = 'SELECT unsubscribed
316 FROM #__acym_scenario_process
317 WHERE scenario_id = '.intval($scenarioId).'
318 AND user_id = '.intval($userId).'
319 LIMIT 1';
320
321 $result = acym_loadResult($queryUnsubscribed);
322
323 return !empty($result);
324 }
325 }
326