Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Add topic for new email template directives #6107

Merged
merged 20 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions src/guides/v2.2/frontend-dev-guide/templates/template-email.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,45 +64,6 @@ To add the store and sales related information to a template, use system variabl

System variables are placeholders which are replaced by particular values when the actual email is generated. For example, the Store Hours (`{% raw %}{{config path="general/store_information/hours"}}{% endraw %}`) variable is replaced by the value set in the **STORES** > Settings > **Configuration** > GENERAL > **General** > **Store Information** section.

Here is a list of the most commonly used email template variables that are available:

* Email Footer Template: `{% raw %}{{template config_path="design/email/footer_template"}}{% endraw %}`
* Email Header Template: `{% raw %}{{template config_path="design/email/header_template"}}{% endraw %}`
* Email Logo Image Alt: `{% raw %}{{var logo_alt}}{% endraw %}`
* Email Logo Image URL: `{% raw %}{{var logo_url}}{% endraw %}`
* Email Logo Image Height: `{% raw %}{{var logo_height}}{% endraw %}`
* Email Logo Image Width: `{% raw %}{{var logo_width}}{% endraw %}`
* Template CSS: `{% raw %}{{var template_styles|raw}}{% endraw %}`
* Base Unsecure URL: `{% raw %}{{config path="web/unsecure/base_url"}}{% endraw %}`
* Base Secure URL: `{% raw %}{{config path="web/secure/base_url"}}{% endraw %}`
* General Contact Name: `{% raw %}{{config path="trans_email/ident_general/name"}}{% endraw %}`
* Sales Representative Contact Name: `{% raw %}{{config path="trans_email/ident_sales/name"}}{% endraw %}`
* Sales Representative Contact Email: `{% raw %}{{config path="trans_email/ident_sales/email"}}{% endraw %}`
* Custom1 Contact Name: `{% raw %}{{config path="trans_email/ident_custom1/name"}}{% endraw %}`
* Custom1 Contact Email: `{% raw %}{{config path="trans_email/ident_custom1/email"}}{% endraw %}`
* Custom2 Contact Name: `{% raw %}{{config path="trans_email/ident_custom2/name"}}{% endraw %}`
* Custom2 Contact Email: `{% raw %}{{config path="trans_email/ident_custom2/email"}}{% endraw %}`
* Store Name: `{% raw %}{{config path="general/store_information/name"}}{% endraw %}`
* Store Phone Number: `{% raw %}{{config path="general/store_information/phone"}}{% endraw %}`
* Store Hours: `{% raw %}{{config path="general/store_information/hours"}}{% endraw %}`
* Country: `{% raw %}{{config path="general/store_information/country_id"}}{% endraw %}`
* Region/State: `{% raw %}{{config path="general/store_information/region_id"}}{% endraw %}`
* Zip/Postal Code: `{% raw %}{{config path="general/store_information/postcode"}}{% endraw %}`
* City: `{% raw %}{{config path="general/store_information/city"}}{% endraw %}`
* Street Address 1: `{% raw %}{{config path="general/store_information/street_line1"}}{% endraw %}`
* Street Address 2: `{% raw %}{{config path="general/store_information/street_line2"}}{% endraw %}`
* Store Contact Address: `{% raw %}{{config path="general/store_information/address"}}{% endraw %}`
* Customer Account URL: `{% raw %}{{var this.getUrl($store, 'customer/account/')}}{% endraw %}`
* Customer Email: `{% raw %}{{var customer.email}}{% endraw %}`
* Customer Name: `{% raw %}{{var customer.name}}{% endraw %}`
* Billing Address: `{% raw %}{{var formattedBillingAddress|raw}}{% endraw %}`
* Email Order Note: `{% raw %}{{var order.getEmailCustomerNote()}}{% endraw %}`
* Order ID: `{% raw %}{{var order.increment_id}}{% endraw %}`
* Order Items Grid: `{% raw %}{{layout handle="sales_email_order_items" order=$order area="frontend"}}{% endraw %}`
* Payment Details: `{% raw %}{{var payment_html|raw}}{% endraw %}`
* Shipping Address: `{% raw %}{{var formattedShippingAddress|raw}}{% endraw %}`
* Shipping Description: `{% raw %}{{var order.getShippingDescription()}}{% endraw %}`

{:.bs-callout-info}
You can also create your own custom variables and set their values in the Admin, under **SYSTEM** > **Custom Variables**.

