Как стать автором
Поиск
Написать публикацию
Обновить
2.97

Joomla *

Cистема управления содержимым (CMS)

Сначала показывать
Порог рейтинга

Как триггерить события для плагинов на манер Joomla 5+?

В Joomla 6 должны удалить метод triggerEvent(), с помощью которого раньше вызывались события для плагинов. Теперь чтобы в своём коде вызвать событие для плагина и получить от него результаты нужно:

  • создать объект класса события

  • передать в него именованные параметры

use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;

// Грузим плагины нужных групп
PluginHelper::importPlugin('system');
// Создаём объект события
$event = AbstractEvent::create('onAfterInitUniverse', [
    'subject' => $this,
    'data'    => $data, // какие-то данные
    'article' => $article, // ещё материал вдовесок
    'product' => $product, // и товаров подвезли
]);
// Триггерим событие
Factory::getApplication()->getDispatcher()->dispatch(
    $event->getName(), // Тут можно строку передать 'onAfterInitUniverse'
    $event
);
// Получаем результаты
// В случае с AbstractEvent это может быть не 'result',
// а что-то ещё - куда сами отдадите данные.
// 2-й аргумент - значение по умолчанию, 
// если не получены результаты
$results = $event->getArgument('result', []);

Плюсы такого подхода - вам не нужно запоминать порядок аргументов и проверять их наличие. Если вы написали свой класс события, то в плагине можно получать аргументы с помощью методов $event->getArticle(), $event->getData(), $event->getProduct() и подобными - реализуете сами под свои нужды.

Если такой класс события написали, то создаёте экземпляр своего класса события и укажите его явно в аргументе eventClass

use Joomla\Component\MyComponent\Administrator\Event\MyCoolEvent;

$event = MyCoolEvent::create('onAfterInitUniverse', [
    'subject'    => $this,
    'eventClass' => MyCoolEvent::class, // ваш класс события
    'data'       => $data, // какие-то данные
    'article'    => $article, // ещё материал вдовесок
    'product'    => $product, // и товаров подвезли
]);

Ожидаемо, что класс вашего события будет расширять AbsractEvent или другие классы событий Joomla.

🙁 Есть неприятный нюанс - нельзя просто так вызывать событие и ничего не передать в аргументы. Аргумент subject обязательный. Но если вы всё-таки не хотите туда ничего передавать - передайте туда пустой stdClass или объект Joomla\registry\Registry.

Чат русскоязычного Joomla-сообщества.

Теги:
-1
Комментарии0

Совет по Joomla: значение переменной по умолчанию с помощью класса Joomla\Registry\Registry.

Очень часто в Joomla мы видим, что данные приходят не в виде массива или простого объекта (stdClass), а их нужно получать с помощью метода get(). Например, в плагине или модуле:

 $option = $this->params->get('my_option');

Это означает, что мы получили данные в виде объекта Joomla\Registry\Registry.

❓ Зачем это?

Возможность указать значение переменной по умолчанию. Лично для меня первое и самое важное - это возможность указать значение переменной по умолчанию, если его (значения) в параметрах нет.

Вы выпускаете новую версию своего мега-плагина и вводите в него новую опцию my_option. Мы используем её везде в PHP коде. Однако, мы же понимаем, что люди обновятся, а обновлять настройки плагина не полезут. Не полезут до тех пор, пока не станет любопытно "а что же там нового?" или что-нибудь не отвалится. И пока они не установят новый параметр и не сохранят настройки плагина - в коде он так и не появится.

Чтобы ничего не отвалилось нам в коде нужно предположить сценарий по умолчанию, которому, соответственно, нужны значения параметров по умолчанию. Вот тут нам и поможет класс Joomla\Registry\Registry.

 $myOption = $this->params->get('my_option', 'default_value_if_null_or_not_exists'); // string по умолчанию

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

Теги:
0
Комментарии0

Раскопал интересный тип поля в Joomla - Groupedlist.

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

Но как сделать такой список для использования в описаниях форм в xml? Первой мыслью было сделать свой тип поля, расширяющий стандартный \Joomla\CMS\Form\Field\ListField. Однако, в ядре Joomla нашёлся уже готовый класс поля для группированных списков \Joomla\CMS\Form\Field\GroupedlistField. Он расширяет напрямую FormField и имеет 2 метода - getGroups() и getInput().

В getInput() вызывается метод getGroups() для получения массивов с группами опций и его можно было спокойно заменить на getcollectLayoutData(), где этой работе самое и место, но это не слишком принципиально. И там и там работа делается. Поэтому нас интересует именно метод getGroups().

Мы создаём свой класс поля, расширяем GroupedlistField. Делаем обязательно свой $type для поля и реализуем метод getGroups(). Всё.

<?php
use Joomla\CMS\Form\Field\GroupedlistField;
use Joomla\CMS\HTML\HTMLHelper;

class ServerschemelistField extends GroupedlistField
{
    // type совпадает с именем файла и класса
    // без суффикса 'Field'
    protected $type = 'Serverschemelist';

