[EmailQueue] - Очередь писем

С сайта бывает требуется отсылать много писем. Но многие хостеры ограничивают число писем что можно сразу отправить. Например на одном хостинге можно отправить только 60 писем в минуту. Чтобы обойти это ограничение нужно организовывать очередь писем и отправлять письма частями по, например, 50 штук. Чтобы не писать такую очередь каждый раз когда отправка многих писем нужна в компоненте, написал отдельный компонент что организует такую очередь.


В админке сделано относительно удобное управление очередью.
Для отправки писем из очереди поставите в cron файл core/components/emailqueue/cron/send.php с нужной периодичностью.
Настройки:
emailqueue_limit — сколько писем отправлять за раз.
emailqueue_store_days — сколько дней письма хранить в очереди.
Для помещения писемь в очередь используйте примерно такой код:
if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
	return;
}
$queue_email = $modx->newObject('EmailQueueItem');
$data1 = array(
	'sender_package'=>'UserTest', //дополнение отправитель письма
	'to'=>$invite->user_email, //емаил
	'subject'=>$modx->lexicon('usertest_invite_subject',array('test_name' => $test->name)), // тема письма
	'body'=>$modx->getChunk('tpl.UserTest.InviteEmail',array('test_name' => $test->name,'link'=>$invite->url)), // тело письма
	'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
);
if($modx->getOption('usertest_invite_email_from', null, false))
	$data1['from'] = $modx->getOption('usertest_invite_email_from'); //необязательно
if($modx->getOption('usertest_invite_email_from_name', null, false))
	$data1['from_name'] = $modx->getOption('usertest_invite_email_from_name'); //необязательно
$queue_email->fromArray($data1);
if($queue_email->save())
	$modx->log(modX::LOG_LEVEL_INFO,"Приглашение на $email добавлено в очередь писем!");
Дополнительно можно задать:
$queue_email->reply_to // обратный адрес 
$queue_email->attachments //полные имена приклепляемых файлов через запятую
Компонент доступен в Modstore https://modstore.pro/packages/alerts-mailing/emailqueue.

Думаю небольшие компоненты для разработчиков лучше делать бесплатными. Так что компонетнт по крайней мере пока бесплатный :). Но от доната не откажусь. Кто захочет поблагодарить Номер кошелька Яндекс 410011280920822

Гитхаб https://github.com/touol/EmailQueue

UPD 10.12.2018
В версии 1.1.0-beta
Сделан более удобный topBar.
Добавлен статус «Отправляется» для для избежания отправки дубликатов писем.
Добавлены хуки EmailQueue и EmailQueueAutoResponder для замены хуков email и FormItAutoResponder в FormIt/AjaxForm.

EmailQueue — Cниппет для drop-in replacement замены хука email для FormIt/AjaxForm
Просто в вызове поменять хук на EmailQueue
EmailQueueAutoResponder — Cниппет для drop-in replacement замены хука FormItAutoResponder для FormIt/AjaxForm
Просто в вызове поменять хук на EmailQueueAutoResponder
[[!Formit?
    &hooks=`EmailQueue,EmailQueueAutoResponder`
    &emailTpl=`feedbackEmail`
    &emailTo=`test@mail.ru`
    &emailSubject=`Сообщение с сайта`
    &replyTo=`[[+email]]`
    &emailFrom=`[[++emailsender]]`
	
    &fiarTpl=`feedbackEmail`
    &fiarSubject=`Сообщение с сайта`
    &fiarReplyTo=`[[+email]]`
    &fiarFrom=`[[++emailsender]]`
]]
Поддержку вложений не делал, т.к. не было необходимости.
И не все параметры FormIt поддерживает (например emailHtml, emailToName, emailCC, итд), т.к. компонент их не учитывает.

Если хотите помочь развитию компонента делайте мне пулл-реквест на гитхабе. Или просто публикуйте изменения кода в комментариях :).
Александр Туниеков
10 июня 2018, 05:44
modx.pro
7
2 739
+12
Поблагодарить автора Отправить деньги

Комментарии: 69

