---
title: "Логирование действий пользователя в Joomla. Создание плагина Actionlog - WebTolk"
description: "Как написать собственный плагин логирования действий пользователя Joomla? Примеры кода плагина группы actionlog для Joomla. Скриншоты, возможности компонента Лог действий пользователя."
url: "https://web-tolk.ru/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog"
date: "2025-04-30T05:34:10+00:00"
language: "ru-RU"
---

# Логирование действий пользователя в Joomla. Создание плагина Actionlog

 Автор: Сергей Толкачев Создано: 30 апреля 2025 Обновлено: 01 сентября 2025 Просмотров: 1590    ![](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/header.webp)

На вашем сайте, внутреннем портале, в интернет-магазине кто-то что-то сделал, а потом всё сломалось: сайт открывается криво (или не открывается вообще?), перестали работать фоновые задачи в планировщике, установились неправильные скидки всем пользователям на все товары... Предположим, что с безопасностью у вас всё хорошо: Joomla свежая, актуальная, расширения - тоже. Да и в логах сервера и логах Joomla тоже чисто... Тогда остаётся ещё один источник информации - Лог действий пользователей Joomla. Основная его задача - помочь быстро найти кому настучать по рогам причину и устранить её.

Статья изначально [опубликована на Хабре](https://habr.com/ru/articles/900338/). Копирую к себе.

**Всё, что связано с действиями в админке - может логироваться: от обновления системы и компонентов, изменения настроек до редактирования и сохранения конкретных элементов (статьи, товары, категории и т.д.) и загрузки файлов через медиа менеджер**. Лично мне нередко этот функционал помогал найти тех контент-менеджеров, кто дублирует названия товаров, создавая дубли и тем самым негативно влияя на SEO. Или же вдруг обнаруживается недозаполненная, но опубликованная карточка товара, которой не должно было быть, но она появилась. И ладно если б это был единичный случай.

**Оговорка 1:**статья рассчитана не только на тех, кто хорошо знает Joomla, но и на любопытствующих коллег из других технологических стеков. Поэтому некоторым очевидным терминам всё равно дано краткое пояснение.

**Оговорка 2:** на данный момент я рассматриваю Joomla скорее как PHP-фреймворк с натянутой на него готовой админкой, а не просто CMS. Расширение типа "компонент" в Joomla обычно представляет из себя набор различных CRUD-ов (в терминологии PHP-фреймворков), записи о действиях в которых можно хранить в логах. Поэтому статью следует больше рассматривать как начальную точку для проектирования и написания своего плагина (расширение, срабатывающие на триггеры событий Event Dispatcher) для своего же (скорее всего) компонента (CRUD-а).

## Оглавление

- Список литературы
- [Описание возможностей. Скриншоты](#overview). - [Параметры компонента Лог действий пользователя в Joomla.](#com_actionlogs_params) - [Версионность контента. Просмотр изменений](#overview-versioning).
- [Архитектура базы данных](#database-structure)
- [Анализ плагина action logs ядра Joomla](#analize-joomla-actionlog-plugin)
- [Решение без создания плагина](#without-plugin)
- [Создание собственного плагина](#custom-joomla-plugin-building) - [Триггеры событий для плагинов (Event Dispatching)](#joomla-plugins-triggers-event-dispatching) - [Дополнительные детали](#additional)

## Список литературы

- [User Action Logs - документация для Joomla 3](https://docs.joomla.org/J3.x:User_Action_Logs). Практически все положения из неё остались верны и для последующих версий.
- [Joomla User Manual](https://jdocmanual.org/en/jdocmanual?article=user/users/user-actions-log) - чуть обновлённый вариант документации для Joomla 5. Так же есть ИИ-перевод на русский язык.
- На [новом портале документации Joomla manual.joomla.org](https://manual.joomla.org/docs/building-extensions/) на момент написания статьи ещё нет примера плагина Action log, но ссылку оставлю, так как вся актуальная информация по ядру собирается именно там.
- [Триггеры ядра Joomla при CRUD-операциях](https://habr.com/ru/articles/874320/) - сопутствующая статья.
- [Создание плагинов с учётом новой структуры Joomla 4](https://habr.com/ru/articles/736412/) - сопутствующая статья.
- [Joomla Extensions Development](https://www.dionysopoulos.me/book.html) - объёмная книга греческого разработчика Николаса Дионисопулоса. Посвящена Joomla разработке в целом, освещает вопросы архитектуры, причины применения некоторых подходов и т.д.

## Описание возможностей компонента Лога пользователей Joomla

Этот компонент был добавлен в ядро Joomla начиная с версии 3.9.0. Найти его можно в левом меню панели администратора: **Пользователи - Лог действий пользователей**.

![](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/1.webp)

Он предоставляет собой простой интерфейс для чтения и фильтрации записей.

 ![](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/2.webp)Для всех записей указано расширение, к которому запись о событии относится.

На скриншоте я выделил колонку "расширение". По ней можно понять контекст записи о действии. На скриншоте видны разные компоненты:

- **"расширения"** - действия, связанные с установкой, удалением и обновлением расширений, сохранением параметров.
- **"материалы"** - штатный компонент для создания статей в Joomla. Его же нередко используют для создания каталогов чего угодно.
- **"SW JProjects"** - сторонний компонент Joomla для ведения каталога расширений Joomla и сервера обновлений для них
- **"Лог действий"** - собственно компонент лога действий пользователя Joomla

Так, например, компонент "пользователи" (com_users) логирует не только собственно действия с пользователями, но и моменты входа и выхода в админку.

![](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/3.webp)

Правильный выход через кнопку "выйти" в наше время никто не делает, конечно. Это скорее исключение, так как все просто закрывают вкладку браузера. Но момент входа записывается точно, поэтому даже если какой-то компонент не умеет записывать логи об интересующих нас действиях пользователей, мы можем резко сузить круг "подозреваемых лиц" до тех, кто в данный момент времени находился в админке.

Мы можем отфильтровать записи по типу расширения и увидеть только то, что нас интересует. Для этого пользуемся кнопкой "параметры поиска" в списке записей событий. Список расширений зависит от установленных компонентов и того, поддерживают ли они стандартный для Joomla action log.

 ![Фильтр по расширению Joomla для действий пользователя в логе](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/4.webp)Фильтр действий пользователя Joomla по расширению

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

![Фильтр по дате действия пользователя в логе Joomla](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/5.webp)

Ну и конечно доступна фильтрация записей по конкретному пользователю.

### Параметры компонента Лог действий пользователя в Joomla

 ![Настройки компонента лог действий пользователей. Список логируемых компонентов.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/6.webp)Настройки компонента Лог действий пользователей Joomla

Настраиваемых параметров немного. Мы можем дополнительно логировать IP-адрес пользователя. По умолчанию эта функция отключена, видимо из-за европейских GDPR.

**Самое полезное здесь** - это настройка списка компонентов, в которых должны логироваться события.

**Самое интересное здесь** - возможность логирования запросов к REST API Joomla.

 ![Логируемые методы запросов к REST API в Joomla](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/7.webp)Логируемые методы запросов к REST API в Joomla

Если мы включим параметр "логировать запросы", то в выпадающем списке можно выбрать какие именно методы логировать: GET (получение данных по REST API). POST (создание сущности) и т.д. Подробнее о REST API в статье на Хабре ["Web Services в Joomla 4"](https://habr.com/ru/articles/747730/) (и она же на моём сайте: [Web Services в Joomla 4](https://web-tolk.ru/blog/index.php?option=com_content&view=article&id=83&catid=10&lang=ru-RU)).

 ![лог запросов к REST API Joomla. Вид в панели администратора.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/8.webp)Пользователь Joomla выполнил GET запрос по index.php/v1/content/articles/{ID}

Таким образом если ваша Joomla связана с внешними системами по REST API, то историю взаимодействия с ними тоже можно отслеживать.

### Версионность контента. Просмотр изменений.

Порой нам хотелось бы знать *что именно было изменено* и на что.**Версионность контента** возможна только в тех компонентах, что его поддерживают и **где параметр сохранения версий включён**. Так, в стандартных материалах Joomla должен быть включён параметр в настройках компонента - Форма - История версий.

![Версии контента в Joomla. Как включить.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/9.webp)

Тогда на странице редактирования материала Joomla появится кнопка "версии".

![Кнопка версий материала Joomla](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/10.webp)

Где можно не только их просмотреть и восстановить...

![Список версий материала Joomla.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/11.webp)

... но и сравнить, увидев конкретные внесённые изменения. Изменения показываются в отдельном всплывающем окне. Изменения подсвечиваются.

 ![Сравнение версий материала Joomla. Подсветка изменений.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/12.webp)Сравнение версий материала Joomla. Подсветка изменений.

Если вы пишете свой собственный компонент, то можете реализовать это удобнее с точки зрения пользовательского опыта и в своём сообщении добавить сразу ссылку на diff-ы.

## Архитектура базы данных

В базе данных Joomla 4 таблицы, связанных с логированием действий пользователей:

- #__actionlogs - основная таблица для хранения логов
- #__actionlogs_extensions - таблица для хранения списка логируемых расширений. Данные этой таблицы образуют список компонентов для параметра **Логировать действия в расширениях** в настройках компонента. Столбец extension в ней содержит значение системного имени компонента вида com_content, com_menus, com_modules и т.д.
- #__actionlogs_users - параметры логирования действий для конкретных пользователей. Эти параметры можно настроить в профиле пользователя, вкладка "Лог действий"
- #__action_log_config - параметры для конструирования языковой константы сообщения лога и данных для неё.

## Анализ плагина action logs ядра Joomla

В Joomla штатные действия ядра записывает плагин **Лог действий - Joomla**. Он не имеет настраиваемых параметров. Файл класса плагина находится в **plugins/actionlog/joomla/src/Extension/Joomla.php**.

Логика работы плагина в целом состоит из следующих шагов:

1. **Проверить, в допустимом ли контексте вызвано событие.** Обычно у компонента несколько сущностей (категории материалов и материалы; категории товаров, товары, производители товаров, характеристики товаров и т.д.), список их контекстов заносится в свойство класса плагина.
2. **Получить системное имя компонента вида****com_mycomponent****.** В разных случаях мы получаем его или из контекста с помощью explode('.', $context), либо из $_GET массива с помощью объекта Input: $option = $app->getInput()->get('option').
3. **Проверить, а разрешено ли логировать действия в данном компоненте.**
4. В зависимости от типа события - **сконструировать языковую константу** и получить данные для подмены плейсхолдеров в ней.
5. **Собрать массив с сообщением** и данными.
6. **Добавить массив в лог.**

### Языковые константы

Языковые константы ядра Joomla для лога действий пользователя распределены между файлами локализаций двух плагинов - групп system и actionlog:

- administrator/language/ru-RU/plg_actionlog_joomla.ini
- administrator/language/ru-RU/plg_system_actionlogs.ini

Если мы откроем файлы локализации плагина группы actionlog Joomla, то увидим в нём следующие строки:

```
PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED="Пользователь <a href=\"{accountlink}\">{username}</a> создал {type} <a href=\"{itemlink}\">{title}</a>"
PLG_SYSTEM_ACTIONLOGS_CONTENT_ARCHIVED="Пользователь <a href=\"{accountlink}\">{username}</a> переместил в архив {type} <a href=\"{itemlink}\">{title}</a>"
PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED="Пользователь <a href=\"{accountlink}\">{username}</a> удалил {type} {title}"
PLG_SYSTEM_ACTIONLOGS_CONTENT_PUBLISHED="Пользователь <a href=\"{accountlink}\">{username}</a> опубликовал {type} <a href=\"{itemlink}\">{title}</a>"
PLG_SYSTEM_ACTIONLOGS_CONTENT_TRASHED="Пользователь <a href=\"{accountlink}\">{username}</a> переместил в корзину {type} <a href=\"{itemlink}\">{title}</a>"
PLG_SYSTEM_ACTIONLOGS_CONTENT_UNPUBLISHED="Пользователь <a href=\"{accountlink}\">{username}</a> снял с публикации {type} <a href=\"{itemlink}\">{title}</a>"
PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED="Пользователь <a href=\"{accountlink}\">{username}</a> обновил {type} <a href=\"{itemlink}\">{title}</a>"
```

Имя языковой константы конструируется по формуле `[text_prefix] + [entity] + [action]`.

`$text_prefix` - это обычно свойство контроллера или модели компонента. В нём хранится системное имя компонента: `com_categories`, `com_content`, `com_menus`. Или [имя компонента + сущность]: `com_banners_client`, `com_banners_banners`.

 ![Text_prefix для языковых констант Joomla](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/13.webp)Text_prefix для языковых констант Joomla

Набор типов действий по умолчанию в Joomla интуитивно понятны. Суффикс ADD для вновь созданных элементов, DELETED - для удалённых и т.д.

Фрагмент entity в имени языковой константы в Joomla по умолчанию равен "CONTENT". Он используется в случаях, когда отсутствуют созданные языковые константы, но вывести что-то нужно.

#### Типы изменяемых сущностей

Ещё ряд языковых констант даёт нам **название типов изменяемых сущностей, отвечая на вопрос "что?"** - что было изменено / создано / удалено и т.д. В файле **administrator/language/ru-RU/plg_actionlog_joomla.ini** это строки

```
;...
PLG_ACTIONLOG_JOOMLA_TYPE_ARTICLE="материал"
PLG_ACTIONLOG_JOOMLA_TYPE_BANNER="баннер"
PLG_ACTIONLOG_JOOMLA_TYPE_BANNER_CLIENT="клиента"
PLG_ACTIONLOG_JOOMLA_TYPE_CATEGORY="категорию"
PLG_ACTIONLOG_JOOMLA_TYPE_COMPONENT="компонент"
PLG_ACTIONLOG_JOOMLA_TYPE_COMPONENT_CONFIG="Настройки компонента"
PLG_ACTIONLOG_JOOMLA_TYPE_CONTACT="контакт"
PLG_ACTIONLOG_JOOMLA_TYPE_FIELD="поле"
;...
```

Они формируются по формуле `[text_prefix] + _TYPE_ + [entity]`.

#### Плейсхолдеры в языковых константах (переменные для строковой замены)

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

- `{accountlink}` - ссылку на пользователя в админке Joomla
- `{username}` - имя пользователя
- `{type}` - тип сущности: материал, баннер, модуль, параметры конфигурации, товар и т.д.
- `{itemlink}` - ссылка в админке на сущность, с которой произошло событие
- `{title}` - название / заголовок сущности

Также штатно обрабатываются ещё следующие плейсхолдеры, работающие везде:

- `{userid}` - id юзера, совершающего действие. Используется для формирования ссылок на него.
- `{app}` - тип приложения: панель управления, API (Joomla REST API), CLI, сайт.

Некоторые плейсхолдеры используются только в определенных частях системы, например в REST API Joomla:

- `{verb}` - для логирования запросов REST API - тип запроса GET, POST, PUT и т.д.
- `{url}` - для логирования запросов REST API - url запроса

### Конструирование языковых констант

Посмотрим на структуру таблицы `#__action_log_config`. В ней содержатся необходимые данные для конструирования языковых констант и источники данных для плейсхолдеров.

 ![Скриншот таблицы #__action_log_config Joomla](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/14.webp)Скриншот таблицы #__action_log_config Joomla
- `text_prefix` - см. о нём выше. Префикс для языковых констант.
- `type_alias` - содержит в себе контекст выполнения действия. `$context` содержит в себе системное имя компонента и сущности вида `<com_component>.<entity>`. Если в этом списке нет нужного контекста - штатный плагин Joomla не сработает.
- `table_name` - имя таблицы базы данных, из которых нужно выбирать данные в некоторых случаях. Например при смене состояния: опубликовано / не опубликовано * перемещено в корзину / перемещено в архив. При пакетных действиях на триггер `onContentChangeState` приходит список `id` и чтобы получить заголовок статьи нужно сходить за ним в базу.
- `type_title` - поле, в котором находится фрагмент языковой константы типа изменяемой сущности для данного контекста. Например, для контекста `com_content.article` при формировании языковой константы для плейсхолдера `{type}` будет создано **PLG_ACTIONLOG_JOOMLA_TYPE_*ARTICLE***.
- `id_holder` и `title_holder` - содержат в себе названия свойств объекта `Table` изменяемой сущности. Например, если мы отслеживаем материал Joomla, то для ссылки на созданный / изменённый материал нам нужен его `id` и он хранится в таблице `#__content` в столбце с названием `id`. Аналогично заголовок материала хранится в таблице `#__content` в столбце с названием `title`. Однако, у баннеров столбец для заголовка баннера называется `name` и т.д.

Посмотрим на примере одного события `onContentAfterSave` как работает штатный плагин.

```
<?php
// \Joomla\Plugin\Actionlog\Joomla\Extension\Joomla::onContentAfterSave
// Файл plugins/actionlog/joomla/src/Extension/Joomla.php

use Joomla\CMS\Event\Model;

/**
 * After save content logging method
 * This method adds a record to #__action_logs contains (message, date, context, user)
 * Method is called right after the content is saved
 *
 * @param   Model\AfterSaveEvent $event  The event instance.
 *
 * @return  void
 *
 * @since   3.9.0
 */

public function onContentAfterSave(Model\AfterSaveEvent $event): void
{
    // Контекст вида com_content.article
    $context = $event->getContext();
    // Исторически это $article, хотя правильнее назвать $item
    $article = $event->getItem();
    // Логический флаг: создаётся новый элемент или редактируется уже существующий
    $isNew   = $event->getIsNew();

    if (isset($this->contextAliases[$context])) {
        $context = $this->contextAliases[$context];
    }

    // Параметры компонента. Тут находится список
    // разрешённых к логированию компонентов.
    // Этот список переопределяется настройками
    // конкретного юзера в его профиле.
    $params = $this->getActionLogParams($context);

    // Not found a valid content type, don't process further
    if ($params === null) {
        return;
    }

    // $option - это системное имя компонента вида com_component.
    [$option, $contentType] = explode('.', $params->type_alias);

    // Можно ли логировать действия в данном компонент для данного юзера?
    if (!$this->checkLoggable($option)) {
        return;
    }

    // Конструируем языковую константу
    // Добавляем нужные суффиксы к языковой константе.
    if ($isNew) {
        $messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_ADDED';
        $defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED';
    } else {
        $messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_UPDATED';
        $defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
    }

    // If the content type doesn't have its own language key, use default language key
    if (!$this->getApplication()->getLanguage()->hasKey($messageLanguageKey)) {
        $messageLanguageKey = $defaultLanguageKey;
    }

    $id = empty($params->id_holder) ? 0 : $article->{$params->id_holder};

    // Массив с сообщением
    $message = [
        'action'   => $isNew ? 'add' : 'update',
        'type'     => $params->text_prefix . '_TYPE_' . $params->type_title,
        'id'       => $id,
        'title'    => $article->{$params->title_holder} ?? '',
        'itemlink' => ActionlogsHelper::getContentTypeLink($option, $contentType, $id, $params->id_holder, $article),
    ];

    $this->addLog([$message], $messageLanguageKey, $context);
}
```

В массиве `$message` хранятся данные для плейсхолдеров в языковой константе. Дополнительно, 4-м аргументов в `$this->addLog()` можно передать `id` пользователя, для которого создаётся запись. Обычно это `id` текущего пользователя.

## Решение без создания плагина

Можно обойтись и без создания собственного плагина. Если для вас сообщения вида **"Пользователь [Сиреневый Енот] [обновил] [сущность] [ссылка на сущность]"** достаточно информативны, то можно обойтись просто добавлением своих параметров в базу данных.

Вы добавляете свой компонент в список возможных логируемых компонентов (для выпадающего списка в настройках компонента Лог пользователей) в таблицу `#__actionlogs_extensions` и затем добавляете параметры для конструирования языковых констант в таблицу `#__action_log_config`. А дальше вам нужно лишь создать 4 файла локализации с текстами сообщений: 2 на английском и 2 на русском языках. Всю дальнейшую работу будет выполнять плагин ядра Joomla.

Сделать это можно при создании очередного релиза вашего компонента в sql-файлах пакета буквально парой запросов.

Задача решена. Дальше статью можно не читать.

![Сиреневый енот работает на компьютере.](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/enot-2.webp)

## Создание своего плагина

Если вам нужны более информативные сообщения в логах, где используется больше плейсхолдеров в языковых константах или же не все данные приходят в объекте Table изменяемой сущности, то тогда потребуется создать полноценный плагин. Создаём плагин группы actionlog по типовой для Joomla 4+ структуре файлов и классов. Здесь читаем статью [Создание плагинов с учётом новой структуры Joomla 4](https://habr.com/ru/articles/736412/) (она же на моём сайте [Создание плагинов с учётом новой структуры Joomla 4](https://web-tolk.ru/blog/index.php?option=com_content&view=article&id=80&catid=10&lang=ru-RU)).

### Триггеры событий для плагинов (Event Dispatching)

Ранее писал статью [Триггеры ядра Joomla при CRUD-операциях](https://web-tolk.ru/blog/index.php?option=com_content&view=article&id=122&catid=10&lang=ru-RU), в которой рассказывается о различных событиях, вызываемых в моделях ядра Joomla. Модели компонентов ядра и немалая часть сторонних компонентов (но далеко не все) наследуют \Joomla\CMS\MVC\Model\AdminModel (**libraries/src/MVC/Model/AdminModel.php**). Поэтому в них доступны все стандартные события:

- **onContentBeforeDelete** - перед удалением сущности
- **onContentAfterDelete** - после удаления
- **onContentBeforeSave** - перед сохранением данных сущности
- **onContentAfterSave** - после сохранения
- **onContentBeforeChangeState** - перед изменением состояния (опубликовано, не опубликовано, в корзине...)
- **onContentChangeState** - после изменения состояния
- **onBeforeBatch** - перед пакетной обработкой нескольких сущностей

В некоторых случаях названия события отличаются, подробнее об этом смотрим в указанной статье.

При триггере события в плагин передаются данные, для каждого события - свои. Приведу примеры:

#### onContentAfterSave - после сохранения

Событие вызывается после сохранения любой сущности в Joomla (повторюсь, если модели компонента наследуют AdminModel). В аргументах события мы имеем:

- `$context` - string - контекст события вида `<component_name>.<entity>`: `com_content.article`, `com_contact.contact` и т.д.
- `$article` - объект `Table` сохранённой сущности. Вообще-то оно должно называться `$item`, так как это может быть не только статья, но и что угодно. Но видимо плагин изначально писали для материалов и оно так и осталось.
- `$isNew` - bool - логический флаг новая ли сущность или уже существующая.
- `$data` - array - данные отправленной в модель формы. Не все данные могут быть в объекте `Table`, некоторые поля формы могут храниться в других таблицах. Но в триггер передаётся лишь одна. Здесь зависит от того, какое поведение реализует модель компонента.

Штатный плагин подразумевает, что всё-таки большая часть данных формы хранится в одной основной таблице и аргумент `$data` не использует.

#### onContentAfterDelete - после удаления

Доступные аргументы - `$context` и `$item`. Необходимое нам системное имя компонента - [получаем уже из объекта Input](https://web-tolk.ru/blog/index.php?option=com_content&view=article&id=95:sovet-po-joomla-kak-poluchat-dannye-iz-superglobalnykh-massivov-post-get-i-drugikh&catid=10:blog&lang=ru-RU&Itemid=114).

#### onContentChangeState - после смены состояния

**Состояния:** опубликовано / не опубликовано / в корзине / в архиве. Также ваш компонент может иметь собственную, более широкую систему состояний.

В аргументах события:

- `$context` - string - контекст события вида `<component_name>.<entity>`: `com_content.article`, `com_contact.contact` и т.д.
- `$pks` - array - массив с id изменяемых сущностей.
- `$value` - int - числовое значение состояние. 0 - не опубликовано, 1 - опубликовано, 2 - в архиве, -2 - в корзине.

---

Далее, на каждое событие вы конструируете языковые константы тем же образом. что и штатный плагин. А в массив $message помещаете данные для всех необходимых плейсхолдеров. Приведу пример из плагина логирования действий пользователей в админке компонента [SW JProjects](https://web-tolk.ru/blog/dev/components/sw-jprojects) ([GitHub](https://github.com/WebTolk/SWJProjects)).

```
<?php

/**
 * After save content logging method.
 * This method adds a record to `#__action_logs` contains (message, date, context, user)
 * Method is called right after the content is saved
 *
 * @param   string  $context
 * @param   object  $item
 * @param   bool    $isNew
 * @param   array   $data
 *
 * @return  void
 *
 * @since   2.4.0 // $context, $item, $isNew, $data
 * @todo    use Model\AfterSaveEvent $event when Joomla 6 will be released
 */

public function onContentAfterSave($context, $item, $isNew, $data): void
{
// На момент выпуска релиза ещё немало сайтов было на Joomla 4,
// поэтому из-за сохранения обратной совместимости с ней
// используется ещё старый подход.
// На новые рельсы перейдём после релиза Joomla 6 осенью 2025г.

//		$context = $event->getContext();
//		$item = $event->getItem();
//		$isNew = $event->getIsNew();
//		$data = $event->getData();

    // Массив с допустимыми контекстами для данного компонента
    if (!in_array($context, $this->contextList))
    {
        return;
    }

    list($option, $contentType) = explode('.', $context);

    // Можно ли логировать данный компонент для конкретного юзера?
    if (!$this->checkLoggable($option))
    {
        return;
    }
    // Новая сущность или редактируемая старая?
    if ($isNew) {
        $messageLanguageKey = 'PLG_ACTIONLOG_SWJPROJECTS_' . strtoupper($contentType) . '_ADDED';
        $data['id']         = $item->id;
    } else if ($context == 'com_swjprojects.key' && $data['key_regenerate'] == 1) {
        $messageLanguageKey = 'PLG_ACTIONLOG_SWJPROJECTS_KEY_REGENERATED';
    } else {
        $messageLanguageKey = 'PLG_ACTIONLOG_SWJPROJECTS_' . strtoupper($contentType) . '_UPDATED';
    }
    // Для получения title отдельный метод, так как в компоненте не стандартная мультиязычность
    $message = [
        'action'   => $isNew ? 'add' : 'update',
        'type'     => 'PLG_ACTIONLOG_SWJPROJECTS_TYPE_' . strtoupper($contentType),
        'id'       => $item->id,
        'title'    => $this->getItemTitle($context, $data),
        'itemlink' => 'index.php?option=com_swjprojects&task=' . $contentType . '.edit&id=' . $item->id,
    ];

    // В случае необходимости можно использовать любое количество
    // плейсхолдеров для замены.
    // Добавляем здесь нужные для нужных контекстов.
    if (!in_array($contentType, ['project', 'category', 'key']))
    {
        $message['projectTitle'] = $this->getProjectTitle($item->project_id);
        $message['projectLink']  = 'index.php?option=com_swjprojects&task=project.edit&id=' . $item->project_id;
    }

    $this->addLog([$message], $messageLanguageKey, $context);
}
```

В этом примере видно, что для некоторых контекстов (категория, проект и лицензионный ключ) нам не хватает стандартных значений и мы добавляем свои. Языковые константы выглядят следующим образом:

```
; Добавление документации для цифрового проекта
PLG_ACTIONLOG_SWJPROJECTS_DOCUMENT_ADDED="Пользователь <a href=\"{accountlink}\">{username}</a> создал документацию <a href=\"{itemlink}\">{title}</a> для проекта <a href=\"{projectLink}\">{projectTitle}</a> в компоненте SW JProjects"

; Добавление лицензионного ключа
PLG_ACTIONLOG_SWJPROJECTS_KEY_ADDED="Пользователь <a href=\"{accountlink}\">{username}</a> создал лицензионный ключ id <code>{title}</code> в компоненте SW JProjects"

; Работа с версиями программного обеспечения
PLG_ACTIONLOG_SWJPROJECTS_VERSION_PUBLISHED="Пользователь <a href=\"{accountlink}\">{username}</a> опубликовал версию <a href=\"{itemlink}\">{title}</a> для проекта <a href=\"{projectLink}\">{projectTitle}</a> в компоненте SW JProjects"
PLG_ACTIONLOG_SWJPROJECTS_VERSION_TRASHED="Пользователь <a href=\"{accountlink}\">{username}</a> переместил в корзину версию <a href=\"{itemlink}\">{title}</a> для проекта <a href=\"{projectLink}\">{projectTitle}</a> в компоненте SW JProjects"
```

Так это выглядит в панели администратора.

![лог действий пользователей в компоненте Joomla SW JProjects](https://web-tolk.ru/blog/images/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog/15.webp)

### Дополнительные детали

#### Метод checkLoggable()

Конструктор класса плагина обычно выглядит следующим образом:

```
<?php

/**
 * Constructor.
 *
 * @param   DispatcherInterface  $dispatcher  The dispatcher
 * @param   array                $config      An optional associative array of configuration settings
 *
 * @since   2.4.0
 */
public function __construct(DispatcherInterface $dispatcher, array $config)
{
    parent::__construct($dispatcher, $config);

    $params = ComponentHelper::getComponent('com_actionlogs')->getParams();
    // Массив с системными именами логируемых компонентов
    $this->loggableExtensions = $params->get('loggable_extensions', []);
    // То же самое для REST API
    $this->loggableApi = $params->get('loggable_api', 0);
    // Логируемые методы REST API
    $this->loggableVerbs = $params->get('loggable_verbs', []);

}
```

А сам метод проверки выглядит так:

```
<?php

/**
 * Function to check if a component is loggable or not
 *
 * @param   string  $extension  The extension that triggered the event
 *
 * @return  boolean
 *
 * @since   2.4.0
 */

protected function checkLoggable(string $extension): bool
{
    return in_array($extension, $this->loggableExtensions);
}
```

## Заключение

Иногда сохранённые логи действий пользователей помогают быстрее найти решение проблемы, если она вызвана человеческим фактором. Да, может быть не всё удобно с точки зрения удобства использования и поиска, но это Open Source и это в составе ядра Joomla.

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

- [Публикация на Хабре](https://habr.com/ru/articles/900338/)

## Об авторе

![Толкачев Сергей Юрьевич](https://web-tolk.ru/images/uslugi/sergey-tolkachyov-apr-2023.webp)

### Толкачев Сергей Юрьевич

Joomla-разработчик. [Контрибьютер ядра Joomla](https://github.com/joomla/joomla-cms/pulls?q=is%3Apr+author%3Asergeytolkachyov+). Один из ведущих Telegram-канала русскоязычного Joomla-сообщества [JoomlaFeed](https://t.me/joomlafeed), один из модераторов [чата русскоязычного Joomla-сообщества](https://t.me/joomlaru). Мои расширения в официальном маркетплейсе расширений Joomla - [Joomla Extensions Directory](https://extensions.joomla.org/profile/profile/details/528051/). Имею публикации в [официальном журнале международного Joomla-сообщества - Joomla Community Magazine](https://magazine.joomla.org/authors/sergeytolkachyov) и на [официальном сайте русскоязычного Joomla-сообщества](https://joomlaportal.ru/users/sergey-tolkachyov).

Муж. Отец 3 детей.

Россия, Саратов.

## JSON-LD Schema

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "item": {
                "@id": "https://web-tolk.ru/",
                "name": "Главная"
            }
        },
        {
            "@type": "ListItem",
            "position": 2,
            "item": {
                "@id": "https://web-tolk.ru/blog",
                "name": "Блог"
            }
        },
        {
            "@type": "ListItem",
            "position": 3,
            "item": {
                "name": "Логирование действий пользователя в Joomla. Создание плагина Actionlog"
            }
        }
    ]
}
```

```json
{
    "@context": "https://schema.org",
    "@graph": [
        {
            "@type": "Organization",
            "@id": "https://web-tolk.ru/#/schema/Organization/base",
            "name": "WebTolk",
            "url": "https://web-tolk.ru/",
            "logo": {
                "@type": "ImageObject",
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo",
                "url": "images/webtolk-1080p.jpg",
                "contentUrl": "images/webtolk-1080p.jpg",
                "width": 1920,
                "height": 1080
            },
            "image": {
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo"
            },
            "sameAs": [
                "https://github.com/WebTolk",
                "https://github.com/sergeytolkachyov",
                "https://vk.com/web_tolk",
                "https://vk.com/webtolkru",
                "https://tenchat.ru/sergeytolkachyov",
                "https://t.me/sergeytolkachyov",
                "https://t.me/webtolkru"
            ]
        },
        {
            "@type": "WebSite",
            "@id": "https://web-tolk.ru/#/schema/WebSite/base",
            "url": "https://web-tolk.ru/",
            "name": "WebTolk",
            "publisher": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            }
        },
        {
            "@type": "WebPage",
            "@id": "https://web-tolk.ru/#/schema/WebPage/base",
            "url": "https://web-tolk.ru/blog/logirovanie-dejstvij-polzovatelya-v-joomla-sozdanie-plagina-actionlog",
            "name": "Логирование действий пользователя в Joomla. Создание плагина Actionlog - WebTolk",
            "description": "Как написать собственный плагин логирования действий пользователя Joomla? Примеры кода плагина группы actionlog для Joomla. Скриншоты, возможности компонента Лог действий пользователя.",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebSite/base"
            },
            "about": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            },
            "inLanguage": "ru-RU",
            "breadcrumb": {
                "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17"
            }
        },
        {
            "@type": "Article",
            "@id": "https://web-tolk.ru/#/schema/com_content/article/134",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebPage/base"
            }
        }
    ]
}
```