    /**
     * Method to get the field options.
     *
     * @return  array  The field option objects.
     *
     * @throws  Exception
     *
     * @since  1.0.0
     */
    protected function getGroups(): array
    {
        // наши группы
        $group1 = [];
        $group2 = [];
        // Собираем первую группу опций
        foreach ($data as $item) {
            $optionattr = [];
            // Атрибуты для <option>
            if ($something_happend) {
                $optionattr['option.attr'] = [
                    'selected' => 'selected',
                    'onclick'  => 'earthQuake()',
                    'showon'   => 'field1:value1000',
                ];
            }

            $group1[] = HTMLHelper::_(
                'select.option',
                $item->option_value,
                $item->option_label_text,
                $optionattr
            );
        }
        // Аналогично собираем $group2
        // ...
        $groups = [
            ['Имя группы 1'] = $group1,
            ['Имя группы 2'] = $group2,
        ];
        // В parent::getGroups() будут значения
        // из xml-описания формы, если они есть.
        // Соединяем их с нашими.
        return array_merge(parent::getGroups(), $groups);
    }
}
Теги:
+1
Комментарии0

Использование атрибута showon для создания зависимых выпадающих списков в Joomla 5.3.3

Я не раз слышал о том что можно использовать атрибут showon для создания зависимых выпадающих списков, но не понимал как именно использовать. Скрывать один зависимый list и показывать другой мне совсем не представлялось удобным решением. На мой взгляд в зависимом выпадающем списке должна быть загрузка опций или отображение только актуальных.

Удивил меня ChatGPT, когда вместо JavaScript-кода предложил использовать атрибут showon, но не для самого элемента list, а для его опций.

<field
    name="fielda"
    type="list"
    label="FIELDA_LABEL">
    <option value="editor">Editor</option>
    <option value="text">Text</option>
    <option value="textarea">Textarea</option>
</field>

<field
    name="fieldb"
    type="list"
    label="FIELDB_LABEL">
    <option value="0">Always Visible</option>
    <option showon="fielda:text,textarea" value="2">Option 2</option>
    <option showon="fielda:text" value="3">Option 3</option>
</field>

А вот так можно применять атрибут showon в своём элементе, который показывает значения выбранной характеристики товара JoomShopping:

/**
 * @return array
 *
 * @since 1.0.0
 */
protected function getOptions(): array
{
	require_once(JPATH_SITE . '/components/com_jshopping/bootstrap.php');
	$options = parent::getOptions();

	/** @var ProductFieldValueTable $productfieldvalue */
	$productfieldvalue = JSFactory::getTable('productfieldvalue');

	$list              = $productfieldvalue->getAllList();

	$default = $this->element['default'] ?? null;

	if (isset($default) && ((string) $default) === '')
	{
		$options[] = HTMLHelper::_('select.option', '', '');
	}

	foreach ($list as $item)
	{
		$conditions = FormHelper::parseShowOnConditions('extra_field_id:' . $item->field_id, $this->formControl, $this->group);
		$encodedConditions = htmlspecialchars(json_encode($conditions));

		$options[] = HTMLHelper::_(
			'select.option',
			$item->id,
			$item->name,
			[
				'attr'        =>
					[
						'data-showon' => $encodedConditions
					],
				'option.attr' => "optionattr",
			]
		);
	}
	
    return $options;
}
Теги:
0
Комментарии0

WT Yandex map items v.2.1.0 модуль для Joomla.

Выводит материалы Joomla в виде меток на Яндекс.Карты. Используется API 3.0.

v.2.1.0. Что нового?

Сохранение последнего вида карты.
Добавлены новые опции, позволяющие как для одного экземпляра модуля, так и для всех сохранять на устройстве пользователя последний использованный центр (координаты) и масштаб (zoom) карты. Это позволит открыть карту в том же месте после обновления страницы или при повторном открытии браузера.

Определение местоположения пользователя.
Модуль может определять местоположение пользователя и центрировать карту на нём. При одновременном использовании с функцией сохранения последнего вида карты определение геопозиции будет срабатывать только в первый раз. В дальнейшем, если обнаружены сохранённые данные центра и масштаба - будут использоваться они.

CSS классы для маркеров карты.
Всем маркерам карты добавлен CSS-класс wt-yandex-map-items-marker. Для просмотренных маркеров (по которым кликали) добавляется CSS-класс wt-yandex-map-items-marker-viewed, что позволит выделять просмотренные маркеры с помощью стилей в CSS-файлах вашего шаблона. Также для контейнеров маркеров ymaps на карте добавлены data-атрибуты: data-module-id - id модуля и data-marker-id - id маркера.

Обработка GET-параметров в URL.

Карта может реагировать на GET-параметры в url:

  • map[zoom] - устанавливает параметр масштаба.

  • map[center_latitude] и map[center_longitude] - широта и долгота центра карты.

  • map[marker_id] - id маркера, на котором центрируется карта. Таким образом вы можете создавать ссылку на карту с указанием конкретного маркера, на котором карта сфокусируется после загрузки маркеров. Например, https://site.ru/map?map[marker_id]=18465. Или же ссылку с указанием конкретных координат: https://site.ru/map?map[zoom]=16&map[center_latitude]=51.529706&map[center_longitude]=46.033922

Страница расширения

GitHub расширения

Видео-обзор на Youtube

Видео-обзор на VK Видео

Видео-обзор на Rutube

Теги:
-1
Комментарии0

Совет по Joomla: расположение полей Form в параметрах модулей и плагинов.

Обычно поля настроек модулей и плагинов идут столбиком - сверху вниз. Название поля находится слева, а само поле - справа. В вёрстке админки мы видим div.control-group, в котором находятся label и поле. Посмотрим как можно просто кастомизировать админку.

