Первый плагин
Событие — момент в работе Joomla, о котором она сообщает всем желающим: «текст статьи готовится к выводу», «пользователь вошёл», «запись сохранена». Плагин — класс, который подписан на такие события и выполняет свой код, когда они происходят. Напишем плагин, который заменяет в статьях метку {year} текущим годом.
Где лежит
Плагины разложены по группам. Группа — это папка внутри plugins; плагин группы content обрабатывает события содержимого. Создайте вручную:
plugins/content/hello/
├── hello.xml манифест
├── services/
│ └── provider.php подключение плагина
└── src/
└── Extension/
└── Hello.php класс плагинаПолное имя такого плагина — plg_content_hello.
Манифест
<?xml version="1.0" encoding="UTF-8"?>
<!-- plugins/content/hello/hello.xml -->
<extension type="plugin" group="content" method="upgrade">
<name>plg_content_hello</name>
<version>1.0.0</version>
<description>Подставляет текущий год в текст статей</description>
<namespace path="src">My\Plugin\Content\Hello</namespace>
<files>
<folder plugin="hello">services</folder>
<folder>src</folder>
</files>
</extension>От манифеста модуля он отличается типом plugin, атрибутом group и атрибутом plugin="hello" у первой папки.
Провайдер
// plugins/content/hello/services/provider.php
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 My\Plugin\Content\Hello\Extension\Hello;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new Hello(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('content', 'hello'),
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
},
);
}
};Провайдер создаёт объект плагина и передаёт ему диспетчер событий, настройки и приложение. В другом плагине меняются только класс, группа и имя.
Класс плагина
// plugins/content/hello/src/Extension/Hello.php
namespace My\Plugin\Content\Hello\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Event\Content\ContentPrepareEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
final class Hello extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onContentPrepare' => 'replaceYear',
];
}
public function replaceYear(ContentPrepareEvent $event): void
{
$article = $event->getItem();
if (empty($article->text)) {
return;
}
$article->text = str_replace('{year}', date('Y'), $article->text);
}
}Интерфейс SubscriberInterface требует метод getSubscribedEvents(): он возвращает пары «событие → метод». Событие onContentPrepare происходит перед выводом текста; обработчик получает объект события, достаёт из него статью и меняет её текст.
Класс ContentPrepareEvent появился в Joomla 5. В Joomla 4 обработчик получает общий объект Joomla\Event\Event, а статья лежит в его аргументах.
Установка и результат
- «Система → Установка → Поиск» → установите
plg_content_hello. - «Система → Плагины» → найдите плагин и включите его: после установки плагины выключены.
- Допишите в текст статьи строку
© {year} Анна Ивановаи откройте статью на сайте.
Вместо метки выведен текущий год.
Параметры плагина
Настройки описывают в манифесте так же, как у модуля:
<config>
<fields name="params">
<fieldset name="basic">
<field name="marker" type="text" label="Метка для замены" default="{year}" />
</fieldset>
</fields>
</config>В классе плагина они доступны в свойстве $this->params:
$marker = $this->params->get('marker', '{year}');
$article->text = str_replace($marker, date('Y'), $article->text);Поле «Метка для замены» появится в форме плагина в разделе «Система → Плагины».
Частая ошибка
Событие onContentPrepare вызывается не только для статей, но и для модулей, описаний категорий и других текстов — много раз на одной странице. Медленный код в обработчике замедлит весь сайт. Проверяйте контекст — $event->getContext() === 'com_content.article' — и выходите из метода сразу, если текст вам не подходит.