Prestashop 1.6 - 使用电子邮件挂钩

Prestashop 1.6 - Using emails hooks

我目前正在使用 Prestashop 1.6.1.5 建立商店。

由于我必须发送的电子邮件很多,我想在一个地方管理我的电子邮件骨架。
我认为更简单的方法是将电子邮件管理到 smarty 文件中并[扩展我的布局][1]。

现在有没有人可以通过透明的全局方式来管理电子邮件布局?

谢谢,

本.

[编辑]

深入了解 MailCore class,我看到可以在发送前使用三个挂钩(简化和评论)操作电子邮件模板:

// 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

所以在我看来,使用这些挂钩是管理我的电子邮件布局的好方法,但我不明白定义挂钩调用的函数是如何工作的。

为了使其全球化,我尝试覆盖 MailCore(进入 /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';
    }

}

清除 Prestashop 缓存后,class 覆盖工作(来自构造函数的日志)但是 none 我的挂钩被调用。
我还尝试使用 $this->registerHook('...'); 将钩子注册到我的 class 构造中,但没有任何效果。

如果有人能帮忙,那就太好了。

谢谢,

本.

仅对于自定义邮件,您不需要挂钩或覆盖任何内容。在调用 Mail::send() 之前,您在这里要做的就是通过 smarty {header}{content}{footer}.

生成三个模板变量
public function someModuleMailFunc($email_template)
{
    $header_tpl = $this->context->smarty->createTemplate('path_to_header_tpl_smarty');
    $header_tpl->assign(array(
        // assign header template vars
    ));

    // put some conditionals here to load a proper content for specific mail
    if ($email_template == 'some_mail_template') {    
        $content_tpl = $this->context->smarty->createTemplate('path_to_content_tpl_smarty');
        $content_tpl->assign(array(
            // assign content template vars
        ));
    }
    $footer_tpl = $this->context->smarty->createTemplate('path_to_footer_tpl_smarty');
    $footer_tpl->assign(array(
        // assign footer template vars
    ));

    $email_vars = array(
        '{header}' => $header_tpl->fetch(),
        '{content}' => $content_tpl->fetch(),
        '{footer}' => $footer_tpl->fetch()        
    );

    Mail::send('en', $email_template, $subject, $email_vars, $to); 
}

现在确保您的 some_email_template.html 结构像这样。

{header}
{content}
{footer}

您也不需要为每种语言准备电子邮件模板,因为您使用可以调用语言函数的 smarty 模板 {l s='text'}

并且该示例仅适用于 html 模板,添加处理 txt 模板的代码。

要更改核心电子邮件...现在事情变得复杂了。

一种方法是挂接到 actionEmailAddAfterContentactionGetExtraMailTemplateVars

public function hookActionEmailAddAfterContent($params)
{
    // here we can replace the email templates with {header} {content} {footer}
    // this param is passed by reference
    $params['template_html'] = '{header}{content}{footer}';
}

public function hookActionGetExtraMailTemplateVars($params)
{
    // here we generate vars for {header} {content} {footer}

    $header_tpl = $this->context->smarty->createTemplate('path_to_header_tpl_smarty');
    $header_tpl->assign(array(
        // assign header template vars
    ));

    // existing template vars can be accessed with $params['template_vars']
    // so they can be reinserted into your templates

    // put some conditionals here to load a proper content for specific mail
    if ($params['template'] == 'some_mail_template') {    
        $content_tpl = $this->context->smarty->createTemplate('path_to_content_tpl_smarty');
        $content_tpl->assign(array(
            // assign content template vars
        ));
    }
    $footer_tpl = $this->context->smarty->createTemplate('path_to_footer_tpl_smarty');
    $footer_tpl->assign(array(
        // assign footer template vars
    ));

    // this param is also passed by reference
    $params['extra_template_vars']['{header}'] = $header_tpl->fetch();
    $params['extra_template_vars']['{content}'] = $content_tpl->fetch();
    $params['extra_template_vars']['{footer}'] = $footer_tpl->fetch();
}

这样额外的参数将被 Mail::send() 拾取,并且由于我们之前修改了模板,它将在其中插入页眉内容和页脚。

问题是您仍然需要为每种语言添加电子邮件模板文件,即使它们从未被使用过,因为 Mail::send() 仍然会尝试在执行这些挂钩之前加载它们。除非你想覆盖当然的方法。

此外,通过后台为经理编辑电子邮件变得毫无用处,因为除了 {header}{content}{footer} 自定义模块邮件和编辑核心邮件之外,他们看不到任何内容。

电子邮件模板的整个事情有点混乱,我不知道为什么开发人员决定以这种方式创建邮件系统而不使用 smarty。也许是为了更轻松地实施后台电子邮件编辑……我不知道。也许如果任何 prestashop 开发人员看到这个,他们可能会解释它。

@TheDrot : 感谢这些有用的解释:-)

正如 TheDrot 指出的那样,这个电子邮件有点乱。我想:

  • 能够编辑来自 BO 的电子邮件。
  • 全局管理电子邮件布局(核心、模块、自定义...)
  • 有几个布局,select我将直接在模板中使用的布局。

所以我决定覆盖 MailCore class 并实现以下基本但有效的“布局扩展”系统。
当然,这个解决方案的主要缺点是我必须在更新 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.

布局

布局放置在 /themes/{theme}/mails/layouts 目录中。
可以使用电子邮件中可用的任何变量,内容位置使用 {{CONTENT}} 标签定义。

/themes/mytheme/mails/layouts/my-layout.html :

<h1>A header</h1>
{{CONTENT}}
<h1>A footer</h1>

电子邮件模板

进入电子邮件模板,要继承的布局是使用 {extends:name-of-layout} 标签定义的:

/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>

邮件class

这里是主要功能:如果“extends”标签存在于模板中并且创建了所需的布局,则使用模板填充布局。

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

然后 Send 函数在一个地方被修改以将 layout 函数应用于模板:

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

    // ...
}