Search code examples
phpemailhooksmartyprestashop-1.6

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.


Solution

  • @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);
    
        // ...
    }