Шаблоны писем в Joomla. Вступление

С недавнего времени (с выходом Joomla 4) в CMS Joomla появился замечательный функционал - шаблоны писем. Эти шаблоны позволяют администратору настроить "под себя" все системные уведомления, которые отсылает CMS как пользователям, так и администраторам.

Шаблоны писем CMS Joomla
Шаблоны писем CMS Joomla

В предыдущих версиях Joomla, все это было реализовано через языковые переменные, что было крайне неудобно и максимально ограничивало администратора в "творческом полёте", именно администратора или контент менеджера, которые ни чего не понимают в разработке и им было практически невозможно поменять дизайн писем без привлечения разработчика. Слава Богу, это в прошлом!

На данный момент (Joomla 5) функционал шаблонов писем значительно расширен (и это, конечно же, не предел). Теперь администратор из админ. панели может отредактировать в удобном HTML редакторе (TinyMCE) любое системное письмо, которое отправляет Joomla. Может добавить любой текст, изображения, файлы - это круто!

Так же CMS позволяет вставлять необходимые переменные в тело письма (имя пользователя, почту, имя сайта и т.д.) так, как нужно в конкретном случае. Поддерживается редактирование для разных языков, то есть предусмотрена полная мультиязычность, как и в остальном функционале Joomla.

Редактор и переменные
Редактор и переменные

И все это предлагается "под капотом" Joomla и отлично работает!

Но, как это всегда бывает, стандартного функционала может не хватить для определенных задач. Именно об этом и пойдет речь в этой статье.

Не хватает переменных...

В каждом наборе переменных, любого шаблона письма, содержится необходимый минимум, который Joomla предлагает "из коробки".

Естественно, что для каждого конкретного случая этот набор должен быть скорректирован и доработан. СMS Joomla предоставляет нам такую возможность с помощью использования плагинов.

Ко мне обратился заказчик, с просьбой расширить список переменных в шаблонах, а, конкретно, добавить поля пользователей в шаблон письма об успешной активации пользователя. Далее я расскажу, как я это реализовал.

Спасибо, Бро!

Успешной реализации задачи очень помог Сергей Толкачев. В своем посте на Хабре он описал триггеры событий, которые позволяют добавить новые переменные к шаблонам писем.

И в целом, моя статья, это только расширенное описание поста Сергея. За что ему огромное спасибо!

Кстати, не забудем поблагодарить и заказчика расширения, который спонсировал все это производство, а так же дал согласие на публикацию решения в свободном доступе - это веб студия Креативные Бизнес Системы.

Триггеры для плагина

И так... Задача поставлена: добавить в шаблоны писем переменные, которые будут содержать значения полей пользователей.

Для решения нам нужно:

  1. Вывести переменные, в нашем случае это поля пользователей, в шаблоны писем в панели администратора Joomla. Для этого в Joomla предусмотрен триггер BeforeRenderingMailTemplateEvent.

  2. Передать новые переменные в письмо администратору. Для этого предусмотрен триггер onMailBeforeRendering.

Давайте рассмотрим каждое событие отдельно.

onMailBeforeTagsRendering

Это событие поможет нам добавить переменные (шорт-коды) в шаблон письма в панели администратора.

Шорт-коды шаблона письма
Шорт-коды шаблона письма

В моем случае нужно добавить к стандартным переменным поля пользователя. В качестве шорт-кодов будем использовать имя (не заголовок) полей.

Поля пользователя
Поля пользователя

Для получения всех полей пользователей используем

use \Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
$user_fields = FieldsHelper::getFields('com_users.user', true);

Вся функция будет выглядеть так

    public function onMailBeforeTagsRendering(Event $event): void
    {
        $template = $event->getArgument(1); //получаем весь объект 
        $tags =  $template->params['tags']; //получаем стандартные шорт-коды
        $user_fields = FieldsHelper::getFields('com_users.user', true); //получаем все поля пользователя
        $newtags = [];
        foreach($user_fields as $field) {
            $newtags[] = $field->name; //формируем массив с полями по имени поля
        }
        $tags = array_merge($tags, $newtags); //добавляем поля пользователя к стандартным шорт-кодам
        $template->params['tags'] = $tags;// добавляем все в объект
    }