Название поля НАД полем - parentclass="stack".

Если в XML-манифесте модуля или плагина добавить к полю атрибут parentclass, то мы можем указывать любые CSS-стили для div.control-group. Если указать CSS-класс stack, то название поля встанет над самим полем. Это удобно для больших сабформ - экономится место на экране.

<field type="text"
     name="map_center"
     label="MOD_WTYANDEXMAPITEMS_MAP_CENTER"
     description="MOD_WTYANDEXMAPITEMS_MAP_CENTER_DESC"
     default="51.533562, 46.034266"
     hint="51.533562, 46.034266"
     parentclass="stack"
/>

2 и более полей в ряд в параметрах модуля/плагина - классы span-*

Мы можем 2 или 3 небольших поля поставить рядом (для десктопов). Табы настроек являются grid-сеткой из 4-х колонок. Для поля можно указать ширину в виде количества колонок. Нам нужно в parentclass добавить класс span-*-inline. Допустимы числа от 1 до 4.

span-1-inline - поле будет шириной в 1 колонку сетку. span-4-inline - ширина в 4 колонки, равносильно поведению по умолчанию. Этот код выведет 2 поля в админке в параметрах модуля рядом на десктопах. Поскольку используется также класс stack - название поля будет над самим полем.

<field type="list"
     name="map_zoom"
     label="MOD_WTYANDEXMAPITEMS_MAP_ZOOM"
     default="7"
     parentclass="stack span-2-inline"
     filter="integer">
          <option value="0">0</option>
          <option value="1">1</option>
          <option value="21">21</option>
</field>
<field type="list"
     name="map_type"
     label="MOD_WTYANDEXMAPITEMS_MAP_TYPE"
     parentclass="stack span-2-inline"
     default="scheme">
          <option value="scheme">MOD_WTYANDEXMAPITEMS_MAP_TYPE_MAP</option>
          <option value="satellite">MOD_WTYANDEXMAPITEMS_MAP_TYPE_SATELLITE</option>
</field>

Чат русскоязычного сообщества Joomla

Теги:
0
Комментарии0

Совет по Joomla: расположение полей Form в параметрах модулей и плагинов.

Обычно поля настроек модулей и плагинов идут столбиком - сверху вниз. Название поля находится слева, а само поле - справа. В вёрстке админки мы видим div.control-group, в котором находятся label и поле. Посмотрим как можно просто кастомизировать админку.

Название поля НАД полем - parentclass="stack".

Если в XML-манифесте модуля или плагина добавить к полю атрибут parentclass, то мы можем указывать любые CSS-стили для div.control-group. Если указать CSS-класс stack, то название поля встанет над самим полем. Это удобно для больших сабформ - экономится место на экране.

<field type="text"
     name="map_center"
     label="MOD_WTYANDEXMAPITEMS_MAP_CENTER"
     description="MOD_WTYANDEXMAPITEMS_MAP_CENTER_DESC"
     default="51.533562, 46.034266"
     hint="51.533562, 46.034266"
     parentclass="stack"
/>

2 и более полей в ряд в параметрах модуля/плагина - классы span-*.

Мы можем 2 или 3 небольших поля поставить рядом (для десктопов). Табы настроек являются grid-сеткой из 4-х колонок. Для поля можно указать ширину в виде количества колонок. Нам нужно в parentclass добавить класс span-*-inline. Допустимы числа от 1 до 4.

  • span-1-inline - поле будет шириной в 1 колонку сетку.

  • span-4-inline - ширина в 4 колонки, равносильно поведению по умолчанию.

Этот код выведет 2 поля в админке в параметрах модуля рядом на десктопах. Поскольку используется также класс stack - название поля будет над самим полем.

<field type="list"
     name="map_zoom"
     label="MOD_WTYANDEXMAPITEMS_MAP_ZOOM"
     default="7"
     parentclass="stack span-2-inline"
     filter="integer">
          <option value="0">0</option>
          <option value="1">1</option>
          <option value="21">21</option>
</field>
<field type="list"
     name="map_type"
     label="MOD_WTYANDEXMAPITEMS_MAP_TYPE"
     parentclass="stack span-2-inline"
     default="scheme">
          <option value="scheme">MOD_WTYANDEXMAPITEMS_MAP_TYPE_MAP</option>
          <option value="satellite">MOD_WTYANDEXMAPITEMS_MAP_TYPE_SATELLITE</option>
</field>
Теги:
0
Комментарии0

Joomla: как тестировать? Всего 8 минут.

Над CMS Joomla постоянно ведётся работа: создаётся новый функционал, исправляются ошибки, делаются мелкие правки. Разработка ведётся на GitHub. Изменения оформляются в виде Pull Request (PR). Для того, чтобы изменения могли войти в ядро - их обязательно должны успешно протестировать минимум 2 человека КРОМЕ автора изменений. А помочь с большинством PR можно очень и очень быстро, это не занимает много времени, чему подтверждением служит это видео.

Смотреть видео на YouTube

Смотреть на Vk Video

Смотреть на RuTube

Теги:
Рейтинг0
Комментарии0

Свои типы полей в Joomla.

