Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/fluent-smtp/app/Models/Logger.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
3
+
namespace FluentMail\App\Models;
4
+
5
+
use Exception;
6
+
use InvalidArgumentException;
7
+
use FluentMail\Includes\Support\Arr;
8
+
9
+
class Logger extends Model
10
+
{
11
+
const STATUS_PENDING = 'pending';
12
+
const STATUS_FAILED = 'failed';
13
+
const STATUS_SENT = 'sent';
14
+
15
+
protected $fillables = [
16
+
'to',
17
+
'from',
18
+
'subject',
19
+
'body',
20
+
'status',
21
+
'response',
22
+
'extra',
23
+
'created_at'
24
+
];
25
+
26
+
protected $searchables = [
27
+
'to',
28
+
'from',
29
+
'subject'
30
+
];
31
+
32
+
protected $table = null;
33
+
34
+
public function __construct()
35
+
{
36
+
parent::__construct();
37
+
38
+
$this->table = $this->db->prefix . FLUENT_MAIL_DB_PREFIX . 'email_logs';
39
+
}
40
+
41
+
public function get($data)
42
+
{
43
+
$db = $this->getDb();
44
+
$page = isset($data['page']) ? (int)$data['page'] : 1;
45
+
$perPage = isset($data['per_page']) ? (int)$data['per_page'] : 15;
46
+
$offset = ($page - 1) * $perPage;
47
+
48
+
$query = $db->table(FLUENT_MAIL_DB_PREFIX . 'email_logs')
49
+
->limit($perPage)
50
+
->offset($offset)
51
+
->orderBy('id', 'DESC');
52
+
53
+
if (!empty($data['status'])) {
54
+
$query->where('status', sanitize_text_field($data['status']));
55
+
}
56
+
57
+
if (!empty($data['date_range']) && is_array($data['date_range']) && count($data['date_range']) == 2) {
58
+
$dateRange = $data['date_range'];
59
+
$from = $dateRange[0] . ' 00:00:01';
60
+
$to = $dateRange[1] . ' 23:59:59';
61
+
$query->whereBetween('created_at', $from, $to);
62
+
}
63
+
64
+
if (!empty($data['search'])) {
65
+
$search = trim(sanitize_text_field($data['search']));
66
+
$query->where(function ($q) use ($search) {
67
+
$searchColumns = $this->searchables;
68
+
69
+
$columnSearch = false;
70
+
if (strpos($search, ':')) {
71
+
$searchArray = explode(':', $search);
72
+
$column = array_shift($searchArray);
73
+
if (in_array($column, $this->fillables)) {
74
+
$columnSearch = true;
75
+
$q->where($column, 'LIKE', '%' . trim(implode(':', $searchArray)) . '%');
76
+
}
77
+
}
78
+
79
+
if (!$columnSearch) {
80
+
$firstColumn = array_shift($searchColumns);
81
+
$q->where($firstColumn, 'LIKE', '%' . $search . '%');
82
+
foreach ($searchColumns as $column) {
83
+
$q->orWhere($column, 'LIKE', '%' . $search . '%');
84
+
}
85
+
}
86
+
87
+
});
88
+
}
89
+
90
+
$result = $query->paginate();
91
+
$result['data'] = $this->formatResult($result['data']);
92
+
93
+
return $result;
94
+
}
95
+
96
+
protected function buildWhere($data)
97
+
{
98
+
$where = [];
99
+
100
+
if (isset($data['filter_by_value'])) {
101
+
$where[$data['filter_by']] = $data['filter_by_value'];
102
+
}
103
+
104
+
if (isset($data['query'])) {
105
+
foreach ($this->searchables as $column) {
106
+
if (isset($where[$column])) {
107
+
$where[$column] .= '|' . $data['query'];
108
+
} else {
109
+
$where[$column] = $data['query'];
110
+
}
111
+
}
112
+
}
113
+
114
+
$args = [1];
115
+
$andWhere = $orWhere = '';
116
+
$whereClause = "WHERE 1 = '%d'";
117
+
118
+
foreach ($where as $key => $value) {
119
+
if (in_array($key, ['status', 'created_at'])) {
120
+
if ($key == 'created_at') {
121
+
if (is_array($value)) {
122
+
$args[] = $value[0];
123
+
$args[] = $value[1];
124
+
} else {
125
+
$args[] = $value;
126
+
$args[] = $value;
127
+
}
128
+
$andWhere .= " AND `{$key}` >= '%s' AND `{$key}` < '%s' + INTERVAL 1 DAY";
129
+
} else {
130
+
$args[] = $value;
131
+
$andWhere .= " AND `{$key}` = '%s'";
132
+
}
133
+
} else {
134
+
if (strpos($value, '|')) {
135
+
$nestedOr = '';
136
+
$values = explode('|', $value);
137
+
foreach ($values as $itemValue) {
138
+
$args[] = '%' . $this->db->esc_like($itemValue) . '%';
139
+
$nestedOr .= " OR `{$key}` LIKE '%s'";
140
+
}
141
+
$orWhere .= ' OR (' . trim($nestedOr, 'OR ') . ')';
142
+
} else {
143
+
$args[] = '%' . $this->db->esc_like($value) . '%';
144
+
$orWhere .= " OR `{$key}` LIKE '%s'";
145
+
}
146
+
}
147
+
}
148
+
149
+
if ($orWhere) {
150
+
$orWhere = 'AND (' . trim($orWhere, 'OR ') . ')';
151
+
}
152
+
153
+
$whereClause = implode(' ', [$whereClause, trim($andWhere), $orWhere]);
154
+
155
+
return [$whereClause, $args];
156
+
}
157
+
158
+
protected function formatResult($result)
159
+
{
160
+
$result = is_array($result) ? $result : func_get_args();
161
+
foreach ($result as $key => $row) {
162
+
$result[$key] = $this->maybeUnserialize((array)$row);
163
+
$result[$key]['id'] = (int)$result[$key]['id'];
164
+
$result[$key]['retries'] = (int)$result[$key]['retries'];
165
+
$result[$key]['from'] = htmlspecialchars($result[$key]['from']);
166
+
$result[$key]['subject'] = wp_kses_post(
167
+
wp_unslash($result[$key]['subject'])
168
+
);
169
+
}
170
+
171
+
return $result;
172
+
}
173
+
174
+
protected function maybeUnserialize(array $data)
175
+
{
176
+
foreach ($data as $key => $value) {
177
+
if ($this->isUnserializable($key)) {
178
+
$data[$key] = $this->unserialize($value);
179
+
}
180
+
}
181
+
182
+
return $data;
183
+
}
184
+
185
+
protected function isUnserializable($key)
186
+
{
187
+
$allowedFields = [
188
+
'to',
189
+
'headers',
190
+
'attachments',
191
+
'response',
192
+
'extra'
193
+
];
194
+
195
+
return in_array($key, $allowedFields);
196
+
}
197
+
198
+
protected function unserialize($data)
199
+
{
200
+
if (is_serialized($data)) {
201
+
if (preg_match('/(^|;)O:[0-9]+:/', $data)) {
202
+
return $data;
203
+
}
204
+
return unserialize(trim($data), ['allowed_classes' => false]);
205
+
}
206
+
207
+
return $data;
208
+
}
209
+
210
+
protected function formatHeaders($headers)
211
+
{
212
+
foreach ((array)$headers as $key => $header) {
213
+
if (is_array($header)) {
214
+
$header = $this->formatHeaders($header);
215
+
} else {
216
+
$header = htmlspecialchars($header);
217
+
}
218
+
219
+
$headers[$key] = $header;
220
+
}
221
+
222
+
return $headers;
223
+
}
224
+
225
+
public function add($data)
226
+
{
227
+
try {
228
+
$data = array_merge($data, [
229
+
'created_at' => current_time('mysql')
230
+
]);
231
+
232
+
return $this->getDb()->table(FLUENT_MAIL_DB_PREFIX . 'email_logs')
233
+
->insert($data);
234
+
235
+
} catch (Exception $e) {
236
+
return $e;
237
+
}
238
+
}
239
+
240
+
public function delete(array $id)
241
+
{
242
+
if ($id && $id[0] == 'all') {
243
+
return $this->db->query("TRUNCATE TABLE {$this->table}");
244
+
}
245
+
246
+
$ids = array_filter($id, 'intval');
247
+
248
+
if ($ids) {
249
+
return $this->getDb()->table(FLUENT_MAIL_DB_PREFIX . 'email_logs')
250
+
->whereIn('id', $ids)
251
+
->delete();
252
+
}
253
+
254
+
return false;
255
+
}
256
+
257
+
public function navigate($data)
258
+
{
259
+
$filterBy = Arr::get($data, 'filter_by');
260
+
foreach (['date', 'daterange', 'datetime', 'datetimerange'] as $field) {
261
+
if ($filterBy == $field) {
262
+
$data['filter_by'] = 'created_at';
263
+
}
264
+
}
265
+
266
+
$id = $data['id'];
267
+
268
+
$dir = isset($data['dir']) ? $data['dir'] : null;
269
+
270
+
list($where, $args) = $this->buildWhere($data);
271
+
272
+
$args = array_merge($args, [$id]);
273
+
274
+
$sqlNext = "SELECT * FROM {$this->table} {$where} AND `id` > '%d' ORDER BY id LIMIT 2";
275
+
$sqlPrev = "SELECT * FROM {$this->table} {$where} AND `id` < '%d' ORDER BY id DESC LIMIT 2";
276
+
277
+
if ($dir == 'next') {
278
+
$query = $this->db->prepare($sqlNext, $args);
279
+
} else if ($dir == 'prev') {
280
+
$query = $this->db->prepare($sqlPrev, $args);
281
+
} else {
282
+
foreach (['next' => $sqlNext, 'prev' => $sqlPrev] as $key => $sql) {
283
+
284
+
$keyResult = $this->db->get_results(
285
+
$this->db->prepare($sql, $args)
286
+
);
287
+
288
+
$result[$key] = $this->formatResult($keyResult);
289
+
}
290
+
291
+
return $result;
292
+
}
293
+
294
+
$result = $this->db->get_results($query);
295
+
296
+
if (count($result) > 1) {
297
+
$next = true;
298
+
$prev = true;
299
+
} else {
300
+
if ($dir == 'next') {
301
+
$next = false;
302
+
$prev = true;
303
+
} else {
304
+
$next = true;
305
+
$prev = false;
306
+
}
307
+
}
308
+
309
+
return [
310
+
'log' => $result ? $this->formatResult($result[0])[0] : null,
311
+
'next' => $next,
312
+
'prev' => $prev
313
+
];
314
+
}
315
+
316
+
public function find($id)
317
+
{
318
+
319
+
$row = $this->getDb()->table(FLUENT_MAIL_DB_PREFIX . 'email_logs')
320
+
->where('id', $id)
321
+
->first();
322
+
323
+
$row->extra = $this->unserialize($row->extra);
324
+
325
+
$row->response = $this->unserialize($row->response);
326
+
327
+
return (array)$row;
328
+
}
329
+
330
+
public function resendEmailFromLog($id, $type = 'retry')
331
+
{
332
+
$email = $this->find($id);
333
+
334
+
$email['to'] = $this->unserialize($email['to']);
335
+
$email['headers'] = $this->unserialize($email['headers']);
336
+
$email['attachments'] = $this->unserialize($email['attachments']);
337
+
$email['extra'] = $this->unserialize($email['extra']);
338
+
339
+
// Convert PHPMailer attachment format to wp_mail format
340
+
$wpMailAttachments = [];
341
+
if (!empty($email['attachments']) && is_array($email['attachments'])) {
342
+
foreach ($email['attachments'] as $attachment) {
343
+
if (is_array($attachment)) {
344
+
// PHPMailer format: [path, filename, name, encoding, type, isString, disposition, cid]
345
+
if (isset($attachment[0]) && is_string($attachment[0])) {
346
+
$filePath = $attachment[0];
347
+
if (file_exists($filePath) && is_readable($filePath)) {
348
+
$wpMailAttachments[] = $filePath;
349
+
}
350
+
}
351
+
} elseif (is_string($attachment)) {
352
+
if (file_exists($attachment) && is_readable($attachment)) {
353
+
$wpMailAttachments[] = $attachment;
354
+
}
355
+
}
356
+
}
357
+
}
358
+
359
+
$headers = [];
360
+
361
+
foreach ($email['headers'] as $key => $value) {
362
+
363
+
if($key == 'content-type' && $value == 'multipart/alternative') {
364
+
$value = 'text/html';
365
+
}
366
+
367
+
if (is_array($value)) {
368
+
$values = [];
369
+
$value = array_filter($value);
370
+
foreach ($value as $v) {
371
+
if (is_array($v) && isset($v['email'])) {
372
+
$v = $v['email'];
373
+
}
374
+
$values[] = $v;
375
+
}
376
+
if ($values) {
377
+
$headers[] = "{$key}: " . implode(';', $values);
378
+
}
379
+
} else {
380
+
if ($value) {
381
+
$headers[] = "{$key}: $value";
382
+
}
383
+
}
384
+
}
385
+
386
+
$headers = array_merge($headers, [
387
+
'From: ' . $email['from']
388
+
]);
389
+
390
+
$to = [];
391
+
foreach ($email['to'] as $recipient) {
392
+
if (isset($recipient['name'])) {
393
+
$to[] = $recipient['name'] . ' <' . $recipient['email'] . '>';
394
+
} else {
395
+
$to[] = $recipient['email'];
396
+
}
397
+
}
398
+
399
+
try {
400
+
if (!defined('FLUENTMAIL_LOG_OFF')) {
401
+
define('FLUENTMAIL_LOG_OFF', true);
402
+
}
403
+
404
+
$result = wp_mail(
405
+
$to,
406
+
$email['subject'],
407
+
$email['body'],
408
+
$headers,
409
+
$wpMailAttachments // Use the converted attachment format
410
+
);
411
+
412
+
$updateData = [
413
+
'status' => 'sent',
414
+
'updated_at' => current_time('mysql'),
415
+
];
416
+
417
+
if (!$result && $type == 'check_realtime' && $email['status'] == 'failed') {
418
+
$updateData['status'] = 'failed';
419
+
}
420
+
421
+
if ($type == 'resend') {
422
+
$updateData['resent_count'] = intval($email['resent_count']) + 1;
423
+
} else {
424
+
$updateData['retries'] = intval($email['retries']) + 1;
425
+
}
426
+
427
+
if ($this->updateLog($updateData, ['id' => $id])) {
428
+
$email = $this->find($id);
429
+
$email['to'] = $this->unserialize($email['to']);
430
+
$email['headers'] = $this->unserialize($email['headers']);
431
+
$email['attachments'] = $this->unserialize($email['attachments']);
432
+
$email['extra'] = $this->unserialize($email['extra']);
433
+
return $email;
434
+
}
435
+
} catch (\PHPMailer\PHPMailer\Exception $e) {
436
+
throw $e;
437
+
}
438
+
}
439
+
440
+
public function updateLog($data, $where)
441
+
{
442
+
return $this->db->update($this->table, $data, $where);
443
+
}
444
+
445
+
public function getStats()
446
+
{
447
+
$succeeded = $this->db->get_var("select COUNT(id) from {$this->table} where status='sent'");
448
+
$failed = $this->db->get_var("select COUNT(id) from {$this->table} where status='failed'");
449
+
450
+
return [
451
+
'sent' => $succeeded,
452
+
'failed' => $failed
453
+
];
454
+
}
455
+
456
+
public function deleteLogsOlderThan($days)
457
+
{
458
+
try {
459
+
460
+
$date = gmdate('Y-m-d H:i:s', current_time('timestamp') - $days * DAY_IN_SECONDS);
461
+
$query = $this->db->prepare("DELETE FROM {$this->table} WHERE `created_at` < %s", $date);
462
+
return $this->db->query($query);
463
+
464
+
} catch (Exception $e) {
465
+
if (wp_get_environment_type() != 'production') {
466
+
error_log('Message: ' . $e->getMessage());
467
+
}
468
+
}
469
+
}
470
+
471
+
public function getTotalCountStat($status, $startDate, $endDate = false)
472
+
{
473
+
if ($endDate) {
474
+
$query = $this->db->prepare(
475
+
"SELECT COUNT(*)
476
+
FROM {$this->table}
477
+
WHERE status = %s
478
+
AND created_at >= %s
479
+
AND created_at <= %s",
480
+
$status,
481
+
$startDate,
482
+
$endDate
483
+
);
484
+
} else {
485
+
$query = $this->db->prepare(
486
+
"SELECT COUNT(*)
487
+
FROM {$this->table}
488
+
WHERE status = %s
489
+
AND created_at >= %s",
490
+
$status,
491
+
$startDate
492
+
);
493
+
}
494
+
495
+
return (int)$this->db->get_var($query);
496
+
}
497
+
498
+
public function getSubjectCountStat($status, $startDate, $endDate)
499
+
{
500
+
$query = $this->db->prepare(
501
+
"SELECT COUNT(DISTINCT(subject))
502
+
FROM {$this->table}
503
+
WHERE status = %s
504
+
AND created_at >= %s
505
+
AND created_at <= %s",
506
+
$status,
507
+
$startDate,
508
+
$endDate
509
+
);
510
+
511
+
return (int)$this->db->get_var($query);
512
+
}
513
+
514
+
public function getSubjectStat($status, $statDate, $endDate, $limit = 5)
515
+
{
516
+
$query = $this->db->prepare(
517
+
"SELECT subject,
518
+
COUNT(DISTINCT id) AS emails_sent
519
+
FROM {$this->table}
520
+
WHERE created_at >= %s
521
+
AND created_at <= %s
522
+
AND status = %s
523
+
GROUP BY subject
524
+
ORDER BY emails_sent DESC
525
+
LIMIT {$limit}",
526
+
$statDate,
527
+
$endDate,
528
+
$status
529
+
);
530
+
531
+
return $this->db->get_results($query, ARRAY_A);
532
+
}
533
+
534
+
}
535
+