Теперь в шаблонах писем, в переменных, у нас отображаются и шорт-коды полей пользователей.

Новые шорт-коды
Новые шорт-коды

Но этого еще не достаточно, нужно еще "заставить" Joomla понимать, какие данные отправлять, используя эти шорт-коды.

onMailBeforeRendering

Это событие позволит нам научить Joomla понимать новые шорт-коды и корректно отправлять данные по ним в письма.

Аргументом $event для этой функции является экземпляр класса BeforeRenderingMailTemplateEvent

подключаем

use Joomla\CMS\Event\Mail\BeforeRenderingMailTemplateEvent;

У меня стояла задача, передать поля пользователей в письме администратору о необходимости активации нового пользователя, поэтому следующий код будет немного не стандартный, но очень хорошо подойдет для примера

    public function onMailBeforeRendering(BeforeRenderingMailTemplateEvent $event): void
    {
        $templateId = $event->getTemplateId(); //id шаблона, как контекст
        $app = Factory::getApplication();

        if($templateId == 'com_users.registration.admin.verification_request') { //нужный мне шаблон письма
            $activiti = $app->input->get('token', '', 'string');//получение токена (ключа активации), который содержится в ссылке на активацию
            $db = Factory::getContainer()->get(DatabaseInterface::class); //подключаем базу
            $db->setQuery("SELECT `id` FROM `#__users` WHERE `activation` = '{$activiti}'");
            $user_id =  $db->loadResult(); //получаем id пользователя, что бы передать его поля
       } 
        $user_fields = FieldsHelper::getFields('com_users.user', ['id' => $user_id], true); //получаем все поля пользователя
        $template = $event->getTemplate();//получаем весь объект письма
        foreach($user_fields as $field) {
            $data[$field->name] = $field->value; //получаем поля пользователя в нужном нам виде по имени поля
        }
        $template->addTemplateData($data);// передаем в объект новый массив шорт-кодов
    }

Давайте подробно рассмотрим $data - массив, который передает данные о новых переменных (шорт-кодах).

$template = $event->getTemplate();//получаем весь объект письма
$data = [
  'field_name' => 'field_vale',
];
$template->addTemplateData($data);// передаем в объект новый массив шорт-кодов

Теперь в шаблоне письма используем {field_name}, чтобы в самом письме получить field_value.

Всю логику разобрали. Далее соберем все в системный плагин, который позволит реализовать нужный функционал.

Системный плагин ShortMail

Структура плагина

- language
-- en-GB
--- plg_system_shortmail.ini
--- plg_system_shortmail.sys.ini
- services
-- provider.php
- src
-- Extension
--- Shortmail.php
- shortmail.xml

Файл манифеста shortmail.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
    <name>plg_system_shortmail</name>
    <author>Alexandr Novikov</author>
    <creationDate>2025-01-20</creationDate>
    <copyright>(C) 2025 Alexandr Novikov. All rights reserved.</copyright>
    <license>GNU General Public License version 2 or later</license>
    <authorEmail>support@joomlab.ru</authorEmail>
    <authorUrl>https://joomlab.ru</authorUrl>
    <version>1.0.0</version>
    <description><![CDATA[Дополнительные поля пользователей в шаблоне письма]]></description>
    <namespace path="src">joomLab\Plugin\System\Shortmail</namespace>
    <files>
        <folder plugin="shortmail">services</folder>
        <folder>src</folder>
        <folder>language</folder>
    </files>
    <languages folder="language">
        <language tag="en-GB">en-GB/plg_system_shortmail.ini</language>
        <language tag="en-GB">en-GB/plg_system_shortmail.sys.ini</language>
    </languages>
    <config>
        <fields name="params">
            <fieldset name="basic">
                <!-- Add your plugin parameters here -->
            </fieldset>
        </fields>
    </config>