Это большая тема, о которой можно говорить очень много. Самое главное, что возможности применения ограничиваются только вашей больной фантазией. Вы строите интерфейс своего модуля или плагина и вам нужно подтянуть данные из сторонней системы (список чего-нибудь по какому-нибудь API), чтобы сохранить выбранный id в Joomla. Или сделать какую-то проверку и в зависимости от неё показать то или иное сообщение пользователю. Для этого подойдут свои пользовательские типы полей.

Интерфейс Joomla по большей части описан в XML-файлах. У каждого из них свои параметры. Некоторые не описаны в документации (manual.joomla.org), поэтому самым любопытным будет полезно заглянуть в собственно файлы фреймворка по пути libraries/src/Form/FormField.php, а так же в libraries/src/Form/Fields. У каждого класса поля перечислены его специфические свойства, которые можно описывать в XML. А в своём типе поля вы можете устанавливать эти значения программно.

В моём модуле WT Quick links под капотом происходят изменения. Теперь для работы (в админке) ему нужен вспомогательный плагин. А в самом модуле нам бы проверить, а не выключен ли он?

В Joomla есть тип поля Note - заметка. Его можно использовать для вывода примечаний.

<field type="note"
     name="your_note_for_user"
     label="Заголовок примечания"
     title="Альтернативный способ для заголовка"
     description="Текст примечания"
     class="col-12 alert alert-info"
     heading="h1"
     close="true"
/>

heading- указывать уровень заголовка. close - позволяет закрыть это примечание.

В классе поля libraries/src/Form/Field/NoteField.php описана логика вывода. И в принципе оно нам подходит для нашей задачи. Но оно будет выводить сообщение всегда, а нам нужно только тогда, когда плагин отключён.

Поэтому берём и создаём свой класс поля, который мы унаследуем от NoteField. Это значит, что у нас в руках будет весь инструментарий стандартного поля Note + то, что мы сами добавим.

В XML-манифест добавляем наше поле:

<field type="systempluginstatus" 
     name="systempluginstatus"
     addfieldprefix="Joomla\Module\Wtquicklinks\Site\Fields"/>
  • type - имя файла и класса,

  • addfieldprefix - указываем namespace к нашему классу, может быть любой нам нужный

  • name - нельзя полю без имени...

Это означает, что Joomla будет использовать класс поля из файла modules/mod_wt_quick_links/src/Fields/SystempluginstatusField.php. А в классе поля будет написано следующее:

<?php
// namespace для атрибута addfieldprefix
namespace Joomla\Module\Wtquicklinks\Site\Fields;
// нельзя напрямую обращаться к этому файлу
defined('_JEXEC') or die;
// подключаем родительский класс для переопределения
use Joomla\CMS\Form\Field\NoteField;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;

// имя класса и имя файла точь-в-точь
class SystempluginstatusField extends NoteField
{
     protected $type = 'Systempluginstatus'; // тип поля без "Field" в конце

     protected function getLabel()
          {
               // если плагин не включён
               if(PluginHelper::isEnabled('system','wtquicklinks')) {
                    // меняем свойства родительского класса поля
                    $this->class = 'alert alert-danger w-100';
                    $this->element['label'] = '⚠️ А-а-а-а!';
                    $this->element['description'] = 'Плагин не включён!!';
                    // и просто рендерим его с нашими свойствами
                    return parent::getLabel();
               }
          // А иначе всё хорошо, скрываем поле из виду.
          $this->parentclass = 'd-none';
          return '';
     }
}

Просто и удобно. И людям приятно, что о них позаботились и рассказали почему что-то не работает.

Теги:
Рейтинг0
Комментарии0

Джентельменский набор расширений Joomla, которые стоят на каждом моём сайте

Сел немного формализовать свои процессы. Получился вот такой список:

  • Blank page - компонент пустой страницы. Обычно используем для главной.

  • Revars (GitHub) - плагин шорт-кодов для контактов, реквизитов и т.д.

  • WT Revars insert (GitHub) - плагин кнопки редактора для вставки шорт-кодов

  • RadicalForm (GitHub) - плагин формы обратной связи. Подробная инструкция на сайте. Статья Разработка форм обратной связи для магазинов на Joomla на Хабре как создать форму + примеры кода. Есть интеграции с Б24 и Amo.

  • WT Quick links (GitHub) - модуль-конструктор повторяемых элементов

  • WT SEO Meta templates - комплект плагинов для автоматического заполнения <title> и meta-description по формуле.

  • JL Sitemap (GitHub) - компонент XML-карты сайта

  • View logs (GitHub) - просмотр логов Joomla в админке

  • WT Eternal admin - плагин автоматического продления сессии в панели администратора.

  • Reset Media Version (GitHub) - для верстальщиков: гарантированная загрузка новых версий css и js файлов, а не из кэша браузера.

Установка остальных расширений уже зависят от конкретного проекта.

И так же welcome в чат русского Joomla-сообщества.

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии0

Использование MVC-фабрики в плагинах Joomla 5

Обычно, концепция Model‑View‑Controller используется в компонентах Joomla, но я хочу поделиться опытом её применения в плагинах.

Для чего это надо? — Сделать код более простым и понятным.

Задача плагина Joomla — подписываться на события и выполнять определенные действия. Но не стоит размещать весь код в классе плагина, лучше разделить его на несколько классов. Особенно, если для ваших задач Joomla предлагает готовые родительские классы.

Почему я предлагаю использовать MVC-фабрику? - Она не только создаёт экземплр класса, но и устанавливает основные зависимости из DI контейнера.

