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 +127 -21 10.11.1 → 11.1.0 View file →
@@ -40,8 +40,9 @@
40 40 self::TYPE_TEMPLATE,
41 41 self::TYPE_SCENARIO,
42 42 ];
43 43
44 + // Used by some sending methods to know the priority of a sent email (transactional => reset password / account confirmation...)
44 45 const TYPES_TRANSACTIONAL = [
45 46 self::TYPE_NOTIFICATION,
46 47 self::TYPE_OVERRIDE,
47 48 self::TYPE_WELCOME,
@@ -49,8 +50,9 @@
49 50 self::TYPE_TEMPLATE,
50 51 self::TYPE_SCENARIO,
51 52 ];
52 53
54 + // Types on which the click statistics are active
53 55 const TYPES_WITH_STATS = [
54 56 self::TYPE_STANDARD,
55 57 self::TYPE_AUTOMATION,
56 58 self::TYPE_WELCOME,
@@ -59,8 +61,9 @@
59 61 self::TYPE_MAILBOX_ACTION,
60 62 self::TYPE_SCENARIO,
61 63 ];
62 64
65 + // Types that don't let the user modify the name
63 66 const TYPES_NO_NAME = [
64 67 self::TYPE_NOTIFICATION,
65 68 self::TYPE_OVERRIDE,
66 69 ];
@@ -88,8 +91,11 @@
88 91 $this->table = 'mail';
89 92 $this->pkey = 'id';
90 93 }
91 94
95 + /**
96 + * Get mails depending on filters (search, ordering, pagination)
97 + */
92 98 public function getMatchingElements(array $settings = []): array
93 99 {
94 100 $query = 'SELECT mail.* FROM #__acym_mail AS mail';
95 101 $queryCount = 'SELECT COUNT(mail.id) AS total FROM #__acym_mail AS mail';
@@ -96,8 +102,9 @@
96 102
97 103 $filters = [];
98 104 $tagJoin = '';
99 105
106 + // Tag filter
100 107 if (!empty($settings['tag'])) {
101 108 $tagJoin = ' JOIN #__acym_tag AS tag ON mail.id = tag.id_element ';
102 109 $filters[] = 'tag.name = '.acym_escapeDB($settings['tag']);
103 110 $filters[] = 'tag.type = "mail"';
@@ -302,8 +309,11 @@
302 309
303 310 return $mail;
304 311 }
305 312
313 + /**
314 + * Get mails depending on their type (standard, welcome, unsubscribe, notification)
315 + */
306 316 public function getMailsByType(?string $typeMail, array $settings): array
307 317 {
308 318 if (empty($settings['key'])) {
309 319 $settings['key'] = '';
@@ -318,13 +328,15 @@
318 328
319 329 $query = 'SELECT * FROM #__acym_mail AS mail';
320 330 $queryCount = 'SELECT count(*) FROM #__acym_mail AS mail';
321 331
332 + // Mail type filtering
322 333 $filters = [];
323 334 if (!empty($typeMail)) {
324 335 $filters[] = 'mail.type = '.acym_escapeDB($typeMail);
325 336 }
326 337
338 + // Search filter
327 339 if (!empty($search)) {
328 340 $filters[] = 'mail.name LIKE '.acym_escapeDB('%'.$search.'%');
329 341 }
330 342
@@ -363,8 +375,9 @@
363 375 $query .= 'ON list.id = userLists.list_id
364 376 WHERE mailLists.mail_id IN ('.implode(',', $ids).')
365 377 GROUP BY mailLists.list_id, mailLists.mail_id';
366 378
379 + //This line if for guys with big database to not break the page
367 380 acym_query('SET SQL_BIG_SELECTS=1');
368 381
369 382 return acym_loadObjectList($query);
370 383 }
@@ -435,11 +448,14 @@
435 448 }
436 449
437 450 $mail = $this->encode($mail);
438 451
452 + // Clean autosave value
439 453 $mail->autosave = null;
440 454
441 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
442 458 if (isset($mail->thumbnail) && ((empty($mail->thumbnail) && !is_null($mail->thumbnail)) || strpos($mail->thumbnail, 'data:image/png;base64') !== false)) {
443 459 unset($mail->thumbnail);
444 460 }
445 461
@@ -464,11 +480,12 @@
464 480 if (in_array($oneAttribute, ['body', 'headers'])) {
465 481 $mail->$oneAttribute = preg_replace('#<input[^>]*value="[^"]*"[^>]*>#Uis', '', $mail->$oneAttribute);
466 482 $mail->$oneAttribute = preg_replace('#<script.*</script>#Uis', '', $mail->$oneAttribute);
467 483
484 + //Remove tinyMce content edit
468 485 $mail->$oneAttribute = str_replace(' contenteditable="true"', '', $mail->$oneAttribute);
469 486 } else {
470 - $mail->$oneAttribute = strip_tags($mail->$oneAttribute);
487 + $mail->$oneAttribute = acym_stripTags($mail->$oneAttribute);
471 488 }
472 489 }
473 490
474 491 $mailID = parent::save($mail);
@@ -516,13 +533,8 @@
516 533
517 534 $this->deleteMediaFolder($elements);
518 535 acym_arrayToInteger($elements);
519 536
520 - $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
521 - if (in_array($favoriteTemplate, $elements)) {
522 - $this->config->saveConfig(['favorite_template' => 0]);
523 - }
524 -
525 537 $allThumbnailToDelete = acym_loadResultArray('SELECT DISTINCT thumbnail FROM #__acym_mail WHERE id IN ('.implode(',', $elements).')');
526 538
527 539 $translations = acym_loadResultArray('SELECT id FROM #__acym_mail WHERE parent_id IN ('.implode(',', $elements).')');
528 540 $elements = array_merge($elements, $translations);
@@ -529,8 +541,13 @@
529 541 if (!empty($translations)) {
530 542 acym_query('UPDATE #__acym_mail SET `parent_id` = null WHERE `id` IN ('.implode(',', $translations).')');
531 543 }
532 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 +
533 550 acym_query('UPDATE #__acym_list SET welcome_id = null WHERE welcome_id IN ('.implode(',', $elements).')');
534 551 acym_query('UPDATE #__acym_list SET unsubscribe_id = null WHERE unsubscribe_id IN ('.implode(',', $elements).')');
535 552 acym_query('DELETE FROM #__acym_queue WHERE mail_id IN ('.implode(',', $elements).')');
536 553 acym_query('DELETE FROM #__acym_mail_has_list WHERE mail_id IN ('.implode(',', $elements).')');
@@ -547,8 +564,25 @@
547 564
548 565 return $return;
549 566 }
550 567
568 + public function getFavoriteTemplateId(): int
569 + {
570 + $favoriteTemplate = (int)$this->config->get('favorite_template', 0);
571 + if (empty($favoriteTemplate)) {
572 + return 0;
573 + }
574 +
575 + $exists = acym_loadResult('SELECT `id` FROM #__acym_mail WHERE `id` = '.intval($favoriteTemplate));
576 + if (empty($exists)) {
577 + $this->config->saveConfig(['favorite_template' => 0]);
578 +
579 + return 0;
580 + }
581 +
582 + return $favoriteTemplate;
583 + }
584 +
551 585 public function deleteMediaFolder(array $elements): void
552 586 {
553 587 if (empty($elements)) {
554 588 return;
@@ -580,13 +614,14 @@
580 614 $stillUsedThumbnails = acym_loadResultArray('SELECT thumbnail FROM #__acym_mail WHERE thumbnail IN ('.implode(',', $thumbnails).')');
581 615 $thumbnailToDelete = array_diff($thumbnails, $stillUsedThumbnails);
582 616 foreach ($thumbnailToDelete as $one) {
583 617 if (!empty($one) && file_exists(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one)) {
584 - unlink(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
618 + acym_deleteFile(ACYM_UPLOAD_FOLDER_THUMBNAIL.$one);
585 619 }
586 620 }
587 621 }
588 622
623 + // Delete one attachment from a newsletter
589 624 public function deleteOneAttachment(int $mailId, int $idAttachment): bool
590 625 {
591 626 if (empty($mailId)) {
592 627 return false;
@@ -648,9 +683,11 @@
648 683 $inline = '';
649 684
650 685 if (preg_match_all('#@import[^;]*;#is', $stylesheet, $results)) {
651 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
652 688 $inline .= trim($oneResult)."\n";
689 + //We also remove it from the stylesheet to avoid having a duplicate.
653 690 $stylesheet = str_replace($oneResult, '', $stylesheet);
654 691 }
655 692 }
656 693
@@ -715,11 +752,11 @@
715 752 }
716 753
717 754 $uploadPath = acym_cleanPath(ACYM_ROOT.ACYM_MEDIA_FOLDER.'templates');
718 755
719 - if (!is_writable($uploadPath)) {
720 - @chmod($uploadPath, '0755');
721 - if (!is_writable($uploadPath)) {
756 + if (!acym_isWritable($uploadPath)) {
757 + acym_chmod($uploadPath, 0755);
758 + if (!acym_isWritable($uploadPath)) {
722 759 acym_enqueueMessage(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $uploadPath), 'warning');
723 760 }
724 761 }
725 762
@@ -747,9 +784,9 @@
747 784 $allowedFileType = false;
748 785 }
749 786
750 787 if (!$allowedFileType) {
751 - acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', $extension, 'zip'), 'error');
788 + acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', esc_html($extension), 'zip'), 'error');
752 789
753 790 return null;
754 791 }
755 792
@@ -836,8 +873,9 @@
836 873 public function detectTemplates(string $folder): bool
837 874 {
838 875 $allFiles = acym_getFiles($folder);
839 876 if (!empty($allFiles)) {
877 + // Search and install template.html file first
840 878 $keyTmpl = array_search('template.html', $allFiles);
841 879 if ($keyTmpl !== false) {
842 880 if ($this->installTemplate($folder.DS.$allFiles[$keyTmpl])) {
843 881 return true;
@@ -842,8 +880,9 @@
842 880 if ($this->installTemplate($folder.DS.$allFiles[$keyTmpl])) {
843 881 return true;
844 882 }
845 883 }
884 + // Try to install first html file found
846 885 foreach ($allFiles as $oneFile) {
847 886 if (preg_match('#^.*(html|htm)$#i', $oneFile)) {
848 887 if ($this->installTemplate($folder.DS.$oneFile)) {
849 888 return true;
@@ -1079,8 +1118,9 @@
1079 1118 if (empty($mail) || empty($user)) {
1080 1119 return 0;
1081 1120 }
1082 1121
1122 + // Get the current user values
1083 1123 $mailerHelper = new MailerHelper();
1084 1124 $pluginHelper = new PluginHelper();
1085 1125 $extractedTags = $pluginHelper->extractTags($mail, 'subscriber');
1086 1126
@@ -1107,8 +1147,9 @@
1107 1147
1108 1148 $userFields = $userClass->getAllUserFields($user);
1109 1149 foreach ($userFields as $map => $value) {
1110 1150 $mailerHelper->addParam('subscriber:'.$map.'|info:current', $value);
1151 + // Might not be used anymore
1111 1152 $mailerHelper->addParam('user:'.$map, $value);
1112 1153 }
1113 1154
1114 1155 $emailsSent = 0;
@@ -1161,8 +1202,11 @@
1161 1202
1162 1203 return $isArray ? $decodedMails : $decodedMails[0];
1163 1204 }
1164 1205
1206 + /**
1207 + * Decode one mail from UTF8. (decode only attributes defined in $fieldsToDecode)
1208 + */
1165 1209 protected function utf8Decode($mail)
1166 1210 {
1167 1211 if (!empty($mail)) {
1168 1212 foreach (self::FIELDS_ENCODING as $oneField) {
@@ -1190,8 +1234,9 @@
1190 1234 if (empty($mail->body)) {
1191 1235 return $mail;
1192 1236 }
1193 1237
1238 + //If we don't display the powered by we remove it
1194 1239 if ($this->config->get('display_built_by', 0) != 1) {
1195 1240 if (strpos($mail->body, 'acym__powered_by_acymailing') !== false) {
1196 1241 $mailBodyDom = new \DOMDocument();
1197 1242 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
@@ -1251,8 +1296,9 @@
1251 1296 </tbody>
1252 1297 </table>';
1253 1298
1254 1299 $mailBodyDom = new \DOMDocument();
1300 + //Some inserted content add specific tags that shows warnings
1255 1301 @$mailBodyDom->loadHTML('<?xml encoding="utf-8" ?>'.$mail->body);
1256 1302
1257 1303 $htmlToAddDom = new \DOMDocument();
1258 1304 @$htmlToAddDom->loadHTML('<?xml encoding="utf-8" ?>'.$poweredByWYSID);
@@ -1283,8 +1329,15 @@
1283 1329
1284 1330 return $mail;
1285 1331 }
1286 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 + */
1287 1340 protected function utf8Encode($mail)
1288 1341 {
1289 1342 if (!empty($mail)) {
1290 1343 foreach (self::FIELDS_ENCODING as $oneField) {
@@ -1347,8 +1400,11 @@
1347 1400 )
1348 1401 );
1349 1402 }
1350 1403
1404 + /**
1405 + * Get all multilingual mails linked to a parent mail, also get the parent mail
1406 + */
1351 1407 public function getMultilingualMails(int $parentId): array
1352 1408 {
1353 1409 $mails = $this->decode(
1354 1410 acym_loadObjectList(
@@ -1497,28 +1553,75 @@
1497 1553 )
1498 1554 );
1499 1555 }
1500 1556
1501 - public function hasUserAccess(int $mailId): bool
1557 + public function hasUserAccess(int $mailId, bool $write = false, ?int $userId = null): bool
1502 1558 {
1503 - $userId = acym_currentUserId();
1559 + $userId = $userId ?? acym_currentUserId();
1504 1560 if (empty($userId)) {
1505 1561 return false;
1506 1562 }
1507 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 +
1508 1569 if (acym_isAdmin()) {
1570 + return $this->isTypeAllowed($mail->type);
1571 + }
1572 +
1573 + if ($mail->creator_id == $userId) {
1509 1574 return true;
1510 1575 }
1511 1576
1512 - return acym_loadResult(
1513 - 'SELECT COUNT(*)
1514 - FROM #__acym_mail
1515 - WHERE id = '.intval($mailId).'
1516 - AND (creator_id = '.intval($userId).'
1517 - OR type = '.acym_escapeDB(self::TYPE_TEMPLATE).')'
1518 - ) > 0;
1577 + return !$write && $mail->type === self::TYPE_TEMPLATE && $this->isAccessAllowedForUser($mail->access, $userId);
1519 1578 }
1520 1579
1580 + private function isAccessAllowedForUser(?string $access, int $userId): bool
1581 + {
1582 + if (empty($access)) {
1583 + return false;
1584 + }
1585 +
1586 + foreach (acym_getGroupsByUser($userId) as $userGroup) {
1587 + if (strpos($access, ','.$userGroup.',') !== false) {
1588 + return true;
1589 + }
1590 + }
1591 +
1592 + return false;
1593 + }
1594 +
1595 + private function isTypeAllowed(?string $type): bool
1596 + {
1597 + $aclByType = [
1598 + self::TYPE_NOTIFICATION => ['configuration'],
1599 + self::TYPE_OVERRIDE => ['override'],
1600 + self::TYPE_MAILBOX_ACTION => ['bounces'],
1601 + self::TYPE_AUTOMATION => ['automation'],
1602 + self::TYPE_SCENARIO => ['scenarios'],
1603 + self::TYPE_STANDARD => ['campaigns'],
1604 + self::TYPE_FOLLOWUP => ['campaigns'],
1605 + self::TYPE_WELCOME => ['lists', 'campaigns'],
1606 + self::TYPE_UNSUBSCRIBE => ['lists', 'campaigns'],
1607 + self::TYPE_TEMPLATE => ['mails', 'campaigns'],
1608 + ];
1609 +
1610 + // An add-on type is not mapped to a feature, don't lock it
1611 + if (empty($aclByType[$type])) {
1612 + return true;
1613 + }
1614 +
1615 + foreach ($aclByType[$type] as $oneAcl) {
1616 + if (acym_isAllowed($oneAcl)) {
1617 + return true;
1618 + }
1619 + }
1620 +
1621 + return false;
1622 + }
1623 +
1521 1624 public function getVersionsById(int $mailId, bool $includeParent = false): array
1522 1625 {
1523 1626 $where = $includeParent ? ' OR `id` = '.intval($mailId) : '';
1524 1627
@@ -1531,8 +1634,11 @@
1531 1634 )
1532 1635 );
1533 1636 }
1534 1637
1638 + /**
1639 + * Get all abtest mails linked to a parent mail, also get the parent mail
1640 + */
1535 1641 public function getParentAndChildMails(int $parentId): array
1536 1642 {
1537 1643 return $this->decode(
1538 1644 acym_loadObjectList(
@@ -1596,7 +1702,7 @@
1596 1702 }
1597 1703
1598 1704 public function getMailType(int $mailId): string
1599 1705 {
1600 - return acym_loadResult('SELECT type FROM #__acym_mail WHERE id = '.intval($mailId));
1706 + return (string)acym_loadResult('SELECT type FROM #__acym_mail WHERE id = '.intval($mailId));
1601 1707 }
1602 1708 }