Joomla для разработчиков
СтатистикаПолезное из мира Joomla: - документация для разработчиков и опытных пользователей; - интересные находки. Мы ВКонтакте - https://vk.com/jpathru Наш веб-сайт - https://jpath.ru Есть интересный материал? @drekun
- Последний пост
- 25 июл.
- Последнее чтение
- 07:11
- Постов за неделю
- 0
- Всего постов
- 21
- Тип
- открытый
- Язык
- русский
- Категория
- Технологии
- В каталоге с
- 12 авг.
- 1/24сутки в ленте
- 432
- 1/48двое суток
- 495
- 1/72трое суток
- 533
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
🚨 AJAX - главная «слепая зона» безопасности В 2026 году продолжается волна атак на Joomla-сайты. Ботнеты массово сканируют сети в поиске одной и той же уязвимости — отсутствия проверки прав доступа в AJAX-обработчиках. Почему com_ajax стал идеальным «черным ходом» для хакеров и как защитить свой код? Разбираем анатомию ошибки. 🔍 Как устроена уязвимость? com_ajax создавался как легкий маршрутизатор, чтобы плагины и модули могли обрабатывать AJAX-запросы без создания собственных роутов. Главный капкан архитектуры — com_ajax НЕ проверяет авторизацию за вас. Разработчики часто путают три разных уровня защиты: 1. CSRF-токен (Session::checkToken()) — проверяет только то, что запрос отправлен с вашего сайта. Но он НЕ проверяет, кто его отправил. 2. Аутентификация ($user->guest) — проверяет, залогинен ли пользователь. 3. Авторизация (ACL / authorise()) — проверяет, имеет ли пользователь право выполнять действие. 💥 Реальные инциденты и уязвимости в Joomla: • Astroid Framework: Отсутствие авторизации в AJAX-обработчиках позволяет гостям и пользователям с минимальными правами вызывать служебные функции. • Novarian Framework: Аналогичные «слепые зоны» в AJAX-маршрутизации, открывающие доступ к выполнению кода без проверки ролей. • JoomShaper Helix3 (Волна взломов AntonKill): Массовый дефейс Joomla-сайтов через неаутентифицированный AJAX-эндпоинт фреймворка шаблона. • AcyMailing: Повышение привилегий из-за отсутствия проверки прав во внутреннем роутере (уязвимость затрагивает Joomla 9.11.0 – 10.8.1). ❌ Как делать НЕЛЬЗЯ (уязвимый код): Session::checkToken() or die('Invalid Token'); $input = Factory::getApplication()->getInput(); $this->deleteItem($input->getInt('id')); (Проверен только CSRF, но любой гость может удалить элемент). ✅ Как ДОЛЖНО быть (безопасный код): Session::checkToken() or die('Invalid Token'); $app = Factory::getApplication(); $user = $app->getIdentity(); if (!$user->authorise('core.manage', 'com_yourcomponent')) { throw new Exception('Access Denied', 403); } $this->deleteItem($app-> getInput()->getInt('id')); 🛠 Чек-лист базовой проверки: • Если вы пишете код: Никогда не полагайтесь только на com_ajax. Всегда вызывайте $user->authorise() перед выполнением действия. • Если вы администрируете сайты: Регулярно обновляйте фреймворки шаблонов (Astroid, Novarian, Helix) и сторонние расширения. • Проверьте старый код: Наличие checkToken() в AJAX-хендлере не делает его безопасным! 📖 Подробную хронологию разбора этой уязвимости читайте в статье на mySites.guru. 💬 Проверяете ACL на каждом AJAX-вызове в своих компонентах и плагинах? #joomla #php #security #webdev #cms #backend
👩💻 Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla. Как работает ядро Joomla 6 на уровне Приложения (Application)? Сколько видов Приложений на самом деле есть в Joomla и в чём между ними разница? Какой жизненный цикл каждого Приложения Joomla, включая роутинг, диспетчеризацию и события плагинов? На эти и другие вопросы попытается дать ответ эта статья, опирающаяся на кодовую базу Joomla 6.1.0. Также в статье хронология вызова триггеров плагинов в разных сценариях. 👉 Бонус для любопытных читателей: а вы знали, что Joomla можно использовать в качестве daemon-процесса? Читать статью @joomlafeed #joomla #разработка
👩💻 Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory. Разработчики сайтов, веб-мастера, рассматривая Joomla как CMS, чаще всего используют компоненты ядра такими, какие они есть. Но компоненты ядра, обеспечивающие CRUD-ы в Joomla, следует рассматривать ещё и как примеры использования Joomla в качестве фреймворка. Иногда реалии проекта таковы, что требуется внести изменения именно в логику классов ядра Joomla. Я покажу это на нескольких примерах: как исхитрялись раньше и какие возможности появились в современных версиях Joomla. В статье речь идёт не о том, чтобы править файлы ядра. Это плохая идея почти всегда. При обновлении Joomla такие изменения будут потеряны, а сопровождать их потом придётся вручную. Речь о другом: как изменить точку создания MVC-классов компонента через плагин и DI-контейнер, не залезая в core-файлы. Ну и приятный бонус - Joomla-археология и немного красивого и ужасного треша из практики 😎 Читать статью #joomla #разработка
👩💻 Особенность Joomla: json-значения для пользовательских полей и их рендер в subform и вне дочерней формы. Опять длинное название, но куда уж без этого... Итак, если вы делаете плагин пользовательского поля - его можно использовать через FieldsHelper. И в процессе ваши данные проходят через различные этапы обработки (недавно был пост+статья на эту тему). И может так оказаться, что ваше поле хранит в rawvalue json (и в базе данных соответственно тоже), а в value вы на его основе рендерите значение. Это стандартный подход Joomla. Так работают, например, поля accessiblemedia. Однако, если вы поместили ваше поле в дочернюю форму (пользовательское поле типа subform и включили "Рендеринг значений = Да", то у вашего замечательного поля может появиться поломанный Json в value вместо нормального значения. Например: {"basePath":"...","layout":"..."} ❓ Что там под капотом Joomla происходит? 1. В обычном потоке Joomla сначала вызывает событие onCustomFieldsBeforePrepareField, а потом onCustomFieldsPrepareField. 2. Внутри subform же для подполей при render_values=1 вызывается только событие - onCustomFieldsPrepareField. 3. Если преобразование значения (например, json_decode) сделано в вашем плагине только в beforePrepareField, оно не обработает данные для подполя и... 4. В шаблоне поля строка заэкранируется (htmlentities), кавычки превратятся в тыкву в " и вы получите кривой json, вместо вашего значения 👉 Собственно полезный совет по Joomla: Для полей, которые могут жить внутри subform, делайте нормализацию значения и в onCustomFieldsPrepareField тоже, не только в beforePrepareField. @webtolkru #joomla #webdev #разработка #php #development
👩💻 События плагинов и порядок их срабатывания при работе с пользовательскими полями Joomla и использовании FieldsHelper. В процессе работы с Joomla бывает необходимо работать с пользовательским интерфейсом более тонко, чем обычно. Все формы Joomla состоят из стандартных полей, содержанием, стилем отображения, состоянием (включено/выключено, доступно для редактирования или нет и т.д.) можно управлять с помощью плагинов. Да и для нестандартных проектов хорошей практикой является создание одного системного или нескольких плагинов групп "под проект", в которых храниться весь "нестандарт". В этой статье описаны все триггеры, которые вызываются через Event Dispatcher из administrator/components/com_fields/src/Helper/FieldsHelper.php, с привязкой к жизненному циклу (порядку этапов работы запроса), аргументам, изменяемым данным и дальнейшему распространению по Joomla. Это поможет вам работать с Joomla свободнее и не опасаясь при этом потерять изменения при очередном обновлении движка. Подходы, описанные в статье, полезны в тех случаях, когда вы работаете с данными в com_fields - механизме создания и редактирования пользовательских полей ядра Joomla и при использовании FieldsHelper. Многие сторонние компоненты не используют эту возможность, поэтому данная статья будет полезна лишь частично. Читать статью на Хабре. @joomlafeed #joomla #разработка
👩💻 Совет по Joomla: использовать выключенное состояние для кнопок в списках элементов админки - listCheck(). Мы добавляем в тулбар панели администратора Joomla некую кнопку, которая что-то делает со списком id выделенных элементов и ajax-запросом отсылаем их в свой плагин. Но нам надо предупредить нажатия на кнопку в тех случаях, когда ни один элемент не был выбран. Для этого можно написать свою проверку на js. А можно воспользоваться встроенной в Joomla. Как это сделать - читаем в посте на Хабре. @joomlafeed #joomla #php #разработка #webdev
👩💻 Обработка HTTP ответа в Joomla 6+. Изменения по сравнению с Joomla 3 - Joomla 5. В Joomla для выполнения внешних запросов из PHP к сторонним API используется класс Joomla\Http\Http напрямую или же Joomla\Http\HttpFactory, который возвращает для работы преднастроенный по умолчанию класс Http. О работе с HTTP-запросами подробно рассказывалось в статье 2021 года Создание внешних запросов с использованием HttpFactory (Joomla). Некоторые изменения касаются работы с ответами на запросы. Например, наш запрос: use Joomla\Http\HttpFactory; $http = (new HttpFactory)->getHttp($options, ['curl', 'stream']); $response = $http->get('https://any-url.ru/api/any/endpoint'); Раньше можно было получить код ответа или тело ответа как свойство $response - $response->code или $response->body. Однако, Joomla, начиная с Joomla 4 во многом переходит на стандарты PSR. В частности для работы с HTTP-ответами - на PSR-7. Также хорошая статья на Хабре о PSR-7: PSR-7 в примерах. Прямое обращение к свойствам code, headers, body объявлено устаревшим в Joomla 6.0.0 и обещают удалить в Joomla 7.0.0. Вместо этого нужно работать с HTTP-ответом по стандартам PSR-7. Код ответа. Было $response->code. Стало $response->getStatusCode(). Заголовки ответа. Было $response->headers. Стало $response->getHeaders(). Тело ответа. Было $response->body. Стало (string)$response->getContents(). В тело ответа теперь приходит не строка, а поток - объект класса Laminas\Diactoros\Stream. Поэтому его нужно привести к строке (если это json, к примеру): (string)$response->getContents(). Чаще всего в коде Joomla встречается именно такой вариант. Однако, есть и вариант с перемещением указателя чтения на начало потока: // Получили ответ в виде потока $stream = $response->getBody(); // "перемотали" на начало $stream->rewind(); // Получили строковый ответ $json = $stream->getContents(); В итоге результат одинаковый. @joomlafeed #joomla #разработка #php
👩💻 Совет по Joomla: замена File::append() в Joomla 6. В Joomla постепенно, хоть порой и непоследовательно, убирают старое API, доставшееся в наследство от CMS и заменяют его на API от Joomla Framework. При этом порой не обходится без потерь. Многие классы и методы ядра годами имеют статус deprecated, многим имеется альтернатива. Такими классами являются классы для работы с файловой системой сервера Joomla\CMS\Filesystem. Они перемещены в плагин обратной совместимости Joomla 6 и могут ещё работать. Но лучше постепенно переходить на новые методы, а в новых расширениях использовать их сразу. Одним из "пострадавших" методов стал File::append(), который добавлял содержимое в конец файла. Это обёртка для file_put_contents() с разными проверками, использованием потоков, если надо и т.д. Заменой для этого метода станет File::write() с флагом appendToFile. Параметры метода - string $file – Абсолютный путь к файлу - string $buffer – Содержимое - bool $useStreams false – Использовать потоки - bool $appendToFile false – дописывать ли в конец или перезаписывать файл use Joomla\Filesystem\Filesystem; $absoluteFilePath = JPATH_SITE . '/dir/file.txt'; $content = 'Содержимое файла'; // appentToFile - 4-й аргумент File::write($absoluteFilePath, $content, false, true); Либо с PHP 8.0 можно использовать именованные аргументы, и тогда не важен их порядок и наличие необязательных. use Joomla\Filesystem\Filesystem; $absoluteFilePath = JPATH_SITE . '/dir/file.txt'; $content = 'Содержимое файла'; //Именованные аргументы, appentToFile теперь 3-й аргумент File::write(file: $absoluteFilePath, buffer: $content, appendToFile: true); Класс находится в libraries/vendor/joomla/filesystem/src/File.php. Кстати, для некоторых удалённых методов теперь нужно использовать нативные функции php. - вместо Folder::exists() ➡️ is_dir() - вместо File::exists() ➡️ is_file() или file_exists() @webtolkru #joomla #php #webdev #разработка #development
👩💻 Совет по Joomla: несколько значений по умолчанию в XML-формах для стандартных полей типа list. При работе над плагином возникла необходимость указать стандартный набор из нескольких элементов стандартного поля списка. И хотелось указать их в стандартном же атрибуте default для полей. ❓ Когда это нужно? Когда Ваши пользователи устанавливают плагин и НЕ заходят в настройки - в коде вы можете использовать значения по умолчанию с помощью класса Registry (писал об этом ранее) и всегда быть уверенным, что хоть какие-то жизненно необходимые параметры к вам придут всегда. 🧐 Но как сделать то же самое для интерфейса админки? Пользователя нужно направлять, предлагать очевидный работоспособный сценарий для начала, а дальше он уже сам разберется. Когда человек заходит в параметры свежеустановленного плагина в Form ещё нет данных и параметры по умолчанию выставляются из атрибутов default в xml-полях. <field name="showdesc" type="radio" label="PLG_CFI_PARAM_SHOWDESC" class="btn-group btn-group-yesno" default="1"> <option value="0">JNO</option> <option value="1">JYES</option> </field> Здесь по умолчанию будет включено "Да". И если пользователь не переключит параметр, то при сохранении мы ожидаемо получим "да" в params плагина. Для поля списков type="list" можно указать значение по умолчанию и многие знают, что его можно указать только одно. <field type="list" name="article_fields" label="article_fields" description="article_fields_desc" layout="joomla.form.field.list-fancy-select" multiple="true" default="id" > <option value="id">id</option> <option value="title">title</option> <option value="alias">alias</option> <option value="introtext">introtext</option> <option value="fulltext">fulltext</option> <option value="state">state</option> </field> Но у нас поле с множественным выбором (атрибут multiple) и хотелось бы указать несколько значений по умолчанию... Оказывается, так можно сделать. Для этого в атрибуте default нужно указать json с нужными параметрами в виде {int}key : {string} value. Например, default='{"0":"id","1":"title"}'. <field type="list" name="article_fields" label="article_fields" description="article_fields_desc" layout="joomla.form.field.list-fancy-select" multiple="true" default='{"0":"id","1":"title"}' > <option value="id">id</option> <option value="title">title</option> <option value="alias">alias</option> <option value="introtext">introtext</option> <option value="fulltext">fulltext</option> <option value="state">state</option> </field> ⚠️ Обратите внимание на кавычки! Поскольку json_decode не понимает одинарные кавычки собственно json нужно писать с двойными, а значение для атрибута default писать в одинарные. 🙏 За подсказанное решение огромное спасибо участникам нашего сообщества - разработчикам Дмитрию Васюкову (@fictionlabs) и Игорю Бердичевскому (@septdir). @joomlafeed #joomla #разработка #webdev #development
без подписи
без подписи
👩💻 Как триггерить события для плагинов на манер 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. @joomlafeed #joomla #php #webdev
👩💻 Свои типы полей в 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'; 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 ''; } } Просто и удобно. И людям приятно, что о них позаботились и рассказали почему что-то не работает. @webtolkru #joomla #php #webdev #разработка
без подписи
без подписи
без подписи
без подписи
без подписи
Новый способ добавления и рендера полей в Joomla Form - метод renderControlFields() (Joomla 5.3+). При отображении форм компонента в админке раньше разработчикам нужно было описывать скрытые поля в лейаутах. И выглядело это так: <?php // Это файл в /layouts/components/com_component/your_layout.php ?> <input type="hidden" name="task" value=""> <input type="hidden" name="return" value="<?php echo $input->getBase64('return'); ?>"> <input type="hidden" name="forcedLanguage" value="<?php echo $input->get('forcedLanguage', '', 'cmd'); ?>"> <?php echo HTMLHelper::_('form.token'); ?> Начиная с Joomla 5.3 добавлен новый способ добавления таких полей (их называют control fields) - программно. И теперь это можно сделать просто в Controller / View. // В Controller/View добавляем поля $this->form ->addControlField('task', '') ->addControlField('return', $input->getBase64('return', '')) ->addControlField('forcedLanguage', $forcedLanguage); А в layout'е просто используем метод renderControlFields() // В layout формы echo $this->form->renderControlFields(); Многие компоненты ядра уже используют этот подход. Соответствующий PR был принят в Joomla 5.3 осенью 2024г. Смотреть Pull Request @joomlafeed #joomla #разработка #php
👩💻 Совет по Joomla: $db->getQuery(true) стал устаревшим. Раньше при создании запросов в базу данных мы всегда получали объект запроса с помощью метода getQuery(). Если передавался параметр $new = true, то получали новый запрос. Если без - последний. use Joomla\CMS\Factory; $db = Factory::getContainer()->get('DatabaseDriver'); $query = $db->getQuery(new: true); // и строим запрос Под капот Joomla 4 вошёл Joomla Framework (PHP фреймворк а-ля Laravel, Yii и т.д.), а для Joomla 5 он обновился - версия 3.4.1 вышла 6 октября 2023г. В методе фреймворка есть примечание о том, что параметр $new, который передавался в метод getQuery() помечен как устаревший и будет удалён в версии фреймворка 4.0. The parameter $new is deprecated and will be removed in 4.0, use %s::createQuery() instead. В Joomla 4 версия Joomla Framework была 2.x., в Joomla 5 - 3.x. Поэтому есть предположение, что с выходом Joomla 6 обновится и версия фреймворка - станет 4.0. Поэтому для получения объекта для новых запросов следует использовать метод createQuery(). Он уже представлен в ядре и широко используется. При этом метод getQuery() останется, но, скорее всего, будет выполнять функцию только геттера - получения текущего запроса. @joomlafeed #joomla #php #разработка