Например, плагин должен ответить на AJAX-запрос и вернуть массив данных. Для этого надо написать свой класс модели, наследующий Joomla\CMS\MVC\Model\ListModel.

Как это сделать? - Примерно так же как и в компонентах.

Классу плагина подключаем трейт Joomla\CMS\MVC\Factory\MVCFactoryAwareTrait.

В файле provider.php плагина регистрируем сервис-провайдер MVC-фабрики и передаём ему пространство имён плагина.

$container->registerServiceProvider(new MVCFactory('Joomla\\Plugin\\RadicalMart\\WishboxCdek'));

Устанавливаем плагину экземпляр фабрики, полученный из контенера.

$plugin->setMVCFactory($container->get(MVCFactoryInterface::class));

В src папке плагина, создаём папку Administrator, в ней папку Model и в ней файл с классом модели. То есть классы моделей будут расположены по пути Root\plugins\radicalmart\wishboxcdek\src\Administrator\Model.

Обратите внимание на папку Administrator, метод создания моделей обязательно требует либо Site либо Administrator в namespace.

namespace Joomla\Plugin\RadicalMart\WishboxCdek\Administrator\Model;

Ещё в класс модели надо установить свойство option:

protected $option = 'com_radicalmart';

Теперь мы можем получать экземпляр модели плагина Orders , следующим образом:

/** @var OrdersModel $ordersModel */
$ordersModel = $app->bootPlugin('wishboxcdek', 'radicalmart')
	->getMVCFactory()
	->createModel('Orders', 'Administrator');

В своём плагине я наследовал класс модели от BaseDatabaseModel, для работы с другими классами может потребоваться переопределение каких-либо свойств и методов.

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Joomla 6 нуждается в вашей помощи с тестированием.

Вышел недавно релиз Joomla 6 alpha1. Это релиз, в который включены уже проверенные изменения, но ещё очень многие исправления и улучшения ждут своей очереди. Joomla следит за качеством и безопасностью своего кода и каждое изменение должно быть успешно протестировано как минимум ещё двумя участниками сообщества. Разработка Joomla ведётся на платформе GitHub.

Филипп Уолтон (Philip Walton) - один из разработчиков, кто активно вносит свой вклад в ядро Joomla. Он уже несколько месяцев посвящает свои послеполуденные часы пятницы работе с Joomla и предлагает присоединиться к нему.

📆 Пятница, 30 мая 2025, с 15:00 до 17:00 по UTC (Лондон) - с 18:00 до 20:00 по Москве.
В чате Google Meet Филипп готов помочь с тестированием тем, кто будет делать это в первый раз. А так же он подготовил список лёгких Pull Request, которые можно протестировать довольно быстро. Чем больше тестов будет проведено сейчас, тем меньше ошибок вылезет потом.

На данный момент 148 (уже 147 на момент написания заметки) PR на GitHub Joomla ждут тестирования.

Open to all. All together.

Также вам поможет сделать первые шаги это видео.

GitHub Joomla.

Да, это вечер пятницы. Но тестирование занимает порой минут 15, а доброе дело сделано. Просто убедитесь, что разработчик чего-то не пропустил и всё работает как ожидается.

Теги:
Рейтинг0
Комментарии1

Для чего нужен атрибут useglobal в полях форм и как его применять в Joomla 5

В официальной документации есть краткая статья Conditional Field Display Customizing Forms Based on Global Settings, у меня не получилось перевести её дословно, поэтому расскажу своими словами.

Давайте разберёмся что это такое на примере компонента com_contact. Посмотрим настройки компонента.

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

Видим что параметр Contact Category в положении Hide (у вас может быть в другом).

Теперь откроем страницу создания/изменения контакта и перейдём на вкладку Display.

Мы видим что параметр Category имеет первый вариант Use Global (Hide). То есть нам не надо помнить значение из общих настроек компонента, оно перед нами. Если мы изменим его в компоненте, то оно изменится и здесь.

Как это использовать разработчику компонентов

У меня есть простой компонент для экспорта товаров из JoomShopping в XML com_wishboxjshoppingxmlexport.

Добавлю в настройки компонента поле test_mode в файле: Root/administrator/components/com_wishboxjshoppingxmlexport/config.xml

<field
		name="test_mode"
		type="list"
		label="COM_WISHBOXJSHOPPINGXMLEXPORT_FIELD_TEST_MODE_LABEL"
		default="0"
>
	<option value="0">JNO</option>
	<option value="1">JYES</option>
</field>

И добавлю такое же поле в сущность (item) в файле: Root/administrator/components/com_wishboxjshoppingxmlexport/forms/item.xml

<field
		name="test_mode"
		type="list"
		label="COM_WISHBOXJSHOPPINGXMLEXPORT_FIELD_TEST_MODE_LABEL"
		default="0"
>
	<option value="0">JNO</option>
	<option value="1">JYES</option>
</field>

Добавим полю в форме Item атрибут useglobal="true"

<field
		name="test_mode"
		type="list"
		label="COM_WISHBOXJSHOPPINGXMLEXPORT_FIELD_TEST_MODE_LABEL"
		default="0"
        useglobal="true"
>
	<option value="0">JNO</option>
	<option value="1">JYES</option>
</field>

Готово! На странице создания/изменения Item‑а экспорта у поля test_mode появилась опция Use Global (Yes). То есть достаточно добавить атрибут useglobal="true".

