bgaze bgaze - 24 days ago 14
Smarty Question

Prestashop 1.6 - Using emails hooks

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.

[EDIT]



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.

Answer

@TheDrot : Thank for these usefull explanations :-)

As TheDrot pointed it out, this emails thing is kinda messy. I want to :

  • be able to edit emails from BO.
  • manage email layouts globally (core, modules, custom, ...)
  • have several layout and select the one I will use directly into template.

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

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>

Email template

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>

Mail class

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);

    // ...
}
Comments