В процессе работы с Joomla бывает необходимо работать с пользовательским интерфейсом более тонко, чем обычно. Все формы Joomla состоят из стандартных полей, содержанием, стилем отображения, состоянием (включено/выключено, доступно для редактирования или нет и т.д.) можно управлять с помощью плагинов. Да и для нестандартных проектов хорошей практикой является создание одного системного или нескольких плагинов групп "под проект", в которых хранится весь "нестандарт".
В этой статье описаны все триггеры (события), которые вызываются через Event Dispatcher из administrator/components/com_fields/src/Helper/FieldsHelper.php, с привязкой к жизненному циклу (порядку этапов работы запроса), аргументам, изменяемым данным и дальнейшему распространению по Joomla. Это поможет вам работать с Joomla свободнее и не опасаясь при этом потерять изменения при очередном обновлении движка.
Подходы, описанные в статье, полезны в тех случаях, когда вы работаете с данными в com_fields - механизме создания и редактирования пользовательских полей ядра Joomla и при использовании FieldsHelper. Многие сторонние компоненты не используют эту возможность, поэтому данная статья будет полезна лишь частично.
Скрытый текст
Впервые решил попробовать напрячь ИИ и заставить его пособирать инфу по ядру Joomla и, надо сказать, впервые из этого вышло что-то более-менее удобоваримое, пригодное для дальнейшей обработки. Хотя стиль получился не совсем мой и читатель, думаю, легко это заметит - статья получилась шибко "структурированная", - но информация в ней полезная, поэтому оставим её в таком виде. После детальной проверки собранной информации можно сказать, что ИИ сэкономил времени час-полтора. Наверное, это неплохо.
Базовые понятия: context, item, subject
Перед разбором событий важно различать три важных понятия:
context(контекст)-
- Это строка формата
компонент.секция, напримерcom_content.article. - По этой строке Joomla понимает, для какого типа сущности загружать и рендерить кастомные поля.
- Итого:
contextотвечает на вопрос «для чего именно сейчас работаем с полями?».
- Это строка формата
item(текущий объект данных)-
- Это объект конкретной записи, с которой вы работаете (статья, категория, контакт и т.д.).
- Обычно в
itemесть как минимумid, частоcatid,languageи другие поля компонента. - Итого:
itemотвечает на вопрос «для какого конкретного объекта сейчас грузим/рендерим значения?».
subject(основной объект события)-
- В событиях
onCustomFieldsBeforePrepareField,onCustomFieldsPrepareFieldиonCustomFieldsAfterPrepareFieldэто объект поля (одно кастомное поле). - В
PrepareDomEventэто тоже объект поля, для которого строится XML-нода формы. - Итого:
subjectотвечает на вопрос «что именно мы сейчас меняем внутри обработчика события?».
- В событиях
Примеры context для разных компонентов Joomla
Контекст компонента материалов Joomla (com_content)
com_content.article— поля статьи.com_content.categories— поля категории материалов.
Контекст компонента категорий Joomla (com_categories)
При редактировании категории через com_categories плагин fields приводит контекст к виду <extension>.categories (например, com_content.categories), чтобы выбрать правильные поля для целевого компонента. См.: plugins/system/fields/src/Extension/Fields.php:289 (onContentPrepareForm())
Контекст компонента Контакты (com_contact)
com_contact.contact— поля контакта.com_contact.mail— поля, которые могут использоваться в контексте отправки письма контакту.
Пример для новичков
Если событие пришло с context = com_content.article, item->id = 35 и subject->name = author_badge, то это значит:
- Работаем с полями статьи (
com_content.article). - Конкретная статья имеет ID
35. - Сейчас в обработчике мы работаем с одним поле, у которого системное имя
author_badge.
Где это в жизненном цикле полей Joomla?
Следует различать 2 процесса, у которых есть общие этапы (например, определение context, загрузка полей и обработка через плагины), но разные цели: цикл редактирования формы (нужно собрать и показать поля в админке) и цикл отображения значений (нужно подготовить и вывести значения полей в контенте снаружи сайта).
Цикл 1: Редактирование формы
- Модель компонента запускает
preprocessForm(...). - Базовый вызов события
onContentPrepareFormпроисходит вFormBehaviorTrait::preprocessForm(), когда модель вызываетparent::preprocessForm($form, $data, $group), файлlibraries/src/MVC/Model/FormBehaviorTrait.php:184. - Примеры моделей:
administrator/components/com_content/src/Model/ArticleModel.php:1007(модель материала в админке Joomla)administrator/components/com_categories/src/Model/CategoryModel.php:394(модель категории в стандартном компоненте категорий)components/com_contact/src/Model/FormModel.php:210(модель формы обратной связи компонента контактов снаружи сайта)- В отдельных моделях возможен ручной вызов события через
dispatch(...)и объект события. -
components/com_contact/src/Model/ContactModel.php:384(модель одного контакта во фронтенде, методgetForm())
-
Системный плагин
fields(plugins/system/fields/src/Extension/Fields.php) отрабатывает событиеonContentPrepareFormи вызываетFieldsHelper::prepareForm(...). До вызоваFieldsHelperплагин нормализуетcontext(включаяcom_categories.category...-><extension>.categories) и приводит$dataк объекту. FieldsHelper::prepareForm(...)(\Joomla\Component\Fields\Administrator\Helper\FieldsHelper) строит поля формы.- Далее
FieldsHelperзагружает список полей, строит XML группыcom_fields, вызывает событиеonCustomFieldsPrepareDom, загружает XML вForm(бывшийJForm) и проставляет значения. -
Если метод
FieldsHelper::extract(...)ничего не вернул или в контексте нет полей, форма не модифицируется. -
После сохранения/удаления системный плагин
fieldsсохраняет или очищает значения полей. - Сохранение: вычисляет итоговое значение по каждому полю и пишет в
#__fields_valuesчерезFieldModel::setFieldValue(...). - Удаление: очищает значения через
cleanupValues(...). - Для пользователей com_users (события
onUserAfterSave/onUserAfterDelete) используется та же логика через проксирование данных в content-события. Например, внутри событияonUserAfterSaveвызывается событиеonContentAfterSaveс контекстом пользователя.
Цикл 2: Отображение значений
- Системный плагин
fieldsподключается к событиям отображения контента. Основные точки:onContentPrepare,onContentAfterTitle,onContentBeforeDisplay,onContentAfterDisplay. - На этих шагах вызывается
FieldsHelper::getFields(...). - В
onContentPrepareполя подготавливаются сprepareValue=trueи складываются в$item->jcfields(для ручного использования в шаблонах/оверрайдах). - В display-ветке (события
onContentAfterTitle,onContentBeforeDisplay,onContentAfterDisplay) значения фильтруются по display-позиции и рендерятся через layoutfields.render. И тогда в вашем материале или контакте вы получаете отрендеренные поля в позициях "до вывода контента", "после вывода контента", "после заголовка". - Условия и ветвления отображения.
- Если
contextне поддерживается (FieldsHelper::extract(...)), обработка пропускается. - Для
com_tags.tagиспользуется ветка с перекладкой контекста наtype_alias.
- Если
Блок-схемы процессов
Ниже 2 блок-схемы для двух процессов из статьи.
Отображение поля для редактирования (форма создания/редактирования в Joomla)
Смотреть блок-схему

