Customise messages

For arlanix/notification 1.0.0

Customise messages

This page is for the PHP developer whose store wants more in its notifications than the modules put there, such as an order's city or a price written as money. A rule's message is written in the admin as a template of the event's fields, {order.increment_id}, and is built when the event happens, from the event's data. What your module can change is the data, the fields a rule may name, and how a value is written.

To Do
show a value another way in one rule a modifier in the rule's text: `{order.grand_total
show a value another way in every rule an event data provider that changes it, or a modifier of your own
change an event's default text, a field's label or sample your own etc/arlanix_notification_events.xml
add a field to another module's event the field in your XML, its value from an event data provider
add a variable to every event a {message.*} variable

A modifier

A field takes modifiers after a bar, as the manual's list has them, and they run left to right: {order.grand_total|currency} writes $129.00 in the store's base currency, {order.grand_total|currency:EUR} in euros, {customer.name|upper|truncate:20} the name in capitals, cut to twenty characters. An event's *_formatted fields, such as order.grand_total_formatted, are already written in the order's own currency.

The texts and labels of an event

etc/arlanix_notification_events.xml files of every module are merged by the event's code and the field's path, so yours can correct one attribute of another module's event and leave the rest. Add your module to the other's in etc/module.xml <sequence>, so yours is read after it:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Arlanix_Notification:etc/arlanix_notification_events.xsd">
    <event code="sales_order_placed">
        <field path="order.grand_total_formatted" label="Order total"/>
        <subject>Order #{order.increment_id} for {order.grand_total_formatted}</subject>
    </event>
</config>

The subject and body are what a rule sends when its own are empty; a rule with its own text keeps it. Declare events lists every attribute.

A field of your own on an event of another module

Declare the field on the event in your XML, then give it its value with an event data provider. The field is then offered like the event's own: under Variables, in the conditions, and with the message when it has attribute="true".

<event code="sales_order_placed">
    <field path="order.city" label="City" attribute="true" sample="London"/>
</event>
<event code="sales_order_status_changed">
    <field path="order.city" label="City" attribute="true" sample="London"/>
</event>
<?php
declare(strict_types=1);

namespace Vendor\Module\Model\Notification;

use Arlanix\Notification\Model\Event\EventDataProviderInterface;
use Magento\Sales\Api\OrderRepositoryInterface;

class OrderCity implements EventDataProviderInterface
{
    public function __construct(private readonly OrderRepositoryInterface $orders)
    {
    }

    public function provide(string $event, array $data): array
    {
        $orderId = (int)($data['order']['id'] ?? 0);
        if ($orderId === 0 || !str_starts_with($event, 'sales_')) {
            return $data;
        }
        $address = $this->orders->get($orderId)->getShippingAddress();
        $data['order']['city'] = (string)$address?->getCity();

        return $data;
    }
}

Register it in your etc/di.xml:

<type name="Arlanix\Notification\Model\Event\EventDataProviders">
    <arguments>
        <argument name="providers" xsi:type="array">
            <item name="vendor_order_city" xsi:type="array">
                <item name="class" xsi:type="object">Vendor\Module\Model\Notification\OrderCity</item>
                <item name="sortOrder" xsi:type="number">10</item>
            </item>
        </argument>
    </arguments>
</type>
  • provide() gets the event's code and its data, nested as the paths name it, and returns the data the rules read. It may change a value as well as add one: setting $data['order']['grand_total_formatted'] changes it in every rule of the event.
  • It runs only when an active rule answers the event, once for all of its rules, after the transaction that emitted the event has committed, before any condition is checked.
  • Providers run by sortOrder, each on what the one before returned. One that throws is logged to var/log/arlanix.log and leaves the data as it was; the message is still sent.
  • Preview and Publish test message use the field's sample, not the provider, so give it one that looks like a real value.
  • The manual's events list the fields of each event. Every event about an order, sales_order_placed to sales_shipment_created, also carries the order's id in order.id, which is not offered to rules.

A message variable for every event

A {message.*} variable is offered to every rule whatever its event, as {message.topic} and {message.time} are. Implement Arlanix\Notification\Model\Rule\MessageVariableInterface:

<?php
declare(strict_types=1);

namespace Vendor\Module\Model\Notification;

use Arlanix\Notification\Model\Rule\MessageVariableInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;

class Weekday implements MessageVariableInterface
{
    public function __construct(private readonly TimezoneInterface $timezone)
    {
    }

    public function getLabel(): string
    {
        return (string)__('Day of the week');
    }

    public function getValue(string $event, array $data): string
    {
        return $this->timezone->date()->format('l');
    }
}
<type name="Arlanix\Notification\Model\Rule\MessageVariables">
    <arguments>
        <argument name="variables" xsi:type="array">
            <item name="weekday" xsi:type="object">Vendor\Module\Model\Notification\Weekday</item>
        </argument>
    </arguments>
</type>
  • The item's name is the variable's: weekday is {message.weekday}. Use lower-case letters, digits and _. The message's own names, topic, event, source, severity, time, rule and store_url, are refused, so a rule keeps its meaning.
  • getValue() is read each time a rule publishes, and for Preview, with the samples the event's fields declare as $data. One that throws leaves the variable empty and is logged.
  • Variables lists it under every event with its label, and a rule's save accepts it.

Write a modifier

A modifier is one step after a bar, {name|words:12}. The modifiers come from Arlanix_Core and are shared with Arlanix Product Feed, so a modifier you register is offered there too. The following one keeps the first words of a text:

<?php
declare(strict_types=1);

namespace Vendor\Module\Model\Modifier;

use Arlanix\Core\Model\Template\Modifier\Description;
use Arlanix\Core\Model\Template\Modifier\StringModifier;

class Words extends StringModifier
{
    protected function transform(string $value, array $arguments): string
    {
        $limit = max(1, (int)($arguments[0] ?? 20));
        $words = preg_split('/\s+/u', trim($value), -1, PREG_SPLIT_NO_EMPTY) ?: [];

        return implode(' ', array_slice($words, 0, $limit));
    }

    public function describe(): Description
    {
        return new Description('words:20', __('Keep the first words'), 'words:12');
    }
}
<type name="Arlanix\Core\Model\Template\ModifierPool">
    <arguments>
        <argument name="modifiers" xsi:type="array">
            <item name="words" xsi:type="object">Vendor\Module\Model\Modifier\Words</item>
        </argument>
    </arguments>
</type>
  • Arguments arrive as strings in the order written, words:12 giving ['12'], and a missing one is absent: give each a default, as currency takes the store's base currency when none is named.
  • Return a value the modifier has no meaning for unchanged. An exception from a modifier stops the message.
  • An item under the name of one of the core modifiers, such as currency, replaces it everywhere.
  • A rule naming a modifier no module registers is refused when it is saved.

Check it

Run bin/magento setup:upgrade and bin/magento cache:clean config. Open Arlanix > Notifications > Rules, choose the event: Variables lists your field and your variable. Write them into the subject or body and press Preview, then send a real event and look at the message in the Message Log.