Из каких глобальных настроек берётся значение

  • из настроек компонента (компонент определяется параметром option в URL, кроме редактирования пункта меню, для меню подставляется компонент из ссылки пункта меню);

  • из общих настроек Joomla если не получили из настроек компонента.

В каких типах полей стандартных полей работает этот атрибут

  • ListField — Добавляется опция Use Global (...);

  • NumberField — Значение из глобальных настроек отображается как placeholder;

  • TextField — Placeholder.

А так же во всех полях, которые наследуют одно их вышеперечисленных.

Теги:
Всего голосов 1: ↑1 и ↓0+3
Комментарии0

Ближайшие события

Работаем с событиями без плагина или как перехватить редирект в Joomla 5

В расширении быстрого заказа для JoomShopping. Я создаю экземпляр модели Checkout и вызываю метод checkStep().

$checkoutModel = JSFactory::getModel('checkout', 'Site');
$checkoutModel->checkStep(2);

В методе checkStep есть условия, при выполнении которых происходит установка сообщения и редирект.

if (!$check)
{
    JSError::raiseNotice('', 'Message');
    $mainframe->redirect('Url');
    exit();
}

Но мне надо получить результат метода checkStep или исключение. И продолжить работу.

Смотрим какое событие вызывается при редиректе — ApplicationEvents::BEFORE_RESPOND.

Подписываться на события могут не только плагины, но и любые другие callable объекты.

Создадим функцию которая получает заголовки редиректа, удаляет их и бросает исключение:

$onApplicationBeforeRespond = function()
{
	$app = Factory::getApplication();
	$response = $app->getResponse();
	$status = $response->getHeader('Status');
	$location = $response->getHeader('Location');
	$response = $response->withoutHeader('Status');
	$response = $response->withoutHeader('Location');
	$app->setResponse($response);

	throw new RedirectCaughtException($status[0], $location[0]);
};

Перед вызовом $checkoutModel->checkStep(2); подпишем нашу функцию на прослушивание события, а после отпишем. И обернём вызов в try/catch.

$dispatcher->addListener(
	ApplicationEvents::BEFORE_RESPOND,
	$onApplicationBeforeRespond
);

try
{
	$checkoutModel->checkStep(2);
}
catch (RedirectCaughtException $e)
{
    // Обрабатываем исключение
}

$dispatcher->removeListener(
	ApplicationEvents::BEFORE_RESPOND,
	$onApplicationBeforeRespond
);

Таким образом мы можем перехватывать редиректы внутри блока try.

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Свой класс события для плагинов Joomla. Продолжение.

Продолжение потому, что начало уже было в статье Виталия Некрасова на Хабре.

Кратко.

В Joomla 5+ для событий аргументы упаковываются в собственные классы событий: ContentPrepareEvent, AfterSaveEvent и т.д. Данные из них мы получаем в виде $event->getArgument('argument_name') или [$var, $var2] = array_values($event->getArguments());. Также для разных типов событий могут быть специфичные методы типа $article = $event->getItem(); в ContentPrepareEvent и т.д. И в статье Виталия как раз об этом рассказывается.

А так же рассказывается о методах onGet и onSet. В ядре Joomla в классе \Joomla\CMS\Event\AbstractEvent сказано:

/**
   * Add argument to event.
   * It will use a pre-processing method if one exists. The method has the signature:
   *
   * onSet($value): mixed
   *
   * where:
   *
   * $value  is the value being set by the user
   * It returns the value to return to set in the $arguments array of the event.
   *
   * @param   string  $name   Argument name.
   * @param   mixed   $value  Value.
   *
   * @return  $this
   *
   * @since   4.0.0
   */

Добрался я тоже до своего класса события для плагинов, порылся в ядре и подумал, что onSet... и onGet... методы не обязательно делать (хотя в статье по ссылке об этом не упоминается). Это методы для "предварительных проверок и манипуляций" с данными перед тем, как они будут отданы через getArgument() или get<ArgumentName>. Метод getData() отдаст данные, которые предварительно будут обработаны методом onGetData(). Но обработаны они будут только в том случае, если метод реализован. Если нет, то ничего страшного. Ошибки не будет.😎

Эти методы напоминают своеобразные плагины внутри плагинов. На мой взгляд излишнее усложнение, хотя сеттеры и геттеры должны заниматься по идее только сеттерством и геттерством, а проверку/ приведение типов можно отдать в методы onSet... / onGet....

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Класс расширения (Extension) для компонентов Joomla 5

Перевод с английского: Joomla! Programmers Documentation for Joomla 5.2

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

  • Роутер Joomla может использовать роутер нашего компонента для анализа и создания ЧПУ-адресов;

  • Если наш компонент поддерживает категории, то com_categories будет отображать в представлении категорий сводку по каждой категории, содержащую количество материалов с этой категорией, с разбивкой по статусу публикации;

  • Если наш компонент поддерживает пользовательские поля, то com_fields будет вызывать метод getContexts(), чтобы получить типы материалов, к которым можно привязать пользовательские поля;

  • Если наш компонент поддерживает мультиязычные связи, то com_associations потребуется знать типы материалов, для которых возможны связи;

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

Причины введения класса расширения становятся понятнее, если рассмотреть, как другие части кода Joomla взаимодействовали с нашим расширением в Joomla 3.

В Joomla 3 все эти части кода обращались к кодовой базе нашего компонента довольно хаотичным образом — вызывая функции из различных вспомогательных файлов.