Отображение значения поля на пользовательской части сайта Joomla
Смотреть блок-схему

События из FieldsHelper
1) onCustomFieldsBeforePrepareField (перед рендером пользовательского поля)
Событие вызывается перед основным рендером каждого конкретного поля в методе FieldsHelper::getFields(). Условие вызова: только в ветке подготовки значения, когда в getFields() есть $item->id и установлен флаг $prepareValue==true.
Аргументом события onCustomFieldsBeforePrepareField является экземпляр класса объекта события $event - BeforePrepareFieldEvent, наследующийся от AbstractPrepareFieldEvent: - libraries/src/Event/CustomFields/BeforePrepareFieldEvent.php:21 (класс BeforePrepareFieldEvent) - libraries/src/Event/CustomFields/AbstractPrepareFieldEvent.php:31 (класс AbstractPrepareFieldEvent)
Из $event можно получить:
$event->getContext(): string- контекст$event->getItem(): object- материал, контакт, категорию и т.д. Уточняем ЧТО это за зверь по контексту.$event->getField(): object- этоsubject, само поле.
Что можно менять:
- Свойства поля (
$field->value,$field->rawvalue, доп. свойства вроде$field->apivalue). - Объект
itemиcontextменять напрямую как аргументы нельзя (immutable event, то есть событие с «непереназначаемыми» аргументами), но можно менять сам объектitem, если нужно.
Куда изменения идут дальше: тот же объект поля передается в onCustomFieldsPrepareField, затем участвует в финальном значении field->value, дальше попадает в jcfields или layout поля.
2) onCustomFieldsPrepareField (момент рендера поля)
Момент вызова: сразу после Before... в FieldsHelper::getFields(). Условие вызова: только в той же prepare-ветке ($item->id + активный $prepareValue/совпадение display-режима поля). На этом событии плагины пользовательских полей подключают лейауты плагина пользовательского поля из папки tmpl.
Event class (класс объекта события): - libraries/src/Event/CustomFields/PrepareFieldEvent.php:25 (class PrepareFieldEvent)
Event-методы:
$event->getContext(): string- получаем контекст выполнения$event->getItem(): object- получаем материал, категорию и иже с ними$event->getField(): object- получаем само поле$event->addResult(mixed $result): static- сохраняем результат работы (черезResultAware, то есть через встроенный механизм накопления результата)$event->getArgument('result', [])- метод для получения аргументов класса события. Обычно используется после вызова события в helper/dispatcher-коде.
Что можно менять:
- Добавлять результат рендера через
$event->addResult(...)в адаптере базового плагина\Joomla\Component\Fields\Administrator\Plugin\FieldsPlugin, файлadministrator/components/com_fields/src/Plugin/FieldsPlugin.php, от которого наследуются все плагины пользовательских полей. - Модифицировать
subject(поле) по месту.
Куда изменения идут дальше: FieldsHelper берет result, фильтрует пустые значения, склеивает массив в строку и передает в onCustomFieldsAfterPrepareField.
3) onCustomFieldsAfterPrepareField (после получения рендера поля)
Момент вызова: после того как получен рендер-результат поля. Условие вызова: только в той же prepare-ветке getFields(); если prepare не выполняется, событие не вызывается.
Event class (класс объекта события): класс AfterPrepareFieldEvent (файл libraries/src/Event/CustomFields/AfterPrepareFieldEvent.php).
Event API (методы):
$event->getContext(): string- контекст видаcom_content.articleи т.д.$event->getItem(): object- сущность, для которой сделали поле.$event->getField(): object- само поле$event->getValue(): mixed- а вот тут уже можно получить финальное начение поля на текущий момент.$event->updateValue(mixed $value): static
На этом этапе можно менять финальный вывод через $event->updateValue(...).
Дальше результат изменений попадает в $field->value (смотрим снова FieldsHelper::getFields()).
Примечание по совместимости: передача value по ссылке сохранена для обратной совместимости со старым кодом, но помечена как устаревшая (deprecated).
4) onCustomFieldsPrepareDom
На этом этапе можно изменять поля XML-формы. Например, в зависимости от условий устанавливать полям атрибуты readonly и disabled, добавлять / удалять css-классы, описания, согласно синтаксису XML-манифестов Joomla, но программным способом.
public function onCustomFieldsPrepareDom($field, \DOMElement $parent, Form $form)
{
$fieldNode = parent::onCustomFieldsPrepareDom($field, $parent, $form);
if (!$fieldNode) {
return $fieldNode;
}
$fieldNode->setAttribute('disabled', 'true');
$fieldNode->setAttribute('readonly', 'true');
$fieldNode->setAttribute('class', 'text-danger fw-bold');
// Возвращаем изменённое поле
return $fieldNode;
}
Момент вызова №1: при сборке XML формы в prepareForm().
Момент вызова №2: в FieldModel::checkDefaultValue() при проверке default value через правило валидации.
Event class для события - Joomla\CMS\Event\CustomFields\PrepareDomEvent:
Event API (методы):
$event->getField(): object- (поле, основной объект события)$event->getFieldset(): \DOMElement- филдсет поля$event->getForm(): \Joomla\CMS\Form\Form- форма целиком (Joomla\CMS\Form\Form)
Что можно менять:
- DOM-структуру поля (атрибуты, child-узлы,
<option>,validateи т.д.). - Объект
form(например,setFieldAttribute,setValue).
Куда изменения идут дальше:
- XML загружается в
Form. -administrator/components/com_fields/src/Helper/FieldsHelper.php:486(prepareForm()) - Затем значения поля проставляются в form group
com_fields. -administrator/components/com_fields/src/Helper/FieldsHelper.php:511(prepareForm())
5) onCustomFieldsGetTypes
Момент вызова: при сборке списка типов полей (getFieldTypes()). Этот список мы видим при создании нового поля в панели администратора. Один плагин может реализовывать несколько типов полей. Для этого плагин должен иметь несколько лейаутов в папке tmpl и соответствующих им xml-файлов параметров в папке params. Например, tmpl/fieldtype1.php и params/fieldtype1.xml. Событие берёт типы полей именно по наличию php-файлов лейаутов.
Event class - \Joomla\CMS\Event\CustomFields\GetTypesEvent (файл libraries/src/Event/CustomFields/GetTypesEvent.php)
Event API (методы):
$event->addResult(array $typeDefinitionList): static(черезResultAware, основной способ для обработчика)$event->getArgument('result', [])(обычно используется после вызова события в helper/dispatcher-коде)
Аргументы:
- Событие без обязательного
subjectpayload (payload = набор данных, переданных в событие). - Канал
result(массив описаний типов).
Что можно менять:
- Добавлять описания типов через
$event->addResult(...): -type-label-path(где form fields) -rules(где form rules) - Базовый (родительский класс, от которого наследуются плагины полей)
FieldsPluginделает это автоматически.
Куда изменения идут дальше: FieldsHelper нормализует path и rules, затем использует их в prepareForm() для FormHelper::addFieldPath/addRulePath.
Ограничения мутабельности (возможности менять данные) и immutable events
CustomFields events наследуются от immutable-базы: libraries/src/Event/AbstractImmutableEvent.php:22. Это означает, что нельзя переназначать аргументы события (например, $event['context'] = ...). Но, можно сделать следующее:
- Можно менять состояние объектов, переданных в аргументах (
subject,form,fieldset) — то есть менять их свойства/атрибуты. - Можно работать через
ResultAware- есть методaddResult(), который позволяет добавлять результат обработчика в общий итог. - Для
AfterPrepareFieldEventможно менять значение черезupdateValue(обновить итоговый вывод поля).
Как данные прокидываются дальше по Приложению Joomla
Поток getFields(...)
- Загружает значения из
#__fields_values(rawvalue/value), учитываетvaluesToOverrideиdefault_value.administrator/components/com_fields/src/Helper/FieldsHelper.php:184(getFields())administrator/components/com_fields/src/Helper/FieldsHelper.php:195(getFields())administrator/components/com_fields/src/Helper/FieldsHelper.php:204(getFields())administrator/components/com_fields/src/Helper/FieldsHelper.php:207(getFields())
- Прогоняет цепочку
Before -> Prepare -> After(если активна prepare-ветка, то есть этап подготовки отображаемого значения). - Возвращает массив полей; далее он используется:
- для
$item->jcfieldsвonContentPrepare - для layout рендера HTML-вёрстки поля в
onContentAfterTitle/onContentBeforeDisplay/onContentAfterDisplay
- для
Поток prepareForm(...)
- Строит XML
<fields name="com_fields">. - На каждый field вызывает
onCustomFieldsPrepareDom, где можно работать с XML-формой через\DOMElement(fieldset) и объект JoomlaForm. - Загружает XML в форму и выставляет значения.
Практические примеры из com_fields и core plugins
- Пример
BeforePrepareField: нормализация значения перед рендером (media,list,radio,subform).plugins/fields/media/src/Extension/Media.php(beforePrepareField())plugins/fields/list/src/Extension/ListPlugin.php(beforePrepareField())plugins/fields/radio/src/Extension/Radio.php(beforePrepareField())plugins/fields/subform/src/Extension/Subform.php(beforePrepareField())
- Пример
PrepareField: базовый HTML-рендер через layout.administrator/components/com_fields/src/Plugin/FieldsPlugin.php:211(onCustomFieldsPrepareField())
- Пример
AfterPrepareField: пост-обработка HTML (email cloak).plugins/content/emailcloak/src/Extension/EmailCloak.php:108(onCustomFieldsAfterPrepareField())
- Пример
PrepareDom: сборка XML ноды поля, валидации и options.administrator/components/com_fields/src/Plugin/FieldsPlugin.php:245(onCustomFieldsPrepareDom())administrator/components/com_fields/src/Plugin/FieldsListPlugin.php:38(onCustomFieldsPrepareDom())plugins/fields/subform/src/Extension/Subform.php:265(onCustomFieldsPrepareDom())
Рецепты
Как прокинуть своё кастомное значение в layout поля через плагин
Допустим, что нам нужно передать собственное дополнительное значение из плагина в layout этого поля (файл plugins/fields/<your_plugin>/tmpl/<type>.php). Мы можем на событии onCustomFieldsBeforePrepareField добавить своё свойство в объект поля ($field). А в onCustomFieldsPrepareField (обычно базовый FieldsPlugin) этот же $field уже доступен в layout, поэтому наше уникальное свойство можно читать напрямую.
Пример плагина:
<?php
use Joomla\CMS\Event\CustomFields\BeforePrepareFieldEvent;
use Joomla\Component\Fields\Administrator\Plugin\FieldsPlugin;
use Joomla\Event\SubscriberInterface;
final class MyCustomSystemPlugin extends FieldsPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onCustomFieldsBeforePrepareField' => 'beforePrepareField',
];
}
public function beforePrepareField(BeforePrepareFieldEvent $event): void
{
$field = $event->getField();
/**
* Этот метод срабатывает абсолютно для КАЖДОГО поля. Поэтому срабатываем только в нужном нам типе.
* В данном случае это поле Яндекс.Карты wtyandexmap
*/
if ($field->type !== 'wtyandexmap') {
return;
}
/**
* Тут любая логика. А мы к примеру, добавим,
* свою картинку в класс поля,
* чтобы вытащить её напрямую в макете поля.
* Наименования кастомных свойств лучше брендировать своим префиксом
* или префиксом проекта, дабы не было случайных пересечений
* с другими расширениями.
*/
$field->wt_custom_layout_data = [
'icon' => 'images/path/to/image.webp'
];
}
}
Пример layout-файла поля (plugins/fields/mycustomsystem/tmpl/mycustomsystem.php):
<?php
/** @var \stdClass $field */
echo '<div class="cf-mytype">';
echo '<span class="cf-value">' . htmlspecialchars((string) $field->value, ENT_QUOTES, 'UTF-8') . '</span>';
// Теперь тут доступно наше кастомное свойство.
if (!empty($field->wt_custom_layout_data['icon'])) {
echo HTMLHelper::image($field->wt_custom_layout_data['icon'], 'icon-alt', ['class' => 'img-fluid', 'title'=>'icon title']);
}
echo '</div>';
Где это в ядре Joomla подтверждается:
FieldsHelperпередаёт поле какsubjectвBeforePrepareFieldEvent. -administrator/components/com_fields/src/Helper/FieldsHelper.php(методgetFields())- Затем то же поле передаётся в
PrepareFieldEvent. -administrator/components/com_fields/src/Helper/FieldsHelper.php(тот же методgetFields()) - Базовый
FieldsPluginвнутриonCustomFieldsPrepareField()включает макет вывода, где переменная$fieldдоступна напрямую. -administrator/components/com_fields/src/Plugin/FieldsPlugin.php:211(методonCustomFieldsPrepareField())
Примеры кода
Изменение или замена HTML-вывода значения (value) в момент рендера поля
Пример реализации onCustomFieldsPrepareField для изменения итогового HTML значения поля (то, что увидит пользователь на странице). В результате мы контролируем конечную разметку, которую FieldsHelper положит в $field->value.
<?php
public function onCustomFieldsPrepareField($context, $item, $field)
{
if ($field->type !== 'mytype') {
return '';
}
// Тут мы полностью заменяем HTML-вёрстку из $field->getInput() класса поля на своё
// или делаем return parent::onCustomFieldsPrepareField($context, $item, $field);
return '<span class="text-danger">По каким-то причинам мы не хотим показывать value этого поля. Увы... а могли бы сделать <code>return htmlspecialchars((string) $field->value, ENT_QUOTES, "UTF-8");</code>.</span>';
}
Но с этим событием нужно быть аккуратнее, так как здесь результат будет зависеть от порядка срабатывания плагинов и от их приоритета. Лучше архитектурно планировать изменения полей в другом месте жизненного цикла, если это необходимо.
Изменение или замена HTML-вывода значения (value) в ПОСЛЕ рендера поля
Комментарий к примеру: пример пост-обработки уже готового значения поля на onCustomFieldsAfterPrepareField. Результат: можно обернуть, заменить или дополнительно фильтровать HTML перед выводом.
<?php
use Joomla\CMS\Event\CustomFields\AfterPrepareFieldEvent;
public function onCustomFieldsAfterPrepareField(AfterPrepareFieldEvent $event): void
{
if (empty($event->getValue())) {
return;
}
$event->updateValue('<div class="wrapped-field">' . $event->getValue() . '</div>');
}
Работа с XML-формой поля Joomla. Добавление / изменение атрибутов поля и т.д.
Комментарий к примеру: пример построения DOM-ноды формы (XML-элемента <field>) на onCustomFieldsPrepareDom. Результат: поле появляется в Form с нужным атрибутом validate=color и участвует в стандартной обработке формы.
<?php
// Пример из плагина пользовательского поля Color
// файл plugins/fields/color/src/Extension/Color.php
use Joomla\CMS\Form\Form;
public function onCustomFieldsPrepareDom($field, \DOMElement $parent, Form $form)
{
$fieldNode = parent::onCustomFieldsPrepareDom($field, $parent, $form);
if (!$fieldNode) {
return $fieldNode;
}
$fieldNode->setAttribute('validate', 'color');
return $fieldNode;
}
Этот механизм может быть полезен тогда, когда вы работаете со стандартными полями Joomla в своём расширении и вам необходимо модифицировать состояние полей в зависимости от данных. Например, не показывать поле и исключить его из обработки в зависимости от наличия данных в других полях: не указал пользователь API-ключ для интеграции со сторонним сервисом - не показываем ему остальную форму вообще (можно вернуть null и не добавлять узел поля в поля в DOM-дерево, но тут нужно учитывать логику обработки данных в моделях - как они к этому отнесутся). Или не даём редактировать эти поля с помощью readonly и disabled. Но нужно учитывать, что это событие ограничивает применимость логики к сущности одного конкретного поля и описывает его поведение. Если нужно работать большими блоками в масштабах всей формы, то разумнее использовать событие onContentPrepareForm и манипулировать формой на том этапе. Также нужно не забывать о том, что если поле не пришло в data['com_fields'], при сохранении в поле может остаться прежнее rawvalue.