Дмитрий
10 июня 2018, 16:00
+1
Скоро придется отправить 4000 писем пользователям сайта, который переношу с WP, и как раз об этом думал, как же решить это. Большое спасибо за компонентище!
    Александр Туниеков
    10 июня 2018, 16:22
    +1
    Надеюсь скоро опубликуют. Рад что вам пригодиться. Забыл только очистку всей очереди добавить :(, но попозже добавлю. Если вам быстрее компонент надо будет могу выслать по почте.
      Дмитрий
      10 июня 2018, 16:43
      0
      да не, не раньше, чем через 2 недели нужно будет)
    tvset
    11 июня 2018, 19:16
    0
    Очень здорово! очень нужно, спасибо автор!
    Тоже со временем воспользуюсь, возьму на заметку.
      Василий Столейков
      13 июня 2018, 16:05
      0
      Спасибо большое!
      Использовал для этих целей очереди Sendex, добавлял туда письма и дёргал очередь через крон. Ваше дополнение удобнее и видна статистика отправленных писем.
        Александр Туниеков
        13 июня 2018, 17:49
        +2
        Еще дополнение на модерации и его еще в глаза никто не видел, а уже 3 спасибо :). Всем пожалуйста. Сейчас думаю на всякий случай в гитхаб выложу. Вдруг кому-то уже надо.
        Александр Туниеков
        13 июня 2018, 18:03
        +2
        Выложил на гитхаб
          Иван Бондаренко
          18 июня 2018, 14:40
          0
          Александр, обнови пост. Компонент доступен в Модстор — modstore.pro/packages/alerts-mailing/emailqueue
          Василий Столейков
          18 июня 2018, 18:34
          0
          Какая-то странная дата отправки на демо-сайте: -1-11-30 00:00:00
            Александр Туниеков
            18 июня 2018, 18:53
            +1
            Это такая дата по умолчанию была. Так 0000-00-00 00:00:00 mysql записывает. Модераторы модсторе тоже сказали это поменять. В новой версии по умолчанию дата отправки null и в поле пусто. Сейчас обновил компонент на демо, чтоб вас это не смущало.
              Василий Столейков
              18 июня 2018, 18:56
              0
              Да, спасибо, пустота будет лучше нулей и непонятных дат.
              Я обычно даты в свои таблицы записываю в unix-формате и никаких проблем с этим. Пример беру с дат в таблице ресурсов, там все они в unix-е.
                Василий Столейков
                18 июня 2018, 18:58
                0
                Да, и ещё не сразу понял назначение двух одинаковых кнопок с самолётиками в панели с кнопками над таблицей, запутало как-то.
                  Александр Туниеков
                  18 июня 2018, 19:10
                  0
                  Хм… плохо у меня с дизайном :(. Воображения на картинки не хватает. Если есть идеи какие значки повешать на эти действия, напишите плиз :). Если писать словами, то в 1 слово не обойдешься и ширины экрана в итоге не хватит.
                    Василий Столейков
                    18 июня 2018, 19:15
                    0
                    А что если сделать кнопки выпадающим списком как в других компонентах:

                    Тогда и текстом можно будет нормально написать, и сколько угодно кнопок туда напихать по мере развития компонента.
                    Хотя бы для некоторых кнопок.
                      Александр Туниеков
                      18 июня 2018, 19:26
                      0
                      Хорошая идея. Только выпадающие списки я такие еще не делал. посмотрю как там в этом компоненте сделано.
                      Развития компонента пока не планируется :). В смысле, а есть куда его развивать? Рассылки все равно лучше делать через специализированные сервисы. Хостеры, если блокируют письма, то ничего не сообщают и не понятно ушло письмо или нет. Этот компонент на случай если сервис еще не имеет смысла подключать.
                        Василий Столейков
                        18 июня 2018, 19:31
                        0
                        а есть куда его развивать?
                        Есть!

                        Например первое что я поискал, это возможность подключения шаблонов.

                        Это можно например реализовать так: отдельное поле template в таблице, в который можно будет указывать id существующего шаблона или чанка. В форме можно это селектом сделать самоподгружающим эти эти элементы, но на первое время и простое поле тоже сойдёт, главное чтобы работало. В самом шаблоне чтобы были доступны переменными (желательно через Fenom если установлен) все поля письма. Ну и если шаблон указан, то отправляется письмо красиво оформленное, если нет, то простой текст.

                        Для многих это довольно важно, т.к. кастомизация, сохранения брендового стиля и т.д.
                          Александр Туниеков
                          18 июня 2018, 19:50
                          0
                          Ну тут идея компонента не совсем такая :). В очереди хранятся уже готовые письма. А письма в очередь добавляются кодом, в котором как раз письмо красиво и оформляется.
                          'body'=>$modx->getChunk('tpl.UserTest.InviteEmail',array('test_name' => $test->name,'link'=>$invite->url)),
                          Создание и редактирование писем в компоненте это просто на всякий случай. Чтоб протестировать компонент или просто отправку писем. Посмотреть что вообше в очереди лежит.
                            Василий Столейков
                            18 июня 2018, 20:20
                            0
                            Просто дело в том, что при редактировании письма код не очень-то и подредактируешь, будет каша. Тогда уже лучше редактор кода привязать к полю редактирования в админке…
                            Спасибо за объяснение, забыл про getChunk (что компонент больше для разработчиков) и сосредоточился на удобстве формы из админки…
                      Василий Столейков
                      18 июня 2018, 19:25
                      0
                      Например для кнопки «Восстановить письма с ошибками в очереди» больше подойдёт значок icon-refresh.
                      Кнопки Ошибки и Все непонятно что делают, просят больше описания действий. Ну и значков просят, а то высота кнопок прыгает.
                      Две кнопки удалить — тоже можно было бы их в выпадалку запрятать и текст написать.

                      Я просто не сразу заметил, что у кнопок есть подсказка при наведении, да и не всегда она у меня срабатывает, видно потому что навешана на иконку значка, а не на всю кнопку.
                        Александр Туниеков
                        18 июня 2018, 19:32
                        0
                        Кнопки Ошибки и Все — это не кнопки вообще. Это подписи что следующие кнопки к Ошибкам относяться и к всем письмам. Мда… замученный у меня дизайн получился. Буду выпадающим списком делать, но не пока занят через неделю думаю сделаю.
                          Василий Столейков
                          18 июня 2018, 19:33
                          0
                          Ок, я тебе только идей подкинул для рихтовки, а так молодец — компонент очень классный и очень нужный! Буду его использовать на многих сайтах.
                      Александр Туниеков
                      18 июня 2018, 19:15
                      0
                      В новой версии еще и 2 значка удаления. 1 удаляет письма с ошибками. Другой очищает всю очередь.
                        Василий Столейков
                        18 июня 2018, 19:17
                        0
                        Не, это будет путаница однозначно!
                        Лучше в выпадающий список запрятать некоторые кнопки.
                  Ксения
                  25 июня 2018, 09:34
                  0
                  Добрый день! Подскажите пожалуйста, как лучше организовать помещение в очередь если используется сниппет (вызывается по cron)
                  users()
                      ->members('en')
                      ->profile()
                      ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
                      ->joinGroup('group2')
                      ->each(function($user, $idx) {
                          if (is_email($user['email'])) {
                              email()->to($user['email'])->subject('Поздравляем с Днём рождения!')->tpl('chunkName', $user)->send();
                          }
                      });
                  который использует компонент modHelpers?
                  На первый взгляд кажется, что проще переписать весь сниппет и сделать по вашему шаблону, но непонятно, что вписывать в графе
                  $data1 = array(
                  	'sender_package'=>'UserTest', //дополнение отправитель письма
                    Александр Туниеков
                    25 июня 2018, 09:52
                    0
                    Примерно так думаю
                    if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                    	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                    	return;
                    }
                    users()
                        ->members('en')
                        ->profile()
                        ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
                        ->joinGroup('group2')
                        ->each(function($user, $idx) {
                            if (is_email($user['email'])) {
                                $data1 = array(
                    			'sender_package'=>'UsersSend', //дополнение отправитель письма
                    			'to'=>$user['email'], //емаил
                    			'subject'=>'Поздравляем с Днём рождения!', // тема письма
                    			'body'=>$modx->getChunk('chunkName', $user), // тело письма
                    			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
                    		);
                    		$queue_email->fromArray($data1);
                    		if(!$queue_email->save())
                    			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                            }
                        });
                    sender_package Нужно только чтоб видеть откуда письмо в очереди. Если вдруг несколько компонентов сразу отправляют. Можно написать что угодно или даже оставить пустым.
                    Александр Туниеков
                    25 июня 2018, 10:06
                    +1
                    ошибку заметил
                    if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                    	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                    	return;
                    }
                    users()
                        ->members('en')
                        ->profile()
                        ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
                        ->joinGroup('group2')
                        ->each(function($user, $idx) {
                            if (is_email($user['email'])) {
                                	$queue_email->newObject('EmailQueueItem'); //забыл создать объект :(
                    		$data1 = array(
                    			'sender_package'=>'UsersSend', //дополнение отправитель письма
                    			'to'=>$user['email'], //емаил
                    			'subject'=>'Поздравляем с Днём рождения!', // тема письма
                    			'body'=>$modx->getChunk('chunkName', $user), // тело письма
                    			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
                    		);
                    		$queue_email->fromArray($data1);
                    		if(!$queue_email->save())
                    			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                            }
                        });
                      Ксения
                      25 июня 2018, 10:36
                      0
                      Спасибо, буду пробовать.
                      ps пока помню — есть небольшая ошибка в верхнем меню компонента.
                      тэг title (всплывающая подсказка) при наведении на кнопки не выводится в FF, а в chrome выводится только при наведении на иконку внутри кнопки, а не на саму кнопку. Может пригодится.
                        Александр Туниеков
                        25 июня 2018, 10:42
                        0
                        Ну это известная проблема. Позже переделаю на выпадающий список
                        Ксения
                        25 июня 2018, 11:16
                        0
                        Получила вот такое
                        Fatal error: Call to a member function newObject() on a non-object
                        Если без этой
                        $queue_email->newObject('EmailQueueItem');
                        строки то
                        Fatal error: Call to a member function getChunk() on a non-object
                          Александр Туниеков
                          25 июня 2018, 11:26
                          0
                          Блин поторопился. Конечно
                          $queue_email=$modx->newObject('EmailQueueItem');
                            Александр Туниеков
                            25 июня 2018, 11:32
                            0
                            Fatal error: Call to a member function getChunk() on a non-object
                            У вас в начале скрипта сам $modx подключен?
                            что то вроде такого
                            require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/config.core.php';
                            require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
                            require_once MODX_CONNECTORS_PATH . 'index.php';
                              Ксения
                              25 июня 2018, 11:38
                              0
                              Нет. Сейчас буду разбираться.
                                Ксения
                                25 июня 2018, 11:53
                                0
                                Подключила
                                require_once
                                , изменила
                                $queue_email=$modx->newObject('EmailQueueItem');
                                и всё равно выход то же
                                Fatal error: Call to a member function newObject() on a non-object
                                  Александр Туниеков
                                  25 июня 2018, 12:04
                                  0
                                  Полностью код можно? И путь до файла
                                    Ксения
                                    25 июня 2018, 12:16
                                    0
                                    <?php
                                    error_reporting(E_ALL | E_STRICT);
                                    ini_set('display_errors', 1);
                                    require_once dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))) . '/config.core.php';
                                    require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
                                    require_once MODX_CONNECTORS_PATH . 'index.php';
                                    
                                    
                                    if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                                    	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                                    	return;
                                    }
                                    users()
                                        ->members('ru')
                                        ->profile()
                                        ->select('email,dob')
                                        ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
                                        ->each(function($user, $idx) {
                                            if (is_email($user['email'])) {
                                               $queue_email = $modx->newObject('EmailQueueItem');
                                    		$data1 = array(
                                    			'sender_package'=>'UsersSend', //дополнение отправитель письма
                                    			'to'=>$user['email'], //емаил
                                    			'from_name'=>'Тест',
                                    			'subject'=>'Поздравляем с Днём рождения!', // тема письма
                                    			'body'=>$modx->getChunk('email_ru', $user), // тело письма
                                    			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
                                    		);
                                    		$queue_email->fromArray($data1);
                                    		if(!$queue_email->save())
                                    			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                                            }
                                        });
                                    Про путь до файла не поняла. Делаю стандартный сниппет в админке, запускаю по Cron Manager.
                                      Александр Туниеков
                                      25 июня 2018, 12:35
                                      0
                                      Не работал с Cron Manager. По идее в нем не надо $modx подключать. он уже должен быть подключен. Надо разбираться. Так ничего не понятно. Мой скайп touols. Можите доступ скинуть?
                                      странно что на $modx->getService не ругается
                                        Ксения
                                        25 июня 2018, 12:46
                                        0
                                        Подключать $modx не надо по моему тоже. Через 1-1.5 часа сделаю копию на Modhost и если там не заработает пришлю доступы.
                                          Александр Туниеков
                                          25 июня 2018, 12:59
                                          0
                                          Поставил Cron Manager. Запустил такой код. И все успешно.
                                          <?php
                                          error_reporting(E_ALL | E_STRICT);
                                          ini_set('display_errors', 1);
                                          
                                          
                                          if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                                          	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                                          	return;
                                          }
                                          $user['email'] = 'a@b.ru';
                                          $queue_email = $modx->newObject('EmailQueueItem');
                                          $data1 = array(
                                          	'sender_package'=>'UsersSend', //дополнение отправитель письма
                                          	'to'=>$user['email'], //емаил
                                          	'from_name'=>'Тест',
                                          	'subject'=>'Поздравляем с Днём рождения!', // тема письма
                                          	'body'=>$modx->getChunk('email_ru', $user), // тело письма
                                          	'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
                                          );
                                          $queue_email->fromArray($data1);
                                          if(!$queue_email->save())
                                          	$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                                            Ксения
                                            12 сентября 2018, 07:37
                                            0
                                            Здравствуйте! После перехода с php5.6 на php7.2 этот код перестал работать. В очередь попадает письмо с пустым полем «Сообщение».
                                            то есть
                                            'body'=>$modx->getChunk('email_ru', $user), // тело письма
                                            ничего не передаёт.
                                            Не знаете с чем может быть связано?
                                              Сергей Шлоков
                                              12 сентября 2018, 08:48
                                              0
                                              Попробуйте указать текст
                                              'body'=>'Текст для проверки', // тело письма

                                              П.С. Лично мне кажется странным вот этот код
                                              $user['email'] = 'a@b.ru';
                                              Зачем создавать массив?
                                              Да и вообще весь описанный код странный. Но это вопрос к автору кода.
                                              Ксения
                                              12 сентября 2018, 09:27
                                              0
                                              'body'=>'Текст для проверки', // тело письма
                                              поле тоже остается пустым

                                              И ошибок никаких не выдает. Журнал тоже пуст.
                                              Сергей Шлоков
                                              12 сентября 2018, 09:34
                                              0
                                              Тогда ждите ответа от автора дополнения.
                                              Александр Туниеков
                                              12 сентября 2018, 11:40
                                              0
                                              Здравствуйте! Вот чтоб именно пустое поле получилось вообще странно :(. Пока скачиваю Опенсервер новый. Попробую поставить PHP 7.2.00 и посмотреть.
                                              Хм. Он 3 часа обещает скачиваться. Может все таки дадите доступ к вашему MODX?
                                              Ксения
                                              12 сентября 2018, 11:56
                                              0
                                              Посмотрела в БД — не пустое, стоит 0
                                              А так же стоит 0 в поле attachments, которое вроде и не указано нигде в вызове.

                                              с доступом ну никак, могу сделать копию
                                              Александр Туниеков
                                              12 сентября 2018, 12:03
                                              0
                                              Сделайте плиз если не трудно. у меня на php 7.2 ничего тестового нет
                                              Ксения
                                              12 сентября 2018, 12:09
                                              0
                                              Отправила
                                              Александр Туниеков
                                              12 сентября 2018, 12:58
                                              +1
                                              В файле схемы были не правильно заданы типы полей phptype. Вместо text поставил string и все заработало. На рабочем сайте обновите компонент и проверяйте как там работает.
                                              В emailqueue.mysql.schema.xml вместо этого
                                              <field key="body" dbtype="text" phptype="text" null="true" default=""/>
                                              <field key="attachments" dbtype="text" phptype="text" null="true" default=""/>
                                              сделал это
                                              <field key="body" dbtype="text" phptype="string" null="true" default=""/>
                                              <field key="attachments" dbtype="text" phptype="string" null="true" default=""/>
                                              Я эти поля как в modExtra сделано так и делал.
                                          Александр Туниеков
                                          25 июня 2018, 13:04
                                          +1
                                          Похоже я понял в чем косяк. $modx внутри функции вызывается. А внутри функции его нет. Попробуйте перед if (is_email($user['email'])) { вставить global $modx;

                                          ->each(function($user, $idx) {
                                                 global $modx;
                                          	 if (is_email($user['email'])) {
                                                     $queue_email = $modx->newObject('EmailQueueItem');
                                            Ксения
                                            25 июня 2018, 13:18
                                            0
                                            Да, так ругается уже на последнюю строку
                                            $queue_email->fromArray($data1);
                                            Fatal error: Call to a member function fromArray() on a non-object
                                            Если вызвать
                                            global $modx;
                                            ещё и в начале сниппета, тогда работает. Только глаз режет :-)
                                            Спасибо!
                                              Сергей Шлоков
                                              12 сентября 2018, 08:11
                                              0
                                              Или передайте в функцию $modx третьим параметром —
                                              ->each(function($user, $idx, $modx) {
                                              	 if (is_email($user['email'])) {
                                                         $queue_email = $modx->newObject('EmailQueueItem');
                                              Ксения
                                              12 сентября 2018, 08:36
                                              0
                                              Этот вопрос давно решился этим
                                              if (is_email($user['email'])) {
                                              global $modx;
                                              сейчас вопрос другой
                                              Сергей Шлоков
                                              12 сентября 2018, 08:40
                                              +1
                                              Вы написали про «режет глаз». Я описал способ избавиться от этого. На будущее.
                          Евгений Дурягин
                          10 декабря 2018, 04:13
                          +1
                          Пример сниппета для drop-in replacement замены хука email для FormIt/AjaxForm
                          Просто в вызове поменять хук на email_queue (или свое название сниппета)
                          [[!Formit?
                              &hooks=`email_queue`
                              &emailTpl=`feedbackEmail`
                              &emailTo=`test@mail.ru`
                              &emailSubject=`Сообщение с сайта`
                              &replyTo=`[[+email]]`
                              &emailFrom=`[[++emailsender]]`
                          ]]
                          Поддержку вложений не делал, т.к. не было необходимости.
                          И не все параметры FormIt поддерживает (например emailHtml, emailToName, emailCC, итд), т.к. компонент их не учитывает.

                          <?php
                          
                          $fields = $hook->getValues();
                          
                          $tpl = $modx->getOption('emailTpl', $hook->formit->config, '');
                          /* get from name */
                          $emailFrom = $modx->getOption('emailFrom', $hook->formit->config, '');
                          if (empty($emailFrom)) {
                              $emailFrom = !empty($fields['email']) ? $fields['email'] : $modx->getOption('emailsender');
                          }
                          $emailFrom = $hook->_process($emailFrom, $fields);
                          $emailFromName = $modx->getOption('emailFromName', $hook->formit->config, $modx->getOption('site_name', null, $emailFrom));
                          $emailFromName = $hook->_process($emailFromName, $fields);
                          
                          /* get subject */
                          $useEmailFieldForSubject = $modx->getOption('emailUseFieldForSubject', $hook->formit->config, true);
                          if (!empty($fields['subject']) && $useEmailFieldForSubject) {
                              $subject = $fields['subject'];
                          } else {
                              $subject = $modx->getOption('emailSubject', $hook->formit->config, '');
                          }
                          $subject = $hook->_process($subject, $fields);
                          /* check email to */
                          $emailTo = $modx->getOption('emailTo', $hook->formit->config, '');
                          $emailToName = $modx->getOption('emailToName', $hook->formit->config, $emailTo);
                          if (empty($emailTo)) {
                              $hook->errors['emailTo'] = $modx->lexicon('formit.email_no_recipient');
                              $modx->log(\modX::LOG_LEVEL_ERROR, '[FormIt] '.$modx->lexicon('formit.email_no_recipient'));
                              return false;
                          }
                          /* compile message */
                          $origFields = $fields;
                          if (empty($tpl)) {
                              $tpl = 'fiDefaultEmailTpl';
                              $f = [];
                              $multiSeparator = $modx->getOption('emailMultiSeparator', $hook->formit->config, "\n");
                              $multiWrapper = $modx->getOption('emailMultiWrapper', $hook->formit->config, "[[+value]]");
                              foreach ($fields as $k => $v) {
                                  if ($k == 'nospam') {
                                      continue;
                                  }
                                  if (is_array($v) && !empty($v['name']) && isset($v['error']) && $v['error'] == UPLOAD_ERR_OK) {
                                      $v = $v['name'];
                                      $f[$k] = '<strong>'.$k.'</strong>: '.$v.'<br />';
                                  } elseif (is_array($v)) {
                                      $vOpts = array();
                                      foreach ($v as $vKey => $vValue) {
                                          if (is_string($vKey) && !empty($vKey)) {
                                              $vKey = $k.'.'.$vKey;
                                              $f[$vKey] = '<strong>'.$vKey.'</strong>: '.$vValue.'<br />';
                                          } else {
                                              $vOpts[] = str_replace('[[+value]]', $vValue, $multiWrapper);
                                          }
                                      }
                                      $newValue = implode($multiSeparator, $vOpts);
                                      if (!empty($vOpts)) {
                                          $f[$k] = '<strong>'.$k.'</strong>:'.$newValue.'<br />';
                                      }
                                  } else {
                                      $f[$k] = '<strong>'.$k.'</strong>: '.$v.'<br />';
                                  }
                              }
                              $fields['fields'] = implode("\n", $f);
                          } else {
                              /* handle file/checkboxes in email tpl */
                              $multiSeparator = $modx->getOption('emailMultiSeparator', $hook->formit->config, "\n");
                              if (empty($multiSeparator)) {
                                  $multiSeparator = "\n";
                              }
                              if ($multiSeparator == '\n') {
                                  $multiSeparator = "\n"; /* allow for inputted newlines */
                              }
                              $multiWrapper = $modx->getOption('emailMultiWrapper', $hook->formit->config, "[[+value]]");
                              if (empty($multiWrapper)) {
                                  $multiWrapper = '[[+value]]';
                              }
                              foreach ($fields as $k => &$v) {
                                  if (is_array($v) && !empty($v['name']) && isset($v['error']) && $v['error'] == UPLOAD_ERR_OK) {
                                      $v = $v['name'];
                                  } elseif (is_array($v)) {
                                      $vOpts = array();
                                      foreach ($v as $vKey => $vValue) {
                                          if (is_string($vKey) && !empty($vKey)) {
                                              $vKey = $k.'.'.$vKey;
                                              $fields[$vKey] = $vValue;
                                              unset($fields[$k]);
                                          } else {
                                              $vOpts[] = str_replace('[[+value]]', $vValue, $multiWrapper);
                                          }
                                      }
                                      $v = implode($multiSeparator, $vOpts);
                                      if (!empty($vOpts)) {
                                          $fields[$k] = $v;
                                      }
                                  }
                              }
                          }
                          $message = $hook->formit->getChunk($tpl, $fields);
                          $message = $hook->_process($message, $this->config);
                          
                          
                          if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                          	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                          	$hook->addError('email', 'При отправке произошла ошибка');
                          	return false;
                          }
                          
                          $data = array(
                          	'sender_package' => 'FormIt',
                          	'from' => $emailFrom,
                          	'from_name' => $emailFromName,
                          	'subject' => $subject,
                          	'body' => $message,
                          	'date' => date("Y-m-d H:i:s"),
                          );
                          
                          $emailReplyTo = $modx->getOption('emailReplyTo', $hook->formit->config, '');
                          if (!empty($emailReplyTo)) {
                              $date['replyto'] = $emailReplyTo;
                          }
                          $emailTo = array_map('trim', explode(',', $emailTo));
                          
                          foreach ($emailTo as $to) {
                              $to = $hook->_process($to, $fields);
                              if (!empty($to)) {
                                  $data['to'] = $to;
                                  $queue_email = $modx->newObject('EmailQueueItem');
                                  $queue_email->fromArray($data);
                                  $queue_email->save();
                              }
                          }
                          
                          return true;
                            Александр Туниеков
                            10 декабря 2018, 05:32
                            0
                            Добрый день! Когда писал компонент вот на использование в хуке для FormIt не планировал, что это понадобиться. Идея была в том, что за раз нельзя бывает много писем отправить и чтобы это сделать понадобился компонент. А в FormIt одно письмо за раз отправляется. Для чего вам этот хук? Мне просто не очень понятно зачем он. Раз написали думаю нужен :), но вот зачем теряюсь в догадках :).
                              Василий Столейков
                              10 декабря 2018, 07:51
                              0
                              Наверное потому что при загруженном функционале каждая секунда на счету, а добавление письма в очередь в базу происходит почти мгновенно в отличие от ожидания отправки письма…
                                Евгений Дурягин
                                10 декабря 2018, 13:42
                                0
                                Ну чтобы письма обратной связи и прочие тоже отправлялись через очередь.
                                Вот сейчас отправлять нужно от домена заказчика, а чтобы соблюсти все DKIM, SPF и прочее без правки DNS вариант только через доступ по SMTP.
                                А коннект к SMTP визуально секунды 3 и это тормозит на фронте.
                                Плюс опять же возможны какие-то лимиты по кол-ву в час, одновременных соединений и тп.
                                И у пользователя возникает ошибка и форма не отправляется.
                                  Евгений Дурягин
                                  10 декабря 2018, 13:51
                                  0
                                  И кстати с большим кол-вом писем могут быть проблемы. В скрипте не обнаружил защиту от повторного запуска скрипта.
                                  Допустим лимит стоит 50 писем за раз, а запуск скрипта каждую минуту.
                                  В большинстве случаев все работает и 50 писем успевают отправится за 1 минуту.
                                  Но в какой-то час SMTP или сеть начнет тормозить и отправка станет занимать 3-5 сек.
                                  За минуту успеют отправится только 20 писем, затем запуститься второй скрипт и начнет отправлять оставшиеся 30 письма, т.к. они еще не помечены как отправленные. В итоге пользователю будут приходить дубли.
                                    Александр Туниеков
                                    10 декабря 2018, 14:55
                                    +1
                                    Мда… не продумал. Думаю тогда сделать статус Отправляется и в кроне перед циклом отправки добавить цикл, выставляющий этот статус. Это просто можно сегодня сделать. А вот я обещал еще кнопки сделать удобнее. Это сложнее надеюсь за 2 часа справлюсь и тоже сегодня сделаю.
                                  Александр Туниеков
                                  10 декабря 2018, 15:13
                                  0
                                  Не возражаете если я ваш хук включу в состав компонента?
                                    Евгений Дурягин
                                    10 декабря 2018, 15:29
                                    0
                                    Конечно нет. К тому же я его не сам писал, а взял из FormIt github.com/Sterc/FormIt/blob/develop/core/components/formit/src/FormIt/Hook/Email.php#L61
                                    Просто убрал все лишнее что сейчас не нужно было мне и адаптировал под компонент.

                                    И он на первое время сойдет, но для полноценной замены желательно чтобы поддерживал все параметры FormIt, такие как скрытые копии, html или текст сообщение, вложения и прочее.
                                    docs.modx.com/extras/revo/formit/formit.hooks/formit.hooks.email

                                    Можно не все их в таблицу добавлять, оставить только важные, которые отображать в компоненте, а остальные в поле properties в json-виде, так расширять можно будет без правки схемы. Главное чтобы скрипт отправки поддерживал.

                                    И у FormIt есть еще хук FormItAutoResponder docs.modx.com/extras/revo/formit/formit.hooks/formit.hooks.formitautoresponder
                                    Это бывает полезно для обратной связи когда email уходит менеджеру, а пользователю дополнительно отправляется другой email со своим шаблоном «Спасибо за ваш отзыв. Менеджер уже занимается вашим вопросом»
                                      Александр Туниеков
                                      10 декабря 2018, 15:52
                                      0
                                      Конечно нет. К тому же я его не сам писал, а взял из FormIt github.com/Sterc/FormIt/blob/develop/core/components/formit/src/FormIt/Hook/Email.php#L61
                                      Просто убрал все лишнее что сейчас не нужно было мне и адаптировал под компонент.
                                      Ну я так и пишу. Подсмотреть убрать лишнее и добавить нужное :).

                                      С поддержкой всех параметров сложно. Не думаю что я буду это делать :(. У меня в этом потребности нет. И все прописать достаточно сложно. Сегодня я сделаю топбар и статус отправляется и выложу на гитхаб. Если вы захотите, можете сделать форк доработать компонент прислать мне пулл-реквест.

                                      насчет FormItAutoResponder подумаю как прикрутить.
                                        Евгений Дурягин
                                        10 декабря 2018, 18:58
                                        0
                                        насчет FormItAutoResponder подумаю как прикрутить.

                                        Ну если кто и будет это делать, то просто создать еще один сниппет, который понимает парметры FormItAutoResponder и добавляет еще одно письмо в очередь.

                                        Я для своего хука так и делал. Через FormIt можно указать несколько получателей в параметре &emailTo, я на каждого создавал отдельное письмо в очереди.
                                          Александр Туниеков
                                          10 декабря 2018, 19:45
                                          +1
                                          Сделал EmailQueueAutoResponder и остальное что хотел :). Смотрите изменения в теме и качайте новую версию с модсторе. Планировал 2 часа а потратил весь вечер :(.
                                            Василий Столейков
                                            10 декабря 2018, 22:45
                                            0
                                            Планировал 2 часа а потратил весь вечер :(.
                                            А так всегда… Главное что довёл дело до логического конца, молодец!
                                              Василий Столейков
                                              10 декабря 2018, 22:52
                                              0
                                              Классно выглядит новая версия!
                                              Теперь всё логично и понятно — компонент имеет вид завершенного продукта.
                                              Можно смело использовать на всех проектах без опаски за непонятный функционал (такая путаница с кнопками была раньше).
                                    Andrey Extra
                                    12 декабря 2018, 04:06
                                    0
                                    Можно сделать отправку письма через 12 часов после постановки в очередь? т.е. задержка после добавления в очередь
                                      Александр Туниеков
                                      12 декабря 2018, 04:47
                                      1
                                      0
                                      Ну можно либо крон раз в 12 часов поставить либо в кроне send.php строка 11
                                      $q->where(array('status'=>1, 'date:<'=> date('Y-m-d', strtotime("-12 hours"))));
                                      Примерно так. Не тестировал.
                                        Александр Туниеков
                                        12 декабря 2018, 05:31
                                        0
                                        Можно еще часть отправлять через 12 часов а часть сразу :). Это допустим те, что через 12 часов отправляете в очередь с sender_package = «12hours». А в кроне при отправке фильтровать по sender_package.
                                        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                        69