А в Joomla 4 это упрощено:

Начиная с Joomla 4, другие компоненты получают доступ к нашему компоненту com_example, вызывая метод:

$extension = $app->bootComponent("com_example");

Затем они могут вызывать необходимые им методы через этот экземпляр класса расширения.

Справка:

Класс расширения (Extension) компонента вы можете найти в административной части компонента. Например, для компонента com_content это файл Root/administrator/components/com_content/src/Extension/ContentComponent.php, для com_exampleRoot/administrator/components/com_example/src/Extension/ExampleComponent.php

Сразу после создания экземпляра класса расширения компонента код библиотеки Joomla вызовет метод boot вашего класса расширения, передавая дочерний экземпляр Контейнера внедрения зависимостей (Dependency Injection Container):

$extension->boot($container);

По сути, это возможность делать буквально всё, что вам нужно. Например, метод иногда используется для настройки определённых классов, которые будут использоваться с вызовами HtmlHelper::_(). Или же можно сохранить ссылку на ваш дочерний DI-контейнер (который иначе может быть сложно получить).

После первого создания экземпляра вашего компонента Joomla кэширует этот экземпляр. При повторном вызове

$extension = $app->bootComponent("com_example");

Joomla просто возвращает существующий экземпляр вашего класса расширения, вместо повторного создания объекта и вызова метода boot(). Вы даже можете вызвать bootComponent, передав свой компонент, чтобы получить ссылку на свой собственный объект расширения.

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Расширения и дочерние контейнеры в Joomla 5

Перевод с английского: Joomla! Programmers Documentation for Joomla 5.2

Всякий раз, когда Joomla загружает расширение, она создает дочерний Dependency Injection Container (далее контейнер), исключительно для использования этого расширения. Это показано на схеме ниже.

Child DIC
Child DIC

Дочерний контейнер содержит указатель на родительский контейнер и функционирует аналогично основному контейнеру, но с некоторыми отличиями:

  • При каждом вызове метода set() для этого дочернего контенера пара ключ-значение добавляется в дочерний контейнер;

  • При каждом вызове метода get() для этого дочернего контейнера ресурс извлекается из дочернего контейнера, но если он там не найден, то поиск выполняется также в родительском контейнере.

Начиная с версии Joomla 4, разработчики Joomla рекомендуют создателям расширений использовать внедрение зависимостей (dependency injection) для своих расширений, определяя файл services/provider.php. Загрузка расширения теперь выполняется в два этапа, которые обрабатываются внутри файла services/provider.php:

  1. Класс расширения регистрируется в дочернем контейнере;

  2. Класс расширения извлекается из дочернего контейнера, создавая экземпляр этого класса.

Давайте рассмотрим минимальный пример этого для компонента com_example с пространством имён Mycompany\Component\Example.

use Joomla\CMS\Extension\ComponentInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Mycompany\Component\Example\Administrator\Extension\ExampleComponent;
return new class implements ServiceProviderInterface
{
  public function register(Container $container): void
  {
    $container->set(
      ComponentInterface::class,
      function (Container $container)
      {
        $component = new ExampleComponent();
        return $component;
      }
    );
  }
};

Мы видим, что когда Joomla выполняет require этого PHP-файла, возвращается экземпляр класса.

$provider = require $path;  // $path points to the relevant services/provider.php file

Переменная $provider указывает на объект, который является экземпляром этого анонимного класса. Кроме того, класс реализует интерфейс Joomla\DI\ServiceProviderInterface, что, по сути, означает, что он содержит метод register с указанной выше сигнатурой.

Когда Joomla выполняет

