PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.4
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Classes / MailClass.php

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

1,677 lines 60.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Classes;
4
5 use AcyMailing\Helpers\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 $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
538 if (in_array($favoriteTemplate, $elements)) {
539 $this->config->saveConfig(['favorite_template' => 0]);
540 }
541
542 $allThumbnailToDelete = acym_loadResultArray('SELECT DISTINCT thumbnail FROM #__acym_mail WHERE id IN ('.implode(',', $elements).')');
543
544 $translations = acym_loadResultArray('SELECT id FROM #__acym_mail WHERE parent_id IN ('.implode(',', $elements).')');
545 $elements = array_merge($elements, $translations);
546 if (!empty($translations)) {
547 acym_query('UPDATE #__acym_mail SET `parent_id` = null WHERE `id` IN ('.implode(',', $translations).')');
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 deleteMediaFolder(array $elements): void
569 {
570 if (empty($elements)) {
571 return;
572 }
573
574 acym_arrayToInteger($elements);
575 $results = acym_loadResultArray('SELECT mail_settings FROM #__acym_mail WHERE mail_settings IS NOT NULL AND id IN ('.implode(',', $elements).')');
576
577 foreach ($results as $template) {
578 $settings = json_decode($template, true);
579
580 if (empty($settings['media_folder']) || !file_exists(ACYM_TEMPLATE.$settings['media_folder'])) {
581 continue;
582 }
583 acym_deleteFolder(ACYM_TEMPLATE.$settings['media_folder']);
584 }
585 }
586
587 public function deleteUnusedThumbnails(array $thumbnails): void
588 {
589 if (empty($thumbnails)) {
590 return;
591 }
592
593 foreach ($thumbnails as $key => $oneThumb) {
594 $thumbnails[$key] = acym_escapeDB($oneThumb);
595 }
596
597 $stillUsedThumbnails = acym_loadResultArray('SELECT thumbnail FROM #__acym_mail WHERE thumbnail IN ('.implode(',', $thumbnails).')');
598 $thumbnailToDelete = array_diff($thumbnails, $stillUsedThumbnails);
599 foreach ($thumbnailToDelete as $one) {
600 if (!empty($one) && file_exists(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one)) {
601 acym_deleteFile(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
602 }
603 }
604 }
605
606 // Delete one attachment from a newsletter
607 public function deleteOneAttachment(int $mailId, int $idAttachment): bool
608 {
609 if (empty($mailId)) {
610 return false;
611 }
612
613 $mail = $this->getOneById($mailId);
614
615 $attachments = $mail->attachments;
616 if (empty($attachments)) {
617 return false;
618 }
619 $decodedAttach = json_decode($attachments, true);
620 unset($decodedAttach[$idAttachment]);
621 $newAttachments = [];
622 if (!empty($decodedAttach)) {
623 foreach ($decodedAttach as $oneAttach) {
624 $newAttachments[] = $oneAttach;
625 }
626 }
627 $attachDb = json_encode($newAttachments);
628
629 return (bool)acym_query('UPDATE #__acym_mail SET attachments = '.acym_escapeDB($attachDb).' WHERE id = '.intval($mailId).' OR parent_id = '.intval($mailId));
630 }
631
632 public function createTemplateFile(int $id): string
633 {
634 if (empty($id)) {
635 return '';
636 }
637
638 $template = $this->getOneById($id);
639 if (empty($template->id)) {
640 return '';
641 }
642
643 $css = $this->buildCSS($template->stylesheet);
644 if (empty($css)) {
645 return '';
646 }
647
648 acym_createDir(ACYM_TEMPLATE.'css');
649
650 $cssfile = ACYM_TEMPLATE.'css'.DS.'template_'.$id.'.css';
651 if (acym_writeFile($cssfile, $css)) {
652 return $cssfile;
653 } else {
654 acym_enqueueMessage('Could not create the file '.$cssfile, 'error');
655
656 return '';
657 }
658 }
659
660 public function buildCSS(?string $stylesheet): string
661 {
662 if (empty($stylesheet)) {
663 return '';
664 }
665
666 $inline = '';
667
668 if (preg_match_all('#@import[^;]*;#is', $stylesheet, $results)) {
669 foreach ($results[0] as $oneResult) {
670 //We add the @import CSS at the very beginning for the CSS stylesheet otherwise it does not work
671 $inline .= trim($oneResult)."\n";
672 //We also remove it from the stylesheet to avoid having a duplicate.
673 $stylesheet = str_replace($oneResult, '', $stylesheet);
674 }
675 }
676
677 $inline .= $stylesheet;
678
679 return $inline;
680 }
681
682 public function doupload(): bool
683 {
684 $zipFilePath = $this->uploadTemplate();
685 if (empty($zipFilePath)) {
686 return false;
687 }
688
689 $templateFolder = $this->extractTemplate($zipFilePath);
690 if (empty($templateFolder)) {
691 return false;
692 }
693
694 if (!$this->installExtractedTemplate($templateFolder)) {
695 return false;
696 }
697
698 return true;
699 }
700
701 private function uploadTemplate(): ?string
702 {
703 $importFile = acym_getVar('file', 'uploadedfile', '', 'files');
704
705 $fileError = $importFile['error'];
706 if ($fileError > 0) {
707 switch ($fileError) {
708 case 1:
709 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_1'), 'error');
710
711 return null;
712 case 2:
713 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_2'), 'error');
714
715 return null;
716 case 3:
717 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_3'), 'error');
718
719 return null;
720 case 4:
721 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_4'), 'error');
722
723 return null;
724 default:
725 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_5', $fileError), 'error');
726
727 return null;
728 }
729 }
730
731 if (empty($importFile['name'])) {
732 acym_enqueueMessage(acym_translation('ACYM_BROWSE_FILE'), 'error');
733
734 return null;
735 }
736
737 $uploadPath = acym_cleanPath(ACYM_ROOT.ACYM_MEDIA_FOLDER.'templates');
738
739 if (!acym_isWritable($uploadPath)) {
740 acym_chmod($uploadPath, 0755);
741 if (!acym_isWritable($uploadPath)) {
742 acym_enqueueMessage(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'warning');
743 }
744 }
745
746 if (!(bool)ini_get('file_uploads')) {
747 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_6'), 'error');
748
749 return null;
750 }
751
752 if (!extension_loaded('zlib')) {
753 acym_raiseError(500, acym_translation('ACYM_MISSING_ZLIB'));
754
755 return null;
756 }
757
758 $filename = strtolower(acym_makeSafeFile($importFile['name']));
759 $extension = strtolower(substr($filename, strrpos($filename, '.') + 1));
760
761 $allowedFileType = true;
762 if ($extension !== 'zip') {
763 $allowedFileType = false;
764 } elseif (!in_array($importFile['type'], ['application/zip', 'application/x-zip-compressed'])) {
765 $allowedFileType = false;
766 } elseif (function_exists('mime_content_type') && !in_array(mime_content_type($importFile['tmp_name']), ['application/zip', 'application/x-zip-compressed'])) {
767 $allowedFileType = false;
768 }
769
770 if (!$allowedFileType) {
771 acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', $extension, 'zip'), 'error');
772
773 return null;
774 }
775
776 if (class_exists('ZipArchive')) {
777 $zip = new \ZipArchive();
778 if (filesize($importFile['tmp_name']) > self::MAX_ALLOWED_TEMPLATE_SIZE || !$zip->open($importFile['tmp_name']) || $zip->count() > self::MAX_ALLOWED_TEMPLATE_FILES) {
779 acym_enqueueMessage(acym_translation('ACYM_UPLOADED_ARCHIVE_NOT_ALLOWED'), 'error');
780
781 return null;
782 }
783
784 $zip->close();
785 }
786
787 $tmpPath = acym_getCMSConfig('tmp_path', ACYM_MEDIA.'tmp'.DS);
788 $zipFilePath = acym_cleanPath($tmpPath.DS.$filename);
789
790 $uploaded = acym_uploadFile($importFile['tmp_name'], $zipFilePath);
791 if (!$uploaded) {
792 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_7', $importFile['tmp_name'], $zipFilePath), 'error');
793
794 return null;
795 }
796
797 return $zipFilePath;
798 }
799
800 private function extractTemplate(string $zipFilePath, bool $deleteZip = true): ?string
801 {
802 $extractdir = acym_cleanPath(dirname($zipFilePath).DS.uniqid().uniqid().uniqid().'_template');
803
804 $result = acym_extractArchive($zipFilePath, $extractdir);
805
806 if (!$result) {
807 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_9', $zipFilePath, $extractdir), 'error');
808
809 return null;
810 }
811
812 if ($deleteZip) {
813 acym_deleteFile($zipFilePath);
814 }
815
816 $allFiles = acym_getFiles($extractdir, '.', true, true, [], []);
817 foreach ($allFiles as $oneFile) {
818 if (function_exists('mime_content_type')) {
819 $fileType = mime_content_type($oneFile);
820 if (in_array($fileType, self::ALLOWED_TEMPLATE_FILE_TYPES) && preg_match('#\.(jpg|gif|png|jpeg|ico|bmp|html|htm|css|webp)$#i', $oneFile)) {
821 continue;
822 }
823 } elseif (preg_match('#\.(jpg|gif|png|jpeg|ico|bmp|html|htm|css|webp)$#i', $oneFile)) {
824 continue;
825 }
826
827 if (acym_deleteFile($oneFile)) {
828 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_8', $oneFile), 'warning');
829 }
830 }
831
832 return $extractdir;
833 }
834
835 public function installExtractedTemplate(string $templateFolder, bool $deleteFolder = true): bool
836 {
837 if ($this->detectTemplates($templateFolder)) {
838 $messages = $this->templateNames;
839 array_unshift($messages, acym_translationSprintf('ACYM_TEMPLATES_INSTALL', count($this->templateNames)));
840 acym_enqueueMessage($messages);
841 if ($deleteFolder && is_dir($templateFolder)) {
842 acym_deleteFolder($templateFolder);
843 }
844
845 return true;
846 }
847
848 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_10'), 'error');
849 if ($deleteFolder && is_dir($templateFolder)) {
850 acym_deleteFolder($templateFolder);
851 }
852
853 return false;
854 }
855
856 public function detectTemplates(string $folder): bool
857 {
858 $allFiles = acym_getFiles($folder);
859 if (!empty($allFiles)) {
860 // Search and install template.html file first
861 $keyTmpl = array_search('template.html', $allFiles);
862 if ($keyTmpl !== false) {
863 if ($this->installTemplate($folder.DS.$allFiles[$keyTmpl])) {
864 return true;
865 }
866 }
867 // Try to install first html file found
868 foreach ($allFiles as $oneFile) {
869 if (preg_match('#^.*(html|htm)$#i', $oneFile)) {
870 if ($this->installTemplate($folder.DS.$oneFile)) {
871 return true;
872 }
873 }
874 }
875 }
876
877 $status = false;
878 $allFolders = acym_getFolders($folder);
879 if (!empty($allFolders)) {
880 foreach ($allFolders as $oneFolder) {
881 $status = $this->detectTemplates($folder.DS.$oneFolder) || $status;
882 }
883 }
884
885 return $status;
886 }
887
888 private function installTemplate(string $filepath): bool
889 {
890 $fileContent = acym_fileGetContent($filepath);
891
892 $newTemplate = new \stdClass();
893
894 if (strpos($filepath, '_template') === false) {
895 $newTemplate->name = basename(dirname($filepath));
896 } else {
897 $newTemplate->name = basename($filepath);
898 $newTemplate->name = substr($newTemplate->name, 0, strrpos($newTemplate->name, '.'));
899 }
900
901 $newTemplate->name = trim(preg_replace('#[^a-z0-9]#i', ' ', $newTemplate->name));
902
903 if (preg_match('#< *title[^>]*>(.*)< */ *title *>#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->name = $results[1];
904
905 if (preg_match('#< *meta *name="fromname" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromname = $results[1];
906 if (preg_match('#< *meta *name="fromemail" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromemail = $results[1];
907 if (preg_match('#< *meta *name="replyname" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->replyname = $results[1];
908 if (preg_match('#< *meta *name="replyemail" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->replyemail = $results[1];
909 if (preg_match('#< *meta *name="subject" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->subject = $results[1];
910 if (preg_match('#< *meta *name="settings" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) {
911 $newTemplate->settings = htmlspecialchars_decode($results[1]);
912 }
913
914 $newFolder = preg_replace('#[^a-z0-9]#i', '_', strtolower($newTemplate->name));
915 $newTemplateFolder = $newFolder;
916 $i = 1;
917 while (is_dir(ACYM_TEMPLATE.$newTemplateFolder)) {
918 $newTemplateFolder = $newFolder.'_'.$i;
919 $i++;
920 }
921 $newTemplate->mail_settings = ['media_folder' => $newTemplateFolder];
922
923 $moveResult = acym_copyFolder(dirname($filepath), ACYM_TEMPLATE.$newTemplateFolder);
924 if ($moveResult !== true) {
925 acym_display([acym_translationSprintf('ACYM_ERROR_COPYING_FOLDER_TO', dirname($filepath), ACYM_TEMPLATE.$newTemplateFolder), $moveResult], 'error');
926
927 return false;
928 }
929
930 if (!file_exists(ACYM_TEMPLATE.$newTemplateFolder.DS.'index.html')) {
931 $indexFile = '<html><body bgcolor="#FFFFFF"></body></html>';
932 acym_writeFile(ACYM_TEMPLATE.$newTemplateFolder.DS.'index.html', $indexFile);
933 }
934
935 $fileContent = str_replace(
936 [
937 'src="./',
938 'src="../',
939 'src="images/',
940 'url("images/',
941 'url(&quot;images/',
942 ],
943 [
944 'src="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/',
945 'src="'.ACYM_TEMPLATE_URL,
946 'src="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
947 'url("'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
948 'url(&quot;'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/images/',
949 ],
950 $fileContent
951 );
952
953 $fileContent = preg_replace('#(src|background)[ ]*=[ ]*\"(?!(https?://|/))(?:\.\./|\./)?#', '$1="'.ACYM_TEMPLATE_URL.$newTemplateFolder.'/', $fileContent);
954
955 if (preg_match('#< *body[^>]*>(.*)< */ *body *>#Uis', $fileContent, $results)) {
956 $newTemplate->body = $results[1];
957 } else {
958 $newTemplate->body = $fileContent;
959 }
960
961 $newTemplate->stylesheet = '';
962 if (preg_match_all('#< *style[^>]*>(.*)< */ *style *>#Uis', $fileContent, $results)) {
963 $newTemplate->stylesheet .= preg_replace('#(<!--|-->)#s', '', implode("\n", $results[1]));
964 }
965 $cssFiles = [];
966 $cssFiles[ACYM_TEMPLATE.$newTemplateFolder] = acym_getFiles(ACYM_TEMPLATE.$newTemplateFolder, '\.css$');
967 $subFolders = acym_getFolders(ACYM_TEMPLATE.$newTemplateFolder);
968 foreach ($subFolders as $oneFolder) {
969 $cssFiles[ACYM_TEMPLATE.$newTemplateFolder.DS.$oneFolder] = acym_getFiles(ACYM_TEMPLATE.$newTemplateFolder.DS.$oneFolder, '\.css$');
970 }
971
972 foreach ($cssFiles as $cssFolder => $cssFile) {
973 if (empty($cssFile)) continue;
974 $newTemplate->stylesheet .= "\n".acym_fileGetContent($cssFolder.DS.reset($cssFile));
975 }
976
977 if (!empty($newTemplate->stylesheet)) {
978 if (preg_match('#body *\{[^\}]*background-color:([^;\}]*)[;\}]#Uis', $newTemplate->stylesheet, $backgroundresults)) {
979 $newTemplate->stylesheet = preg_replace('#(body *\{[^\}]*)background-color:[^;\}]*[;\}]#Uis', '$1', $newTemplate->stylesheet);
980 }
981
982 $quickstyle = [
983 'tag_h1' => 'h1',
984 'tag_h2' => 'h2',
985 'tag_h3' => 'h3',
986 'tag_h4' => 'h4',
987 'tag_h5' => 'h5',
988 'tag_h6' => 'h6',
989 'tag_a' => 'a',
990 'tag_ul' => 'ul',
991 'tag_li' => 'li',
992 'acym_unsub' => '\.acym_unsub',
993 'acym_online' => '\.acym_online',
994 'acym_title' => '\.acym_title',
995 'acym_content' => '\.acym_content',
996 'acym_readmore' => '\.acym_readmore',
997 ];
998 foreach ($quickstyle as $styledb => $oneStyle) {
999 if (preg_match('#[^a-z\. ,] *'.$oneStyle.' *{([^}]*)}#Uis', $newTemplate->stylesheet, $quickstyleresults)) {
1000 $newTemplate->stylesheet = str_replace($quickstyleresults[0], '', $newTemplate->stylesheet);
1001 }
1002 }
1003 }
1004
1005 $foldersForPicts = [$newTemplateFolder];
1006 $otherFolders = acym_getFolders(ACYM_TEMPLATE.$newTemplateFolder);
1007 foreach ($otherFolders as $oneFold) {
1008 $foldersForPicts[] = $newTemplateFolder.DS.$oneFold;
1009 }
1010 $allPictures = [];
1011 foreach ($foldersForPicts as $oneFolder) {
1012 $allPictures[$oneFolder] = acym_getFiles(ACYM_TEMPLATE.$oneFolder);
1013 }
1014
1015 $uploadsFolder = ACYM_UPLOAD_FOLDER_THUMBNAIL;
1016
1017 $thumbNb = intval($this->config->get('numberThumbnail', 2));
1018
1019 foreach ($allPictures as $folder => $pictfolders) {
1020 foreach ($pictfolders as $onePict) {
1021 if (!preg_match('#\.(jpg|gif|png|jpeg|ico|bmp)$#i', $onePict)) continue;
1022 if (preg_match('#(thumbnail|screenshot|muestra)#i', $onePict)) {
1023 $thumbNb++;
1024 $newNamePict = 'thumbnail_'.$thumbNb.'.png';
1025 copy(ACYM_TEMPLATE.str_replace(DS, '/', $folder).'/'.$onePict, $uploadsFolder.$newNamePict);
1026 $newTemplate->thumbnail = $newNamePict;
1027 }
1028 }
1029 }
1030
1031 $this->config->saveConfig(['numberThumbnail' => $thumbNb]);
1032
1033 $newTemplate->drag_editor = strpos($newTemplate->body, 'acym__wysid__template__content') !== false ? 1 : 0;
1034 $newTemplate->type = self::TYPE_TEMPLATE;
1035 $newTemplate->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
1036
1037 $this->save($newTemplate);
1038
1039 $this->templateNames[] = $newTemplate->name;
1040
1041 return true;
1042 }
1043
1044 public function sendAutomation(int $mailId, array $userIds, string $sendingDate, array $automationAdmin = [])
1045 {
1046 if (empty($mailId)) {
1047 return acym_translationSprintf('ACYM_EMAILS_ADDED_QUEUE', 0);
1048 }
1049
1050 if (empty($sendingDate)) {
1051 return acym_translation('ACYM_WRONG_DATE');
1052 }
1053
1054 if (empty($userIds)) {
1055 return acym_translation('ACYM_USER_NOT_FOUND');
1056 }
1057
1058 acym_arrayToInteger($userIds);
1059
1060 $nbSent = $this->sendNotification($mailId, $userIds, $automationAdmin);
1061 if (!is_null($nbSent)) {
1062 return $nbSent;
1063 }
1064
1065 $result = acym_query(
1066 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`)
1067 SELECT '.intval($mailId).', user.id, '.acym_escapeDB($sendingDate).'
1068 FROM #__acym_user AS user
1069 WHERE user.active = 1 AND user.id IN ('.implode(',', $userIds).')'
1070 );
1071
1072 if ($result === 0) {
1073 return acym_translation('ACYM_CAMPAIGN_ALREADY_QUEUED');
1074 }
1075
1076 $mailStatClass = new MailStatClass();
1077 $mailStat = $mailStatClass->getOneRowByMailId($mailId);
1078
1079 $newMailStat = new \stdClass();
1080 $newMailStat->mail_id = intval($mailId);
1081 $newMailStat->total_subscribers = intval($result);
1082 if (empty($mailStat)) {
1083 $newMailStat->send_date = $sendingDate;
1084 }
1085
1086 $mailStatClass->save($newMailStat);
1087
1088 return $result;
1089 }
1090
1091 private function sendNotification(int $mailId, array $userIds, array $automationAdmin): ?int
1092 {
1093 if (empty($automationAdmin['user_id'])) {
1094 return null;
1095 }
1096
1097 $userClass = new UserClass();
1098 $mail = $this->getOneById($mailId);
1099 $user = $userClass->getOneById($automationAdmin['user_id']);
1100
1101 if (empty($mail) || empty($user)) {
1102 return 0;
1103 }
1104
1105 // Get the current user values
1106 $mailerHelper = new MailerHelper();
1107 $pluginHelper = new PluginHelper();
1108 $extractedTags = $pluginHelper->extractTags($mail, 'subscriber');
1109
1110 $isNotification = false;
1111 foreach ($extractedTags as $dtext => $oneTag) {
1112 if (empty($oneTag->info) || $oneTag->info !== 'current') {
1113 continue;
1114 }
1115
1116 $isNotification = true;
1117
1118 if (!empty($user->{$oneTag->id})) {
1119 $mailerHelper->addParam(str_replace(['{', '}'], '', $dtext), $user->{$oneTag->id});
1120 }
1121 }
1122
1123 if (!$isNotification) {
1124 if (isset($automationAdmin['automationAdmin']) && $automationAdmin['automationAdmin']) {
1125 return 0;
1126 } else {
1127 return null;
1128 }
1129 }
1130
1131 $userFields = $userClass->getAllUserFields($user);
1132 foreach ($userFields as $map => $value) {
1133 $mailerHelper->addParam('subscriber:'.$map.'|info:current', $value);
1134 // Might not be used anymore
1135 $mailerHelper->addParam('user:'.$map, $value);
1136 }
1137
1138 $emailsSent = 0;
1139 foreach ($userIds as $userId) {
1140 if ($mailerHelper->sendOne($mail->id, $userId)) {
1141 $emailsSent++;
1142 }
1143 }
1144
1145 return $emailsSent;
1146 }
1147
1148 public function encode($mails = [])
1149 {
1150 $isArray = true;
1151 if (!is_array($mails)) {
1152 $mails = [$mails];
1153
1154 $isArray = false;
1155 }
1156
1157 $encodedMails = array_map([$this, 'removePoweredByAcyMailing'], $mails);
1158
1159 $encodedMails = array_map([$this, 'utf8Encode'], $encodedMails);
1160
1161 return $isArray ? $encodedMails : $encodedMails[0];
1162 }
1163
1164 public function decode($mails = [])
1165 {
1166 if (is_null($mails)) {
1167 return null;
1168 }
1169
1170 $isArray = true;
1171 if (!is_array($mails)) {
1172 $mails = [$mails];
1173
1174 $isArray = false;
1175 }
1176
1177 $decodedMails = array_map([$this, 'utf8Decode'], $mails);
1178
1179 $decodedMails = array_map([$this, 'addPoweredByAcyMailing'], $decodedMails);
1180
1181 foreach ($decodedMails as $i => $oneMail) {
1182 if (!isset($oneMail->access) || is_array($oneMail->access)) continue;
1183 $decodedMails[$i]->access = empty($oneMail->access) ? '' : explode(',', $oneMail->access);
1184 }
1185
1186 return $isArray ? $decodedMails : $decodedMails[0];
1187 }
1188
1189 /**
1190 * Decode one mail from UTF8. (decode only attributes defined in $fieldsToDecode)
1191 */
1192 protected function utf8Decode($mail)
1193 {
1194 if (!empty($mail)) {
1195 foreach (self::FIELDS_ENCODING as $oneField) {
1196 if (in_array($oneField, $this->exceptKeysDecode)) {
1197 continue;
1198 }
1199
1200 if (is_array($mail)) {
1201 if (empty($mail[$oneField])) continue;
1202 $value = &$mail[$oneField];
1203 } else {
1204 if (empty($mail->$oneField)) continue;
1205 $value = &$mail->$oneField;
1206 }
1207
1208 $value = acym_utf8Decode($value);
1209 }
1210 }
1211
1212 return $mail;
1213 }
1214
1215 private function addPoweredByAcyMailing(object $mail): object
1216 {
1217 if (empty($mail->body)) {
1218 return $mail;
1219 }
1220
1221 //If we don't display the powered by we remove it
1222 if ($this->config->get('display_built_by', 0) != 1) {
1223 if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1224 $mailBodyDom = new \DOMDocument();
1225 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1226 $tables = $mailBodyDom->getElementsByTagName('table');
1227 foreach ($tables as $table) {
1228 if ($table->getAttribute('id') != 'acym__powered_by_acymailing') continue;
1229 $table->parentNode->removeChild($table);
1230 break;
1231 }
1232 $mail->body = $mailBodyDom->saveHTML();
1233 }
1234
1235 return $mail;
1236 }
1237
1238 if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1239 return $mail;
1240 }
1241
1242 $isWysidEditor = strpos($mail->body, 'acym__wysid__template') !== false;
1243 $urlPoweredByImage = ACYM_IMAGES.'editor/poweredby_black.png';
1244
1245 if (!$isWysidEditor) {
1246 $poweredByHTML = '<p id="acym__powered_by_acymailing">';
1247 $poweredByHTML .= '<a href="'.ACYM_ACYMAILING_WEBSITE.'?utm_campaign=powered_by_v7&utm_source=acymailing_plugin&utm_medium=built_with_footer" target="blank">';
1248 $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.'"/>';
1249 $poweredByHTML .= '</a></p>';
1250
1251 $mail->body = $mail->body.$poweredByHTML;
1252 } else {
1253 $poweredByWYSID = '<table id="acym__powered_by_acymailing" class="row" bgcolor="#ffffff" style="background-color: transparent" cellpadding="0" cellspacing="0" border="0">
1254 <tbody bgcolor style="background-color: inherit;">
1255 <tr>
1256 <th class="small-12 medium-12 large-12 columns" valign="top" style="height: auto;">
1257 <table border="0" cellpadding="0" cellspacing="0"
1258 style="min-height: 0px; display: table; height: auto;">
1259 <tbody style="min-height: 0px; display: table-row-group;">
1260 <tr
1261 style="position: relative; top: inherit; left: inherit; right: inherit; bottom: inherit; height: auto;">
1262 <td class="large-12">
1263 <div style="position: relative;">
1264 <p style="word-break: break-word; text-align: center;">
1265 <a href="'.ACYM_ACYMAILING_WEBSITE.'?utm_campaign=powered_by_v7&utm_source=acymailing_plugin&utm_medium=built_with_footer" target="_blank">
1266 <img src="'.$urlPoweredByImage.'"
1267 title="poweredby" alt="Email built with AcyMailing"
1268 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;"
1269 height="40" width="199">
1270 </a>
1271 </p>
1272 </div>
1273 </td>
1274 </tr>
1275 </tbody>
1276 </table>
1277 </th>
1278 </tr>
1279 </tbody>
1280 </table>';
1281
1282 $mailBodyDom = new \DOMDocument();
1283 //Some inserted content add specific tags that shows warnings
1284 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1285
1286 $htmlToAddDom = new \DOMDocument();
1287 @$htmlToAddDom->loadHTML('<?xml encoding="utf-8" ?>'.$poweredByWYSID);
1288 $tableToAdd = $htmlToAddDom->getElementById('acym__powered_by_acymailing');
1289
1290 $tds = $mailBodyDom->getElementsByTagName('td');
1291 foreach ($tds as $td) {
1292 $classes = explode(' ', $td->getAttribute('class'));
1293 if (!in_array('acym__wysid__row', $classes)) continue;
1294 if (!empty($tableToAdd)) $td->appendChild($mailBodyDom->importNode($tableToAdd, true));
1295 break;
1296 }
1297 $mail->body = $mailBodyDom->saveHTML();
1298 }
1299
1300 return $mail;
1301 }
1302
1303 protected function removePoweredByAcyMailing(object $mail): object
1304 {
1305 if (empty($mail->body) || strpos($mail->body, 'acym__wysid__template') !== false) {
1306 return $mail;
1307 }
1308
1309 $regexPoweredByAcyMailing = '#<p id="acym__powered_by_acymailing.*<\/p>#U';
1310
1311 $mail->body = preg_replace($regexPoweredByAcyMailing, '', $mail->body);
1312
1313 return $mail;
1314 }
1315
1316 /**
1317 * Encode one mail in UTF8 for handling specific characters as emoji. (encode only attributes defined in $fieldsToEncode)
1318 *
1319 * @param $mail
1320 *
1321 * @return mixed
1322 */
1323 protected function utf8Encode($mail)
1324 {
1325 if (!empty($mail)) {
1326 foreach (self::FIELDS_ENCODING as $oneField) {
1327 if (is_array($mail)) {
1328 if (empty($mail[$oneField])) continue;
1329 $value = &$mail[$oneField];
1330 } else {
1331 if (empty($mail->$oneField)) continue;
1332 $value = &$mail->$oneField;
1333 }
1334
1335 $value = acym_utf8Encode($value);
1336 }
1337 }
1338
1339 return $mail;
1340 }
1341
1342 public function getTranslationId(int $parentId, string $langCode): int
1343 {
1344 return (int)acym_loadResult(
1345 'SELECT `id`
1346 FROM #__acym_mail
1347 WHERE `parent_id` = '.intval($parentId).'
1348 AND `language` = '.acym_escapeDB($langCode)
1349 );
1350 }
1351
1352 public function getTranslationsById(int $mailId, bool $full = false, bool $includeParent = false)
1353 {
1354 $data = $full ? '*' : '`language`, `subject`, `preheader`, `body`, `autosave`';
1355 $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1356
1357 return $this->decode(
1358 acym_loadObjectList(
1359 'SELECT '.$data.'
1360 FROM #__acym_mail
1361 WHERE `parent_id` = '.intval($mailId).$where,
1362 'language'
1363 )
1364 );
1365 }
1366
1367 public function deleteByTranslationLang(array $languageCodes): void
1368 {
1369 if (empty($languageCodes)) {
1370 return;
1371 }
1372
1373 foreach ($languageCodes as $key => $oneLangCode) {
1374 $languageCodes[$key] = acym_escapeDB($oneLangCode);
1375 }
1376
1377 $this->delete(
1378 acym_loadResultArray(
1379 'SELECT `id`
1380 FROM #__acym_mail
1381 WHERE `parent_id` IS NOT NULL
1382 AND `language` IN ('.implode(', ', $languageCodes).')'
1383 )
1384 );
1385 }
1386
1387 /**
1388 * Get all multilingual mails linked to a parent mail, also get the parent mail
1389 */
1390 public function getMultilingualMails(int $parentId): array
1391 {
1392 $mails = $this->decode(
1393 acym_loadObjectList(
1394 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
1395 'language'
1396 )
1397 );
1398
1399 return $this->translateMailSettings($mails);
1400 }
1401
1402 private function translateMailSettings(array $mails): array
1403 {
1404 if (empty($mails)) {
1405 return $mails;
1406 }
1407
1408 $defaultLanguage = $this->config->get('multilingual_default');
1409
1410 if (empty($mails[$defaultLanguage])) {
1411 $firstMail = reset($mails);
1412 if (empty($firstMail) || empty($firstMail->parent_id)) {
1413 return $mails;
1414 }
1415 $mainMail = $this->getOneById($firstMail->parent_id);
1416 } else {
1417 $mainMail = $mails[$defaultLanguage];
1418 }
1419
1420 if (!empty($mainMail->translation)) {
1421 $translation = json_decode($mainMail->translation, true);
1422
1423 foreach ($mails as $lang => $mail) {
1424 if (empty($mail->parent_id) || empty($translation[$lang])) {
1425 continue;
1426 }
1427
1428 if (!empty($translation[$lang]['from_name'])) $mail->from_name = $translation[$lang]['from_name'];
1429 if (!empty($translation[$lang]['from_email'])) $mail->from_email = $translation[$lang]['from_email'];
1430 if (!empty($translation[$lang]['reply_to_name'])) $mail->reply_to_name = $translation[$lang]['reply_to_name'];
1431 if (!empty($translation[$lang]['reply_to_email'])) $mail->reply_to_email = $translation[$lang]['reply_to_email'];
1432 }
1433 }
1434
1435 return $mails;
1436 }
1437
1438 public function getMailAttachments(int $mailId)
1439 {
1440 return acym_loadResult('SELECT attachments FROM #__acym_mail WHERE id = '.intval($mailId));
1441 }
1442
1443 public function isTransactionalMail(object $mail): bool
1444 {
1445 return $mail->type !== self::TYPE_STANDARD;
1446 }
1447
1448 public function isOneTimeMail(object $mail): bool
1449 {
1450 return in_array(
1451 $mail->type,
1452 [
1453 self::TYPE_NOTIFICATION,
1454 self::TYPE_OVERRIDE,
1455 self::TYPE_WELCOME,
1456 self::TYPE_UNSUBSCRIBE,
1457 self::TYPE_TEMPLATE,
1458 ]
1459 );
1460 }
1461
1462 public function getAutomaticMailIds(array $mailIds): array
1463 {
1464 acym_arrayToInteger($mailIds);
1465
1466 $query = 'SELECT DISTINCT campaign2.mail_id
1467 FROM #__acym_campaign AS campaign1
1468 JOIN #__acym_campaign AS campaign2 ON campaign1.id = campaign2.parent_id
1469 WHERE campaign1.mail_id IN ('.implode(',', $mailIds).')';
1470 $generatedMailIds = acym_loadResultArray($query);
1471
1472 if (empty($generatedMailIds)) return [];
1473
1474 acym_arrayToInteger($generatedMailIds);
1475
1476 return $generatedMailIds;
1477 }
1478
1479 public function getMultilingualMailIds(array $mailIds): array
1480 {
1481 acym_arrayToInteger($mailIds);
1482
1483 $query = 'SELECT DISTINCT mail2.id
1484 FROM #__acym_mail AS mail1
1485 JOIN #__acym_mail AS mail2 ON mail1.id = mail2.parent_id
1486 WHERE mail1.id IN ('.implode(',', $mailIds).')';
1487
1488 $multipleMailIds = acym_loadResultArray($query);
1489
1490 if (empty($multipleMailIds)) return [];
1491
1492 acym_arrayToInteger($multipleMailIds);
1493
1494 return $multipleMailIds;
1495 }
1496
1497 public function duplicateMail(int $mailId, ?string $type = null): ?object
1498 {
1499 $mail = $this->getOneById($mailId);
1500
1501 if (empty($mail)) {
1502 return null;
1503 }
1504
1505 $newMail = new \stdClass();
1506 $newMail->name = $mail->name.'_copy';
1507 $newMail->thumbnail = '';
1508 $newMail->type = empty($type) ? $mail->type : $type;
1509 $newMail->drag_editor = $mail->drag_editor;
1510 $newMail->body = $mail->body;
1511 $newMail->subject = $mail->subject;
1512 $newMail->from_name = $mail->from_name;
1513 $newMail->from_email = $mail->from_email;
1514 $newMail->reply_to_name = $mail->reply_to_name;
1515 $newMail->reply_to_email = $mail->reply_to_email;
1516 $newMail->bcc = $mail->bcc;
1517 $newMail->settings = $mail->settings;
1518 $newMail->stylesheet = $mail->stylesheet;
1519 $newMail->attachments = $mail->attachments;
1520 $newMail->headers = $mail->headers;
1521 $newMail->preheader = $mail->preheader;
1522
1523 $newMail->id = $this->save($newMail);
1524
1525 return $newMail;
1526 }
1527
1528 public function getTemplatesForMailboxAction(): array
1529 {
1530 return $this->decode(
1531 acym_loadObjectList(
1532 'SELECT `id`, `name`
1533 FROM #__acym_mail
1534 WHERE `type` = '.acym_escapeDB(self::TYPE_TEMPLATE).'
1535 AND `body` LIKE '.acym_escapeDB('%'.acym_utf8Encode('{emailcontent}').'%')
1536 )
1537 );
1538 }
1539
1540 public function hasUserAccess(int $mailId, bool $write = false, ?int $userId = null): bool
1541 {
1542 $userId = $userId ?? acym_currentUserId();
1543 if (empty($userId)) {
1544 return false;
1545 }
1546
1547 $mail = acym_loadObject('SELECT `creator_id`, `type` FROM #__acym_mail WHERE id = '.intval($mailId));
1548 if (empty($mail)) {
1549 return false;
1550 }
1551
1552 if (acym_isAdmin()) {
1553 return $this->isTypeAllowed($mail->type);
1554 }
1555
1556 if ($mail->creator_id == $userId) {
1557 return true;
1558 }
1559
1560 return !$write && $mail->type === self::TYPE_TEMPLATE;
1561 }
1562
1563 private function isTypeAllowed(?string $type): bool
1564 {
1565 $aclByType = [
1566 self::TYPE_NOTIFICATION => ['configuration'],
1567 self::TYPE_OVERRIDE => ['override'],
1568 self::TYPE_MAILBOX_ACTION => ['bounces'],
1569 self::TYPE_AUTOMATION => ['automation'],
1570 self::TYPE_SCENARIO => ['scenarios'],
1571 self::TYPE_STANDARD => ['campaigns'],
1572 self::TYPE_FOLLOWUP => ['campaigns'],
1573 self::TYPE_WELCOME => ['lists', 'campaigns'],
1574 self::TYPE_UNSUBSCRIBE => ['lists', 'campaigns'],
1575 self::TYPE_TEMPLATE => ['mails', 'campaigns'],
1576 ];
1577
1578 // An add-on type is not mapped to a feature, don't lock it
1579 if (empty($aclByType[$type])) {
1580 return true;
1581 }
1582
1583 foreach ($aclByType[$type] as $oneAcl) {
1584 if (acym_isAllowed($oneAcl)) {
1585 return true;
1586 }
1587 }
1588
1589 return false;
1590 }
1591
1592 public function getVersionsById(int $mailId, bool $includeParent = false): array
1593 {
1594 $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1595
1596 return $this->decode(
1597 acym_loadObjectList(
1598 'SELECT *
1599 FROM #__acym_mail
1600 WHERE `parent_id` = '.intval($mailId).$where.' ORDER BY id ASC',
1601 'id'
1602 )
1603 );
1604 }
1605
1606 /**
1607 * Get all abtest mails linked to a parent mail, also get the parent mail
1608 */
1609 public function getParentAndChildMails(int $parentId): array
1610 {
1611 return $this->decode(
1612 acym_loadObjectList(
1613 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
1614 'id'
1615 )
1616 );
1617 }
1618
1619 public function getAllTemplatesByType(array $options): array
1620 {
1621 $limit = $options['limit'] ?? 10;
1622 $offset = $options['offset'] ?? 0;
1623 $filters = $options['filters'] ?? [];
1624 $search = $filters['search'] ?? '';
1625 $templateType = $filters['templateType'] ?? '';
1626
1627 $conditions = ['`type` = '.acym_escapeDB(self::TYPE_TEMPLATE)];
1628
1629 if (!empty($search)) {
1630 $conditions[] = '(name LIKE '.acym_escapeDB('%'.$search.'%').' OR subject LIKE '.acym_escapeDB('%'.$search.'%').')';
1631 }
1632
1633 if (!empty($templateType)) {
1634 if ($templateType === 'drag') {
1635 $conditions[] = 'drag_editor = 1';
1636 } else {
1637 $conditions[] = 'drag_editor = 0';
1638 }
1639 }
1640
1641 $query = 'SELECT * FROM #__acym_mail WHERE '.implode(' AND ', $conditions);
1642
1643 return $this->decode(acym_loadObjectList($query, '', $offset, $limit));
1644 }
1645
1646 public function updateFollowupPriority(bool $previouslyActive, bool $active): void
1647 {
1648 if (!$previouslyActive && $active) {
1649 acym_query(
1650 'UPDATE `#__acym_queue` AS `queue`
1651 INNER JOIN `#__acym_mail` AS `mail` ON `queue`.`mail_id` = `mail`.`id`
1652 SET `queue`.`priority` = 1
1653 WHERE `mail`.`type` = '.acym_escapeDB(self::TYPE_FOLLOWUP)
1654 );
1655 }
1656 }
1657
1658 public function getCampaignIdByMailId(int $mailId): int
1659 {
1660 $mainMailId = $this->getMainMailId($mailId);
1661
1662 return intval(acym_loadResult('SELECT id FROM #__acym_campaign WHERE mail_id = '.intval($mainMailId)));
1663 }
1664
1665 public function getMainMailId(int $mailId): int
1666 {
1667 $parentId = acym_loadResult('SELECT parent_id FROM #__acym_mail WHERE id = '.intval($mailId));
1668
1669 return empty($parentId) ? $mailId : intval($parentId);
1670 }
1671
1672 public function getMailType(int $mailId): string
1673 {
1674 return (string)acym_loadResult('SELECT type FROM #__acym_mail WHERE id = '.intval($mailId));
1675 }
1676 }
1677