Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/fluent-smtp/app/Models/Logger.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
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 +