</extension>

provider.php

<?php
/**
 * @package     joomLab.Plugin
 * @subpackage  System.Shortmail
 *
 * @copyright   (C) 2025 Alexandr Novikov. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use joomLab\Plugin\System\Shortmail\Extension\Shortmail;

return new class () implements ServiceProviderInterface {
    public function register(Container $container): void
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $plugin = new Shortmail(
                    $container->get(DispatcherInterface::class),
                    (array) PluginHelper::getPlugin('system', 'shortmail')
                );
                $plugin->setApplication(Factory::getApplication());
                return $plugin;
            }
        );
    }
}

shortmail.php

<?php
/**
 * @package     joomLab.Plugin
 * @subpackage  System.Shortmail
 *
 * @copyright   (C) 2025 Alexandr Novikov. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace joomLab\Plugin\System\Shortmail\Extension;

defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\Event\Event;
use Joomla\CMS\Event\Mail\BeforeRenderingMailTemplateEvent;
use Joomla\Database\DatabaseInterface;
use \Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\CMS\Factory;

class Shortmail extends CMSPlugin implements SubscriberInterface
{
    protected $autoloadLanguage = true;

    public static function getSubscribedEvents(): array
    {
        return [
            'onMailBeforeRendering' => 'onMailBeforeRendering',
            'onMailBeforeTagsRendering' => 'onMailBeforeTagsRendering'
        ];
    }

    public function onMailBeforeRendering(BeforeRenderingMailTemplateEvent $event): void
    {
        $templateId = $event->getTemplateId();
        $app = Factory::getApplication();
        $activiti = '1';
        if($templateId == 'com_users.registration.admin.verification_request') {
            $activiti = $app->input->get('token', '', 'string');
            $db = Factory::getContainer()->get(DatabaseInterface::class);
            $db->setQuery("SELECT `id` FROM `#__users` WHERE `activation` = '{$activiti}'");
            $user_id =  $db->loadResult();
       }
        $user_fields = FieldsHelper::getFields('com_users.user', ['id' => $user_id], true);
        $template = $event->getTemplate();
        foreach($user_fields as $field) {
            $data[$field->name] = $field->value;
        }
        $template->addTemplateData($data);
    }
    public function onMailBeforeTagsRendering(Event $event): void
    {
        $template = $event->getArgument(1);
        $tags =  $template->params['tags'];
        $user_fields = FieldsHelper::getFields('com_users.user', true);
        $newtags = [];
        foreach($user_fields as $field) {
            $newtags[] = $field->name;
        }
        $tags = array_merge($tags, $newtags);
        $template->params['tags'] = $tags;
    }

}

Контекст

Чуть не забыл про контекст. Структура шаблонов писем отличается от общей структуры CMS, где, помимо триггеров, в основном используется еще и $context для вывода плагина в нужном месте компонента.

В шаблонах писем вместо контекста используется template_id

$templateId = $event->getTemplateId();
//com_users.registration.admin.verification_request
template_id
template_id

Используя template_id вы сможете добавить в разные шаблоны писем свои наборы полей.

Заключение

С помощью этого плагина вы сможете добавить в шаблоны писем поля пользователя. А на примере этого плагина вы сможете добавить в свои шаблоны писем абсолютно любые переменные (шорт-коды), при чем в разных шаблонах они могут быть разные!

Удачи!

Комментарии (6)


  1. codersite
    23.01.2025 09:49

    Очень нужная статья, благодарю


  1. b2z
    23.01.2025 09:49

    Спасибо за полезный материал!

    Маленькая придирка по коду. Если ты в провайдере установил application, то в плагине его надо получать не через Factory, а через $this->getApplication().


    1. novikov__al Автор
      23.01.2025 09:49

      Благодарю! Исправлюсь)