DEV Community

Valentine
Valentine

Posted on • Originally published at vallka.Medium on

Prestashop Modules Programming. Bcc outgoing emails

What is the idea?

Let's start programming Prestashop modules. Where to start from? Here is the documentation:

https://devdocs.prestashop.com/1.7/modules/

Ok. But what would be our first module? It should be very simple, and yet it should be useful. Here is the idea: add BCC to all outgoing emails, that we will have copies of all outgoing emails in out mailbox. Why would we want it? Actually this is what many people want, just try to google. For newly set up website it may be useful. You may want to see how actually the emails look like. And find all versions of emails which Prestashop sends to your customers. Probably after looking to these emails you would want to change them a bit, maybe remove a default "Powered by Prestashop" line at the bottom... You may want to keep monitoring outgoing emails to ensure that next update of Prestashop do not revert your custom emails to default ones (you probably forgot to check "Keep email templates" while updating Prestashop). Ok, let's do it.

Among Prestashop hooks there is one which can be used for our purpose: actionEmailSendBefore

actionEmailSendBefore

Before sending an email This hook is used to filter the content 
or the metadata of an email before sending it or even prevent its sending

Located in: /classes/Mail.php
Enter fullscreen mode Exit fullscreen mode

https://devdocs.prestashop.com/1.7/modules/concepts/hooks/list-of-hooks/

If we have a chance to look inside /classes/Mail.php file, we will see that all the parameters are prefixed with "&", what means we can change them:

$hookBeforeEmailResult = Hook::exec(
        'actionEmailSendBefore',
        [
            'idLang' => &$idLang,
            'template' => &$template,
            'subject' => &$subject,
            'templateVars' => &$templateVars,
            'to' => &$to,
            'toName' => &$toName,
            'from' => &$from,
            'fromName' => &$fromName,
            'fileAttachment' => &$fileAttachment,
            'mode_smtp' => &$mode_smtp,
            'templatePath' => &$templatePath,
            'die' => &$die,
            'idShop' => &$idShop,
            'bcc' => &$bcc,
            'replyTo' => &$replyTo,
        ],
        null,
        true
    );
Enter fullscreen mode Exit fullscreen mode

I couldn't find any place in Prestashop Back Office which could potentially use BCC. So we can assume (for simplicity) it is always empty and we can just set it to our own value in the hook.

Let's create a module!

Luckily, there is a simple way to create a module skeleton. There is a module generator supplied by Prestashop itself:

https://validator.prestashop.com/generator

I wouldn't say it works perfectly but it works. Probably, it is not updated as ofter as new Prestashop versions are released, and contains some bugs. Also, as mentioned in the documentation, if you want to create a Payments module for Prestashop 1.7 you should not use this generator. But for our purpose it as just invaluable.

So put some values to start a module - name, version, author name etc.:

On next page let's select the the following - Yes to Confirm uninstall, No to Create default database table and Need instance. Compliancy min-max - 1.7.x (we do not bother about older versions).

Finally, we are asked about the hooks we want to use. Unfortunately, the generator seems not to know about the hook we are after:

Ok, let's just select a random one, we will change the code later:

Click on Create! button - the generator will generate bcc_outgoing_emails.zip file. This is how we named our module.

What is inside?

Inside the zip file there is a directory with the same name - bcc_outgoing_emails. And inside this directory:

Actually, we don't need most of these files. But it is interesting to look inside and see what is generated.

/bcc_outgoing_emails.php 
Enter fullscreen mode Exit fullscreen mode

This is the main ,php file. By default, Prestashop puts everything in this file. I would rather separate install/uninstall code, admin actions, hooks into different .php files. But for our very simple example the default behavior is good enough.

/sql/
Enter fullscreen mode Exit fullscreen mode

We have actually selected No to Create default database table option, so this directory shouldn't be here at all. We don't need any sql content, so we can simply delete this directory.

/translations/
Enter fullscreen mode Exit fullscreen mode

Ok, let's keeps it. We may want to use it to translate Back Office form. We won't do it now, but it good to know that we have this ability by default.

/upgrade/
Enter fullscreen mode Exit fullscreen mode

Let's keep it as is.

