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 / Storage / AutomationRunStorage.php
mailpoet / lib / Automation / Engine / Storage Last commit date
AutomationRunLogStorage.php 1 year ago AutomationRunStorage.php 2 months ago AutomationStatisticsStorage.php 1 month ago AutomationStorage.php 2 months ago index.php 3 years ago
AutomationRunStorage.php
382 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Automation\Engine\Storage;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Automation\Engine\Data\Automation;
9 use MailPoet\Automation\Engine\Data\AutomationRun;
10 use MailPoet\Automation\Engine\Data\Subject;
11 use MailPoet\Automation\Engine\Exceptions;
12
13 class AutomationRunStorage {
14 /** @var string */
15 private $table;
16
17 /** @var string */
18 private $subjectTable;
19
20 public function __construct() {
21 global $wpdb;
22 $this->table = $wpdb->prefix . 'mailpoet_automation_runs';
23 $this->subjectTable = $wpdb->prefix . 'mailpoet_automation_run_subjects';
24 }
25
26 public function createAutomationRun(AutomationRun $automationRun): int {
27 global $wpdb;
28 $automationTableData = $automationRun->toArray();
29 $subjectTableData = $automationTableData['subjects'];
30 unset($automationTableData['subjects']);
31 $result = $wpdb->insert($this->table, $automationTableData);
32 if ($result === false) {
33 $this->throwDatabaseError();
34 }
35 $automationRunId = $wpdb->insert_id; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
36
37 if (!$subjectTableData) {
38 //We allow for AutomationRuns with no subjects.
39 return $automationRunId;
40 }
41
42
43 $values = [];
44 foreach ($subjectTableData as $entry) {
45 $values[] = $wpdb->prepare('(%d,%s,%s,%s)', $automationRunId, $entry['key'], $entry['args'], $entry['hash']);
46 }
47
48 $result = $wpdb->query(
49 $wpdb->prepare('INSERT INTO %i (`automation_run_id`, `key`, `args`, `hash`) VALUES ', $this->subjectTable) . implode(',', $values)
50 );
51 if ($result === false) {
52 $this->throwDatabaseError();
53 }
54
55 return $automationRunId;
56 }
57
58 public function getAutomationRun(int $id): ?AutomationRun {
59 global $wpdb;
60
61 $data = $wpdb->get_row(
62 $wpdb->prepare('SELECT * FROM %i WHERE id = %d', $this->table, $id),
63 ARRAY_A
64 );
65
66 if (!is_array($data) || !$data) {
67 return null;
68 }
69
70 $subjects = $wpdb->get_results(
71 $wpdb->prepare('SELECT * FROM %i WHERE automation_run_id = %d', $this->subjectTable, $id),
72 ARRAY_A
73 );
74 $data['subjects'] = is_array($subjects) ? $subjects : [];
75 return AutomationRun::fromArray((array)$data);
76 }
77
78 /**
79 * @param Automation $automation
80 * @return AutomationRun[]
81 */
82 public function getAutomationRunsForAutomation(Automation $automation): array {
83 global $wpdb;
84
85 $automationRuns = $wpdb->get_results(
86 $wpdb->prepare(
87 'SELECT * FROM %i WHERE automation_id = %d ORDER BY id',
88 $this->table,
89 $automation->getId()
90 ),
91 ARRAY_A
92 );
93 if (!is_array($automationRuns) || !$automationRuns) {
94 return [];
95 }
96
97 $automationRunIds = array_column($automationRuns, 'id');
98 $subjects = $wpdb->get_results(
99 $wpdb->prepare(
100 '
101 SELECT *
102 FROM %i
103 WHERE automation_run_id IN (' . implode(',', array_fill(0, count($automationRunIds), '%s')) . ')
104 ORDER BY automation_run_id, id
105 ',
106 array_merge(
107 [$this->subjectTable],
108 $automationRunIds,
109 )
110 ),
111 ARRAY_A
112 );
113
114 return array_map(
115 function($runData) use ($subjects): AutomationRun {
116 /** @var array $runData - PHPStan expects as array_map first parameter (callable(mixed): mixed)|null */
117 $runData['subjects'] = array_values(array_filter(
118 is_array($subjects) ? $subjects : [],
119 function($subjectData) use ($runData): bool {
120 /** @var array $subjectData - PHPStan expects as array_map first parameter (callable(mixed): mixed)|null */
121 return (int)$subjectData['automation_run_id'] === (int)$runData['id'];
122 }
123 ));
124 return AutomationRun::fromArray($runData);
125 },
126 $automationRuns
127 );
128 }
129
130 /**
131 * @param Automation $automation
132 * @return int
133 */
134 public function getCountByAutomationAndSubject(Automation $automation, Subject $subject): int {
135 global $wpdb;
136
137 $result = $wpdb->get_col(
138 $wpdb->prepare(
139 '
140 SELECT count(DISTINCT runs.id) AS count FROM %i AS runs
141 JOIN %i AS subjects ON runs.id = subjects.automation_run_id
142 WHERE runs.automation_id = %d
143 AND subjects.hash = %s
144 AND subjects.key = %s
145 ',
146 $this->table,
147 $this->subjectTable,
148 $automation->getId(),
149 $subject->getHash(),
150 $subject->getKey()
151 )
152 );
153
154 return $result ? (int)current($result) : 0;
155 }
156
157 public function getCountByAutomationTriggerAndSubject(Automation $automation, string $triggerKey, Subject $subject): int {
158 global $wpdb;
159
160 $result = $wpdb->get_col(
161 $wpdb->prepare(
162 '
163 SELECT count(DISTINCT runs.id) AS count FROM %i AS runs
164 JOIN %i AS subjects ON runs.id = subjects.automation_run_id
165 WHERE runs.automation_id = %d
166 AND runs.trigger_key = %s
167 AND subjects.hash = %s
168 AND subjects.key = %s
169 ',
170 $this->table,
171 $this->subjectTable,
172 $automation->getId(),
173 $triggerKey,
174 $subject->getHash(),
175 $subject->getKey()
176 )
177 );
178
179 return $result ? (int)current($result) : 0;
180 }
181
182 public function getCountForAutomation(Automation $automation, string ...$status): int {
183 global $wpdb;
184
185 if (!count($status)) {
186 $result = $wpdb->get_col(
187 $wpdb->prepare(
188 'SELECT COUNT(id) as count FROM %i WHERE automation_id = %d',
189 $this->table,
190 $automation->getId()
191 )
192 );
193 return $result ? (int)current($result) : 0;
194 }
195
196 $result = $wpdb->get_col(
197 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- The number of replacements is dynamic.
198 $wpdb->prepare(
199 '
200 SELECT COUNT(id) as count
201 FROM %i
202 WHERE automation_id = %d
203 AND status IN (' . implode(',', array_fill(0, count($status), '%s')) . ')
204 ',
205 $this->table,
206 $automation->getId(),
207 ...$status
208 )
209 );
210 return $result ? (int)current($result) : 0;
211 }
212
213 /**
214 * @return array<int, AutomationRun|null>
215 */
216 public function getLastAutomationRunsForAutomations(Automation ...$automations): array {
217 global $wpdb;
218
219 if (!count($automations)) {
220 return [];
221 }
222
223 $automationIds = array_map(function (Automation $automation) {
224 return $automation->getId();
225 }, $automations);
226
227 // Using a subquery to get the max ID per automation_id (most recent run)
228 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- The number of replacements is dynamic.
229 $runs = $wpdb->get_results(
230 $wpdb->prepare(
231 '
232 SELECT r.*
233 FROM %i AS r
234 INNER JOIN (
235 SELECT automation_id, MAX(id) AS max_id
236 FROM %i
237 WHERE automation_id IN (' . implode(',', array_fill(0, count($automationIds), '%d')) . ')
238 GROUP BY automation_id
239 ) AS latest ON r.id = latest.max_id
240 ',
241 array_merge([$this->table, $this->table], $automationIds)
242 ),
243 ARRAY_A
244 );
245
246 if (!is_array($runs) || !$runs) {
247 return array_fill_keys($automationIds, null);
248 }
249
250 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- The number of replacements is dynamic.
251 $subjects = $wpdb->get_results(
252 $wpdb->prepare(
253 '
254 SELECT *
255 FROM %i
256 WHERE automation_run_id IN (' . implode(',', array_fill(0, count($runs), '%d')) . ')
257 ',
258 array_merge([$this->subjectTable], array_column($runs, 'id'))
259 ),
260 ARRAY_A
261 );
262
263 $result = array_fill_keys($automationIds, null);
264
265 foreach ($runs as $runData) {
266 if (!is_array($runData)) {
267 continue;
268 }
269 $runId = is_numeric($runData['id'] ?? null) ? (int)$runData['id'] : 0;
270 $runData['subjects'] = array_values(array_filter(
271 is_array($subjects) ? $subjects : [],
272 function ($subjectData) use ($runId): bool {
273 if (!is_array($subjectData)) {
274 return false;
275 }
276 $subjectRunId = $subjectData['automation_run_id'] ?? null;
277 return is_numeric($subjectRunId) && (int)$subjectRunId === $runId;
278 }
279 ));
280 $automationId = is_numeric($runData['automation_id'] ?? null) ? (int)$runData['automation_id'] : 0;
281 $result[$automationId] = AutomationRun::fromArray($runData);
282 }
283
284 return $result;
285 }
286
287 public function updateStatus(int $id, string $status): void {
288 global $wpdb;
289 $result = $wpdb->query(
290 $wpdb->prepare(
291 '
292 UPDATE %i
293 SET status = %s, updated_at = current_timestamp()
294 WHERE id = %d
295 ',
296 $this->table,
297 $status,
298 $id
299 )
300 );
301 if ($result === false) {
302 $this->throwDatabaseError();
303 }
304 }
305
306 public function updateNextStep(int $id, ?string $nextStepId): void {
307 global $wpdb;
308 $result = $wpdb->query(
309 $wpdb->prepare(
310 '
311 UPDATE %i
312 SET next_step_id = %s, updated_at = current_timestamp()
313 WHERE id = %d
314 ',
315 $this->table,
316 $nextStepId,
317 $id
318 )
319 );
320 if ($result === false) {
321 $this->throwDatabaseError();
322 }
323 }
324
325 public function getNextStepId(int $id): ?string {
326 global $wpdb;
327 $result = $wpdb->get_row(
328 $wpdb->prepare(
329 'SELECT next_step_id FROM %i WHERE id = %d',
330 $this->table,
331 $id
332 ),
333 ARRAY_A
334 );
335 if (!is_array($result) || !array_key_exists('next_step_id', $result)) {
336 return null;
337 }
338 $value = $result['next_step_id'];
339 return $value !== null ? (string)$value : null;
340 }
341
342 public function getAutomationStepStatisticForTimeFrame(int $automationId, string $status, \DateTimeImmutable $after, \DateTimeImmutable $before, ?int $versionId = null): array {
343 global $wpdb;
344 $andWhere = $versionId ? 'AND version_id = %d' : '';
345 $result = $wpdb->get_results(
346 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- The number of replacements is dynamic.
347 $wpdb->prepare(
348 '
349 SELECT COUNT(id) AS count, next_step_id
350 FROM %i AS log
351 WHERE automation_id = %d AND status = %s AND created_at BETWEEN %s AND %s
352 ' . $andWhere . /* phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The condition uses placeholders. */ '
353 GROUP BY next_step_id
354 ',
355 array_merge(
356 [
357 $this->table,
358 $automationId,
359 $status,
360 $after->format('Y-m-d H:i:s'),
361 $before->format('Y-m-d H:i:s'),
362 ],
363 $versionId ? [$versionId] : []
364 )
365 ),
366 ARRAY_A
367 );
368 return is_array($result) ? $result : [];
369 }
370
371 public function truncate(): void {
372 global $wpdb;
373 $wpdb->query($wpdb->prepare('TRUNCATE %i', $this->table));
374 $wpdb->query($wpdb->prepare('TRUNCATE %i', $this->subjectTable));
375 }
376
377 private function throwDatabaseError(): void {
378 global $wpdb;
379 throw Exceptions::databaseError($wpdb->last_error); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
380 }
381 }
382