[PrestaShop] Extends original e-mail templates: the clean ways! (pt.1)

Understand how PrestaShop mail sender works and hot to extends core PrestaShop e-mail templates by implementing a module to solve a real world use case.

In this series we get a deep dive into how PrestaShop send e-mail message and how to override (or even extends) core mail templates in a clean and elegant way in our custom modules.

Let’s start our journey by presenting what we build at the end of this article. Following the instructions given by these articles you will be able to build a module that provides customer’s email validation using a random (and expirable) generated token!

With this module, each time a new customer registration ends, the customer profile is automatically disabled to avoid customer interaction with our site until email is not verified and a confirmation URL (contains the generated unique token) is send to the customer mail. The token URL can be send:

  1. Overriding a the “Welcome” original template.
  2. Using a custom template specially created for this purpose.

Before getting started building our module, in this article we get a deep dive in how the PrestaShop mail sender really works.

The PrestaShop mail sender

All code involved into the mail sending process is stored into “classes/Mail.php”. To send mails PrestaShop use the method “send” of the class MailCore stored into this file. The method has the following signature and implements Swift Mailer as mail library since PrestaShop adopts Symfony as framework.

public static function send(
$idLang,
$template,
$subject,
$templateVars,
$to,
$toName = null,
$from = null,
$fromName = null,
$fileAttachment = null,
$mode_smtp = null,
$templatePath = _PS_MAIL_DIR_,
$die = false,
$idShop = null,
$bcc = null,
$replyTo = null,
$replyToName = null
);

Method signature analysis

Let start analyzing the method signature. We can notice that this method expect as input the following parameters:

  • $idLang → is the id of the language we want to use to send the email, it’s used to choose the right template based on the language of our shop. If this parameter is not give it will use the default shop language (PS_LANG_DEFAULT) taken from the configurations.
  • $template → is the name of the template we want to use to send the email. If not provided calling the method or by one of the following hook execution the mail sender throws an error.
  • $subject → as you can guess is the subject of the mail. If not provided calling the method or by one of the following hook execution or even if it’s not valid, mail sender throws an exception.
  • $templateVars → these the values that must be injected into templates before the mail send to our customer.
  • $to, $toName, $from and $fromName → these are strings which contains the information about recipient and sender.
  • $fileAttachment → it is an array that contains the information about files that are attached to our mail. This array must have three keys: ‘name’ which is a string with the name of the file, ‘mime’ which is the content type of the attachment we will create and last but not least the ‘content’ which is an instance of Swift_OutputByteStream class. For reference the snippet that PrestaShop use to handle the mails attachments:
if ($fileAttachment && !empty($fileAttachment)) {

// Multiple attachments?
if (!is_array(current($fileAttachment))) {
$fileAttachment = array($fileAttachment);
}

foreach ($fileAttachment as $attachment) {
if (isset($attachment['content'], $attachment['name'], $attachment['mime'])) {
$message->attach(
\Swift_Attachment::newInstance()->setFilename(
$attachment['name'])
->setContentType($attachment['mime'])
->setBody($attachment['content']));
}
}
}
  • $mode_smtp → ‘NULL’ by default
  • $templatePath → ‘_PS_MAIL_DIR_’ by default. It’s the path in which PrestaShop search the mail template that will be used for mail content generation.
  • $die → is a boolean control variable used when an error is thrown to decide if die or simply log the error. Here an example for reference:
if (!Validate::isEmail($addr)) {
self::dieOrLog($die, 'Error: invalid e-mail address');
return false;
}
  • $idShop → is the ID of the shop which send the email. If nothing is passed to method, the context shop is used.
  • $bcc → is a list of email addresses used as BCC or CCN added to the generated email as blind copy. Here the code that PrestaShop use to handle this feature:
if (isset($bcc) && is_array($bcc)) {
foreach ($bcc as $addr) {
$addr = trim($addr);
if (!Validate::isEmail($addr)) {
self::dieOrLog($die, 'Error: invalid e-mail address'){
return false;
}
$message->addBcc(self::toPunycode($addr));
}
} elseif (isset($bcc)) {
$message->addBcc(self::toPunycode($bcc));
}
  • $replyTo and $replyToName → as you can guess are the reply address information that can be added to the email if needed.

How really works the PrestaShop Mail Sender?

Before getting to the heart of the discussion on the customization opportunities we have on the PrestaShop emails, let’s take a look on how the mail senader really works.

During the shop configuration you can provide some configuration that will be used in this context, take a look at these:

$configuration = Configuration::getMultiple([
'PS_SHOP_EMAIL',
'PS_MAIL_METHOD',
'PS_MAIL_SERVER',
'PS_MAIL_USER',
'PS_MAIL_PASSWD',
'PS_SHOP_NAME',
'PS_MAIL_SMTP_ENCRYPTION',
'PS_MAIL_SMTP_PORT',
'PS_MAIL_TYPE',
],
null,
null,
$idShop
);

These are the configuration keys used by PrestaShop in the Mail Sender method, lets analyze them:

  • PS_SHOP_EMAIL → used if $from argument is not set or not valid
  • PS_MAIL_METHOD → using this configuration you can: disable the mail system (exit from the method without really send the mail) or use a SMTP server to send the mail instead of classic php mail() method.
  • PS_MAIL_SERVER → just evaluated if the PS_MAIL_METHOD is SMTP, this configuration contain the address of the smtp server that the mail sender will use to send the mail.
  • PS_MAIL_USER → contains the username used to open the connection to SMTP server. This configuration is evaluated only if the PS_MAIL_METHOD is SMTP.
  • PS_MAIL_PASSWD → contains the password used to open the connection to SMTP server. This configuration is evaluated only if the PS_MAIL_METHOD is SMTP. Here the code used to open the connection to the smtp server:
$connection = \Swift_SmtpTransport::newInstance(
$configuration['PS_MAIL_SERVER'],
$configuration['PS_MAIL_SMTP_PORT'],
$configuration['PS_MAIL_SMTP_ENCRYPTION'])
->setUsername($configuration['PS_MAIL_USER'])
->setPassword($configuration['PS_MAIL_PASSWD']);

As you can see a Swift_SmtpTransport is used to open the connection.

  • PS_SHOP_NAME → contains the name of the shop and is used if the $fromName argument is empty or even not valid.
  • PS_MAIL_SMTP_ENCRYPTION → contains the encryption used to open the connection to SMTP server. This configuration is evaluated only if the PS_MAIL_METHOD is SMTP.
  • PS_MAIL_SMTP_PORT → contains the port used to open the connection to SMTP server. This configuration is evaluated only if the PS_MAIL_METHOD is SMTP.
  • PS_MAIL_TYPE → define which type of template (and so which type of mail) we want to use. We can choose: TXT, HTML or BOTH.

After recovering the configurations and validating the input parameters a new instance of “Swift_Message” is created by calling the “newInstance” method and the first thing done on this object is adding the recipients address (yes, multiple recipients are allowed!).

$message = \Swift_Message::newInstance();
if (is_array($to) && isset($to)) {
foreach ($to as $key => $addr) {
$addr = trim($addr);
if (!Validate::isEmail($addr)) {
self::dieOrLog($die, 'Error: invalid e-mail address');
return false;
}
if (is_array($toName) && isset($toName[$key])) {
$addrName = $toName[$key];
} else {
$addrName = $toName;
}
$addrName = ($addrName == null || $addrName == $addr || !Validate::isGenericName($addrName)) ? '' : self::mimeEncode($addrName);
$message->addTo(self::toPunycode($addr), $addrName);
}
$toPlugin = $to[0];
} else {
/* Simple recipient, one address */
$toPlugin = $to;
$toName = (($toName == null || $toName == $to) ? '' : self::mimeEncode($toName));
$message->addTo(self::toPunycode($to), $toName);
}

Then the bcc are attached to the $message object and the $connection object is instantiated as we seen previously in this article.

Here the $swift object is created by the following instruction:

$swift = \Swift_Mailer::newInstance($connection);

This is the instance of Swift_Mailer class that practically send the email and contains the plug-in registration that we analyze then.

Now it's time to find the template to use and replace its placeholders. It’s done by using the $template and $templatePath arguments considering the language of the shop.

The template content is loaded by using the following code:

$templateHtml .= Tools::file_get_contents($templatePath .   $isoTemplate . ‘.html’);
$templateTxt .= strip_tags(
html_entity_decode(
Tools::file_get_contents($templatePath . $isoTemplate . ‘.txt’),
null,
‘utf-8’
)
);

Before the template placeholders replacements some default variables are added to the $templateVar object, some of these variable are: {shop_name}, {my_account_url}, {history_url} ecc..

Then the $templateVars object is mapped using two different callback methods from Tools class: htmlentitiesDecodeUTF8 and stripslashes.

Now we had the template content loaded and the variables correctly parsed to be injected into the html or the txt mail content, and it’s done using a Swift_Plugins_DecoratorPlugin instance. The Swift_Plugins_DecoratorPlugin class is a SwiftMailer plug in that allow to make customization of the messages on the fly if correctly registered to the SwiftMailer object.

$swift->registerPlugin(new \Swift_Plugins_DecoratorPlugin(array($toPlugin => $templateVars)));

Now the SwiftMessage is finished by adding eventually present attachments as we seen before and the mail is sent to the recipient.

Conclusion

This article provide a detailed overview of how the PrestaShop mail sender works and now you should have all the basics needed to implements your own mails or customize existing ones.

In the following article we will do a deep analysis of all the customization possibilities we have in PrestaShop using hooks and other methods. We will analyze how PrestaShop hooks are integrated into the mail sender and we will get a clear knowledge of how are the pros or the cons of using an hook instead of another.

Stay tuned!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Graziano Casto

Graziano Casto

Software Developer Engineer and SCRUM Master based in Italy.