/views/
Enter fullscreen mode Exit fullscreen mode

Under /views/ directory Prestashop module keeps all templates, images, javascripts and css files. For both front end and back end. Definitely we don't need any front end files, and hardly we need any styling or images for back end. Or javascript. But it is interesting to see how all these files are going to be used in case they were needed.

Let's look inside the main file, bcc_outgoing_emails.php. First what we can notice - the generator correctly set author and version info in php class, but left

*  @author    PrestaShop SA <contact@prestashop.com>
*  @copyright 2007-2021 PrestaShop SA
Enter fullscreen mode Exit fullscreen mode

in the top file comment. We may want to change the comment :)

Ok. More important things. We have chosen (randomly) actionAdminControllerSetMedia hook, but here we see 2 more hooks registered: header and backOfficeHeader:

return parent::install() &&
        $this->registerHook('header') &&
        $this->registerHook('backOfficeHeader') &&
        $this->registerHook('actionAdminControllerSetMedia');
Enter fullscreen mode Exit fullscreen mode

(lines 66-69)

header hook is used for adding js and css files to front end:

 /**
 * Add the CSS & JavaScript files you want to be added on the FO.
 */
public function hookHeader()
{
    $this->context->controller->addJS($this->_path.'/views/js/front.js');
    $this->context->controller->addCSS($this->_path.'/views/css/front.css');
}
Enter fullscreen mode Exit fullscreen mode

(lines 213-220)

We won't be touching front end at all, so we can delete all these lines together with files in /views/js/ and /css/ directories.

Similarly, backOfficeHeader hook is used to add back office js and css files:

/**
* Add the CSS & JavaScript files you want to be loaded in the BO.
*/
public function hookBackOfficeHeader()
{
    if (Tools::getValue('module_name') == $this->name) {
        $this->context->controller->addJS($this->_path.'views/js/back.js');
        $this->context->controller->addCSS($this->_path.'views/css/back.css');
    }
}
Enter fullscreen mode Exit fullscreen mode

(lines 202-211)

Please notice that our back office js and css will only be added to the page with our module configuration form. Yes, it may have sense in many cases, but just be aware of it. In another my project I wanted back.js file to be always available in back office and had to spend some time trying to find out and get rid of the condition:

if (Tools::getValue('module_name') == $this->name) {
Enter fullscreen mode Exit fullscreen mode

Another thing to notice here. These two hooks have been added automatically behind the scenes. Accidentally we have selected for our module a hook actionAdminControllerSetMedia. It looks like this particular hook is intended to be used instead of backOfficeHeader for adding js and css files. This is an open question to Prestashop developers - which hook should be actually used for this purpose and did they forgot to update the generator?

Anyway, both hooks seems to be working for this purpose (as I find out building another module), and we don't need any media in this project, so we can simply delete the lines :)

Let's finally write some code!

Let's delete all mentions of header and backOfficeHeader hooks and change actionAdminControllerSetMedia to what we really need: actionEmailSendBefore

public function install()
{
    Configuration::updateValue('BCC_OUTGOING_EMAILS_LIVE_MODE', false);

    return parent::install() &&
        $this->registerHook('actionEmailSendBefore');
}
Enter fullscreen mode Exit fullscreen mode

... and below:

public function hookActionEmailSendBefore($param)
{
    /* Place your code here. */
}
Enter fullscreen mode Exit fullscreen mode

We know that all hooks get one and only argument - an array of various parameters which differ from hook to hook. It seems like a bug that generator didn't include this $param array in the skeleton, so we have to add it ourselves.

As we are going to write some real code here, it's time to look for our own configuration. Where will we take to email address to which we going bcc? And very handy, the generator did generate some basic configuration for us, and luckily, this is almost exactly what we need! The generator has created 3 configuration variables:

'BCC_OUTGOING_EMAILS_LIVE_MODE' => Configuration::get('BCC_OUTGOING_EMAILS_LIVE_MODE', true),
'BCC_OUTGOING_EMAILS_ACCOUNT_EMAIL' => Configuration::get('BCC_OUTGOING_EMAILS_ACCOUNT_EMAIL', 'contact@prestashop.com'),
'BCC_OUTGOING_EMAILS_ACCOUNT_PASSWORD' => Configuration::get('BCC_OUTGOING_EMAILS_ACCOUNT_PASSWORD', null),
Enter fullscreen mode Exit fullscreen mode

This is almost exactly what we need! We don't need any password, so lets delete all lines where BCC_OUTGOING_EMAILS_ACCOUNT_PASSWORD is mentioned, and for clarity change the name BCC_OUTGOING_EMAILS_ACCOUNT_EMAIL to BCC_OUTGOING_EMAILS_BCC_EMAIL. Let's even keep BCC_OUTGOING_EMAILS_LIVE_MODE variable - using it will add some professional look to our module :)

