Improve email sending system and add Postmark as mail transport
This commit is contained in:
@@ -112,7 +112,7 @@ class Webhook extends Base
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
echo $this->postmarkWebhook->parsePayload($this->request->getJson() ?: array()) ? 'PARSED' : 'IGNORED';
|
||||
echo $this->postmark->receiveEmail($this->request->getJson() ?: array()) ? 'PARSED' : 'IGNORED';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ use Pimple\Container;
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \Core\Helper $helper
|
||||
* @property \Core\EmailClient $emailClient
|
||||
* @property \Core\HttpClient $httpClient
|
||||
* @property \Core\Paginator $paginator
|
||||
* @property \Core\Request $request
|
||||
@@ -22,9 +23,10 @@ use Pimple\Container;
|
||||
* @property \Integration\HipchatWebhook $hipchatWebhook
|
||||
* @property \Integration\Jabber $jabber
|
||||
* @property \Integration\MailgunWebhook $mailgunWebhook
|
||||
* @property \Integration\PostmarkWebhook $postmarkWebhook
|
||||
* @property \Integration\Postmark $postmark
|
||||
* @property \Integration\SendgridWebhook $sendgridWebhook
|
||||
* @property \Integration\SlackWebhook $slackWebhook
|
||||
* @property \Integration\Smtp $smtp
|
||||
* @property \Model\Acl $acl
|
||||
* @property \Model\Action $action
|
||||
* @property \Model\Authentication $authentication
|
||||
|
||||
43
app/Core/EmailClient.php
Normal file
43
app/Core/EmailClient.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Mail client
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class EmailClient extends Base
|
||||
{
|
||||
/**
|
||||
* Send a HTML email
|
||||
*
|
||||
* @access public
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param string $subject
|
||||
* @param string $html
|
||||
*/
|
||||
public function send($email, $name, $subject, $html)
|
||||
{
|
||||
$this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
|
||||
|
||||
$start_time = microtime(true);
|
||||
$author = 'Kanboard';
|
||||
|
||||
if (Session::isOpen() && $this->userSession->isLogged()) {
|
||||
$author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
|
||||
}
|
||||
|
||||
switch (MAIL_TRANSPORT) {
|
||||
case 'postmark':
|
||||
$this->postmark->sendEmail($email, $name, $subject, $html, $author);
|
||||
break;
|
||||
default:
|
||||
$this->smtp->sendEmail($email, $name, $subject, $html, $author);
|
||||
}
|
||||
|
||||
$this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,20 @@
|
||||
|
||||
namespace Core;
|
||||
|
||||
use Pimple\Container;
|
||||
|
||||
/**
|
||||
* HTTP client
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class HttpClient
|
||||
class HttpClient extends Base
|
||||
{
|
||||
/**
|
||||
* HTTP connection timeout in seconds
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const HTTP_TIMEOUT = 2;
|
||||
const HTTP_TIMEOUT = 5;
|
||||
|
||||
/**
|
||||
* Number of maximum redirections for the HTTP client
|
||||
@@ -31,26 +29,7 @@ class HttpClient
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HTTP_USER_AGENT = 'Kanboard Webhook';
|
||||
|
||||
/**
|
||||
* Container instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Pimple\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
const HTTP_USER_AGENT = 'Kanboard';
|
||||
|
||||
/**
|
||||
* Send a POST HTTP request
|
||||
@@ -58,19 +37,20 @@ class HttpClient
|
||||
* @access public
|
||||
* @param string $url
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
* @return string
|
||||
*/
|
||||
public function post($url, array $data)
|
||||
public function post($url, array $data, array $headers = array())
|
||||
{
|
||||
if (empty($url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$headers = array(
|
||||
$headers = array_merge(array(
|
||||
'User-Agent: '.self::HTTP_USER_AGENT,
|
||||
'Content-Type: application/json',
|
||||
'Connection: close',
|
||||
);
|
||||
), $headers);
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
@@ -83,12 +63,21 @@ class HttpClient
|
||||
)
|
||||
));
|
||||
|
||||
$response = @file_get_contents(trim($url), false, $context);
|
||||
$stream = @fopen(trim($url), 'r', false, $context);
|
||||
$response = '';
|
||||
|
||||
if (is_resource($stream)) {
|
||||
$response = stream_get_contents($stream);
|
||||
}
|
||||
else {
|
||||
$this->container['logger']->error('HttpClient: request failed');
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
$this->container['logger']->debug($url);
|
||||
$this->container['logger']->debug(var_export($data, true));
|
||||
$this->container['logger']->debug($response);
|
||||
$this->container['logger']->debug('HttpClient: url='.$url);
|
||||
$this->container['logger']->debug('HttpClient: payload='.var_export($data, true));
|
||||
$this->container['logger']->debug('HttpClient: metadata='.var_export(@stream_get_meta_data($stream), true));
|
||||
$this->container['logger']->debug('HttpClient: response='.$response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
||||
@@ -5,13 +5,40 @@ namespace Integration;
|
||||
use HTML_To_Markdown;
|
||||
|
||||
/**
|
||||
* Postmark Webhook
|
||||
* Postmark integration
|
||||
*
|
||||
* @package integration
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class PostmarkWebhook extends \Core\Base
|
||||
class Postmark extends \Core\Base
|
||||
{
|
||||
/**
|
||||
* Send a HTML email
|
||||
*
|
||||
* @access public
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param string $subject
|
||||
* @param string $html
|
||||
* @param string $author
|
||||
*/
|
||||
public function sendEmail($email, $name, $subject, $html, $author)
|
||||
{
|
||||
$headers = array(
|
||||
'Accept: application/json',
|
||||
'X-Postmark-Server-Token: '.POSTMARK_API_TOKEN,
|
||||
);
|
||||
|
||||
$payload = array(
|
||||
'From' => sprintf('%s <%s>', $author, MAIL_FROM),
|
||||
'To' => sprintf('%s <%s>', $name, $email),
|
||||
'Subject' => $subject,
|
||||
'HtmlBody' => $html,
|
||||
);
|
||||
|
||||
$this->httpClient->post('https://api.postmarkapp.com/email', $payload, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse incoming email
|
||||
*
|
||||
@@ -19,7 +46,7 @@ class PostmarkWebhook extends \Core\Base
|
||||
* @param array $payload Incoming email
|
||||
* @return boolean
|
||||
*/
|
||||
public function parsePayload(array $payload)
|
||||
public function receiveEmail(array $payload)
|
||||
{
|
||||
if (empty($payload['From']) || empty($payload['Subject']) || empty($payload['MailboxHash'])) {
|
||||
return false;
|
||||
@@ -29,7 +56,7 @@ class PostmarkWebhook extends \Core\Base
|
||||
$user = $this->user->getByEmail($payload['From']);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->container['logger']->debug('PostmarkWebhook: ignored => user not found');
|
||||
$this->container['logger']->debug('Postmark: ignored => user not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -37,13 +64,13 @@ class PostmarkWebhook extends \Core\Base
|
||||
$project = $this->project->getByIdentifier($payload['MailboxHash']);
|
||||
|
||||
if (empty($project)) {
|
||||
$this->container['logger']->debug('PostmarkWebhook: ignored => project not found');
|
||||
$this->container['logger']->debug('Postmark: ignored => project not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// The user must be member of the project
|
||||
if (! $this->projectPermission->isMember($project['id'], $user['id'])) {
|
||||
$this->container['logger']->debug('PostmarkWebhook: ignored => user is not member of the project');
|
||||
$this->container['logger']->debug('Postmark: ignored => user is not member of the project');
|
||||
return false;
|
||||
}
|
||||
|
||||
71
app/Integration/Smtp.php
Normal file
71
app/Integration/Smtp.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Integration;
|
||||
|
||||
use Swift_Message;
|
||||
use Swift_Mailer;
|
||||
use Swift_MailTransport;
|
||||
use Swift_SendmailTransport;
|
||||
use Swift_SmtpTransport;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Smtp
|
||||
*
|
||||
* @package integration
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Smtp extends \Core\Base
|
||||
{
|
||||
/**
|
||||
* Send a HTML email
|
||||
*
|
||||
* @access public
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param string $subject
|
||||
* @param string $html
|
||||
* @param string $author
|
||||
*/
|
||||
public function sendEmail($email, $name, $subject, $html, $author)
|
||||
{
|
||||
try {
|
||||
|
||||
$message = Swift_Message::newInstance()
|
||||
->setSubject($subject)
|
||||
->setFrom(array(MAIL_FROM => $author))
|
||||
->setBody($html, 'text/html')
|
||||
->setTo(array($email => $name));
|
||||
|
||||
Swift_Mailer::newInstance($this->getTransport())->send($message);
|
||||
}
|
||||
catch (Swift_TransportException $e) {
|
||||
$this->container['logger']->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SwiftMailer transport
|
||||
*
|
||||
* @access private
|
||||
* @return \Swift_Transport
|
||||
*/
|
||||
private function getTransport()
|
||||
{
|
||||
switch (MAIL_TRANSPORT) {
|
||||
case 'smtp':
|
||||
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
||||
$transport->setUsername(MAIL_SMTP_USERNAME);
|
||||
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
||||
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
||||
break;
|
||||
case 'sendmail':
|
||||
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
||||
break;
|
||||
default:
|
||||
$transport = Swift_MailTransport::newInstance();
|
||||
}
|
||||
|
||||
return $transport;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ namespace Model;
|
||||
|
||||
use Core\Session;
|
||||
use Core\Translator;
|
||||
use Swift_Message;
|
||||
use Swift_Mailer;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Notification model
|
||||
@@ -101,43 +98,22 @@ class Notification extends Base
|
||||
*/
|
||||
public function sendEmails($template, array $users, array $data)
|
||||
{
|
||||
try {
|
||||
foreach ($users as $user) {
|
||||
|
||||
$author = '';
|
||||
|
||||
if (Session::isOpen() && $this->userSession->isLogged()) {
|
||||
$author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
|
||||
// Use the user language otherwise use the application language (do not use the session language)
|
||||
if (! empty($user['language'])) {
|
||||
Translator::load($user['language']);
|
||||
}
|
||||
else {
|
||||
Translator::load($this->config->get('application_language', 'en_US'));
|
||||
}
|
||||
|
||||
$mailer = Swift_Mailer::newInstance($this->container['mailer']);
|
||||
|
||||
foreach ($users as $user) {
|
||||
|
||||
$this->container['logger']->debug('Send email notification to '.$user['username'].' lang='.$user['language']);
|
||||
$start_time = microtime(true);
|
||||
|
||||
// Use the user language otherwise use the application language (do not use the session language)
|
||||
if (! empty($user['language'])) {
|
||||
Translator::load($user['language']);
|
||||
}
|
||||
else {
|
||||
Translator::load($this->config->get('application_language', 'en_US'));
|
||||
}
|
||||
|
||||
// Send the message
|
||||
$message = Swift_Message::newInstance()
|
||||
->setSubject($this->getMailSubject($template, $data))
|
||||
->setFrom(array(MAIL_FROM => $author ?: 'Kanboard'))
|
||||
->setBody($this->getMailContent($template, $data), 'text/html')
|
||||
->setTo(array($user['email'] => $user['name'] ?: $user['username']));
|
||||
|
||||
$mailer->send($message);
|
||||
|
||||
$this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
|
||||
}
|
||||
}
|
||||
catch (Swift_TransportException $e) {
|
||||
$this->container['logger']->error($e->getMessage());
|
||||
$this->emailClient->send(
|
||||
$user['email'],
|
||||
$user['name'] ?: $user['username'],
|
||||
$this->getMailSubject($template, $data),
|
||||
$this->getMailContent($template, $data)
|
||||
);
|
||||
}
|
||||
|
||||
// Restore locales
|
||||
@@ -167,40 +143,40 @@ class Notification extends Base
|
||||
{
|
||||
switch ($template) {
|
||||
case 'file_creation':
|
||||
$subject = $this->getStandardMailSubject(t('New attachment'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('New attachment'), $data);
|
||||
break;
|
||||
case 'comment_creation':
|
||||
$subject = $this->getStandardMailSubject(t('New comment'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('New comment'), $data);
|
||||
break;
|
||||
case 'comment_update':
|
||||
$subject = $this->getStandardMailSubject(t('Comment updated'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Comment updated'), $data);
|
||||
break;
|
||||
case 'subtask_creation':
|
||||
$subject = $this->getStandardMailSubject(t('New subtask'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('New subtask'), $data);
|
||||
break;
|
||||
case 'subtask_update':
|
||||
$subject = $this->getStandardMailSubject(t('Subtask updated'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Subtask updated'), $data);
|
||||
break;
|
||||
case 'task_creation':
|
||||
$subject = $this->getStandardMailSubject(t('New task'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('New task'), $data);
|
||||
break;
|
||||
case 'task_update':
|
||||
$subject = $this->getStandardMailSubject(t('Task updated'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Task updated'), $data);
|
||||
break;
|
||||
case 'task_close':
|
||||
$subject = $this->getStandardMailSubject(t('Task closed'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Task closed'), $data);
|
||||
break;
|
||||
case 'task_open':
|
||||
$subject = $this->getStandardMailSubject(t('Task opened'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Task opened'), $data);
|
||||
break;
|
||||
case 'task_move_column':
|
||||
$subject = $this->getStandardMailSubject(t('Column Change'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Column Change'), $data);
|
||||
break;
|
||||
case 'task_move_position':
|
||||
$subject = $this->getStandardMailSubject(t('Position Change'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Position Change'), $data);
|
||||
break;
|
||||
case 'task_assignee_change':
|
||||
$subject = $this->getStandardMailSubject(t('Assignee Change'), $data);
|
||||
$subject = $this->getStandardMailSubject(e('Assignee Change'), $data);
|
||||
break;
|
||||
case 'task_due':
|
||||
$subject = e('[%s][Due tasks]', $data['project']);
|
||||
|
||||
@@ -64,6 +64,7 @@ class ClassProvider implements ServiceProviderInterface
|
||||
'Webhook',
|
||||
),
|
||||
'Core' => array(
|
||||
'EmailClient',
|
||||
'Helper',
|
||||
'HttpClient',
|
||||
'MemoryCache',
|
||||
@@ -78,9 +79,10 @@ class ClassProvider implements ServiceProviderInterface
|
||||
'HipchatWebhook',
|
||||
'Jabber',
|
||||
'MailgunWebhook',
|
||||
'PostmarkWebhook',
|
||||
'Postmark',
|
||||
'SendgridWebhook',
|
||||
'SlackWebhook',
|
||||
'Smtp',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ServiceProvider;
|
||||
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use Swift_SmtpTransport;
|
||||
use Swift_SendmailTransport;
|
||||
use Swift_MailTransport;
|
||||
|
||||
class MailerProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['mailer'] = function () {
|
||||
switch (MAIL_TRANSPORT) {
|
||||
case 'smtp':
|
||||
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
||||
$transport->setUsername(MAIL_SMTP_USERNAME);
|
||||
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
||||
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
||||
break;
|
||||
case 'sendmail':
|
||||
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
||||
break;
|
||||
default:
|
||||
$transport = Swift_MailTransport::newInstance();
|
||||
}
|
||||
|
||||
return $transport;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,3 @@ $container->register(new ServiceProvider\LoggingProvider);
|
||||
$container->register(new ServiceProvider\DatabaseProvider);
|
||||
$container->register(new ServiceProvider\ClassProvider);
|
||||
$container->register(new ServiceProvider\EventDispatcherProvider);
|
||||
$container->register(new ServiceProvider\MailerProvider);
|
||||
|
||||
@@ -64,6 +64,7 @@ defined('MAIL_SMTP_USERNAME') or define('MAIL_SMTP_USERNAME', '');
|
||||
defined('MAIL_SMTP_PASSWORD') or define('MAIL_SMTP_PASSWORD', '');
|
||||
defined('MAIL_SMTP_ENCRYPTION') or define('MAIL_SMTP_ENCRYPTION', null);
|
||||
defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs');
|
||||
defined('POSTMARK_API_TOKEN') or define('POSTMARK_API_TOKEN', '');
|
||||
|
||||
// Enable or disable "Strict-Transport-Security" HTTP header
|
||||
defined('ENABLE_HSTS') or define('ENABLE_HSTS', true);
|
||||
|
||||
Reference in New Issue
Block a user