Expand All @@ -111,7 +72,7 @@ To add a variable to your template content:
1. In the Magento Admin, navigate to **MARKETING** > Communications > **Email Templates**
1. Create a new template or edit an existing template.
1. Click to place the cursor in the text in which to insert the variable.
1. Click **Insert Variable**. A pop-up containing a list of variables opens, including custom variables. The variables in the **Store Contact Information** are available in all email templates whereas the variables in the **Template Variables** section are specific to the template you're editing. The following figure shows an example:
1. Click **Insert Variable**. A pop-up containing a list of variables opens, including custom variables. The variables in the **Store Contact Information** are available in all email templates whereas the variables in the **Template Variables** section are specific to the template you are editing and the extensions you may have installed. The following figure shows an example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure this info makes it into merchdocs


![The list of available variables]({{ site.baseurl }}/common/images/email_insert_variable21.png){:width="70%"}{:height="70%"}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
group: frontend-developer-guide
title: Migrating custom email templates
functional_areas:
- Frontend
---

{% raw %}
With the release of Magento 2.3.4, we made some changes to custom email templates and how they access data and methods for email content.
This topic describes the changes and provides instructions on how to convert your existing custom email templates.

## Changes to the custom email template workflow

As of Magento 2.3.4, custom email templates are only allowed to use scalar values for variable data.
Direct calls to methods are no longer allowed.
To be more specific, methods can no longer be called from variables from either the `var` directive or when used as parameters.
For example `{{var order.getEmailCustomerNote()}}` or `{{something myVar=$obj.method()}}` will fail to resolve.

A 'custom email template' is any new template created in the Magento admin **Marketing** > Communications > **Email Templates** > **Add New Template** area.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A 'custom email template' is any new template created in the Magento admin **Marketing** > Communications > **Email Templates** > **Add New Template** area.
A 'custom email template' is any new template created in the Magento Admin **Marketing** > Communications > **Email Templates** > **Add New Template** area.

Notice in the incorrect example, the `getConfirmationLink()` method is called directly.

- Old way: `{{var subscriber.getConfirmationLink()}}`
- New way: `{{var subscriber_data.confirmation_link}}`

We refer to this as 'strict mode' for email templates.
All default templates have been converted to this strict mode.

{: .bs-callout-info}
All existing custom email templates will continue to work after upgrading to 2.3.4.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be worthy of calling out more, maybe in one of the pull quote style info boxes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

Any new email template created after installing 2.3.4 must be written in strict mode.

## Abstraction example

Pre-2.3.4, the New Order email template had a line with a direct method call:

```html
<p class="greeting">{{trans "%customer_name," customer_name=$order.getCustomerName()}}</p>
```

As of 2.3.4, with the method call removed:

```html
<p class="greeting">{{trans "%customer_name," customer_name=$order_data.customer_name}}</p>
```

Below, within the `$transport` block, `customer_name` is defined in the `order_data` object and the method call place there.
This `order_data` object is passed to the view page as a `DataObject` and is referenced in the variable as above.

```php
public function send(Invoice $invoice, $forceSyncMode = false)
{
$invoice->setSendEmail($this->identityContainer->isEnabled());
if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) {
$order = $invoice->getOrder();
$this->identityContainer->setStore($order->getStore());
$transport = [
'order' => $order,
'invoice' => $invoice,
'comment' => $invoice->getCustomerNoteNotify() ? $invoice->getCustomerNote() : '',
'billing' => $order->getBillingAddress(),
'payment_html' => $this->getPaymentHtml($order),
'store' => $order->getStore(),
'formattedShippingAddress' => $this->getFormattedShippingAddress($order),
'formattedBillingAddress' => $this->getFormattedBillingAddress($order),
'order_data' => [
'customer_name' => $order->getCustomerName(),
'is_not_virtual' => $order->getIsNotVirtual(),
'email_customer_note' => $order->getEmailCustomerNote(),
'frontend_status_label' => $order->getFrontendStatusLabel()
]
];
$transportObject = new DataObject($transport);
```

In this example, the `customer.name` is being computed within the [model][] file.
Depending on the particular instance of Magento, this data point can be appended within a custom module, directive or any manner of ways.

## Create a custom directive

The above examples show changes to default Magento files. We do not recommend editing core files as changes may be lost when upgrading.
Instead, if you need to call a method for a custom email template variable, create a custom directive.
In this example, we will create and pass a `lifetime_spend` custom value.

1. Create a class that implements `Magento\Framework\Filter\SimpleDirective\ProcessorInterface`:

```php
declare(strict_types=1);
namespace GadgetCorp\CustomEmailDirective\Model;
use Magento\Framework\Filter\SimpleDirective\ProcessorInterface;
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
* Calculates the lifetime spend of all customers
*/
class LifetimeSpendDirective implements ProcessorInterface
{
/**
* @var PriceCurrencyInterface
*/
private $priceCurrency;
/**
* @param PriceCurrencyInterface $priceCurrency
*/
public function __construct(PriceCurrencyInterface $priceCurrency)
{
$this->priceCurrency = $priceCurrency;
}
/**
* @inheritDoc
*/
public function getName(): string
{
return 'lifetime_spend';
}
/**
* @inheritDoc
*/
public function process($value, array $parameters, ?string $html): string
{
$shouldBold = !empty($parameters['should_bold']);
$amount = $this->priceCurrency->getCurrencySymbol() . $this->calculateLifetimeSpend();
return ($shouldBold ? '<strong>' . $amount . '</strong>' : $amount);
}
/**
* @inheritDoc
*/
public function getDefaultFilters(): ?array
{
// Make sure newlines are converted to <br /> tags by default
return ['nl2br'];
}
/**
* Calculate the total amount of money spent by all customers for all time
*
* @return float
*/
private function calculateLifetimeSpend(): float
{
// Add code here to calculate the lifetime spend
return 123.45;
}
}
```

and save the file to <Vendor>/<module>/Model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge this line with #84 so that you don't have a fragment after the long code sample.


1. Add the new directive to the pool by adding this block to `di.xml`.

```xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\Filter\SimpleDirective\ProcessorPool">
<arguments>
<argument name="processors" xsi:type="array">
<item name="lifetime_spend" xsi:type="object">GadgetCorp\CustomEmailDirective\Model\LifetimeSpendDirective</item>
</argument>
</arguments>
</type>
</config>
```

The new variable is now available within the email template as `{{lifetime_spend}}`.
Note in the class above, we also defined the parameter `shouldBold`. We can use that with `{{lifetime_spend should_bold=1}}`.
You may also use multiple filters within a var statement: `{{lifetime_spend should_bold=1 |escape|nl2br}}`.

## Data objects and getUrl

There are a couple of exceptions to strict mode.

One exception is for objects that extend from `DataObject`. These can still be called directly.
Even then, we do not actually call the getter method directly, but rather, resolve which key is needed and call `getData(<keyname>)` instead.

For example, if we have:

```php
$template->setVariables(['customer_data'=>new DataObject('my_key' => 'foo')]);
```

and in the template where we have

```php
{{somedir mydir mydir=$customer_data.getMyKey()}}
```

the directive will resolve to “foo”.

The same is true for `{{directive foo foo=$customer_data.my_key()}}`.
But note that in both cases the DataObject will not have `getMyKey` invoked but rather `getData(‘my_key’)` is invoked instead.

The second exception is for `getUrl`.
Directives that use the format `{{var this.getUrl(params)}}` will still continue to work for now.

## Advanced filtering

As part of this change, we have removed the limit of processing 2 filters per directive.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
As part of this change, we have removed the limit of processing 2 filters per directive.
As part of this change, we have removed the limit of processing two filters per directive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change to

As of Magento 2.3.4, you can now process more than two filters per directive. You can now specify directives like {{var order_data.shipping_description|filter1|filter2|filter3}}.

In other words, emphasize what the reader can do, not what we did.

Now `{{var order_data.shipping_description|filter1|filter2|filter3}}` will work.

## Nested arrays

Getting data from nested arrays is now supported.
For example, if we have:

```php
$template->setVariables(['customer_data'=> ['name' => ['first_name' => 'John']]]);
```
and in the template:

```php
{{mydir test fname=$customer_data.name.first_name}}
```
it will resolve to “John”.

This new syntax also works in combination with the `DataObject` exception.
For example, if we have:

```php
$template->setVariables(['customer_data'=> ['name' => new DataObject('first_name' => 'John')]]);
```
and in the template we have:

```php
{{mydir dir fname=$customer_data.name.first_name}}
```

it will resolve to “John”.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this whole section 👍


{% endraw %}

<!-- Link Definitions -->
[Insert Variable]: https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/templates/template-email.html#customize-content
[New Order email template]: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Sales/view/frontend/email/order_new.html
[model]: https://github.com/magento/magento2ce/blob/2.3-develop/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php
[1]: https://github.com/magento/magento2ce/blob/2.3-develop/app/code/Magento/Email/Model/AbstractTemplate.php

This file was deleted.

Loading