Here is how the hook handle will look like:

public function hookActionEmailSendBefore($param)
{
    if (Configuration::get('BCC_OUTGOING_EMAILS_LIVE_MODE', false)) {
        $bcc = Configuration::get('BCC_OUTGOING_EMAILS_BCC_EMAIL', '');
        if ($bcc) {
            $param['bcc'] = $param['bcc']? "{$param['bcc']},$bcc": $bcc;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

UPD. And here we have an interesting result. I assumed that multiple email addresses in bcc field should be separated by comma. But it appeared to be wrong. In reality it would be difficult to get to to the point where this problem would show. But working on this project, I had 2 copies of the same module installed (by different names) and Prestashop began to complain about wrong format of bcc parameter. It occurred that Prestashop Mail.php expects multiple email addresses in to and bcc fields as arrays. And another interesting result from investigating Mail.php - there is no possibility to set cc at all. Well, I may understand the logic, but is looks at least surprising for me...

So the final version of the hook handler is:

public function hookActionEmailSendBefore($param)
{
    if (Configuration::get('BCC_OUTGOING_EMAILS_LIVE_MODE', false)) {
        $bcc = Configuration::get('BCC_OUTGOING_EMAILS_BCC_EMAIL', '');
        if ($bcc) {
            if (! $param['bcc']) {
                $param['bcc'] = $bcc;
            }
            else {
                if (is_array($param['bcc'])) {
                    array_push($param['bcc'],$bcc);
                }
                else {
                    $param['bcc'] = [$param['bcc'],$bcc];
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Well, a bit more complicated than it was expected :)

Anyway, the whole investigation had rather theoretical value. In practice, I think, the simplest solution to overwrite old bcc setting with a new one would satisfy 99.9% cases.

Done and tested.

Other thing to notice: while the generator generated three configuration variables (we got rid of one of those), in install and uninstall methods only one is mentioned. Looks like the other two were just forgotten, at least in unistall method. Let's add our second variable to uninstall:

public function uninstall()
{
    Configuration::deleteByName('BCC_OUTGOING_EMAILS_LIVE_MODE');
    Configuration::deleteByName('BCC_OUTGOING_EMAILS_BCC_EMAIL');

    return parent::uninstall();
}
Enter fullscreen mode Exit fullscreen mode

Let's leave install method as it is. I think the initialization of BCC_OUTGOING_EMAILS_LIVE_MODE is not needed altogether (and I have changed the default for this ver from true to false - seems more logical for me). but let's leave it alone.

We didn't look at the /views/templates/admin/configure.tpl file, but it basically OK for our purpose. We may want to delete the example texts on the page, update links to the documentation when we had one, but this is straightforward. Just textual changes.

Deploy and install

First we need to create (or rather re-create) the zip file. After deleting the files and directories we didn't use, here is the structure:

Do not forget to put all these file in bcc_outgoing_emails folder, as they were in the original zip. Then go to back office in Prestashop, Modules->Module Manager->Upload a Module. Configuration is self-explaining. Voilà! Our first Prestashop module is installed and working.

Yet another potential problem.

When trying to test a newly installed module, first thing I went to Prestashop Email configuration->Test your email configuration. I expected a test email to be bcc'd too, accordingly the module setting. Nope. It just doesn't work with Test your email configuration. So you need, for example, to register a new user to see any effect. The Welcome email will be sent to bcc address.

GitHub repositary

https://github.com/vallka/bcc_outgoing_emails

Top comments (0)