I am currently building a shop using Prestashop 1.6.1.5.
As emails I must send are numerous, I would like to manage my email skeletton in a single place.
I think that the easier way would be to manage emails into smarty files and to [extend my layout][1].
Does anyone now a transparent and global way to manage email layouts?
Thanks,
Ben.
Diving into MailCore class, I saw that email templates can be manipulated before send using three hooks (simplified and commented) :
// Init empty templates strings.
$template_html = '';
$template_txt = '';
// Manipulate strings before importing templates.
Hook::exec('actionEmailAddBeforeContent', array(
'template_html' => &$template_html,
'template_txt' => &$template_txt,
// ...
), null, true);
// Import templates.
$template_html .= Tools::file_get_contents(/* ... */);
$template_txt .= strip_tags(html_entity_decode(Tools::file_get_contents(/* ... */), null, 'utf-8'));
// Manipulate strings after importing templates.
Hook::exec('actionEmailAddAfterContent', array(
'template_html' => &$template_html,
'template_txt' => &$template_txt,
// ...
), null, true);
// Some MailCore stuff.
// Inject custom vars before generate email content
$extra_template_vars = array();
Hook::exec('actionGetExtraMailTemplateVars', array(
'template_vars' => $template_vars,
'extra_template_vars' => &$extra_template_vars,
// ...
), null, true);
$template_vars = array_merge($template_vars, $extra_template_vars);
// Generate and send email
So it seems to me that using these hooks is the good way to manage my email layout, but I don't get how it's work to define the function called by hooks.
To make it global, I tried to override MailCore (into /override/classes/Mail.php) :
class Mail extends MailCore {
public function __construct($id = null, $id_lang = null, $id_shop = null) {
parent::__construct($id, $id_lang, $id_shop);
PrestaShopLogger::addLog('MailCore overrided!');
}
// Prepend a header to template.
public function hookActionEmailAddBeforeContent($params) {
PrestaShopLogger::addLog('hookActionEmailAddBeforeContent called!');
$params['template_html'] .= '<h1>{myheader}</h1>';
}
// Append a footer to template.
public function hookActionEmailAddAfterContent($params) {
PrestaShopLogger::addLog('hookActionEmailAddAfterContent called!');
$params['template_html'] .= '<h1>{myfooter}</h1>';
}
// Add my custom vars.
public function hookActionGetExtraMailTemplateVars($params) {
PrestaShopLogger::addLog('hookActionGetExtraMailTemplateVars called!');
$params['extra_template_vars']['myheader'] = 'This is a header';
$params['extra_template_vars']['myfooter'] = 'This is a footer';
}
}
After clearing Prestashop cache, class override works (log from constructor) but none of my hooks is called.
I also tried to register hooks using $this->registerHook('...');
into my class constructoe, but without any effect.
If anyone could help, It would be very great.
Thanks,
Ben.
@TheDrot : Thank for these usefull explanations :-)
As TheDrot pointed it out, this emails thing is kinda messy. I want to :
So I decided to override MailCore class and to implement following basic but working "layout extension" system.
Of course, the main drawback of this solution is that I will have to keep my overrided function up to date when updating Prestashop.
I am in a single language context and I don't need layouts for text emails, but managing multiple language and texts emails is easy too.
Following code can of course be highly improved, it's just a quick demo.
Layouts are placed into /themes/{theme}/mails/layouts dir.
Any variable available into the email can be used and content place is defined using {{CONTENT}}
tag.
/themes/mytheme/mails/layouts/my-layout.html :
<h1>A header</h1>
{{CONTENT}}
<h1>A footer</h1>
Into email template, layout to inherit from is defined using {extends:name-of-layout}
tag :
/themes/mytheme/mails/en/password_query.html :
{{extends:my-layout}}
<p>
<b>
Hi {firstname} {lastname},
</b>
</p>
<p>
You have requested to reset your {shop_name} login details.<br/>
Please note that this will change your current password.<br/>
To confirm this action, please use the following link:
</p>
<p>
<a href="{url}" class="button">Change my pasword</a>
</p>
Here is the main function : if "extends" tag exists into template and required layout is founded, populate layout with template.
/override/classes/Mail.php :
public static function layout($theme_path, $template, $content) {
preg_match("/^\{\{extends\:(\w+)\}\}/", ltrim($content), $m);
if (!isset($m[1]) || !file_exists($theme_path . 'mails/layout/' . $m[1] . '.html')) {
return $content;
}
$content = ltrim(str_replace('{{extends:' . $m[1] . '}}', '', $content));
$layout = Tools::file_get_contents($theme_path . 'mails/layout/' . $m[1] . '.html');
return str_replace('{{CONTENT}}', $content, $layout);
}
Then Send function is modified in a single place to apply layout function to template :
public static function Send($id_lang, $template, $subject, $template_vars, $to, $to_name = null, $from = null, $from_name = null, $file_attachment = null, $mode_smtp = null, $template_path = _PS_MAIL_DIR_, $die = false, $id_shop = null, $bcc = null, $reply_to = null) {
// ...
$template_html = '';
$template_txt = '';
Hook::exec('actionEmailAddBeforeContent', array(
'template' => $template,
'template_html' => &$template_html,
'template_txt' => &$template_txt,
'id_lang' => (int) $id_lang
), null, true);
$template_html .= Tools::file_get_contents($template_path . $iso_template . '.html');
$template_txt .= strip_tags(html_entity_decode(Tools::file_get_contents($template_path . $iso_template . '.txt'), null, 'utf-8'));
Hook::exec('actionEmailAddAfterContent', array(
'template' => $template,
'template_html' => &$template_html,
'template_txt' => &$template_txt,
'id_lang' => (int) $id_lang
), null, true);
// Apply self::layout function to template when acquired.
$template_html = self::layout($theme_path, $template_path . $iso_template . '.html', $template_html);
// ...
}