Commit 8de95b2c authored by Avris's avatar Avris

init

parents
tests/_output/*
vendor/*
.idea/*
**/.DS_Store
## Micrus Mailer Module ##
This is a module for [Micrus framework](https://micrus.avris.it) that allows you to easily send emails.
To install this module, open the file `app/Config/modules.yml` and add:
- Avris\Micrus\Mailer\MailerModule
Then run:
composer require avris/micrus-mailer
You will be asked for the connection parameters, for example:
mailer:
smtp: true
host: smtp.gmail.com
port: 465
secure: ssl
username: username@gmail.com
password: apppassword
If you're using GMail, remember to [Allowing less secure apps](https://support.google.com/accounts/answer/6010255?hl=en)
and [create an app password](https://support.google.com/mail/answer/185833?hl=en).
### Manually creating and sending an email ###
use Avris\Micrus\Mailer\Mail\Mail;
use Avris\Micrus\Mailer\Mail\Address
$mail = new Mail();
$mail->addTo(new Address('email@example.com'));
$mail->addCc(new Address('max@mustermann.de'));
$mail->setSubject('Subject');
$mail->setBody('...');
$mail->setAltBody('...');
$mail->embedImage('logo', $this->getRootDir() . '/app/Asset/gfx/logo.png');
$mail->embedImage('banner', $this->getRootDir() . '/app/Asset/gfx/banner.png');
$mail->addAttachment($this->getRootDir() . '/app/Asset/pdf/registration.pdf');
$this->getService('mailer')->send($mail);
### Mail Builder ###
By following some simple conventions and using the MailBuilder service,
you can greatly simplify the process of creating an email.
Let's say you want to create a template `welcome`. In your locale file (like `Locale/en.yml`)
create such structure:
mail:
welcome:
subject: Welcome to %sitename%
body: >
<p>
Hello, %username%!<br/>
Welcome to our site! Click <a href="%tokenUrl%" target="_blank"> to confirm your account.
</p>
<p class="signature">
<img class="pull-right" href="cid:logo"/>
Best,<br>
Team of %sitename%
</p>
altBody: |
Hello, %username%!
Welcome to our site! Visit %tokenUrl% to confirm your account.
Best,
Team of %sitename%
Now in the controller you can just write:
$mail = $this->getService('mailBuilder')->build('welcome', [
'%username%' => $user->getUsername(),
'%sitename%' => 'MyAwesomeSite',
]);
$mail->addTo($user); // $user needs to implement AddressInterface
$mail->embedImage('logo', $this->getRootDir() . '/app/Asset/gfx/logo.png');
$this->getService('mailer')->send($mail);
The builder will automatically translate the subject, body and alternative body.
But that's obviously not enough for most usecases. Hence, if you inject a Templater
in the MailBuilder (default) and the template `Mail/welcome` exists,
it will be automatically rendered instead of the translation
(just call the variables `username` and `sitename` instead of `%username%` and `%sitename%`).
Also, if you have the [Assetic Module](gitlab.com/Avris/Micrus-Assetic) installed
and you create a `mailCss` group, the resulting CSS will be automatically inlined
in the email's HTML. You can also directly specify the css code without using Assetic.
For the development, you might also want to just preview the email in your browser instead of sending it.
In that case, just echo the `$mail` object.
### Config ###
In `config.yml` you can specify the adress from which your emails will be sent:
mailer:
from: email@example.com
fromName: Admin
### Senders ###
The mailer uses a low-level sender service (implementing `Avris\Micrus\Mailer\Sender\SenderInterface`)
to perform the actual sending.
So far only PHPMailer is supported, but potentially this module might also be used with
SwiftMailer, plain SMTP connection, ExactTarget adapter, Amazon SES adapter etc.
### Spooling ###
Because it takes a while to send an email, it's often a good idea to just save it during the user's request
and then perform the sending in the background using a cronjob. That's the default behaviour of Mailer Module,
all you have to do is to set up a cronjob to run every minute:
* * * * * /var/www/yourproject/current/bin/micrus mailer:spool:send --env=prod
If you want to force sending directly, just execute `$mailer->send($mail, true)`.
### Copyright ###
* **Author:** Andrzej Prusinowski [(Avris.it)](https://avris.it)
* **Licence:** [MIT](https://opensource.org/licenses/MIT)
{
"name": "avris/micrus-mailer",
"type": "library",
"description": "Mailer module for the Micrus framework",
"keywords": ["mail","mailer","smtp"],
"license": "MIT",
"homepage": "https://micrus.avris.it",
"authors": [{
"name": "Avris",
"email": "andre@avris.it",
"homepage": "https://avris.it"
}],
"require": {
"avris/micrus": "^2.2",
"avris/micrus-twig": "^2.2",
"tijsverkoyen/css-to-inline-styles": "^2.2",
"symfony/filesystem": "^3.1"
},
"autoload": {
"psr-4": { "Avris\\Micrus\\Mailer\\": "src" }
}
}
<?php
namespace Avris\Micrus\Mailer\Mail;
class Address implements AddressInterface
{
/** @var string */
protected $email;
/** @var string */
protected $name;
/**
* @param string $email
* @param string $name
*/
public function __construct($email, $name = '')
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid email', $email));
}
$this->email = $email;
$this->name = $name;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
}
<?php
namespace Avris\Micrus\Mailer\Mail;
interface AddressInterface
{
/**
* @return string
*/
public function getEmail();
/**
* @return string
*/
public function getName();
}
<?php
namespace Avris\Micrus\Mailer\Mail;
use Avris\Micrus\Exception\InvalidArgumentException;
class Attachment
{
/** @var string */
protected $path;
/** @var string */
protected $filename;
/** @var string */
protected $mimeType;
/**
* @param $path
* @param string $filename
* @throws InvalidArgumentException
*/
public function __construct($path, $filename = '')
{
if (!file_exists($path)) {
throw new InvalidArgumentException(sprintf('File "%s" does not exist', $path));
}
$this->path = $path;
$this->filename = $filename ?: basename($path);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->mimeType = finfo_file($finfo, $path);
finfo_close($finfo);
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* @return string
*/
public function getFilename()
{
return $this->filename;
}
/**
* @return string
*/
public function getMimeType()
{
return $this->mimeType;
}
}
<?php
namespace Avris\Micrus\Mailer\Mail;
class Mail
{
/** @var AddressInterface */
protected $from;
/** @var string */
protected $subject;
/** @var string */
protected $body;
/** @var string */
protected $altBody;
/** @var AddressInterface[] */
protected $to = [];
/** @var AddressInterface[] */
protected $cc = [];
/** @var AddressInterface[] */
protected $bcc = [];
/** @var AddressInterface[] */
protected $replyTo = [];
/** @var Attachment[] */
protected $attachments = [];
/** @var Attachment[] */
protected $embeddedImages = [];
/**
* @return AddressInterface
*/
public function getFrom()
{
return $this->from;
}
/**
* @param AddressInterface $from
* @return Mail
*/
public function setFrom($from)
{
$this->from = $from;
return $this;
}
/**
* @return string
*/
public function getSubject()
{
return $this->subject;
}
/**
* @param string $subject
* @return Mail
*/
public function setSubject($subject)
{
$this->subject = $subject;
return $this;
}
/**
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* @param string $body
* @return Mail
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* @return string
*/
public function getAltBody()
{
return $this->altBody;
}
/**
* @param string $altBody
* @return Mail
*/
public function setAltBody($altBody)
{
$this->altBody = $altBody;
return $this;
}
/**
* @param AddressInterface $address
* @return $this
*/
public function addTo(AddressInterface $address)
{
$this->to[] = $address;
return $this;
}
/**
* @return AddressInterface[]
*/
public function getTo()
{
return $this->to;
}
/**
* @param AddressInterface $address
* @return $this
*/
public function addCc(AddressInterface $address)
{
$this->cc[] = $address;
return $this;
}
/**
* @return AddressInterface[]
*/
public function getCc()
{
return $this->cc;
}
/**
* @param AddressInterface $address
* @return $this
*/
public function addBcc(AddressInterface $address)
{
$this->bcc[] = $address;
return $this;
}
/**
* @return AddressInterface[]
*/
public function getBcc()
{
return $this->bcc;
}
public function addReplyTo(AddressInterface $address)
{
$this->replyTo[] = $address;
return $this;
}
/**
* @return AddressInterface[]
*/
public function getReplyTo()
{
return $this->replyTo;
}
/**
* @param string $path
* @param string $filename
* @return $this
*/
public function addAttachment($path, $filename = '')
{
$this->attachments[] = new Attachment($path, $filename);
return $this;
}
/**
* @param string $cid
* @param string $path
* @param string $filename
* @return $this
*/
public function embedImage($cid, $path, $filename = '')
{
$this->embeddedImages[$cid] = new Attachment($path, $filename);
return $this;
}
/**
* @return Attachment[]
*/
public function getAttachments()
{
return $this->attachments;
}
/**
* @return Attachment[]
*/
public function getEmbeddedImages()
{
return $this->embeddedImages;
}
public function getRecipientsString()
{
$out = [];
foreach ($this->getTo() as $address) {
$out[] = $this->addressToString($address);
}
foreach ($this->getCc() as $address) {
$out[] = 'CC:' . $this->addressToString($address);
}
foreach ($this->getBcc() as $address) {
$out[] = 'BCC:' . $this->addressToString($address);
}
return join(', ', $out);
}
protected function addressToString(AddressInterface $address)
{
return $address->getName()
? sprintf('%s <%s>', $address->getName(), $address->getEmail())
: $address->getEmail();
}
public function __toString()
{
return preg_replace_callback(
'#["\']cid\:([A-Za-z0-9-_]+)["\']#',
function ($matches) {
$cid = $matches[1];
if (!isset($this->embeddedImages[$cid])) {
return $matches[0];
}
$attachment = $this->embeddedImages[$cid];
return sprintf(
'data:%s;base64,%s',
$attachment->getMimeType(),
base64_encode(file_get_contents($attachment->getPath()))
);
},
$this->getBody()
);
}
}
<?php
namespace Avris\Micrus\Mailer;
use Avris\Micrus\Assetic\Manager;
use Avris\Micrus\Mailer\Mail\Address;
use Avris\Micrus\Mailer\Mail\Mail;
use Avris\Micrus\ParameterBag;
use Avris\Micrus\Tool\Locale\Localizator;
use Avris\Micrus\View\Templater;
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
class MailBuilder
{
/** @var ParameterBag */
protected $config;
/** @var Localizator */
protected $localizator;
/** @var Templater */
protected $templater;
/** @var Manager */
protected $asseticManager;
/** @var CssToInlineStyles */
protected $styleInliner;
/** @var string */
protected $css;
/**
* @param ParameterBag $config
* @param Localizator $localizator
* @param Templater $templater
* @param Manager $asseticManager
* @param CssToInlineStyles $styleInliner
* @param string $css
*/
public function __construct(
ParameterBag $config,
Localizator $localizator,
Templater $templater = null,
Manager $asseticManager = null,
CssToInlineStyles $styleInliner = null,
$css = null
) {
$this->config = $config;
$this->localizator = $localizator;
$this->templater = $templater;
$this->asseticManager = $asseticManager;
$this->styleInliner = $styleInliner;
$this->css = $css;
}
public function build($templateName, array $vars = [], $css = null)
{
$mail = new Mail();
if ($this->config->has('from')) {
$mail->setFrom(new Address($this->config->get('from'), $this->config->get('fromName')));
}
$transVars = $this->buildTransVars($vars);
$mail->setSubject($this->localizator->get('mail.' . $templateName . '.subject', $transVars));
$html = $this->buildHtml($templateName, $vars, $transVars);
$mail->setBody(
$this->styleInliner ? $this->styleInliner->convert($html, $this->buildCss($css)) : $html
);
$mail->setAltBody(
$this->localizator->has('mail.' . $templateName . '.altBody')
? $this->localizator->get('mail.' . $templateName . '.altBody', $transVars)
: preg_replace('#^\s+#m', '', strip_tags($html))
);
return $mail;
}
/**
* @param array $vars
* @return array
*/
protected function buildTransVars(array $vars)
{
$transVars = [];
foreach ($vars as $key => $value) {
$string = $this->toString($value);
if ($string === false) {
continue;
}
if (preg_match('#^%[a-zA-Z0-9]+%$#', $key)) {
$transVars[$key] = $string;
} elseif (!isset($vars['%' . $key . '%'])) {
$transVars['%' . $key . '%'] = $string;
}
}
return $transVars;
}
protected function toString($value)
{
if (is_scalar($value)) {
return (string) $value;
}
if (is_object($value) && method_exists($value, '__toString')) {
return (string) $value;
}
if ($value instanceof \DateTime) {
return $value->format(\DateTime::ISO8601);
}
return false;
}
protected function buildHtml($templateName, array $vars, array $transVars)
{
if (!$this->templater) {
return $this->localizator->get('mail.' . $templateName . '.body', $transVars);
}
try {
return $this->templater->render(array_merge(['_view' => 'Mail/' . $templateName], $vars));
} catch (\Exception $e) {
return $this->localizator->get('mail.' . $templateName . '.body', $transVars);
}
}
/**
* @param string $css
* @return string
*/
protected function buildCss($css = null)
{
if ($css) {
return $css;
}
if ($this->css) {
return $this->css;
}
if (!$this->asseticManager) {
return '';
}
$assets = $this->asseticManager->buildAll();
if (!isset($assets['mailCss'])) {
return '';
}
return file_get_contents($this->asseticManager->getAsset('mailCss', true));
}
/**
* @param string $css
* @return $this
*/
public function setCss($css)
{
$this->css = $css;
return $this;
}
}
<?php
namespace Avris\Micrus\Mailer;
use Avris\Micrus\Exception\ConfigException;
use Avris\Micrus\Mailer\Mail\Mail;
use Avris\Micrus\Mailer\Sender\SenderInterface;
use Avris\Micrus\Mailer\Task\SpoolStatus;
use Avris\Micrus\Tool\Cache\CacheClearEvent;
use Psr\Log\LoggerInterface;
class Mailer
{
/** @var SenderInterface */
protected $sender;
/** @var LoggerInterface */
protected $logger;
/** @var string */
protected $spoolDir;
/**
* @param SenderInterface $sender
* @param string $spoolDir
*/
public function __construct(
SenderInterface $sender,
LoggerInterface $logger,
$spoolDir = null
) {
$this->sender = $sender;
$this->logger = $logger;
$this->spoolDir = $spoolDir;
}
public function send(Mail $mail, $force = false)
{
if ($force || !$this->spoolDir) {
try {
$this->sender->send($mail);
} catch (\Exception $e) {
$this->logger->error($e);
return false;
}
}
if (!is_dir($this->spoolDir)) {
if (!@mkdir($this->spoolDir, 0777, true)) {