if ($provider instanceof ServiceProviderInterface)
{
  $provider->register($container);

будет вызван метод register, который добавит запись в дочерний контейнер с

  • key - ComponentInterface::class – это сокращённый способ в PHP указать строку 'Joomla\CMS\Extension\ComponentInterface';

  • value - функция, которая возвращает новый экземпляр класса ExampleComponent (основного класса расширения компонента com_example).

$extension = $container->get($type);

Функция выше будет запущена и вернет новый экземпляр ExampleComponent.

Вы можете самостоятельно изучить код Joomla в файле libraries/src/Extension/ExtensionManagerTrait.php и убедиться, что описанный выше шаблон также применяется к модулям и плагинам.

Обратите также внимание на следующую строку в этом файле:

if ($extension instanceof BootableExtensionInterface)
{
  $extension->boot($container);
}

Таким образом, если класс расширения реализует интерфейс BootableExtensionInterface, Joomla немедленно вызовет метод boot() экземпляра расширения, как описано в документации по расширениям.

Теги:
Всего голосов 2: ↑2 и ↓0+3
Комментарии0

Почему не следует напрямую обращаться к глобальному контейнеру в Joomla 5

Что общего у нижеперечисленных методов?

  • Joomla\CMS\Factory::getCache()

  • Joomla\CMS\Factory::getDbo()

  • Joomla\CMS\Factory::getMailer()

  • Joomla\CMS\Application\AdministratorApplication::getRouter()

  • Joomla\CMS\Application\Administrator\ApiApplication::getRouter()

  • Joomla\CMS\Toolbar\Toolbar::getInstance()

Они объявлены устаревшими с Joomla 4.x и вместо них предлагается использовать Joomla\CMS\Factory::getContainer()->get('Соответствующий_интерфейс').

Например, вместо Joomla\CMS\Factory::getDbo()Factory::getContainer()->get(DatabaseInterface::class);

Но давайте посмотрим на описание метода Joomla\CMS\Factory::getContainer():

Возвращает глобальный контейнер сервисов, создавая его только в случае отсутствия.

Этот метод рекомендуется использовать только в коде, ответственном за создание новых сервисов и требующем разрешения зависимостей. Его следует применять только когда контейнер недоступен другими способами.

Допустимые сценарии использования:

  • Статический метод getInstance(), вызывающий сервис из контейнера (пример: Joomla\CMS\Toolbar\Toolbar::getInstance());

  • Фронт-контроллер приложения, загружающий и исполняющий класс Joomla (пример: файл cli/joomla.php);

  • Получение опциональных зависимостей конструктора во время переходного периода для сохранения обратной совместимости (в этом случае следует добавлять уведомление об устаревании).

Не рекомендуется использовать этот метод как прямую замену статическим вызовам, например заменять Factory::getDbo() на Factory::getContainer()->get(DatabaseInterface::class). Вместо этого код следует рефакторить для использования внедрения зависимостей.

В последнем абзаце видим явное противоречие и рекомендацию использовать зависимости.

Как же внедрять зависимости?

Рассмотрим как это сделано в плагине joomla группы content:

В сервис-провайдере плагина Root/plugins/content/joomla/services/provider.php используются методы setDatabase() и setUserFactory() для установки зависимостей.

$plugin->setDatabase($container->get(DatabaseInterface::class));
$plugin->setUserFactory($container->get(UserFactoryInterface::class));

А в классе расширения плагина plugins/content/joomla/src/Extension/Joomla.php используются методы getDatabase() и getUserFactory(). Аналогично в компонентах.

Мой пример использования:

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

Выводы:

  • Сервис-провайдер расширения — единая точка установки зависимостей для своего расширения.

  • Событие onAfterExtensionBoot - точка для замены зависимости в любом расширении.

  • Если бы расширения напрямую брали зависимости из глобального контейнера, такая замена была бы невозможна.

Материалы по теме:

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Работаем в лоб: Прямое редактирование XML форм Joomla

Используя событие onContentPrepareForm можно изменять почти любую форму Joomla, но методов класса Joomla\CMS\Form\Form обычно не хватает для работы со сложными формами (например с полями типа subform).

Но есть простое решение - работать с формой как c экземпляром SimpleXMLElement.

Получаем XML формы.

echo $form->getXml()->asXMl();
die;

Отправляем его в ChatGPT с описанием что и как надо изменить в форме, и просим написать PHP-код.

Например у меня такая форма:

<?xml version="1.0"?>
<form>
	<config>
		<fieldset label="PLG_CONTENT_WISHBOXRADICALMARTCDEKORDERREGISTRATOR_FIELDSET_LABEL" name="wishboxradicalmartcdekorderregistrator"
                  addfieldprefix="Joomla\Component\Wishboxradicalmartcdek\Administrator\Field">
			<field name="wishboxradicalmartcdekorderregistrator" type="subform"
                   label="PLG_CONTENT_WISHBOXRADICALMARTCDEKORDERREGISTRATOR_FIELD_REGISTRATOR_LABEL"
                   buttons="add,remove,move"
                   multiple="false"
                   hiddenLabel="true">
				<form>
					<fieldset>
						<field name="order_number_prefix" type="text"
                               label="COM_WISHBOXRADICALMARTCDEK_FIELD_ORDER_NUMBER_PREFIX_LABEL"
                               default="test_" />
					</fieldset>
				</form>
			</field>
		</fieldset>
	</config>
</form>

И для изменения атрибута default поля order_number_prefix получаем следующий код:

$fields = $xml->xpath('//field[@name="wishboxradicalmartcdekorderregistrator"]/form/fieldset/field[@name="order_number_prefix"]');

$fields[0]['default'] = 'Test ';

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

Теги:
Рейтинг0
Комментарии0

Заменяем ещё не устаревший метод Joomla\CMS\Toolbar\ToolbarHelper::custom

Недостаток данного метода заключается в том что мы не можем задать id для тега joomla-toolbar-button, он формируется из параметра icon. Это особенно неудобно когда требуется несколько кнопок с одинаковыми иконками.

было:

ToolbarHelper::custom(
	'cities.update',
	'refresh',
	'',
	Text::_('COM_WISHBOXCDEK_TOOLBAR_UPDATE_CITIES'),
	false
);
<joomla-toolbar-button id="toolbar-refresh" task="cities.update">
<button class="button-refresh btn btn-primary" type="button">
    <span class="icon-refresh" aria-hidden="true"></span>
    Обновить города
</button>
</joomla-toolbar-button>

стало:

Factory::getApplication()->getDocument()->getToolbar()->standardButton(
	'cities-update',
	'COM_WISHBOXCDEK_TOOLBAR_UPDATE_CITIES',
	'cities.update'
	)
	->icon('icon-refresh')
	->listCheck(false);
<joomla-toolbar-button id="toolbar-cities-update" task="cities.update">
<button class="button-cities-update btn btn-primary" type="button">
    <span class="icon-refresh" aria-hidden="true"></span>
    Обновить город</button>
</joomla-toolbar-button>
Теги:
Рейтинг0
Комментарии0