PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
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
← All changes | back/Classes/MailClass.php +1708 -0 10.10.1 → 11.1.0 View file →
@@ -1,0 +1,1708 @@
1 +<?php
2 +
3 +namespace AcyMailing\Classes;
4 +
5 +use AcyMailing\Helpers\MailerHelper;
6 +use AcyMailing\Helpers\PaginationHelper;
7 +use AcyMailing\Helpers\PluginHelper;
8 +use AcyMailing\Core\AcymClass;
9 +
10 +class MailClass extends AcymClass
11 +{
12 + const FIELDS_ENCODING = [
13 + 'name',
14 + 'subject',
15 + 'body',
16 + 'autosave',
17 + 'preheader',
18 + ];
19 +
20 + const TYPE_STANDARD = 'standard';
21 + const TYPE_NOTIFICATION = 'notification';
22 + const TYPE_OVERRIDE = 'override';
23 + const TYPE_WELCOME = 'welcome';
24 + const TYPE_UNSUBSCRIBE = 'unsubscribe';
25 + const TYPE_AUTOMATION = 'automation';
26 + const TYPE_FOLLOWUP = 'followup';
27 + const TYPE_MAILBOX_ACTION = 'mailbox_action';
28 + const TYPE_TEMPLATE = 'template';
29 + const TYPE_SCENARIO = 'scenario';
30 +
31 + const ALL_TYPES = [
32 + self::TYPE_STANDARD,
33 + self::TYPE_NOTIFICATION,
34 + self::TYPE_OVERRIDE,
35 + self::TYPE_WELCOME,
36 + self::TYPE_UNSUBSCRIBE,
37 + self::TYPE_AUTOMATION,
38 + self::TYPE_FOLLOWUP,
39 + self::TYPE_MAILBOX_ACTION,
40 + self::TYPE_TEMPLATE,
41 + self::TYPE_SCENARIO,
42 + ];
43 +
44 + // Used by some sending methods to know the priority of a sent email (transactional => reset password / account confirmation...)
45 + const TYPES_TRANSACTIONAL = [
46 + self::TYPE_NOTIFICATION,
47 + self::TYPE_OVERRIDE,
48 + self::TYPE_WELCOME,
49 + self::TYPE_UNSUBSCRIBE,
50 + self::TYPE_TEMPLATE,
51 + self::TYPE_SCENARIO,
52 + ];
53 +
54 + // Types on which the click statistics are active
55 + const TYPES_WITH_STATS = [
56 + self::TYPE_STANDARD,
57 + self::TYPE_AUTOMATION,
58 + self::TYPE_WELCOME,
59 + self::TYPE_UNSUBSCRIBE,
60 + self::TYPE_FOLLOWUP,
61 + self::TYPE_MAILBOX_ACTION,
62 + self::TYPE_SCENARIO,
63 + ];
64 +
65 + // Types that don't let the user modify the name
66 + const TYPES_NO_NAME = [
67 + self::TYPE_NOTIFICATION,
68 + self::TYPE_OVERRIDE,
69 + ];
70 +
71 + const MAX_ALLOWED_TEMPLATE_FILES = 100;
72 + const MAX_ALLOWED_TEMPLATE_SIZE = 20971520;
73 + const ALLOWED_TEMPLATE_FILE_TYPES = [
74 + 'image/jpeg',
75 + 'image/png',
76 + 'image/gif',
77 + 'text/html',
78 + 'image/x-ms-bmp',
79 + 'image/bmp',
80 + 'text/plain',
81 + 'image/vnd.microsoft.icon',
82 + ];
83 +
84 + private array $templateNames = [];
85 + public array $exceptKeysDecode = [];
86 +
87 + public function __construct()
88 + {
89 + parent::__construct();
90 +
91 + $this->table = 'mail';
92 + $this->pkey = 'id';
93 + }
94 +
95 + /**
96 + * Get mails depending on filters (search, ordering, pagination)
97 + */
98 + public function getMatchingElements(array $settings = []): array
99 + {
100 + $query = 'SELECT mail.* FROM #__acym_mail AS mail';
101 + $queryCount = 'SELECT COUNT(mail.id) AS total FROM #__acym_mail AS mail';
102 +
103 + $filters = [];
104 + $tagJoin = '';
105 +
106 + // Tag filter
107 + if (!empty($settings['tag'])) {
108 + $tagJoin = ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element ';
109 + $filters[] = 'tag.name = '.acym_escapeDB($settings['tag']);
110 + $filters[] = 'tag.type = "mail"';
111 + }
112 + $query .= $tagJoin;
113 + $queryCount .= $tagJoin;
114 +
115 + if (!empty($settings['search'])) {
116 + $filters[] = 'mail.name LIKE '.acym_escapeDB('%'.acym_utf8Encode($settings['search']).'%');
117 + }
118 +
119 + if (!empty($settings['gettingTemplates'])) {
120 + $filters[] = 'mail.type = '.acym_escapeDB(self::TYPE_TEMPLATE);
121 + if (!acym_isAdmin()) {
122 + $currentUserId = acym_currentUserId();
123 + $condition = 'mail.creator_id = '.intval($currentUserId);
124 +
125 + $userGroups = acym_getGroupsByUser($currentUserId);
126 + if (!empty($userGroups)) {
127 + $condition .= ' OR access LIKE "%,'.implode(',%" OR access LIKE "%,', $userGroups).',%"';
128 + }
129 +
130 + $filters[] = $condition;
131 + }
132 + } else {
133 + if (!empty($settings['automation']) || empty($settings['onlyStandard'])) {
134 + $filters[] = 'mail.type != '.acym_escapeDB(self::TYPE_NOTIFICATION);
135 + $filters[] = 'mail.type != '.acym_escapeDB(self::TYPE_OVERRIDE);
136 + } else {
137 + $filters[] = 'mail.type IN ('.acym_escapeDB(self::TYPE_STANDARD).', '.acym_escapeDB(self::TYPE_TEMPLATE).')';
138 + }
139 +
140 + if (empty($settings['automation'])) {
141 + $filters[] = 'mail.type IN ('.acym_escapeDB(self::TYPE_TEMPLATE).', '.acym_escapeDB(self::TYPE_WELCOME).', '.acym_escapeDB(self::TYPE_UNSUBSCRIBE).')';
142 + }
143 +
144 + if (!empty($settings['creator_id'])) {
145 + $mailTypeCondition = 'mail.type IN ('.acym_escapeDB(self::TYPE_TEMPLATE).', '.acym_escapeDB(self::TYPE_WELCOME).', '.acym_escapeDB(self::TYPE_UNSUBSCRIBE).')';
146 + $userGroups = acym_getGroupsByUser($settings['creator_id']);
147 + $groupCondition = '(mail.access LIKE "%,'.implode(',%" OR mail.access LIKE "%,', $userGroups).',%")';
148 + $filter = 'mail.creator_id = '.intval($settings['creator_id']).' OR ('.$mailTypeCondition.' AND '.$groupCondition.')';
149 +
150 + if (!acym_isAdmin() && !empty($settings['element_tab'])) {
151 + $filter = '('.$filter.')
152 + OR list.cms_user_id = '.intval($settings['creator_id']).'
153 + OR (list.access LIKE "%,'.implode(',%" OR list.access LIKE "%,', $userGroups).',%")';
154 + }
155 +
156 + $filters['list'] = '('.$filter.')';
157 + }
158 + }
159 +
160 + $filters[] = 'mail.parent_id IS NULL';
161 +
162 + if (!empty($settings['drag_editor'])) {
163 + $filters[] = 'mail.drag_editor = 1';
164 + }
165 +
166 + if (!empty($settings['element_tab'])) {
167 + $query = 'SELECT DISTINCT mail.*, mail_stat.sent as subscribers, mail_stat.open_unique FROM #__acym_mail AS mail';
168 + $query .= ' LEFT JOIN #__acym_mail_stat AS mail_stat ON mail.id = mail_stat.mail_id ';
169 + if (!empty($settings['tag'])) {
170 + $query .= ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element ';
171 + }
172 + if (!acym_isAdmin()) {
173 + $query .= ' LEFT JOIN #__acym_list AS list ON list.'.acym_secureDBColumn($settings['element_tab']).'_id = mail.id';
174 + }
175 + $filters[] = 'mail.type = '.acym_escapeDB($settings['element_tab']);
176 + }
177 +
178 + if (!empty($filters)) {
179 + $query .= ' WHERE ('.implode(') AND (', $filters).')';
180 + if (!acym_isAdmin()) {
181 + unset($filters['list']);
182 + }
183 + $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
184 + }
185 +
186 + if (!empty($settings['ordering']) && !empty($settings['ordering_sort_order'])) {
187 + $query .= ' ORDER BY mail.'.acym_secureDBColumn($settings['ordering']).' '.acym_secureDBColumn(strtoupper($settings['ordering_sort_order']));
188 + }
189 +
190 + if (empty($settings['offset']) || $settings['offset'] < 0) {
191 + $settings['offset'] = 0;
192 + }
193 +
194 + if (empty($settings['elementsPerPage']) || $settings['elementsPerPage'] < 1) {
195 + $pagination = new PaginationHelper();
196 + $settings['elementsPerPage'] = $pagination->getListLimit();
197 + }
198 +
199 + $results['elements'] = $this->decode(acym_loadObjectList($query, '', $settings['offset'], $settings['elementsPerPage']));
200 + $results['total'] = acym_loadObject($queryCount);
201 +
202 + if (!empty($settings['element_tab'])) {
203 + $this->getAllListIdsForWelcomeUnsub($results['elements'], $settings['element_tab']);
204 + }
205 +
206 + $results['status'] = [];
207 +
208 + if (!empty($settings['element_tab'])) {
209 + $urlClickClass = new UrlClickClass();
210 + for ($i = 0; $i < count($results['elements']); $i++) {
211 + $results['elements'][$i]->open = 0;
212 + if (!empty($results['elements'][$i]->subscribers)) {
213 + $results['elements'][$i]->open = number_format($results['elements'][$i]->open_unique / $results['elements'][$i]->subscribers * 100, 2);
214 +
215 + $clicksNb = $urlClickClass->getNumberUsersClicked([$results['elements'][$i]->id]);
216 + $results['elements'][$i]->click = number_format($clicksNb / $results['elements'][$i]->subscribers * 100, 2);
217 + }
218 + }
219 + }
220 +
221 + return $results;
222 + }
223 +
224 + private function getAllListIdsForWelcomeUnsub(array $elements, string $type): void
225 + {
226 + if (empty($elements)) {
227 + return;
228 + }
229 +
230 + $column = $type === self::TYPE_WELCOME ? 'welcome_id' : 'unsubscribe_id';
231 +
232 + foreach ($elements as $element) {
233 + $element->lists = acym_loadObjectList('SELECT color, name FROM #__acym_list WHERE '.acym_secureDBColumn($column).' = '.intval($element->id));
234 + }
235 + }
236 +
237 + public function getAll(?string $key = null): array
238 + {
239 + $allMails = parent::getAll($key);
240 +
241 + return $this->decode($allMails);
242 + }
243 +
244 + public function getOneById(int $id, bool $needTranslatedSettings = false): ?object
245 + {
246 + $mail = $this->decode(parent::getOneById($id));
247 +
248 + if (!empty($mail)) {
249 + $tagsClass = new TagClass();
250 + $mail->tags = $tagsClass->getAllTagsByElementId(TagClass::TYPE_MAIL, intval($id));
251 + }
252 +
253 + if (isset($mail->access) && !is_array($mail->access)) $mail->access = explode(',', $mail->access);
254 +
255 + if (!empty($mail->parent_id) && $needTranslatedSettings) {
256 + $this->getTranslatedSettingsMail($mail);
257 + }
258 +
259 + return $mail;
260 + }
261 +
262 + private function getTranslatedSettingsMail(object $mail): void
263 + {
264 + $parentMailTranslation = acym_loadResult('SELECT translation FROM #__acym_mail WHERE id = '.intval($mail->parent_id));
265 + $parentMailTranslation = empty($parentMailTranslation) ? [] : json_decode($parentMailTranslation, true);
266 +
267 + $translationConfig = $this->config->get('sender_info_translation', '');
268 + $translationConfig = empty($translationConfig) ? [] : json_decode($translationConfig, true);
269 +
270 + if (!empty($parentMailTranslation[$mail->language])) {
271 + $senderTranslation = $parentMailTranslation[$mail->language];
272 + } elseif (!empty($translationConfig[$mail->language])) {
273 + $senderTranslation = $translationConfig[$mail->language];
274 + $senderTranslation['reply_to_email'] = $senderTranslation['replyto_email'];
275 + $senderTranslation['reply_to_name'] = $senderTranslation['replyto_name'];
276 + } else {
277 + return;
278 + }
279 +
280 + $mail->from_name = $senderTranslation['from_name'];
281 + $mail->from_email = $senderTranslation['from_email'];
282 + $mail->reply_to_name = $senderTranslation['reply_to_name'];
283 + $mail->reply_to_email = $senderTranslation['reply_to_email'];
284 + }
285 +
286 + public function getOneByName(string $name, bool $needTranslatedSettings = false, string $type = ''): ?object
287 + {
288 + $query = 'SELECT * FROM #__acym_mail WHERE `parent_id` IS NULL AND `name` = '.acym_escapeDB(acym_utf8Encode($name));
289 + if (!empty($type)) {
290 + $query .= ' AND `type` = '.acym_escapeDB($type);
291 + }
292 +
293 + $mail = acym_loadObject($query);
294 +
295 + if (empty($mail)) {
296 + return null;
297 + }
298 +
299 + $mail = $this->decode($mail);
300 +
301 + if (!empty($mail)) {
302 + $tagsClass = new TagClass();
303 + $mail->tags = $tagsClass->getAllTagsByElementId(TagClass::TYPE_MAIL, intval($mail->id));
304 + }
305 +
306 + if (!empty($mail->parent_id) && $needTranslatedSettings) {
307 + $this->getTranslatedSettingsMail($mail);
308 + }
309 +
310 + return $mail;
311 + }
312 +
313 + /**
314 + * Get mails depending on their type (standard, welcome, unsubscribe, notification)
315 + */
316 + public function getMailsByType(?string $typeMail, array $settings): array
317 + {
318 + if (empty($settings['key'])) {
319 + $settings['key'] = '';
320 + }
321 + if (empty($settings['offset'])) {
322 + $settings['offset'] = 0;
323 + }
324 + if (empty($settings['mailsPerPage'])) {
325 + $settings['mailsPerPage'] = 12;
326 + }
327 + $search = $settings['filters']['search'] ?? '';
328 +
329 + $query = 'SELECT * FROM #__acym_mail AS mail';
330 + $queryCount = 'SELECT count(*) FROM #__acym_mail AS mail';
331 +
332 + // Mail type filtering
333 + $filters = [];
334 + if (!empty($typeMail)) {
335 + $filters[] = 'mail.type = '.acym_escapeDB($typeMail);
336 + }
337 +
338 + // Search filter
339 + if (!empty($search)) {
340 + $filters[] = 'mail.name LIKE '.acym_escapeDB('%'.$search.'%');
341 + }
342 +
343 + if (!empty($filters)) {
344 + $query .= ' WHERE ('.implode(') AND (', $filters).')';
345 + $queryCount .= ' WHERE ('.implode(') AND (', $filters).')';
346 + }
347 +
348 + $query .= ' ORDER BY id DESC';
349 +
350 + $results['mails'] = $this->decode(acym_loadObjectList($query, $settings['key'], $settings['offset'], $settings['mailsPerPage']));
351 + $results['total'] = acym_loadResult($queryCount);
352 +
353 + return $results;
354 + }
355 +
356 + public function getAllListsWithCountSubscribersByMailIds(array $ids): array
357 + {
358 + acym_arrayToInteger($ids);
359 + if (empty($ids)) {
360 + return [];
361 + }
362 +
363 + $query = 'SELECT mailLists.list_id, mailLists.mail_id, list.*, COUNT(userLists.user_id) AS subscribers
364 + FROM #__acym_mail_has_list AS mailLists
365 + JOIN #__acym_list AS list ON mailLists.list_id = list.id
366 + LEFT JOIN #__acym_user_has_list AS userLists
367 + JOIN #__acym_user AS acyuser ON userLists.user_id = acyuser.id
368 + AND userLists.status = 1
369 + AND acyuser.active = 1 ';
370 +
371 + if ($this->config->get('require_confirmation', 1) == 1) {
372 + $query .= ' AND acyuser.confirmed = 1 ';
373 + }
374 +
375 + $query .= 'ON list.id = userLists.list_id
376 + WHERE mailLists.mail_id IN ('.implode(',', $ids).')
377 + GROUP BY mailLists.list_id, mailLists.mail_id';
378 +
379 + //This line if for guys with big database to not break the page
380 + acym_query('SET SQL_BIG_SELECTS=1');
381 +
382 + return acym_loadObjectList($query);
383 + }
384 +
385 + public function getAllListsByMailIds(array $ids): array
386 + {
387 + acym_arrayToInteger($ids);
388 + if (empty($ids)) {
389 + return [];
390 + }
391 +
392 + $query = 'SELECT mailLists.mail_id, list.name, list.color
393 + FROM #__acym_mail_has_list AS mailLists
394 + JOIN #__acym_list AS list ON mailLists.list_id = list.id
395 + WHERE mailLists.mail_id IN ('.implode(',', $ids).')';
396 +
397 + return acym_loadObjectList($query);
398 + }
399 +
400 + public function getAllListsByMailId(int $id): array
401 + {
402 + $mail = $this->getOneById($id);
403 + if (empty($mail)) {
404 + return [];
405 + }
406 +
407 + if (self::TYPE_WELCOME === $mail->type) {
408 + $query = 'SELECT * FROM #__acym_list WHERE welcome_id = '.intval($id);
409 + } elseif (self::TYPE_FOLLOWUP === $mail->type) {
410 + $query = 'SELECT list.* FROM #__acym_followup_has_mail AS followup_mail
411 + JOIN #__acym_followup AS followup ON followup.id = followup_mail.followup_id AND followup_mail.mail_id = '.intval($id).'
412 + JOIN #__acym_list AS list ON list.id = followup.list_id';
413 + } else {
414 + $query = 'SELECT list.*
415 + FROM #__acym_mail_has_list AS mailLists
416 + JOIN #__acym_mail AS mail ON (mailLists.mail_id = mail.id OR mailLists.mail_id = mail.parent_id) AND mail.id = '.intval($id).'
417 + JOIN #__acym_list AS list ON mailLists.list_id = list.id';
418 + }
419 +
420 + return acym_loadObjectList($query, 'id');
421 + }
422 +
423 + public function getAllListsByMailIdAndUserId(int $mailId, int $userId): array
424 + {
425 + if (empty($mailId) || empty($userId)) {
426 + return [];
427 + }
428 +
429 + $query = 'SELECT list.* FROM #__acym_list AS list';
430 + $query .= ' JOIN #__acym_user_has_list AS userlist ON list.id = userlist.list_id AND userlist.user_id = '.intval($userId);
431 + $query .= ' JOIN #__acym_mail_has_list AS maillist ON maillist.list_id = list.id AND maillist.mail_id = '.intval($mailId);
432 +
433 + return acym_loadObjectList($query);
434 + }
435 +
436 + public function save(object $element): ?int
437 + {
438 + $mail = clone $element;
439 +
440 + if (isset($mail->tags)) {
441 + $tags = $mail->tags;
442 + unset($mail->tags);
443 + }
444 +
445 + if (empty($mail->id)) {
446 + $mail->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
447 + if (empty($mail->creator_id)) $mail->creator_id = acym_currentUserId();
448 + }
449 +
450 + $mail = $this->encode($mail);
451 +
452 + // Clean autosave value
453 + $mail->autosave = null;
454 +
455 +
456 + // At this point $mail->thumbnail should be only a filename, so we don't want to override it with an empty string or an encoded image
457 + // But we need to reset the thumbnail sometimes by setting the thumbnail to null
458 + if (isset($mail->thumbnail) && ((empty($mail->thumbnail) && !is_null($mail->thumbnail)) || strpos($mail->thumbnail, 'data:image/png;base64') !== false)) {
459 + unset($mail->thumbnail);
460 + }
461 +
462 + if (!isset($mail->access)) {
463 + $mail->access = '';
464 + }
465 +
466 + foreach ($mail as $oneAttribute => $value) {
467 + if (empty($value) || in_array($oneAttribute, ['thumbnail', 'settings'])) {
468 + continue;
469 + }
470 +
471 + if ($oneAttribute === 'access' && is_array($mail->$oneAttribute)) {
472 + $value = ','.trim(implode(',', $mail->$oneAttribute), ',').',';
473 + $mail->$oneAttribute = $value;
474 + }
475 +
476 + if (is_array($value)) {
477 + $mail->$oneAttribute = json_encode($value);
478 + }
479 +
480 + if (in_array($oneAttribute, ['body', 'headers'])) {
481 + $mail->$oneAttribute = preg_replace('#<input[^>]*value="[^"]*"[^>]*>#Uis', '', $mail->$oneAttribute);
482 + $mail->$oneAttribute = preg_replace('#<script.*</script>#Uis', '', $mail->$oneAttribute);
483 +
484 + //Remove tinyMce content edit
485 + $mail->$oneAttribute = str_replace(' contenteditable="true"', '', $mail->$oneAttribute);
486 + } else {
487 + $mail->$oneAttribute = acym_stripTags($mail->$oneAttribute);
488 + }
489 + }
490 +
491 + $mailID = parent::save($mail);
492 +
493 + if (!empty($mailID) && isset($tags)) {
494 + $tagClass = new TagClass();
495 + $tagClass->setTags(TagClass::TYPE_MAIL, intval($mailID), $tags);
496 + }
497 +
498 + return $mailID;
499 + }
500 +
501 + public function autoSave(object $mail, string $language = 'main'): bool
502 + {
503 + if (empty($mail->id)) {
504 + return false;
505 + }
506 +
507 + $mail->autosave = str_replace(' contenteditable="true"', '', $mail->autosave);
508 +
509 + if (acym_isMultilingual() && $language !== 'main') {
510 + $translationId = $this->getTranslationId($mail->id, $language);
511 + if (empty($translationId)) {
512 + $parentCopy = $this->getOneById($mail->id);
513 + if (empty($parentCopy)) {
514 + return false;
515 + }
516 +
517 + unset($parentCopy->id);
518 + $parentCopy->parent_id = $mail->id;
519 + $parentCopy->language = $language;
520 + $translationId = $this->save($parentCopy);
521 + }
522 + $mail->id = $translationId;
523 + }
524 +
525 + $mail = $this->encode($mail);
526 +
527 + return (bool)parent::save($mail);
528 + }
529 +
530 + public function delete(array $elements): int
531 + {
532 + if (empty($elements)) return 0;
533 +
534 + $this->deleteMediaFolder($elements);
535 + acym_arrayToInteger($elements);
536 +
537 + $allThumbnailToDelete = acym_loadResultArray('SELECT DISTINCT thumbnail FROM #__acym_mail WHERE id IN ('.implode(',', $elements).')');
538 +
539 + $translations = acym_loadResultArray('SELECT id FROM #__acym_mail WHERE parent_id IN ('.implode(',', $elements).')');
540 + $elements = array_merge($elements, $translations);
541 + if (!empty($translations)) {
542 + acym_query('UPDATE #__acym_mail SET `parent_id` = null WHERE `id` IN ('.implode(',', $translations).')');
543 + }
544 +
545 + $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
546 + if (in_array($favoriteTemplate, $elements)) {
547 + $this->config->saveConfig(['favorite_template' => 0]);
548 + }
549 +
550 + acym_query('UPDATE #__acym_list SET welcome_id = null WHERE welcome_id IN ('.implode(',', $elements).')');
551 + acym_query('UPDATE #__acym_list SET unsubscribe_id = null WHERE unsubscribe_id IN ('.implode(',', $elements).')');
552 + acym_query('DELETE FROM #__acym_queue WHERE mail_id IN ('.implode(',', $elements).')');
553 + acym_query('DELETE FROM #__acym_mail_has_list WHERE mail_id IN ('.implode(',', $elements).')');
554 + acym_query('DELETE FROM #__acym_tag WHERE `type` = "mail" AND `id_element` IN ('.implode(',', $elements).')');
555 + acym_query('DELETE FROM #__acym_user_stat WHERE mail_id IN ('.implode(',', $elements).')');
556 + acym_query('DELETE FROM #__acym_url_click WHERE mail_id IN ('.implode(',', $elements).')');
557 + acym_query('DELETE FROM #__acym_mail_stat WHERE mail_id IN ('.implode(',', $elements).')');
558 + acym_query('DELETE FROM #__acym_followup_has_mail WHERE mail_id IN ('.implode(',', $elements).')');
559 + acym_query('DELETE FROM #__acym_mail_archive WHERE mail_id IN ('.implode(',', $elements).')');
560 +
561 + $return = parent::delete($elements);
562 +
563 + $this->deleteUnusedThumbnails($allThumbnailToDelete);
564 +
565 + return $return;
566 + }
567 +
568 + public function getFavoriteTemplateId(): int
569 + {
570 + $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
571 + if (empty($favoriteTemplate)) {
572 + return 0;
573 + }
574 +
575 + $exists = acym_loadResult('SELECT `id` FROM #__acym_mail WHERE `id` = '.intval($favoriteTemplate));
576 + if (empty($exists)) {
577 + $this->config->saveConfig(['favorite_template' => 0]);
578 +
579 + return 0;
580 + }
581 +
582 + return $favoriteTemplate;
583 + }
584 +
585 + public function deleteMediaFolder(array $elements): void
586 + {
587 + if (empty($elements)) {
588 + return;
589 + }
590 +
591 + acym_arrayToInteger($elements);
592 + $results = acym_loadResultArray('SELECT mail_settings FROM #__acym_mail WHERE mail_settings IS NOT NULL AND id IN ('.implode(',', $elements).')');
593 +
594 + foreach ($results as $template) {
595 + $settings = json_decode($template, true);
596 +
597 + if (empty($settings['media_folder']) || !file_exists(ACYM_TEMPLATE.$settings['media_folder'])) {
598 + continue;
599 + }
600 + acym_deleteFolder(ACYM_TEMPLATE.$settings['media_folder']);
601 + }
602 + }
603 +
604 + public function deleteUnusedThumbnails(array $thumbnails): void
605 + {
606 + if (empty($thumbnails)) {
607 + return;
608 + }
609 +
610 + foreach ($thumbnails as $key => $oneThumb) {
611 + $thumbnails[$key] = acym_escapeDB($oneThumb);
612 + }
613 +
614 + $stillUsedThumbnails = acym_loadResultArray('SELECT thumbnail FROM #__acym_mail WHERE thumbnail IN ('.implode(',', $thumbnails).')');
615 + $thumbnailToDelete = array_diff($thumbnails, $stillUsedThumbnails);
616 + foreach ($thumbnailToDelete as $one) {
617 + if (!empty($one) && file_exists(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one)) {
618 + acym_deleteFile(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
619 + }
620 + }
621 + }
622 +
623 + // Delete one attachment from a newsletter
624 + public function deleteOneAttachment(int $mailId, int $idAttachment): bool
625 + {
626 + if (empty($mailId)) {
627 + return false;
628 + }
629 +
630 + $mail = $this->getOneById($mailId);
631 +
632 + $attachments = $mail->attachments;
633 + if (empty($attachments)) {
634 + return false;
635 + }
636 + $decodedAttach = json_decode($attachments, true);
637 + unset($decodedAttach[$idAttachment]);
638 + $newAttachments = [];
639 + if (!empty($decodedAttach)) {
640 + foreach ($decodedAttach as $oneAttach) {
641 + $newAttachments[] = $oneAttach;
642 + }
643 + }
644 + $attachDb = json_encode($newAttachments);
645 +
646 + return (bool)acym_query('UPDATE #__acym_mail SET attachments = '.acym_escapeDB($attachDb).' WHERE id = '.intval($mailId).' OR parent_id = '.intval($mailId));
647 + }
648 +
649 + public function createTemplateFile(int $id): string
650 + {
651 + if (empty($id)) {
652 + return '';
653 + }
654 +
655 + $template = $this->getOneById($id);
656 + if (empty($template->id)) {
657 + return '';
658 + }
659 +
660 + $css = $this->buildCSS($template->stylesheet);
661 + if (empty($css)) {
662 + return '';
663 + }
664 +
665 + acym_createDir(ACYM_TEMPLATE.'css');
666 +
667 + $cssfile = ACYM_TEMPLATE.'css'.DS.'template_'.$id.'.css';
668 + if (acym_writeFile($cssfile, $css)) {
669 + return $cssfile;
670 + } else {
671 + acym_enqueueMessage('Could not create the file '.$cssfile, 'error');
672 +
673 + return '';
674 + }
675 + }
676 +
677 + public function buildCSS(?string $stylesheet): string
678 + {
679 + if (empty($stylesheet)) {
680 + return '';
681 + }
682 +
683 + $inline = '';
684 +
685 + if (preg_match_all('#@import[^;]*;#is', $stylesheet, $results)) {
686 + foreach ($results[0] as $oneResult) {
687 + //We add the @import CSS at the very beginning for the CSS stylesheet otherwise it does not work
688 + $inline .= trim($oneResult)."\n";
689 + //We also remove it from the stylesheet to avoid having a duplicate.
690 + $stylesheet = str_replace($oneResult, '', $stylesheet);
691 + }
692 + }
693 +
694 + $inline .= $stylesheet;
695 +
696 + return $inline;
697 + }
698 +
699 + public function doupload(): bool
700 + {
701 + $zipFilePath = $this->uploadTemplate();
702 + if (empty($zipFilePath)) {
703 + return false;
704 + }
705 +
706 + $templateFolder = $this->extractTemplate($zipFilePath);
707 + if (empty($templateFolder)) {
708 + return false;
709 + }
710 +
711 + if (!$this->installExtractedTemplate($templateFolder)) {
712 + return false;
713 + }
714 +
715 + return true;
716 + }
717 +
718 + private function uploadTemplate(): ?string
719 + {
720 + $importFile = acym_getVar('file', 'uploadedfile', '', 'files');
721 +
722 + $fileError = $importFile['error'];
723 + if ($fileError > 0) {
724 + switch ($fileError) {
725 + case 1:
726 + acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_1'), 'error');
727 +
728 + return null;
729 + case 2:
730 + acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_2'), 'error');
731 +
732 + return null;
733 + case 3:
734 + acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_3'), 'error');
735 +
736 + return null;
737 + case 4:
738 + acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_4'), 'error');
739 +
740 + return null;
741 + default:
742 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_5', $fileError), 'error');
743 +
744 + return null;
745 + }
746 + }
747 +
748 + if (empty($importFile['name'])) {
749 + acym_enqueueMessage(acym_translation('ACYM_BROWSE_FILE'), 'error');
750 +
751 + return null;
752 + }
753 +
754 + $uploadPath = acym_cleanPath(ACYM_ROOT.ACYM_MEDIA_FOLDER.'templates');
755 +
756 + if (!acym_isWritable($uploadPath)) {
757 + acym_chmod($uploadPath, 0755);
758 + if (!acym_isWritable($uploadPath)) {
759 + acym_enqueueMessage(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'warning');
760 + }
761 + }
762 +
763 + if (!(bool)ini_get('file_uploads')) {
764 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_6'), 'error');
765 +
766 + return null;
767 + }
768 +
769 + if (!extension_loaded('zlib')) {
770 + acym_raiseError(500, acym_translation('ACYM_MISSING_ZLIB'));
771 +
772 + return null;
773 + }
774 +
775 + $filename = strtolower(acym_makeSafeFile($importFile['name']));
776 + $extension = strtolower(substr($filename, strrpos($filename, '.') + 1));
777 +
778 + $allowedFileType = true;
779 + if ($extension !== 'zip') {
780 + $allowedFileType = false;
781 + } elseif (!in_array($importFile['type'], ['application/zip', 'application/x-zip-compressed'])) {
782 + $allowedFileType = false;
783 + } elseif (function_exists('mime_content_type') && !in_array(mime_content_type($importFile['tmp_name']), ['application/zip', 'application/x-zip-compressed'])) {
784 + $allowedFileType = false;
785 + }
786 +
787 + if (!$allowedFileType) {
788 + acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', esc_html($extension), 'zip'), 'error');
789 +
790 + return null;
791 + }
792 +
793 + if (class_exists('ZipArchive')) {
794 + $zip = new \ZipArchive();
795 + if (filesize($importFile['tmp_name']) > self::MAX_ALLOWED_TEMPLATE_SIZE || !$zip->open($importFile['tmp_name']) || $zip->count() > self::MAX_ALLOWED_TEMPLATE_FILES) {
796 + acym_enqueueMessage(acym_translation('ACYM_UPLOADED_ARCHIVE_NOT_ALLOWED'), 'error');
797 +
798 + return null;
799 + }
800 +
801 + $zip->close();
802 + }
803 +
804 + $tmpPath = acym_getCMSConfig('tmp_path', ACYM_MEDIA.'tmp'.DS);
805 + $zipFilePath = acym_cleanPath($tmpPath.DS.$filename);
806 +
807 + $uploaded = acym_uploadFile($importFile['tmp_name'], $zipFilePath);
808 + if (!$uploaded) {
809 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_7', $importFile['tmp_name'], $zipFilePath), 'error');
810 +
811 + return null;
812 + }
813 +
814 + return $zipFilePath;
815 + }
816 +
817 + private function extractTemplate(string $zipFilePath, bool $deleteZip = true): ?string
818 + {
819 + $extractdir = acym_cleanPath(dirname($zipFilePath).DS.uniqid().uniqid().uniqid().'_template');
820 +
821 + $result = acym_extractArchive($zipFilePath, $extractdir);
822 +
823 + if (!$result) {
824 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_9', $zipFilePath, $extractdir), 'error');
825 +
826 + return null;
827 + }
828 +
829 + if ($deleteZip) {
830 + acym_deleteFile($zipFilePath);
831 + }
832 +
833 + $allFiles = acym_getFiles($extractdir, '.', true, true, [], []);
834 + foreach ($allFiles as $oneFile) {
835 + if (function_exists('mime_content_type')) {
836 + $fileType = mime_content_type($oneFile);
837 + if (in_array($fileType, self::ALLOWED_TEMPLATE_FILE_TYPES) && preg_match('#\.(jpg|gif|png|jpeg|ico|bmp|html|htm|css|webp)$#i', $oneFile)) {
838 + continue;
839 + }
840 + } elseif (preg_match('#\.(jpg|gif|png|jpeg|ico|bmp|html|htm|css|webp)$#i', $oneFile)) {
841 + continue;
842 + }
843 +
844 + if (acym_deleteFile($oneFile)) {
845 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_8', $oneFile), 'warning');
846 + }
847 + }
848 +
849 + return $extractdir;
850 + }
851 +
852 + public function installExtractedTemplate(string $templateFolder, bool $deleteFolder = true): bool
853 + {
854 + if ($this->detectTemplates($templateFolder)) {
855 + $messages = $this->templateNames;
856 + array_unshift($messages, acym_translationSprintf('ACYM_TEMPLATES_INSTALL', count($this->templateNames)));
857 + acym_enqueueMessage($messages);
858 + if ($deleteFolder && is_dir($templateFolder)) {
859 + acym_deleteFolder($templateFolder);
860 + }
861 +
862 + return true;
863 + }
864 +
865 + acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_10'), 'error');
866 + if ($deleteFolder && is_dir($templateFolder)) {
867 + acym_deleteFolder($templateFolder);
868 + }
869 +
870 + return false;
871 + }
872 +
873 + public function detectTemplates(string $folder): bool
874 + {
875 + $allFiles = acym_getFiles($folder);
876 + if (!empty($allFiles)) {
877 + // Search and install template.html file first
878 + $keyTmpl = array_search('template.html', $allFiles);
879 + if ($keyTmpl !== false) {
880 + if ($this->installTemplate($folder.DS.$allFiles[$keyTmpl])) {
881 + return true;
882 + }
883 + }
884 + // Try to install first html file found
885 + foreach ($allFiles as $oneFile) {
886 + if (preg_match('#^.*(html|htm)$#i', $oneFile)) {
887 + if ($this->installTemplate($folder.DS.$oneFile)) {
888 + return true;
889 + }
890 + }
891 + }
892 + }
893 +
894 + $status = false;
895 + $allFolders = acym_getFolders($folder);
896 + if (!empty($allFolders)) {
897 + foreach ($allFolders as $oneFolder) {
898 + $status = $this->detectTemplates($folder.DS.$oneFolder) || $status;
899 + }
900 + }
901 +
902 + return $status;
903 + }
904 +
905 + private function installTemplate(string $filepath): bool
906 + {
907 + $fileContent = acym_fileGetContent($filepath);
908 +
909 + $newTemplate = new \stdClass();
910 +
911 + if (strpos($filepath, '_template') === false) {
912 + $newTemplate->name = basename(dirname($filepath));
913 + } else {
914 + $newTemplate->name = basename($filepath);
915 + $newTemplate->name = substr($newTemplate->name, 0, strrpos($newTemplate->name, '.'));
916 + }
917 +
918 + $newTemplate->name = trim(preg_replace('#[^a-z0-9]#i', ' ', $newTemplate->name));
919 +
920 + if (preg_match('#< *title[^>]*>(.*)< */ *title *>#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->name = $results[1];
921 +
922 + if (preg_match('#< *meta *name="fromname" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromname = $results[1];
923 + if (preg_match('#< *meta *name="fromemail" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromemail = $results[1];
924 + if (preg_match('#< *meta *name="replyname" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->replyname = $results[1];
925 + if (preg_match('#< *meta *name="replyemail" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->replyemail = $results[1];
926 + if (preg_match('#< *meta *name="subject" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->subject = $results[1];
927 + if (preg_match('#< *meta *name="settings" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) {
928 + $newTemplate->settings = htmlspecialchars_decode($results[1]);
929 + }
930 +
931 + $newFolder = preg_replace('#[^a-z0-9]#i', '_', strtolower($newTemplate->name));
932 + $newTemplateFolder = $newFolder;
933 + $i = 1;
934 + while (is_dir(ACYM_TEMPLATE.$newTemplateFolder)) {
935 + $newTemplateFolder = $newFolder.'_'.$i;
936 + $i++;
937 + }
938 + $newTemplate->mail_settings = ['media_folder' => $newTemplateFolder];
939 +
940 + $moveResult = acym_copyFolder(dirname($filepath), ACYM_TEMPLATE.$newTemplateFolder);
941 + if ($moveResult !== true) {
942 + acym_display([acym_translationSprintf('ACYM_ERROR_COPYING_FOLDER_TO', dirname($filepath), ACYM_TEMPLATE.$newTemplateFolder), $moveResult], 'error');
943 +
944 + return false;
945 + }
946 +
947 + if (!file_exists(ACYM_TEMPLATE.$newTemplateFolder.DS.'index.html')) {
948 + $indexFile = '<html><body bgcolor="#FFFFFF"></body></html>';
949 + acym_writeFile(ACYM_TEMPLATE.$newTemplateFolder.DS.'index.html', $indexFile);
950 + }
951 +
952 + $fileContent = str_replace(
953 + [
954 + 'src="./',
955 + 'src="../',
956 + 'src="images/',
957 + 'url("images/',
958 + 'url(&quot;images/',
959 + ],
960 + [
961 + 'src="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/',
962 + 'src="'.ACYM_TEMPLATE_URL,
963 + 'src="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
964 + 'url("'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
965 + 'url(&quot;'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
966 + ],
967 + $fileContent
968 + );
969 +
970 + $fileContent = preg_replace('#(src|background)[ ]*=[ ]*\"(?!(https?://|/))(?:\.\./|\./)?#', '$1="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/', $fileContent);
971 +
972 + if (preg_match('#< *body[^>]*>(.*)< */ *body *>#Uis', $fileContent, $results)) {
973 + $newTemplate->body = $results[1];
974 + } else {
975 + $newTemplate->body = $fileContent;
976 + }
977 +
978 + $newTemplate->stylesheet = '';
979 + if (preg_match_all('#< *style[^>]*>(.*)< */ *style *>#Uis', $fileContent, $results)) {
980 + $newTemplate->stylesheet .= preg_replace('#(<!--|-->)#s', '', implode("\n", $results[1]));
981 + }
982 + $cssFiles = [];
983 + $cssFiles[ACYM_TEMPLATE.$newTemplateFolder] = acym_getFiles(ACYM_TEMPLATE.$newTemplateFolder, '\.css$');
984 + $subFolders = acym_getFolders(ACYM_TEMPLATE.$newTemplateFolder);
985 + foreach ($subFolders as $oneFolder) {
986 + $cssFiles[ACYM_TEMPLATE.$newTemplateFolder.DS.$oneFolder] = acym_getFiles(ACYM_TEMPLATE.$newTemplateFolder.DS.$oneFolder, '\.css$');
987 + }
988 +
989 + foreach ($cssFiles as $cssFolder => $cssFile) {
990 + if (empty($cssFile)) continue;
991 + $newTemplate->stylesheet .= "\n".acym_fileGetContent($cssFolder.DS.reset($cssFile));
992 + }
993 +
994 + if (!empty($newTemplate->stylesheet)) {
995 + if (preg_match('#body *\{[^\}]*background-color:([^;\}]*)[;\}]#Uis', $newTemplate->stylesheet, $backgroundresults)) {
996 + $newTemplate->stylesheet = preg_replace('#(body *\{[^\}]*)background-color:[^;\}]*[;\}]#Uis', '$1', $newTemplate->stylesheet);
997 + }
998 +
999 + $quickstyle = [
1000 + 'tag_h1' => 'h1',
1001 + 'tag_h2' => 'h2',
1002 + 'tag_h3' => 'h3',
1003 + 'tag_h4' => 'h4',
1004 + 'tag_h5' => 'h5',
1005 + 'tag_h6' => 'h6',
1006 + 'tag_a' => 'a',
1007 + 'tag_ul' => 'ul',
1008 + 'tag_li' => 'li',
1009 + 'acym_unsub' => '\.acym_unsub',
1010 + 'acym_online' => '\.acym_online',
1011 + 'acym_title' => '\.acym_title',
1012 + 'acym_content' => '\.acym_content',
1013 + 'acym_readmore' => '\.acym_readmore',
1014 + ];
1015 + foreach ($quickstyle as $styledb => $oneStyle) {
1016 + if (preg_match('#[^a-z\. ,] *'.$oneStyle.' *{([^}]*)}#Uis', $newTemplate->stylesheet, $quickstyleresults)) {
1017 + $newTemplate->stylesheet = str_replace($quickstyleresults[0], '', $newTemplate->stylesheet);
1018 + }
1019 + }
1020 + }
1021 +
1022 + $foldersForPicts = [$newTemplateFolder];
1023 + $otherFolders = acym_getFolders(ACYM_TEMPLATE.$newTemplateFolder);
1024 + foreach ($otherFolders as $oneFold) {
1025 + $foldersForPicts[] = $newTemplateFolder.DS.$oneFold;
1026 + }
1027 + $allPictures = [];
1028 + foreach ($foldersForPicts as $oneFolder) {
1029 + $allPictures[$oneFolder] = acym_getFiles(ACYM_TEMPLATE.$oneFolder);
1030 + }
1031 +
1032 + $uploadsFolder = ACYM_UPLOAD_FOLDER_THUMBNAIL;
1033 +
1034 + $thumbNb = intval($this->config->get('numberThumbnail', 2));
1035 +
1036 + foreach ($allPictures as $folder => $pictfolders) {
1037 + foreach ($pictfolders as $onePict) {
1038 + if (!preg_match('#\.(jpg|gif|png|jpeg|ico|bmp)$#i', $onePict)) continue;
1039 + if (preg_match('#(thumbnail|screenshot|muestra)#i', $onePict)) {
1040 + $thumbNb++;
1041 + $newNamePict = 'thumbnail_'.$thumbNb.'.png';
1042 + copy(ACYM_TEMPLATE.str_replace(DS, '/', $folder).'/'.$onePict, $uploadsFolder.$newNamePict);
1043 + $newTemplate->thumbnail = $newNamePict;
1044 + }
1045 + }
1046 + }
1047 +
1048 + $this->config->saveConfig(['numberThumbnail' => $thumbNb]);
1049 +
1050 + $newTemplate->drag_editor = strpos($newTemplate->body, 'acym__wysid__template__content') !== false ? 1 : 0;
1051 + $newTemplate->type = self::TYPE_TEMPLATE;
1052 + $newTemplate->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1053 +
1054 + $this->save($newTemplate);
1055 +
1056 + $this->templateNames[] = $newTemplate->name;
1057 +
1058 + return true;
1059 + }
1060 +
1061 + public function sendAutomation(int $mailId, array $userIds, string $sendingDate, array $automationAdmin = [])
1062 + {
1063 + if (empty($mailId)) {
1064 + return acym_translationSprintf('ACYM_EMAILS_ADDED_QUEUE', 0);
1065 + }
1066 +
1067 + if (empty($sendingDate)) {
1068 + return acym_translation('ACYM_WRONG_DATE');
1069 + }
1070 +
1071 + if (empty($userIds)) {
1072 + return acym_translation('ACYM_USER_NOT_FOUND');
1073 + }
1074 +
1075 + acym_arrayToInteger($userIds);
1076 +
1077 + $nbSent = $this->sendNotification($mailId, $userIds, $automationAdmin);
1078 + if (!is_null($nbSent)) {
1079 + return $nbSent;
1080 + }
1081 +
1082 + $result = acym_query(
1083 + 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`)
1084 + SELECT '.intval($mailId).', user.id, '.acym_escapeDB($sendingDate).'
1085 + FROM #__acym_user AS user
1086 + WHERE user.active = 1 AND user.id IN ('.implode(',', $userIds).')'
1087 + );
1088 +
1089 + if ($result === 0) {
1090 + return acym_translation('ACYM_CAMPAIGN_ALREADY_QUEUED');
1091 + }
1092 +
1093 + $mailStatClass = new MailStatClass();
1094 + $mailStat = $mailStatClass->getOneRowByMailId($mailId);
1095 +
1096 + $newMailStat = new \stdClass();
1097 + $newMailStat->mail_id = intval($mailId);
1098 + $newMailStat->total_subscribers = intval($result);
1099 + if (empty($mailStat)) {
1100 + $newMailStat->send_date = $sendingDate;
1101 + }
1102 +
1103 + $mailStatClass->save($newMailStat);
1104 +
1105 + return $result;
1106 + }
1107 +
1108 + private function sendNotification(int $mailId, array $userIds, array $automationAdmin): ?int
1109 + {
1110 + if (empty($automationAdmin['user_id'])) {
1111 + return null;
1112 + }
1113 +
1114 + $userClass = new UserClass();
1115 + $mail = $this->getOneById($mailId);
1116 + $user = $userClass->getOneById($automationAdmin['user_id']);
1117 +
1118 + if (empty($mail) || empty($user)) {
1119 + return 0;
1120 + }
1121 +
1122 + // Get the current user values
1123 + $mailerHelper = new MailerHelper();
1124 + $pluginHelper = new PluginHelper();
1125 + $extractedTags = $pluginHelper->extractTags($mail, 'subscriber');
1126 +
1127 + $isNotification = false;
1128 + foreach ($extractedTags as $dtext => $oneTag) {
1129 + if (empty($oneTag->info) || $oneTag->info !== 'current') {
1130 + continue;
1131 + }
1132 +
1133 + $isNotification = true;
1134 +
1135 + if (!empty($user->{$oneTag->id})) {
1136 + $mailerHelper->addParam(str_replace(['{', '}'], '', $dtext), $user->{$oneTag->id});
1137 + }
1138 + }
1139 +
1140 + if (!$isNotification) {
1141 + if (isset($automationAdmin['automationAdmin']) && $automationAdmin['automationAdmin']) {
1142 + return 0;
1143 + } else {
1144 + return null;
1145 + }
1146 + }
1147 +
1148 + $userFields = $userClass->getAllUserFields($user);
1149 + foreach ($userFields as $map => $value) {
1150 + $mailerHelper->addParam('subscriber:'.$map.'|info:current', $value);
1151 + // Might not be used anymore
1152 + $mailerHelper->addParam('user:'.$map, $value);
1153 + }
1154 +
1155 + $emailsSent = 0;
1156 + foreach ($userIds as $userId) {
1157 + if ($mailerHelper->sendOne($mail->id, $userId)) {
1158 + $emailsSent++;
1159 + }
1160 + }
1161 +
1162 + return $emailsSent;
1163 + }
1164 +
1165 + public function encode($mails = [])
1166 + {
1167 + $isArray = true;
1168 + if (!is_array($mails)) {
1169 + $mails = [$mails];
1170 +
1171 + $isArray = false;
1172 + }
1173 +
1174 + $encodedMails = array_map([$this, 'removePoweredByAcyMailing'], $mails);
1175 +
1176 + $encodedMails = array_map([$this, 'utf8Encode'], $encodedMails);
1177 +
1178 + return $isArray ? $encodedMails : $encodedMails[0];
1179 + }
1180 +
1181 + public function decode($mails = [])
1182 + {
1183 + if (is_null($mails)) {
1184 + return null;
1185 + }
1186 +
1187 + $isArray = true;
1188 + if (!is_array($mails)) {
1189 + $mails = [$mails];
1190 +
1191 + $isArray = false;
1192 + }
1193 +
1194 + $decodedMails = array_map([$this, 'utf8Decode'], $mails);
1195 +
1196 + $decodedMails = array_map([$this, 'addPoweredByAcyMailing'], $decodedMails);
1197 +
1198 + foreach ($decodedMails as $i => $oneMail) {
1199 + if (!isset($oneMail->access) || is_array($oneMail->access)) continue;
1200 + $decodedMails[$i]->access = empty($oneMail->access) ? '' : explode(',', $oneMail->access);
1201 + }
1202 +
1203 + return $isArray ? $decodedMails : $decodedMails[0];
1204 + }
1205 +
1206 + /**
1207 + * Decode one mail from UTF8. (decode only attributes defined in $fieldsToDecode)
1208 + */
1209 + protected function utf8Decode($mail)
1210 + {
1211 + if (!empty($mail)) {
1212 + foreach (self::FIELDS_ENCODING as $oneField) {
1213 + if (in_array($oneField, $this->exceptKeysDecode)) {
1214 + continue;
1215 + }
1216 +
1217 + if (is_array($mail)) {
1218 + if (empty($mail[$oneField])) continue;
1219 + $value = &$mail[$oneField];
1220 + } else {
1221 + if (empty($mail->$oneField)) continue;
1222 + $value = &$mail->$oneField;
1223 + }
1224 +
1225 + $value = acym_utf8Decode($value);
1226 + }
1227 + }
1228 +
1229 + return $mail;
1230 + }
1231 +
1232 + private function addPoweredByAcyMailing(object $mail): object
1233 + {
1234 + if (empty($mail->body)) {
1235 + return $mail;
1236 + }
1237 +
1238 + //If we don't display the powered by we remove it
1239 + if ($this->config->get('display_built_by', 0) != 1) {
1240 + if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1241 + $mailBodyDom = new \DOMDocument();
1242 + @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1243 + $tables = $mailBodyDom->getElementsByTagName('table');
1244 + foreach ($tables as $table) {
1245 + if ($table->getAttribute('id') != 'acym__powered_by_acymailing') continue;
1246 + $table->parentNode->removeChild($table);
1247 + break;
1248 + }
1249 + $mail->body = $mailBodyDom->saveHTML();
1250 + }
1251 +
1252 + return $mail;
1253 + }
1254 +
1255 + if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1256 + return $mail;
1257 + }
1258 +
1259 + $isWysidEditor = strpos($mail->body, 'acym__wysid__template') !== false;
1260 + $urlPoweredByImage = ACYM_IMAGES.'editor/poweredby_black.png';
1261 +
1262 + if (!$isWysidEditor) {
1263 + $poweredByHTML = '<p id="acym__powered_by_acymailing">';
1264 + $poweredByHTML .= '<a href="'.ACYM_ACYMAILING_WEBSITE.'?utm_campaign=powered_by_v7&utm_source=acymailing_plugin&utm_medium=built_with_footer" target="blank">';
1265 + $poweredByHTML .= '<img alt="Email built with AcyMailing" height="40" width="199" style="height: 40px; width:199px; max-width: 100%; height: auto; box-sizing: border-box; padding: 0 5px; display: block; margin-left: auto; margin-right: auto;" src="'.$urlPoweredByImage.'"/>';
1266 + $poweredByHTML .= '</a></p>';
1267 +
1268 + $mail->body = $mail->body.$poweredByHTML;
1269 + } else {
1270 + $poweredByWYSID = '<table id="acym__powered_by_acymailing" class="row" bgcolor="#ffffff" style="background-color: transparent" cellpadding="0" cellspacing="0" border="0">
1271 + <tbody bgcolor style="background-color: inherit;">
1272 + <tr>
1273 + <th class="small-12 medium-12 large-12 columns" valign="top" style="height: auto;">
1274 + <table border="0" cellpadding="0" cellspacing="0"
1275 + style="min-height: 0px; display: table; height: auto;">
1276 + <tbody style="min-height: 0px; display: table-row-group;">
1277 + <tr
1278 + style="position: relative; top: inherit; left: inherit; right: inherit; bottom: inherit; height: auto;">
1279 + <td class="large-12">
1280 + <div style="position: relative;">
1281 + <p style="word-break: break-word; text-align: center;">
1282 + <a href="'.ACYM_ACYMAILING_WEBSITE.'?utm_campaign=powered_by_v7&utm_source=acymailing_plugin&utm_medium=built_with_footer" target="_blank">
1283 + <img src="'.$urlPoweredByImage.'"
1284 + title="poweredby" alt="Email built with AcyMailing"
1285 + style="height: 40px; width:199px; max-width: 100%; height: auto; box-sizing: border-box; padding: 0px 5px; display: inline-block; margin-left: auto; margin-right: auto;"
1286 + height="40" width="199">
1287 + </a>
1288 + </p>
1289 + </div>
1290 + </td>
1291 + </tr>
1292 + </tbody>
1293 + </table>
1294 + </th>
1295 + </tr>
1296 + </tbody>
1297 +</table>';
1298 +
1299 + $mailBodyDom = new \DOMDocument();
1300 + //Some inserted content add specific tags that shows warnings
1301 + @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1302 +
1303 + $htmlToAddDom = new \DOMDocument();
1304 + @$htmlToAddDom->loadHTML('<?xml encoding="utf-8" ?>'.$poweredByWYSID);
1305 + $tableToAdd = $htmlToAddDom->getElementById('acym__powered_by_acymailing');
1306 +
1307 + $tds = $mailBodyDom->getElementsByTagName('td');
1308 + foreach ($tds as $td) {
1309 + $classes = explode(' ', $td->getAttribute('class'));
1310 + if (!in_array('acym__wysid__row', $classes)) continue;
1311 + if (!empty($tableToAdd)) $td->appendChild($mailBodyDom->importNode($tableToAdd, true));
1312 + break;
1313 + }
1314 + $mail->body = $mailBodyDom->saveHTML();
1315 + }
1316 +
1317 + return $mail;
1318 + }
1319 +
1320 + protected function removePoweredByAcyMailing(object $mail): object
1321 + {
1322 + if (empty($mail->body) || strpos($mail->body, 'acym__wysid__template') !== false) {
1323 + return $mail;
1324 + }
1325 +
1326 + $regexPoweredByAcyMailing = '#<p id="acym__powered_by_acymailing.*<\/p>#U';
1327 +
1328 + $mail->body = preg_replace($regexPoweredByAcyMailing, '', $mail->body);
1329 +
1330 + return $mail;
1331 + }
1332 +
1333 + /**
1334 + * Encode one mail in UTF8 for handling specific characters as emoji. (encode only attributes defined in $fieldsToEncode)
1335 + *
1336 + * @param $mail
1337 + *
1338 + * @return mixed
1339 + */
1340 + protected function utf8Encode($mail)
1341 + {
1342 + if (!empty($mail)) {
1343 + foreach (self::FIELDS_ENCODING as $oneField) {
1344 + if (is_array($mail)) {
1345 + if (empty($mail[$oneField])) continue;
1346 + $value = &$mail[$oneField];
1347 + } else {
1348 + if (empty($mail->$oneField)) continue;
1349 + $value = &$mail->$oneField;
1350 + }
1351 +
1352 + $value = acym_utf8Encode($value);
1353 + }
1354 + }
1355 +
1356 + return $mail;
1357 + }
1358 +
1359 + public function getTranslationId(int $parentId, string $langCode): int
1360 + {
1361 + return (int)acym_loadResult(
1362 + 'SELECT `id`
1363 + FROM #__acym_mail
1364 + WHERE `parent_id` = '.intval($parentId).'
1365 + AND `language` = '.acym_escapeDB($langCode)
1366 + );
1367 + }
1368 +
1369 + public function getTranslationsById(int $mailId, bool $full = false, bool $includeParent = false)
1370 + {
1371 + $data = $full ? '*' : '`language`, `subject`, `preheader`, `body`, `autosave`';
1372 + $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1373 +
1374 + return $this->decode(
1375 + acym_loadObjectList(
1376 + 'SELECT '.$data.'
1377 + FROM #__acym_mail
1378 + WHERE `parent_id` = '.intval($mailId).$where,
1379 + 'language'
1380 + )
1381 + );
1382 + }
1383 +
1384 + public function deleteByTranslationLang(array $languageCodes): void
1385 + {
1386 + if (empty($languageCodes)) {
1387 + return;
1388 + }
1389 +
1390 + foreach ($languageCodes as $key => $oneLangCode) {
1391 + $languageCodes[$key] = acym_escapeDB($oneLangCode);
1392 + }
1393 +
1394 + $this->delete(
1395 + acym_loadResultArray(
1396 + 'SELECT `id`
1397 + FROM #__acym_mail
1398 + WHERE `parent_id` IS NOT NULL
1399 + AND `language` IN ('.implode(', ', $languageCodes).')'
1400 + )
1401 + );
1402 + }
1403 +
1404 + /**
1405 + * Get all multilingual mails linked to a parent mail, also get the parent mail
1406 + */
1407 + public function getMultilingualMails(int $parentId): array
1408 + {
1409 + $mails = $this->decode(
1410 + acym_loadObjectList(
1411 + 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
1412 + 'language'
1413 + )
1414 + );
1415 +
1416 + return $this->translateMailSettings($mails);
1417 + }
1418 +
1419 + private function translateMailSettings(array $mails): array
1420 + {
1421 + if (empty($mails)) {
1422 + return $mails;
1423 + }
1424 +
1425 + $defaultLanguage = $this->config->get('multilingual_default');
1426 +
1427 + if (empty($mails[$defaultLanguage])) {
1428 + $firstMail = reset($mails);
1429 + if (empty($firstMail) || empty($firstMail->parent_id)) {
1430 + return $mails;
1431 + }
1432 + $mainMail = $this->getOneById($firstMail->parent_id);
1433 + } else {
1434 + $mainMail = $mails[$defaultLanguage];
1435 + }
1436 +
1437 + if (!empty($mainMail->translation)) {
1438 + $translation = json_decode($mainMail->translation, true);
1439 +
1440 + foreach ($mails as $lang => $mail) {
1441 + if (empty($mail->parent_id) || empty($translation[$lang])) {
1442 + continue;
1443 + }
1444 +
1445 + if (!empty($translation[$lang]['from_name'])) $mail->from_name = $translation[$lang]['from_name'];
1446 + if (!empty($translation[$lang]['from_email'])) $mail->from_email = $translation[$lang]['from_email'];
1447 + if (!empty($translation[$lang]['reply_to_name'])) $mail->reply_to_name = $translation[$lang]['reply_to_name'];
1448 + if (!empty($translation[$lang]['reply_to_email'])) $mail->reply_to_email = $translation[$lang]['reply_to_email'];
1449 + }
1450 + }
1451 +
1452 + return $mails;
1453 + }
1454 +
1455 + public function getMailAttachments(int $mailId)
1456 + {
1457 + return acym_loadResult('SELECT attachments FROM #__acym_mail WHERE id = '.intval($mailId));
1458 + }
1459 +
1460 + public function isTransactionalMail(object $mail): bool
1461 + {
1462 + return $mail->type !== self::TYPE_STANDARD;
1463 + }
1464 +
1465 + public function isOneTimeMail(object $mail): bool
1466 + {
1467 + return in_array(
1468 + $mail->type,
1469 + [
1470 + self::TYPE_NOTIFICATION,
1471 + self::TYPE_OVERRIDE,
1472 + self::TYPE_WELCOME,
1473 + self::TYPE_UNSUBSCRIBE,
1474 + self::TYPE_TEMPLATE,
1475 + ]
1476 + );
1477 + }
1478 +
1479 + public function getAutomaticMailIds(array $mailIds): array
1480 + {
1481 + acym_arrayToInteger($mailIds);
1482 +
1483 + $query = 'SELECT DISTINCT campaign2.mail_id
1484 + FROM #__acym_campaign AS campaign1
1485 + JOIN #__acym_campaign AS campaign2 ON campaign1.id = campaign2.parent_id
1486 + WHERE campaign1.mail_id IN ('.implode(',', $mailIds).')';
1487 + $generatedMailIds = acym_loadResultArray($query);
1488 +
1489 + if (empty($generatedMailIds)) return [];
1490 +
1491 + acym_arrayToInteger($generatedMailIds);
1492 +
1493 + return $generatedMailIds;
1494 + }
1495 +
1496 + public function getMultilingualMailIds(array $mailIds): array
1497 + {
1498 + acym_arrayToInteger($mailIds);
1499 +
1500 + $query = 'SELECT DISTINCT mail2.id
1501 + FROM #__acym_mail AS mail1
1502 + JOIN #__acym_mail AS mail2 ON mail1.id = mail2.parent_id
1503 + WHERE mail1.id IN ('.implode(',', $mailIds).')';
1504 +
1505 + $multipleMailIds = acym_loadResultArray($query);
1506 +
1507 + if (empty($multipleMailIds)) return [];
1508 +
1509 + acym_arrayToInteger($multipleMailIds);
1510 +
1511 + return $multipleMailIds;
1512 + }
1513 +
1514 + public function duplicateMail(int $mailId, ?string $type = null): ?object
1515 + {
1516 + $mail = $this->getOneById($mailId);
1517 +
1518 + if (empty($mail)) {
1519 + return null;
1520 + }
1521 +
1522 + $newMail = new \stdClass();
1523 + $newMail->name = $mail->name.'_copy';
1524 + $newMail->thumbnail = '';
1525 + $newMail->type = empty($type) ? $mail->type : $type;
1526 + $newMail->drag_editor = $mail->drag_editor;
1527 + $newMail->body = $mail->body;
1528 + $newMail->subject = $mail->subject;
1529 + $newMail->from_name = $mail->from_name;
1530 + $newMail->from_email = $mail->from_email;
1531 + $newMail->reply_to_name = $mail->reply_to_name;
1532 + $newMail->reply_to_email = $mail->reply_to_email;
1533 + $newMail->bcc = $mail->bcc;
1534 + $newMail->settings = $mail->settings;
1535 + $newMail->stylesheet = $mail->stylesheet;
1536 + $newMail->attachments = $mail->attachments;
1537 + $newMail->headers = $mail->headers;
1538 + $newMail->preheader = $mail->preheader;
1539 +
1540 + $newMail->id = $this->save($newMail);
1541 +
1542 + return $newMail;
1543 + }
1544 +
1545 + public function getTemplatesForMailboxAction(): array
1546 + {
1547 + return $this->decode(
1548 + acym_loadObjectList(
1549 + 'SELECT `id`, `name`
1550 + FROM #__acym_mail
1551 + WHERE `type` = '.acym_escapeDB(self::TYPE_TEMPLATE).'
1552 + AND `body` LIKE '.acym_escapeDB('%'.acym_utf8Encode('{emailcontent}').'%')
1553 + )
1554 + );
1555 + }
1556 +
1557 + public function hasUserAccess(int $mailId, bool $write = false, ?int $userId = null): bool
1558 + {
1559 + $userId = $userId ?? acym_currentUserId();
1560 + if (empty($userId)) {
1561 + return false;
1562 + }
1563 +
1564 + $mail = acym_loadObject('SELECT `creator_id`, `type`, `access` FROM #__acym_mail WHERE id = '.intval($mailId));
1565 + if (empty($mail)) {
1566 + return false;
1567 + }
1568 +
1569 + if (acym_isAdmin()) {
1570 + return $this->isTypeAllowed($mail->type);
1571 + }
1572 +
1573 + if ($mail->creator_id == $userId) {
1574 + return true;
1575 + }
1576 +
1577 + return !$write && $mail->type === self::TYPE_TEMPLATE && $this->isAccessAllowedForUser($mail->access, $userId);
1578 + }
1579 +
1580 + private function isAccessAllowedForUser(?string $access, int $userId): bool
1581 + {
1582 + if (empty($access)) {
1583 + return false;
1584 + }
1585 +
1586 + foreach (acym_getGroupsByUser($userId) as $userGroup) {
1587 + if (strpos($access, ','.$userGroup.',') !== false) {
1588 + return true;
1589 + }
1590 + }
1591 +
1592 + return false;
1593 + }
1594 +
1595 + private function isTypeAllowed(?string $type): bool
1596 + {
1597 + $aclByType = [
1598 + self::TYPE_NOTIFICATION => ['configuration'],
1599 + self::TYPE_OVERRIDE => ['override'],
1600 + self::TYPE_MAILBOX_ACTION => ['bounces'],
1601 + self::TYPE_AUTOMATION => ['automation'],
1602 + self::TYPE_SCENARIO => ['scenarios'],
1603 + self::TYPE_STANDARD => ['campaigns'],
1604 + self::TYPE_FOLLOWUP => ['campaigns'],
1605 + self::TYPE_WELCOME => ['lists', 'campaigns'],
1606 + self::TYPE_UNSUBSCRIBE => ['lists', 'campaigns'],
1607 + self::TYPE_TEMPLATE => ['mails', 'campaigns'],
1608 + ];
1609 +
1610 + // An add-on type is not mapped to a feature, don't lock it
1611 + if (empty($aclByType[$type])) {
1612 + return true;
1613 + }
1614 +
1615 + foreach ($aclByType[$type] as $oneAcl) {
1616 + if (acym_isAllowed($oneAcl)) {
1617 + return true;
1618 + }
1619 + }
1620 +
1621 + return false;
1622 + }
1623 +
1624 + public function getVersionsById(int $mailId, bool $includeParent = false): array
1625 + {
1626 + $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1627 +
1628 + return $this->decode(
1629 + acym_loadObjectList(
1630 + 'SELECT *
1631 + FROM #__acym_mail
1632 + WHERE `parent_id` = '.intval($mailId).$where.' ORDER BY id ASC',
1633 + 'id'
1634 + )
1635 + );
1636 + }
1637 +
1638 + /**
1639 + * Get all abtest mails linked to a parent mail, also get the parent mail
1640 + */
1641 + public function getParentAndChildMails(int $parentId): array
1642 + {
1643 + return $this->decode(
1644 + acym_loadObjectList(
1645 + 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
1646 + 'id'
1647 + )
1648 + );
1649 + }
1650 +
1651 + public function getAllTemplatesByType(array $options): array
1652 + {
1653 + $limit = $options['limit'] ?? 10;
1654 + $offset = $options['offset'] ?? 0;
1655 + $filters = $options['filters'] ?? [];
1656 + $search = $filters['search'] ?? '';
1657 + $templateType = $filters['templateType'] ?? '';
1658 +
1659 + $conditions = ['`type` = '.acym_escapeDB(self::TYPE_TEMPLATE)];
1660 +
1661 + if (!empty($search)) {
1662 + $conditions[] = '(name LIKE '.acym_escapeDB('%'.$search.'%').' OR subject LIKE '.acym_escapeDB('%'.$search.'%').')';
1663 + }
1664 +
1665 + if (!empty($templateType)) {
1666 + if ($templateType === 'drag') {
1667 + $conditions[] = 'drag_editor = 1';
1668 + } else {
1669 + $conditions[] = 'drag_editor = 0';
1670 + }
1671 + }
1672 +
1673 + $query = 'SELECT * FROM #__acym_mail WHERE '.implode(' AND ', $conditions);
1674 +
1675 + return $this->decode(acym_loadObjectList($query, '', $offset, $limit));
1676 + }
1677 +
1678 + public function updateFollowupPriority(bool $previouslyActive, bool $active): void
1679 + {
1680 + if (!$previouslyActive && $active) {
1681 + acym_query(
1682 + 'UPDATE `#__acym_queue` AS `queue`
1683 + INNER JOIN `#__acym_mail` AS `mail` ON `queue`.`mail_id` = `mail`.`id`
1684 + SET `queue`.`priority` = 1
1685 + WHERE `mail`.`type` = '.acym_escapeDB(self::TYPE_FOLLOWUP)
1686 + );
1687 + }
1688 + }
1689 +
1690 + public function getCampaignIdByMailId(int $mailId): int
1691 + {
1692 + $mainMailId = $this->getMainMailId($mailId);
1693 +
1694 + return intval(acym_loadResult('SELECT id FROM #__acym_campaign WHERE mail_id = '.intval($mainMailId)));
1695 + }
1696 +
1697 + public function getMainMailId(int $mailId): int
1698 + {
1699 + $parentId = acym_loadResult('SELECT parent_id FROM #__acym_mail WHERE id = '.intval($mailId));
1700 +
1701 + return empty($parentId) ? $mailId : intval($parentId);
1702 + }
1703 +
1704 + public function getMailType(int $mailId): string
1705 + {
1706 + return (string)acym_loadResult('SELECT type FROM #__acym_mail WHERE id = '.intval($mailId));
1707 + }
1708 +}