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 +383 -195 10.1.4 → 11.1.0 View file →
@@ -8,17 +8,16 @@
8 8 use AcyMailing\Core\AcymClass;
9 9
10 10 class MailClass extends AcymClass
11 11 {
12 - var $templateNames = [];
12 + const FIELDS_ENCODING = [
13 + 'name',
14 + 'subject',
15 + 'body',
16 + 'autosave',
17 + 'preheader',
18 + ];
13 19
14 - public $exceptKeysDecode = [];
15 - public $autosave;
16 - public $access;
17 - public $templateId;
18 -
19 - const FIELDS_ENCODING = ['name', 'subject', 'body', 'autosave', 'preheader'];
20 -
21 20 const TYPE_STANDARD = 'standard';
22 21 const TYPE_NOTIFICATION = 'notification';
23 22 const TYPE_OVERRIDE = 'override';
24 23 const TYPE_WELCOME = 'welcome';
@@ -41,8 +40,9 @@
41 40 self::TYPE_TEMPLATE,
42 41 self::TYPE_SCENARIO,
43 42 ];
44 43
44 + // Used by some sending methods to know the priority of a sent email (transactional => reset password / account confirmation...)
45 45 const TYPES_TRANSACTIONAL = [
46 46 self::TYPE_NOTIFICATION,
47 47 self::TYPE_OVERRIDE,
48 48 self::TYPE_WELCOME,
@@ -50,8 +50,9 @@
50 50 self::TYPE_TEMPLATE,
51 51 self::TYPE_SCENARIO,
52 52 ];
53 53
54 + // Types on which the click statistics are active
54 55 const TYPES_WITH_STATS = [
55 56 self::TYPE_STANDARD,
56 57 self::TYPE_AUTOMATION,
57 58 self::TYPE_WELCOME,
@@ -60,8 +61,9 @@
60 61 self::TYPE_MAILBOX_ACTION,
61 62 self::TYPE_SCENARIO,
62 63 ];
63 64
65 + // Types that don't let the user modify the name
64 66 const TYPES_NO_NAME = [
65 67 self::TYPE_NOTIFICATION,
66 68 self::TYPE_OVERRIDE,
67 69 ];
@@ -78,8 +80,11 @@
78 80 'text/plain',
79 81 'image/vnd.microsoft.icon',
80 82 ];
81 83
84 + private array $templateNames = [];
85 + public array $exceptKeysDecode = [];
86 +
82 87 public function __construct()
83 88 {
84 89 parent::__construct();
85 90
@@ -86,8 +91,11 @@
86 91 $this->table = 'mail';
87 92 $this->pkey = 'id';
88 93 }
89 94
95 + /**
96 + * Get mails depending on filters (search, ordering, pagination)
97 + */
90 98 public function getMatchingElements(array $settings = []): array
91 99 {
92 100 $query = 'SELECT mail.* FROM #__acym_mail AS mail';
93 101 $queryCount = 'SELECT COUNT(mail.id) AS total FROM #__acym_mail AS mail';
@@ -94,8 +102,9 @@
94 102
95 103 $filters = [];
96 104 $tagJoin = '';
97 105
106 + // Tag filter
98 107 if (!empty($settings['tag'])) {
99 108 $tagJoin = ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element ';
100 109 $filters[] = 'tag.name = '.acym_escapeDB($settings['tag']);
101 110 $filters[] = 'tag.type = "mail"';
@@ -154,11 +163,16 @@
154 163 $filters[] = 'mail.drag_editor = 1';
155 164 }
156 165
157 166 if (!empty($settings['element_tab'])) {
158 - $statJoin = ' LEFT JOIN #__acym_mail_stat AS mail_stat ON mail.id = mail_stat.mail_id ';
159 - $listJoin = acym_isAdmin() ? '' : ' LEFT JOIN #__acym_list AS list ON list.'.acym_escape($settings['element_tab']).'_id = mail.id';
160 - $query = 'SELECT DISTINCT mail.*, mail_stat.sent as subscribers, mail_stat.open_unique FROM #__acym_mail AS mail'.$statJoin.$tagJoin.$listJoin;
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 + }
161 175 $filters[] = 'mail.type = '.acym_escapeDB($settings['element_tab']);
162 176 }
163 177
164 178 if (!empty($filters)) {
@@ -192,14 +206,14 @@
192 206 $results['status'] = [];
193 207
194 208 if (!empty($settings['element_tab'])) {
195 209 $urlClickClass = new UrlClickClass();
196 - for ($i = 0 ; $i < count($results['elements']) ; $i++) {
210 + for ($i = 0; $i < count($results['elements']); $i++) {
197 211 $results['elements'][$i]->open = 0;
198 212 if (!empty($results['elements'][$i]->subscribers)) {
199 213 $results['elements'][$i]->open = number_format($results['elements'][$i]->open_unique / $results['elements'][$i]->subscribers * 100, 2);
200 214
201 - $clicksNb = $urlClickClass->getNumberUsersClicked($results['elements'][$i]->id);
215 + $clicksNb = $urlClickClass->getNumberUsersClicked([$results['elements'][$i]->id]);
202 216 $results['elements'][$i]->click = number_format($clicksNb / $results['elements'][$i]->subscribers * 100, 2);
203 217 }
204 218 }
205 219 }
@@ -206,18 +220,19 @@
206 220
207 221 return $results;
208 222 }
209 223
210 - private function getAllListIdsForWelcomeUnsub(&$elements, $type)
224 + private function getAllListIdsForWelcomeUnsub(array $elements, string $type): void
211 225 {
212 - if (empty($elements)) return true;
213 - $column = $type == self::TYPE_WELCOME ? 'welcome_id' : 'unsubscribe_id';
226 + if (empty($elements)) {
227 + return;
228 + }
214 229
215 - foreach ($elements as $key => $element) {
216 - $elements[$key]->lists = acym_loadObjectList('SELECT color, name FROM #__acym_list WHERE '.$column.' = '.intval($element->id));
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));
217 234 }
218 -
219 - return true;
220 235 }
221 236
222 237 public function getAll(?string $key = null): array
223 238 {
@@ -225,11 +240,11 @@
225 240
226 241 return $this->decode($allMails);
227 242 }
228 243
229 - public function getOneById($id, $needTranslatedSettings = false)
244 + public function getOneById(int $id, bool $needTranslatedSettings = false): ?object
230 245 {
231 - $mail = $this->decode(acym_loadObject('SELECT * FROM #__acym_mail WHERE id = '.intval($id)));
246 + $mail = $this->decode(parent::getOneById($id));
232 247
233 248 if (!empty($mail)) {
234 249 $tagsClass = new TagClass();
235 250 $mail->tags = $tagsClass->getAllTagsByElementId(TagClass::TYPE_MAIL, intval($id));
@@ -243,9 +258,9 @@
243 258
244 259 return $mail;
245 260 }
246 261
247 - private function getTranslatedSettingsMail(&$mail)
262 + private function getTranslatedSettingsMail(object $mail): void
248 263 {
249 264 $parentMailTranslation = acym_loadResult('SELECT translation FROM #__acym_mail WHERE id = '.intval($mail->parent_id));
250 265 $parentMailTranslation = empty($parentMailTranslation) ? [] : json_decode($parentMailTranslation, true);
251 266
@@ -258,9 +273,9 @@
258 273 $senderTranslation = $translationConfig[$mail->language];
259 274 $senderTranslation['reply_to_email'] = $senderTranslation['replyto_email'];
260 275 $senderTranslation['reply_to_name'] = $senderTranslation['replyto_name'];
261 276 } else {
262 - return $mail;
277 + return;
263 278 }
264 279
265 280 $mail->from_name = $senderTranslation['from_name'];
266 281 $mail->from_email = $senderTranslation['from_email'];
@@ -265,13 +280,11 @@
265 280 $mail->from_name = $senderTranslation['from_name'];
266 281 $mail->from_email = $senderTranslation['from_email'];
267 282 $mail->reply_to_name = $senderTranslation['reply_to_name'];
268 283 $mail->reply_to_email = $senderTranslation['reply_to_email'];
269 -
270 - return $mail;
271 284 }
272 285
273 - public function getOneByName(string $name, bool $needTranslatedSettings = false, string $type = '')
286 + public function getOneByName(string $name, bool $needTranslatedSettings = false, string $type = ''): ?object
274 287 {
275 288 $query = 'SELECT * FROM #__acym_mail WHERE `parent_id` IS NULL AND `name` = '.acym_escapeDB(acym_utf8Encode($name));
276 289 if (!empty($type)) {
277 290 $query .= ' AND `type` = '.acym_escapeDB($type);
@@ -276,10 +289,16 @@
276 289 if (!empty($type)) {
277 290 $query .= ' AND `type` = '.acym_escapeDB($type);
278 291 }
279 292
280 - $mail = $this->decode(acym_loadObject($query));
293 + $mail = acym_loadObject($query);
281 294
295 + if (empty($mail)) {
296 + return null;
297 + }
298 +
299 + $mail = $this->decode($mail);
300 +
282 301 if (!empty($mail)) {
283 302 $tagsClass = new TagClass();
284 303 $mail->tags = $tagsClass->getAllTagsByElementId(TagClass::TYPE_MAIL, intval($mail->id));
285 304 }
@@ -290,9 +309,12 @@
290 309
291 310 return $mail;
292 311 }
293 312
294 - public function getMailsByType(?string $typeMail, array $settings)
313 + /**
314 + * Get mails depending on their type (standard, welcome, unsubscribe, notification)
315 + */
316 + public function getMailsByType(?string $typeMail, array $settings): array
295 317 {
296 318 if (empty($settings['key'])) {
297 319 $settings['key'] = '';
298 320 }
@@ -306,13 +328,15 @@
306 328
307 329 $query = 'SELECT * FROM #__acym_mail AS mail';
308 330 $queryCount = 'SELECT count(*) FROM #__acym_mail AS mail';
309 331
332 + // Mail type filtering
310 333 $filters = [];
311 334 if (!empty($typeMail)) {
312 335 $filters[] = 'mail.type = '.acym_escapeDB($typeMail);
313 336 }
314 337
338 + // Search filter
315 339 if (!empty($search)) {
316 340 $filters[] = 'mail.name LIKE '.acym_escapeDB('%'.$search.'%');
317 341 }
318 342
@@ -328,9 +352,9 @@
328 352
329 353 return $results;
330 354 }
331 355
332 - public function getAllListsWithCountSubscribersByMailIds($ids)
356 + public function getAllListsWithCountSubscribersByMailIds(array $ids): array
333 357 {
334 358 acym_arrayToInteger($ids);
335 359 if (empty($ids)) {
336 360 return [];
@@ -351,14 +375,15 @@
351 375 $query .= 'ON list.id = userLists.list_id
352 376 WHERE mailLists.mail_id IN ('.implode(',', $ids).')
353 377 GROUP BY mailLists.list_id, mailLists.mail_id';
354 378
379 + //This line if for guys with big database to not break the page
355 380 acym_query('SET SQL_BIG_SELECTS=1');
356 381
357 382 return acym_loadObjectList($query);
358 383 }
359 384
360 - public function getAllListsByMailIds($ids)
385 + public function getAllListsByMailIds(array $ids): array
361 386 {
362 387 acym_arrayToInteger($ids);
363 388 if (empty($ids)) {
364 389 return [];
@@ -371,14 +396,14 @@
371 396
372 397 return acym_loadObjectList($query);
373 398 }
374 399
375 - public function getAllListsByMailId($id)
400 + public function getAllListsByMailId(int $id): array
376 401 {
377 - if (is_array($id)) $id = $id[0];
378 -
379 402 $mail = $this->getOneById($id);
380 - if (empty($mail)) return [];
403 + if (empty($mail)) {
404 + return [];
405 + }
381 406
382 407 if (self::TYPE_WELCOME === $mail->type) {
383 408 $query = 'SELECT * FROM #__acym_list WHERE welcome_id = '.intval($id);
384 409 } elseif (self::TYPE_FOLLOWUP === $mail->type) {
@@ -394,11 +419,13 @@
394 419
395 420 return acym_loadObjectList($query, 'id');
396 421 }
397 422
398 - public function getAllListsByMailIdAndUserId($mailId, $userId)
423 + public function getAllListsByMailIdAndUserId(int $mailId, int $userId): array
399 424 {
400 - if (empty($mailId) || empty($userId)) return [];
425 + if (empty($mailId) || empty($userId)) {
426 + return [];
427 + }
401 428
402 429 $query = 'SELECT list.* FROM #__acym_list AS list';
403 430 $query .= ' JOIN #__acym_user_has_list AS userlist ON list.id = userlist.list_id AND userlist.user_id = '.intval($userId);
404 431 $query .= ' JOIN #__acym_mail_has_list AS maillist ON maillist.list_id = list.id AND maillist.mail_id = '.intval($mailId);
@@ -405,11 +432,11 @@
405 432
406 433 return acym_loadObjectList($query);
407 434 }
408 435
409 - public function save($mailToSave)
436 + public function save(object $element): ?int
410 437 {
411 - $mail = clone $mailToSave;
438 + $mail = clone $element;
412 439
413 440 if (isset($mail->tags)) {
414 441 $tags = $mail->tags;
415 442 unset($mail->tags);
@@ -421,11 +448,14 @@
421 448 }
422 449
423 450 $mail = $this->encode($mail);
424 451
452 + // Clean autosave value
425 453 $mail->autosave = null;
426 454
427 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
428 458 if (isset($mail->thumbnail) && ((empty($mail->thumbnail) && !is_null($mail->thumbnail)) || strpos($mail->thumbnail, 'data:image/png;base64') !== false)) {
429 459 unset($mail->thumbnail);
430 460 }
431 461
@@ -450,11 +480,12 @@
450 480 if (in_array($oneAttribute, ['body', 'headers'])) {
451 481 $mail->$oneAttribute = preg_replace('#<input[^>]*value="[^"]*"[^>]*>#Uis', '', $mail->$oneAttribute);
452 482 $mail->$oneAttribute = preg_replace('#<script.*</script>#Uis', '', $mail->$oneAttribute);
453 483
484 + //Remove tinyMce content edit
454 485 $mail->$oneAttribute = str_replace(' contenteditable="true"', '', $mail->$oneAttribute);
455 486 } else {
456 - $mail->$oneAttribute = strip_tags($mail->$oneAttribute);
487 + $mail->$oneAttribute = acym_stripTags($mail->$oneAttribute);
457 488 }
458 489 }
459 490
460 491 $mailID = parent::save($mail);
@@ -466,11 +497,14 @@
466 497
467 498 return $mailID;
468 499 }
469 500
470 - public function autoSave($mail, $language = 'main')
501 + public function autoSave(object $mail, string $language = 'main'): bool
471 502 {
472 - if (empty($mail->id)) return false;
503 + if (empty($mail->id)) {
504 + return false;
505 + }
506 +
473 507 $mail->autosave = str_replace(' contenteditable="true"', '', $mail->autosave);
474 508
475 509 if (acym_isMultilingual() && $language !== 'main') {
476 510 $translationId = $this->getTranslationId($mail->id, $language);
@@ -475,9 +509,11 @@
475 509 if (acym_isMultilingual() && $language !== 'main') {
476 510 $translationId = $this->getTranslationId($mail->id, $language);
477 511 if (empty($translationId)) {
478 512 $parentCopy = $this->getOneById($mail->id);
479 - if (empty($parentCopy)) return false;
513 + if (empty($parentCopy)) {
514 + return false;
515 + }
480 516
481 517 unset($parentCopy->id);
482 518 $parentCopy->parent_id = $mail->id;
483 519 $parentCopy->language = $language;
@@ -487,24 +523,18 @@
487 523 }
488 524
489 525 $mail = $this->encode($mail);
490 526
491 - return parent::save($mail);
527 + return (bool)parent::save($mail);
492 528 }
493 529
494 - public function delete($elements)
530 + public function delete(array $elements): int
495 531 {
496 532 if (empty($elements)) return 0;
497 - if (!is_array($elements)) $elements = [$elements];
498 533
499 534 $this->deleteMediaFolder($elements);
500 535 acym_arrayToInteger($elements);
501 536
502 - $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
503 - if (in_array($favoriteTemplate, $elements)) {
504 - $this->config->save(['favorite_template' => 0]);
505 - }
506 -
507 537 $allThumbnailToDelete = acym_loadResultArray('SELECT DISTINCT thumbnail FROM #__acym_mail WHERE id IN ('.implode(',', $elements).')');
508 538
509 539 $translations = acym_loadResultArray('SELECT id FROM #__acym_mail WHERE parent_id IN ('.implode(',', $elements).')');
510 540 $elements = array_merge($elements, $translations);
@@ -511,8 +541,13 @@
511 541 if (!empty($translations)) {
512 542 acym_query('UPDATE #__acym_mail SET `parent_id` = null WHERE `id` IN ('.implode(',', $translations).')');
513 543 }
514 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 +
515 550 acym_query('UPDATE #__acym_list SET welcome_id = null WHERE welcome_id IN ('.implode(',', $elements).')');
516 551 acym_query('UPDATE #__acym_list SET unsubscribe_id = null WHERE unsubscribe_id IN ('.implode(',', $elements).')');
517 552 acym_query('DELETE FROM #__acym_queue WHERE mail_id IN ('.implode(',', $elements).')');
518 553 acym_query('DELETE FROM #__acym_mail_has_list WHERE mail_id IN ('.implode(',', $elements).')');
@@ -529,12 +564,31 @@
529 564
530 565 return $return;
531 566 }
532 567
533 - public function deleteMediaFolder($elements)
568 + public function getFavoriteTemplateId(): int
534 569 {
535 - if (empty($elements)) return;
570 + $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
571 + if (empty($favoriteTemplate)) {
572 + return 0;
573 + }
536 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 +
537 591 acym_arrayToInteger($elements);
538 592 $results = acym_loadResultArray('SELECT mail_settings FROM #__acym_mail WHERE mail_settings IS NOT NULL AND id IN ('.implode(',', $elements).')');
539 593
540 594 foreach ($results as $template) {
@@ -539,18 +593,21 @@
539 593
540 594 foreach ($results as $template) {
541 595 $settings = json_decode($template, true);
542 596
543 - if (empty($settings['media_folder']) || !file_exists(ACYM_TEMPLATE.$settings['media_folder'])) continue;
597 + if (empty($settings['media_folder']) || !file_exists(ACYM_TEMPLATE.$settings['media_folder'])) {
598 + continue;
599 + }
544 600 acym_deleteFolder(ACYM_TEMPLATE.$settings['media_folder']);
545 601 }
546 602 }
547 603
548 - public function deleteUnusedThumbnails($thumbnails)
604 + public function deleteUnusedThumbnails(array $thumbnails): void
549 605 {
550 - if (empty($thumbnails)) return;
606 + if (empty($thumbnails)) {
607 + return;
608 + }
551 609
552 - if (!is_array($thumbnails)) $thumbnails = [$thumbnails];
553 610 foreach ($thumbnails as $key => $oneThumb) {
554 611 $thumbnails[$key] = acym_escapeDB($oneThumb);
555 612 }
556 613
@@ -557,19 +614,20 @@
557 614 $stillUsedThumbnails = acym_loadResultArray('SELECT thumbnail FROM #__acym_mail WHERE thumbnail IN ('.implode(',', $thumbnails).')');
558 615 $thumbnailToDelete = array_diff($thumbnails, $stillUsedThumbnails);
559 616 foreach ($thumbnailToDelete as $one) {
560 617 if (!empty($one) && file_exists(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one)) {
561 - unlink(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
618 + acym_deleteFile(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
562 619 }
563 620 }
564 621 }
565 622
566 - public function deleteOneAttachment($mailId, $idAttachment)
623 + // Delete one attachment from a newsletter
624 + public function deleteOneAttachment(int $mailId, int $idAttachment): bool
567 625 {
568 - $mailId = intval($mailId);
569 626 if (empty($mailId)) {
570 627 return false;
571 628 }
629 +
572 630 $mail = $this->getOneById($mailId);
573 631
574 632 $attachments = $mail->attachments;
575 633 if (empty($attachments)) {
@@ -584,24 +642,23 @@
584 642 }
585 643 }
586 644 $attachDb = json_encode($newAttachments);
587 645
588 - return acym_query('UPDATE #__acym_mail SET attachments = '.acym_escapeDB($attachDb).' WHERE id = '.intval($mailId).' OR parent_id = '.intval($mailId));
646 + return (bool)acym_query('UPDATE #__acym_mail SET attachments = '.acym_escapeDB($attachDb).' WHERE id = '.intval($mailId).' OR parent_id = '.intval($mailId));
589 647 }
590 648
591 - public function createTemplateFile($id)
649 + public function createTemplateFile(int $id): string
592 650 {
593 651 if (empty($id)) {
594 652 return '';
595 653 }
596 - $cssfile = ACYM_TEMPLATE.'css'.DS.'template_'.$id.'.css';
597 654
598 655 $template = $this->getOneById($id);
599 656 if (empty($template->id)) {
600 657 return '';
601 658 }
659 +
602 660 $css = $this->buildCSS($template->stylesheet);
603 -
604 661 if (empty($css)) {
605 662 return '';
606 663 }
607 664
@@ -606,8 +663,9 @@
606 663 }
607 664
608 665 acym_createDir(ACYM_TEMPLATE.'css');
609 666
667 + $cssfile = ACYM_TEMPLATE.'css'.DS.'template_'.$id.'.css';
610 668 if (acym_writeFile($cssfile, $css)) {
611 669 return $cssfile;
612 670 } else {
613 671 acym_enqueueMessage('Could not create the file '.$cssfile, 'error');
@@ -615,9 +673,9 @@
615 673 return '';
616 674 }
617 675 }
618 676
619 - public function buildCSS($stylesheet): string
677 + public function buildCSS(?string $stylesheet): string
620 678 {
621 679 if (empty($stylesheet)) {
622 680 return '';
623 681 }
@@ -625,9 +683,11 @@
625 683 $inline = '';
626 684
627 685 if (preg_match_all('#@import[^;]*;#is', $stylesheet, $results)) {
628 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
629 688 $inline .= trim($oneResult)."\n";
689 + //We also remove it from the stylesheet to avoid having a duplicate.
630 690 $stylesheet = str_replace($oneResult, '', $stylesheet);
631 691 }
632 692 }
633 693
@@ -654,9 +714,9 @@
654 714
655 715 return true;
656 716 }
657 717
658 - private function uploadTemplate()
718 + private function uploadTemplate(): ?string
659 719 {
660 720 $importFile = acym_getVar('file', 'uploadedfile', '', 'files');
661 721
662 722 $fileError = $importFile['error'];
@@ -664,38 +724,39 @@
664 724 switch ($fileError) {
665 725 case 1:
666 726 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_1'), 'error');
667 727
668 - return false;
728 + return null;
669 729 case 2:
670 730 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_2'), 'error');
671 731
672 - return false;
732 + return null;
673 733 case 3:
674 734 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_3'), 'error');
675 735
676 - return false;
736 + return null;
677 737 case 4:
678 738 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOAD_ERROR_4'), 'error');
679 739
680 - return false;
740 + return null;
681 741 default:
682 742 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_5', $fileError), 'error');
683 743
684 - return false;
744 + return null;
685 745 }
686 746 }
747 +
687 748 if (empty($importFile['name'])) {
688 749 acym_enqueueMessage(acym_translation('ACYM_BROWSE_FILE'), 'error');
689 750
690 - return false;
751 + return null;
691 752 }
692 753
693 754 $uploadPath = acym_cleanPath(ACYM_ROOT.ACYM_MEDIA_FOLDER.'templates');
694 755
695 - if (!is_writable($uploadPath)) {
696 - @chmod($uploadPath, '0755');
697 - if (!is_writable($uploadPath)) {
756 + if (!acym_isWritable($uploadPath)) {
757 + acym_chmod($uploadPath, 0755);
758 + if (!acym_isWritable($uploadPath)) {
698 759 acym_enqueueMessage(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'warning');
699 760 }
700 761 }
701 762
@@ -701,15 +762,15 @@
701 762
702 763 if (!(bool)ini_get('file_uploads')) {
703 764 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_6'), 'error');
704 765
705 - return false;
766 + return null;
706 767 }
707 768
708 769 if (!extension_loaded('zlib')) {
709 770 acym_raiseError(500, acym_translation('ACYM_MISSING_ZLIB'));
710 771
711 - return false;
772 + return null;
712 773 }
713 774
714 775 $filename = strtolower(acym_makeSafeFile($importFile['name']));
715 776 $extension = strtolower(substr($filename, strrpos($filename, '.') + 1));
@@ -723,11 +784,11 @@
723 784 $allowedFileType = false;
724 785 }
725 786
726 787 if (!$allowedFileType) {
727 - acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', $extension, 'zip'), 'error');
788 + acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', esc_html($extension), 'zip'), 'error');
728 789
729 - return false;
790 + return null;
730 791 }
731 792
732 793 if (class_exists('ZipArchive')) {
733 794 $zip = new \ZipArchive();
@@ -733,9 +794,9 @@
733 794 $zip = new \ZipArchive();
734 795 if (filesize($importFile['tmp_name']) > self::MAX_ALLOWED_TEMPLATE_SIZE || !$zip->open($importFile['tmp_name']) || $zip->count() > self::MAX_ALLOWED_TEMPLATE_FILES) {
735 796 acym_enqueueMessage(acym_translation('ACYM_UPLOADED_ARCHIVE_NOT_ALLOWED'), 'error');
736 797
737 - return false;
798 + return null;
738 799 }
739 800
740 801 $zip->close();
741 802 }
@@ -746,15 +807,15 @@
746 807 $uploaded = acym_uploadFile($importFile['tmp_name'], $zipFilePath);
747 808 if (!$uploaded) {
748 809 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_7', $importFile['tmp_name'], $zipFilePath), 'error');
749 810
750 - return false;
811 + return null;
751 812 }
752 813
753 814 return $zipFilePath;
754 815 }
755 816
756 - public function extractTemplate($zipFilePath, $deleteZip = true)
817 + private function extractTemplate(string $zipFilePath, bool $deleteZip = true): ?string
757 818 {
758 819 $extractdir = acym_cleanPath(dirname($zipFilePath).DS.uniqid().uniqid().uniqid().'_template');
759 820
760 821 $result = acym_extractArchive($zipFilePath, $extractdir);
@@ -761,9 +822,9 @@
761 822
762 823 if (!$result) {
763 824 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_9', $zipFilePath, $extractdir), 'error');
764 825
765 - return false;
826 + return null;
766 827 }
767 828
768 829 if ($deleteZip) {
769 830 acym_deleteFile($zipFilePath);
@@ -787,32 +848,46 @@
787 848
788 849 return $extractdir;
789 850 }
790 851
791 - public function installExtractedTemplate($templateFolder)
852 + public function installExtractedTemplate(string $templateFolder, bool $deleteFolder = true): bool
792 853 {
793 - if ($this->detecttemplates($templateFolder)) {
854 + if ($this->detectTemplates($templateFolder)) {
794 855 $messages = $this->templateNames;
795 856 array_unshift($messages, acym_translationSprintf('ACYM_TEMPLATES_INSTALL', count($this->templateNames)));
796 857 acym_enqueueMessage($messages);
797 - if (is_dir($templateFolder)) acym_deleteFolder($templateFolder);
858 + if ($deleteFolder && is_dir($templateFolder)) {
859 + acym_deleteFolder($templateFolder);
860 + }
798 861
799 862 return true;
800 863 }
801 864
802 865 acym_enqueueMessage(acym_translationSprintf('ACYM_FILE_UPLOAD_ERROR_10'), 'error');
803 - if (is_dir($templateFolder)) acym_deleteFolder($templateFolder);
866 + if ($deleteFolder && is_dir($templateFolder)) {
867 + acym_deleteFolder($templateFolder);
868 + }
804 869
805 870 return false;
806 871 }
807 872
808 - public function detecttemplates($folder)
873 + public function detectTemplates(string $folder): bool
809 874 {
810 875 $allFiles = acym_getFiles($folder);
811 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
812 885 foreach ($allFiles as $oneFile) {
813 886 if (preg_match('#^.*(html|htm)$#i', $oneFile)) {
814 - if ($this->_installtemplate($folder.DS.$oneFile)) return true;
887 + if ($this->installTemplate($folder.DS.$oneFile)) {
888 + return true;
889 + }
815 890 }
816 891 }
817 892 }
818 893
@@ -819,9 +894,9 @@
819 894 $status = false;
820 895 $allFolders = acym_getFolders($folder);
821 896 if (!empty($allFolders)) {
822 897 foreach ($allFolders as $oneFolder) {
823 - $status = $this->detecttemplates($folder.DS.$oneFolder) || $status;
898 + $status = $this->detectTemplates($folder.DS.$oneFolder) || $status;
824 899 }
825 900 }
826 901
827 902 return $status;
@@ -826,14 +901,23 @@
826 901
827 902 return $status;
828 903 }
829 904
830 - private function _installtemplate($filepath)
905 + private function installTemplate(string $filepath): bool
831 906 {
832 907 $fileContent = acym_fileGetContent($filepath);
833 908
834 909 $newTemplate = new \stdClass();
835 - $newTemplate->name = trim(preg_replace('#[^a-z0-9]#i', ' ', substr(dirname($filepath), strpos($filepath, '_template'))));
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 +
836 920 if (preg_match('#< *title[^>]*>(.*)< */ *title *>#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->name = $results[1];
837 921
838 922 if (preg_match('#< *meta *name="fromname" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromname = $results[1];
839 923 if (preg_match('#< *meta *name="fromemail" *content="([^"]*)"#Uis', $fileContent, $results) && !empty($results[1])) $newTemplate->fromemail = $results[1];
@@ -946,9 +1030,8 @@
946 1030 }
947 1031
948 1032 $uploadsFolder = ACYM_UPLOAD_FOLDER_THUMBNAIL;
949 1033
950 - $newConfig = new \stdClass();
951 1034 $thumbNb = intval($this->config->get('numberThumbnail', 2));
952 1035
953 1036 foreach ($allPictures as $folder => $pictfolders) {
954 1037 foreach ($pictfolders as $onePict) {
@@ -961,67 +1044,40 @@
961 1044 }
962 1045 }
963 1046 }
964 1047
965 - $newConfig->numberThumbnail = $thumbNb;
966 - $this->config->save($newConfig);
1048 + $this->config->saveConfig(['numberThumbnail' => $thumbNb]);
967 1049
968 1050 $newTemplate->drag_editor = strpos($newTemplate->body, 'acym__wysid__template__content') !== false ? 1 : 0;
969 1051 $newTemplate->type = self::TYPE_TEMPLATE;
970 1052 $newTemplate->creation_date = acym_date('now', 'Y-m-d H:i:s', false);
971 1053
972 - $tempid = $this->save($newTemplate);
1054 + $this->save($newTemplate);
973 1055
974 - $this->templateId = $tempid;
975 1056 $this->templateNames[] = $newTemplate->name;
976 1057
977 1058 return true;
978 1059 }
979 1060
980 - public function sendAutomation($mailId, $userIds, $sendingDate, $automationAdmin = [])
1061 + public function sendAutomation(int $mailId, array $userIds, string $sendingDate, array $automationAdmin = [])
981 1062 {
982 - if (empty($mailId)) return acym_translationSprintf('ACYM_EMAILS_ADDED_QUEUE', 0);
983 - if (empty($sendingDate)) return acym_translation('ACYM_WRONG_DATE');
984 - if (empty($userIds)) return acym_translation('ACYM_USER_NOT_FOUND');
985 - acym_arrayToInteger($userIds);
1063 + if (empty($mailId)) {
1064 + return acym_translationSprintf('ACYM_EMAILS_ADDED_QUEUE', 0);
1065 + }
986 1066
987 - if (isset($automationAdmin['automationAdmin']) && $automationAdmin['automationAdmin']) {
988 - $userClass = new UserClass();
989 - $mail = $this->getOneById($mailId);
990 - $user = $userClass->getOneById($automationAdmin['user_id']);
1067 + if (empty($sendingDate)) {
1068 + return acym_translation('ACYM_WRONG_DATE');
1069 + }
991 1070
992 - if (empty($mail) || empty($user)) return false;
1071 + if (empty($userIds)) {
1072 + return acym_translation('ACYM_USER_NOT_FOUND');
1073 + }
993 1074
994 - $mailerHelper = new MailerHelper();
995 - $pluginHelper = new PluginHelper();
996 - $extractedTags = $pluginHelper->extractTags($mail, 'subscriber');
997 - if (!empty($extractedTags)) {
998 - foreach ($extractedTags as $dtext => $oneTag) {
999 - if (empty($oneTag->info) || $oneTag->info != 'current' || empty($user->{$oneTag->id})) continue;
1075 + acym_arrayToInteger($userIds);
1000 1076
1001 - $mailerHelper->addParam(str_replace(['{', '}'], '', $dtext), $user->{$oneTag->id});
1002 - }
1003 - }
1004 -
1005 - if (!empty($automationAdmin['user_id'])) {
1006 - $userClass = new UserClass();
1007 - $user = $userClass->getOneById($automationAdmin['user_id']);
1008 - if (!empty($user)) {
1009 - $userField = $userClass->getAllUserFields($user);
1010 - foreach ($userField as $map => $value) {
1011 - $mailerHelper->addParam('user:'.$map, $value);
1012 - }
1013 - }
1014 - }
1015 -
1016 - $emailsSent = 0;
1017 - foreach ($userIds as $userId) {
1018 - if ($mailerHelper->sendOne($mail->id, $userId)) {
1019 - $emailsSent++;
1020 - }
1021 - }
1022 -
1023 - return $emailsSent;
1077 + $nbSent = $this->sendNotification($mailId, $userIds, $automationAdmin);
1078 + if (!is_null($nbSent)) {
1079 + return $nbSent;
1024 1080 }
1025 1081
1026 1082 $result = acym_query(
1027 1083 'INSERT IGNORE INTO #__acym_queue (`mail_id`, `user_id`, `sending_date`)
@@ -1029,27 +1085,82 @@
1029 1085 FROM #__acym_user AS user
1030 1086 WHERE user.active = 1 AND user.id IN ('.implode(',', $userIds).')'
1031 1087 );
1032 1088
1089 + if ($result === 0) {
1090 + return acym_translation('ACYM_CAMPAIGN_ALREADY_QUEUED');
1091 + }
1033 1092
1034 1093 $mailStatClass = new MailStatClass();
1035 1094 $mailStat = $mailStatClass->getOneRowByMailId($mailId);
1036 1095
1037 - $newMailStat = [
1038 - 'mail_id' => intval($mailId),
1039 - 'total_subscribers' => intval($result),
1040 - ];
1096 + $newMailStat = new \stdClass();
1097 + $newMailStat->mail_id = intval($mailId);
1098 + $newMailStat->total_subscribers = intval($result);
1041 1099 if (empty($mailStat)) {
1042 - $newMailStat['send_date'] = $sendingDate;
1100 + $newMailStat->send_date = $sendingDate;
1043 1101 }
1044 1102
1045 1103 $mailStatClass->save($newMailStat);
1046 1104
1047 - if ($result === 0) {
1048 - return acym_translation('ACYM_CAMPAIGN_ALREADY_QUEUED');
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;
1049 1112 }
1050 1113
1051 - return $result;
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;
1052 1163 }
1053 1164
1054 1165 public function encode($mails = [])
1055 1166 {
@@ -1068,8 +1179,12 @@
1068 1179 }
1069 1180
1070 1181 public function decode($mails = [])
1071 1182 {
1183 + if (is_null($mails)) {
1184 + return null;
1185 + }
1186 +
1072 1187 $isArray = true;
1073 1188 if (!is_array($mails)) {
1074 1189 $mails = [$mails];
1075 1190
@@ -1087,8 +1202,11 @@
1087 1202
1088 1203 return $isArray ? $decodedMails : $decodedMails[0];
1089 1204 }
1090 1205
1206 + /**
1207 + * Decode one mail from UTF8. (decode only attributes defined in $fieldsToDecode)
1208 + */
1091 1209 protected function utf8Decode($mail)
1092 1210 {
1093 1211 if (!empty($mail)) {
1094 1212 foreach (self::FIELDS_ENCODING as $oneField) {
@@ -1110,14 +1228,15 @@
1110 1228
1111 1229 return $mail;
1112 1230 }
1113 1231
1114 - protected function addPoweredByAcyMailing($mail)
1232 + private function addPoweredByAcyMailing(object $mail): object
1115 1233 {
1116 1234 if (empty($mail->body)) {
1117 1235 return $mail;
1118 1236 }
1119 1237
1238 + //If we don't display the powered by we remove it
1120 1239 if ($this->config->get('display_built_by', 0) != 1) {
1121 1240 if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1122 1241 $mailBodyDom = new \DOMDocument();
1123 1242 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
@@ -1177,8 +1296,9 @@
1177 1296 </tbody>
1178 1297 </table>';
1179 1298
1180 1299 $mailBodyDom = new \DOMDocument();
1300 + //Some inserted content add specific tags that shows warnings
1181 1301 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1182 1302
1183 1303 $htmlToAddDom = new \DOMDocument();
1184 1304 @$htmlToAddDom->loadHTML('<?xml encoding="utf-8" ?>'.$poweredByWYSID);
@@ -1196,14 +1316,14 @@
1196 1316
1197 1317 return $mail;
1198 1318 }
1199 1319
1200 - protected function removePoweredByAcyMailing($mail)
1320 + protected function removePoweredByAcyMailing(object $mail): object
1201 1321 {
1202 - if (empty($mail->body)) return $mail;
1322 + if (empty($mail->body) || strpos($mail->body, 'acym__wysid__template') !== false) {
1323 + return $mail;
1324 + }
1203 1325
1204 - if (strpos($mail->body, 'acym__wysid__template') !== false) return $mail;
1205 -
1206 1326 $regexPoweredByAcyMailing = '#<p id="acym__powered_by_acymailing.*<\/p>#U';
1207 1327
1208 1328 $mail->body = preg_replace($regexPoweredByAcyMailing, '', $mail->body);
1209 1329
@@ -1209,8 +1329,15 @@
1209 1329
1210 1330 return $mail;
1211 1331 }
1212 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 + */
1213 1340 protected function utf8Encode($mail)
1214 1341 {
1215 1342 if (!empty($mail)) {
1216 1343 foreach (self::FIELDS_ENCODING as $oneField) {
@@ -1228,11 +1355,11 @@
1228 1355
1229 1356 return $mail;
1230 1357 }
1231 1358
1232 - public function getTranslationId($parentId, $langCode)
1359 + public function getTranslationId(int $parentId, string $langCode): int
1233 1360 {
1234 - return acym_loadResult(
1361 + return (int)acym_loadResult(
1235 1362 'SELECT `id`
1236 1363 FROM #__acym_mail
1237 1364 WHERE `parent_id` = '.intval($parentId).'
1238 1365 AND `language` = '.acym_escapeDB($langCode)
@@ -1238,9 +1365,9 @@
1238 1365 AND `language` = '.acym_escapeDB($langCode)
1239 1366 );
1240 1367 }
1241 1368
1242 - public function getTranslationsById($mailId, $full = false, $includeParent = false)
1369 + public function getTranslationsById(int $mailId, bool $full = false, bool $includeParent = false)
1243 1370 {
1244 1371 $data = $full ? '*' : '`language`, `subject`, `preheader`, `body`, `autosave`';
1245 1372 $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1246 1373
@@ -1253,12 +1380,13 @@
1253 1380 )
1254 1381 );
1255 1382 }
1256 1383
1257 - public function deleteByTranslationLang($languageCodes)
1384 + public function deleteByTranslationLang(array $languageCodes): void
1258 1385 {
1259 - if (!is_array($languageCodes)) $languageCodes = [$languageCodes];
1260 - if (empty($languageCodes)) return;
1386 + if (empty($languageCodes)) {
1387 + return;
1388 + }
1261 1389
1262 1390 foreach ($languageCodes as $key => $oneLangCode) {
1263 1391 $languageCodes[$key] = acym_escapeDB($oneLangCode);
1264 1392 }
@@ -1272,9 +1400,12 @@
1272 1400 )
1273 1401 );
1274 1402 }
1275 1403
1276 - public function getMultilingualMails(int $parentId)
1404 + /**
1405 + * Get all multilingual mails linked to a parent mail, also get the parent mail
1406 + */
1407 + public function getMultilingualMails(int $parentId): array
1277 1408 {
1278 1409 $mails = $this->decode(
1279 1410 acym_loadObjectList(
1280 1411 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
@@ -1284,17 +1415,21 @@
1284 1415
1285 1416 return $this->translateMailSettings($mails);
1286 1417 }
1287 1418
1288 - private function translateMailSettings($mails)
1419 + private function translateMailSettings(array $mails): array
1289 1420 {
1290 - if (empty($mails)) return $mails;
1421 + if (empty($mails)) {
1422 + return $mails;
1423 + }
1291 1424
1292 1425 $defaultLanguage = $this->config->get('multilingual_default');
1293 1426
1294 1427 if (empty($mails[$defaultLanguage])) {
1295 1428 $firstMail = reset($mails);
1296 - if (empty($firstMail) || empty($firstMail->parent_id)) return $mails;
1429 + if (empty($firstMail) || empty($firstMail->parent_id)) {
1430 + return $mails;
1431 + }
1297 1432 $mainMail = $this->getOneById($firstMail->parent_id);
1298 1433 } else {
1299 1434 $mainMail = $mails[$defaultLanguage];
1300 1435 }
@@ -1302,14 +1437,16 @@
1302 1437 if (!empty($mainMail->translation)) {
1303 1438 $translation = json_decode($mainMail->translation, true);
1304 1439
1305 1440 foreach ($mails as $lang => $mail) {
1306 - if (empty($mail->parent_id) || empty($translation[$lang])) continue;
1441 + if (empty($mail->parent_id) || empty($translation[$lang])) {
1442 + continue;
1443 + }
1307 1444
1308 - if (!empty($translation[$lang]['from_name'])) $mails[$lang]->from_name = $translation[$lang]['from_name'];
1309 - if (!empty($translation[$lang]['from_email'])) $mails[$lang]->from_email = $translation[$lang]['from_email'];
1310 - if (!empty($translation[$lang]['reply_to_name'])) $mails[$lang]->reply_to_name = $translation[$lang]['reply_to_name'];
1311 - if (!empty($translation[$lang]['reply_to_email'])) $mails[$lang]->reply_to_email = $translation[$lang]['reply_to_email'];
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'];
1312 1449 }
1313 1450 }
1314 1451
1315 1452 return $mails;
@@ -1314,19 +1451,19 @@
1314 1451
1315 1452 return $mails;
1316 1453 }
1317 1454
1318 - public function getMailAttachments($mailId)
1455 + public function getMailAttachments(int $mailId)
1319 1456 {
1320 1457 return acym_loadResult('SELECT attachments FROM #__acym_mail WHERE id = '.intval($mailId));
1321 1458 }
1322 1459
1323 - public function isTransactionalMail($mail): bool
1460 + public function isTransactionalMail(object $mail): bool
1324 1461 {
1325 1462 return $mail->type !== self::TYPE_STANDARD;
1326 1463 }
1327 1464
1328 - public function isOneTimeMail($mail): bool
1465 + public function isOneTimeMail(object $mail): bool
1329 1466 {
1330 1467 return in_array(
1331 1468 $mail->type,
1332 1469 [
@@ -1338,9 +1475,9 @@
1338 1475 ]
1339 1476 );
1340 1477 }
1341 1478
1342 - public function getAutomaticMailIds($mailIds)
1479 + public function getAutomaticMailIds(array $mailIds): array
1343 1480 {
1344 1481 acym_arrayToInteger($mailIds);
1345 1482
1346 1483 $query = 'SELECT DISTINCT campaign2.mail_id
@@ -1355,9 +1492,9 @@
1355 1492
1356 1493 return $generatedMailIds;
1357 1494 }
1358 1495
1359 - public function getMultilingualMailIds($mailIds)
1496 + public function getMultilingualMailIds(array $mailIds): array
1360 1497 {
1361 1498 acym_arrayToInteger($mailIds);
1362 1499
1363 1500 $query = 'SELECT DISTINCT mail2.id
@@ -1373,14 +1510,14 @@
1373 1510
1374 1511 return $multipleMailIds;
1375 1512 }
1376 1513
1377 - public function duplicateMail($mailId, $type = null)
1514 + public function duplicateMail(int $mailId, ?string $type = null): ?object
1378 1515 {
1379 1516 $mail = $this->getOneById($mailId);
1380 1517
1381 1518 if (empty($mail)) {
1382 - return false;
1519 + return null;
1383 1520 }
1384 1521
1385 1522 $newMail = new \stdClass();
1386 1523 $newMail->name = $mail->name.'_copy';
@@ -1404,9 +1541,9 @@
1404 1541
1405 1542 return $newMail;
1406 1543 }
1407 1544
1408 - public function getTemplatesForMailboxAction()
1545 + public function getTemplatesForMailboxAction(): array
1409 1546 {
1410 1547 return $this->decode(
1411 1548 acym_loadObjectList(
1412 1549 'SELECT `id`, `name`
@@ -1416,31 +1553,77 @@
1416 1553 )
1417 1554 );
1418 1555 }
1419 1556
1420 - public function hasUserAccess(int $mailId): bool
1557 + public function hasUserAccess(int $mailId, bool $write = false, ?int $userId = null): bool
1421 1558 {
1422 - $userId = acym_currentUserId();
1559 + $userId = $userId ?? acym_currentUserId();
1423 1560 if (empty($userId)) {
1424 1561 return false;
1425 1562 }
1426 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 +
1427 1569 if (acym_isAdmin()) {
1570 + return $this->isTypeAllowed($mail->type);
1571 + }
1572 +
1573 + if ($mail->creator_id == $userId) {
1428 1574 return true;
1429 1575 }
1430 1576
1431 - return acym_loadResult(
1432 - 'SELECT COUNT(*)
1433 - FROM #__acym_mail
1434 - WHERE id = '.intval($mailId).'
1435 - AND (creator_id = '.intval($userId).'
1436 - OR type = '.acym_escapeDB(self::TYPE_TEMPLATE).')'
1437 - ) > 0;
1577 + return !$write && $mail->type === self::TYPE_TEMPLATE && $this->isAccessAllowedForUser($mail->access, $userId);
1438 1578 }
1439 1579
1580 + private function isAccessAllowedForUser(?string $access, int $userId): bool
1581 + {
1582 + if (empty($access)) {
1583 + return false;
1584 + }
1440 1585
1441 - public function getVersionsById($mailId, $includeParent = false)
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
1442 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 + {
1443 1626 $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1444 1627
1445 1628 return $this->decode(
1446 1629 acym_loadObjectList(
@@ -1451,21 +1634,22 @@
1451 1634 )
1452 1635 );
1453 1636 }
1454 1637
1455 - public function getParentAndChildMails($parentId)
1638 + /**
1639 + * Get all abtest mails linked to a parent mail, also get the parent mail
1640 + */
1641 + public function getParentAndChildMails(int $parentId): array
1456 1642 {
1457 - $mails = $this->decode(
1643 + return $this->decode(
1458 1644 acym_loadObjectList(
1459 1645 'SELECT * FROM #__acym_mail WHERE parent_id = '.intval($parentId).' OR id = '.intval($parentId),
1460 1646 'id'
1461 1647 )
1462 1648 );
1463 -
1464 - return $mails;
1465 1649 }
1466 1650
1467 - public function getAllTemplatesByType($options)
1651 + public function getAllTemplatesByType(array $options): array
1468 1652 {
1469 1653 $limit = $options['limit'] ?? 10;
1470 1654 $offset = $options['offset'] ?? 0;
1471 1655 $filters = $options['filters'] ?? [];
@@ -1471,9 +1655,8 @@
1471 1655 $filters = $options['filters'] ?? [];
1472 1656 $search = $filters['search'] ?? '';
1473 1657 $templateType = $filters['templateType'] ?? '';
1474 1658
1475 - $query = 'SELECT * FROM #__acym_mail';
1476 1659 $conditions = ['`type` = '.acym_escapeDB(self::TYPE_TEMPLATE)];
1477 1660
1478 1661 if (!empty($search)) {
1479 1662 $conditions[] = '(name LIKE '.acym_escapeDB('%'.$search.'%').' OR subject LIKE '.acym_escapeDB('%'.$search.'%').')';
@@ -1486,22 +1669,22 @@
1486 1669 $conditions[] = 'drag_editor = 0';
1487 1670 }
1488 1671 }
1489 1672
1490 - $query .= ' WHERE '.implode(' AND ', $conditions);
1673 + $query = 'SELECT * FROM #__acym_mail WHERE '.implode(' AND ', $conditions);
1491 1674
1492 1675 return $this->decode(acym_loadObjectList($query, '', $offset, $limit));
1493 1676 }
1494 1677
1495 - public function updateFollowupPriority($oldPriority, $newPriority)
1678 + public function updateFollowupPriority(bool $previouslyActive, bool $active): void
1496 1679 {
1497 - if ($oldPriority == 0 && $newPriority == 1) {
1498 - $updatePriorityQuery = 'UPDATE `#__acym_queue` AS `queue`
1680 + if (!$previouslyActive && $active) {
1681 + acym_query(
1682 + 'UPDATE `#__acym_queue` AS `queue`
1499 1683 INNER JOIN `#__acym_mail` AS `mail` ON `queue`.`mail_id` = `mail`.`id`
1500 1684 SET `queue`.`priority` = 1
1501 - WHERE `mail`.`type` = "'.self::TYPE_FOLLOWUP.'"';
1502 -
1503 - return acym_query($updatePriorityQuery);
1685 + WHERE `mail`.`type` = '.acym_escapeDB(self::TYPE_FOLLOWUP)
1686 + );
1504 1687 }
1505 1688 }
1506 1689
1507 1690 public function getCampaignIdByMailId(int $mailId): int
@@ -1515,6 +1698,11 @@
1515 1698 {
1516 1699 $parentId = acym_loadResult('SELECT parent_id FROM #__acym_mail WHERE id = '.intval($mailId));
1517 1700
1518 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));
1519 1707 }
1520 1708 }