Всем привет! Давно не писал. Взглянул на календарь, а год уже почти и закончился. Нужно исправляться и ОНО у меня для вас есть). Добро пожаловать в новый лонгрид, где весь текст написан естественным интеллектом его автора.
Все мы знаем, что в Joomla есть настраиваемые поля. Их можно создать в любых количествах, можно разбить на группы, можно добавить к статьям сайта. Красота!
Но вот чего из коробки сделать нельзя, так это фильтровать по полям списки материалов. Например, как если бы мы хотели вывести все статьи категории, но с фильтром по одному из настраиваемых полей.
В этой статье будет показана крутая техника, которая позволит добавить на ваш сайт такой функционал, причем без хаков и применения сторонних расширений.
Содержание
- Вступление
- Планирование
- Шаг 1. Создаем файл для нового Представления (View)
- Шаг 2. Создаем файл нового типа xml-поля (Customfield)
- Шаг 3. Создаем файлы контроллера, модели и HtmlView
- Шаг 4. Создаем плагин для регистрации нового View в роутере com_content
- Ограничения данного решения
- Ссылки на скачивание
- Заключение
Вступление
Мне, как думаю и многим из вас, часто не хватает в Joomla возможности отобрать статьи по какому-нибудь настраиваемому полю (или группе полей).
Возьмем простой пример – сайт wedal.ru. Здесь публикуются статьи про Joomla.
- Было бы здорово иметь возможность маркировки и фильтрации таких статей, указывая, для какой версии Joomla они пригодны.
- Было бы здорово иметь маркировки и фильтрации таких статей, указывая, для какого уровня подготовки людей они будут понятны (новичкам, администраторам контента, владельцам сайтов, программистам)
Как решить эту задачу? Из коробки нам доступно несколько способов, каждый из которых имеет критические недостатки:
- Мы могли бы использовать теги. Создать теги версий и теги уровней подготовки. Для каждой статьи устанавливать подходящие теги. Создать пункт меню, отбирающий статьи по тегам. Это будет работать, но при увеличении количества тегов (фактически – полей и их значений) на сайте начнется бардак. Все значения будут сваливаться в единственное у статьи поле «Теги» и там будет каша. Кроме того, теги не очень пригодны для вывода данных внутри статьи в красивом виде. Ну и последнее: пункт меню, создающий список материалов по тегу, очень далек от того, что дает «Блог категории».
- Мы могли бы использовать умный поиск и его функционал «фильтры поиска». Там можно создать дополнительные поля, включить индексацию по ним, проиндексировать весь контент, создать фильтры. Это тоже можно заставить работать, но результат в итоге будет еще хуже, чем с тегами. Я проверял).
Давайте немного помечтаем, как было бы идеально:
- Мы создаем дополнительные поля Joomla – столько, сколько нам нужно, и такие, какие нам нужно (в нашем примере: версия Joomla и уровень подготовки).
- Добавляем значения полей всем статьям определенной категории на сайте (в нашем примере: Уроки Joomla)
- Создаем пункт меню типа «Блог категории», в котором, помимо категории, можно указать значения необходимых нам полей
- В результате на сайте появляется ссылка, созданная через этот пункт меню, ведущая на страницу со своими уникальными URL, Title, H1, Meta Desс, на которой используется тот же макет, что и у обычного Блога категории, со всеми его настройками, выводящий статьи, отфильтрованные по значениям полей, но при этом имеющие родные URL от категории «Уроки Joomla». И главное – разбиение на страницы (пагинация) работает правильно и не нарушается.
Звучит как фантастика? Давайте воплотим ее в реальность!
Планирование
Joomla хороша. Очень. Далее вы это почувствуете.
Чтобы делать выборку статей по значению настраиваемого поля недостаточно просто сделать запрос в базу данных и загрузить объекты всех полученных материалов. Почему? Причин несколько:
- Отсутствие разбиения на страницы (пагинации)
- Код компонента материалов Joomla com_content достаточно сложный и учитывает множество настроек и нюансов, в том числе, контроль доступа, публикации, даты, теги, рейтинги и др.
Как же тогда быть? Мы модифицируем com_content. Но не кидайте тапками, хаков не будет!
Для расширения функционала компонента контента мы создадим собственный, новый view. Он будет наследовать все возможности стандартного view типа «Блог категории», но при это добавлять свои изменения. Количество кода в нем будет минимальным.
Как это вообще возможно? Все дело в том, как Joomla ищет и подключает новые классы. Эта возможность базируется, в том числе, на именовании каталогов и файлов внутри компонента. Проще говоря, зная, по каким названиям и путям Joomla проверяет файлы при поиске классов, мы можем эти классы расширять, вообще не касаясь кода ядра. Да, на пути будет пара трудностей, но они лишь закаляют характер)
Ниже я опишу пошаговый процесс расширения компонента материалов Joomla. Каждый шаг важен!
Шаг 1. Создаем файл для нового Представления (View)
Первым делом создадим файл нового представления (View). Что нам это даст? Возможность выбирать новый тип пункта меню в админке.
1.1 Создаем каталог filter:
/components/com_content/tmpl/filter
1.2 Создаем в нем файлы:
default.php:
<?php
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
// Рендерим стандартный блог категории
// Добавляем путь к папке макетов категорий в начало очереди поиска
$this->addTemplatePath(JPATH_SITE . '/components/com_content/tmpl/category');
// Если используем альтернативный макет из шаблона сайта, нужно также добавить путь к переопределениям в шаблоне
$template = Factory::getApplication()->getTemplate();
$this->addTemplatePath(JPATH_SITE . '/templates/' . $template . '/html/com_content/category');
// Устанавливаем макет. Если этого не сделать, получим стандартный блог категории
$this->setLayout('templates');
// Загружаем и выводим макет
echo $this->loadTemplate();
// Если хочется сделать свой собственный макет, удаляем все, что выше и пишем с нуля
default.xml:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="Фильтр по параметрам" option="filter">
<message>
<![CDATA[Позволяет вывести материалы категории, отфильтрованные по настраиваемым полям]]>
</message>
</layout>
<!-- Add fields to the request variables for the layout. -->
<fields name="request">
<fieldset name="request"
addfieldprefix="Joomla\Component\Categories\Administrator\Field"
>
<field
name="id"
type="modal_category"
label="JGLOBAL_CHOOSE_CATEGORY_LABEL"
extension="com_content"
required="true"
select="true"
new="true"
edit="true"
clear="true"
/>
<field
name="filter_tag"
type="tag"
label="JTAG"
multiple="true"
mode="nested"
custom="deny"
/>
</fieldset>
</fields>
<!-- Add fields to the parameters object for the layout. -->
<fields name="params">
<fieldset name="custom_filter" label="Фильтр материалов">
<field
name="filter_version"
type="Customfield"
fieldname="filter-version"
/>
<field
name="filter_level"
type="Customfield"
fieldname="filter-level"
/>
</fieldset>
<fieldset name="basic" label="JGLOBAL_CATEGORY_OPTIONS">
<!-- Далее идут стандартные поля из xml-файла блога категории Joomla -->
Давайте разберемся с их содержимым.
default.xml мы берем от блога категории (/components/com_content/tmpl/category/blog.xml ) и немного модифицируем, а именно:
- Изменяем строчку:
<layout title="Фильтр по параметрам" option="filter">
- Добавляем новые xml-поля:
<fieldset name="custom_filter" label="Фильтр материалов">
<field
name="filter_version "
type="Customfield"
fieldname="filter-version"
/>
<field
name="filter_level"
type="Customfield"
fieldname="filter-level"
/>
</fieldset>
Эти поля являются только примером. Подробнее о них и о том, как их создавать, речь пойдет ниже.
default.php является основным макетом вывода на странице. И здесь у нас три варианта:
- Сформировать свой уникальный вывод результатов – в этом случае мы просто пишем в макет код вывода в том виде, в котором нам нужно
- Использовать для вывода стандартный макет блога категории Joomla
- Использовать альтернативный макет вывода от блога категории Joomla, переопределенный в шаблон
В случае 2 и 3 вариантов посмотрите на комментарии в коде макета default.php – там я привел примеры как реализовать и тот и другой.
Мы создали новый View и уже сейчас он появится в выборе типов пунктов меню в админке:

Появиться он появился, но работать пока, разумеется, не будет.
Шаг 2. Создаем файл нового типа xml-поля (Customfield)
Следуя фантастическому плану, мы хотим выбирать в настройках пункта меню значение полей для фильтрации. Например:
- Уровень подготовки: новичок
- Версия Joomla: 5.x
Предполагается, что эти поля уже созданы в админке, в разделе полей и представлены в виде списков (классический select, в Joomla этот тип поля называется List).
Как добавить выбор значений полей в наш пункт меню? В Joomla не существует XML-поля такого типа. Создадим свое! Для этого добавим файл:
/administrator/components/com_content/src/Field/CustomfieldField.php
со следующим содержимым:
<?php
namespace Joomla\Component\Content\Administrator\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\FormField;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\CMS\Factory;
class CustomfieldField extends FormField
{
protected $type = 'Customfield';
/**
* Метод для настройки поля на лету
*/
public function setup(\SimpleXMLElement $element, $value, $group = null)
{
$targetFieldName = (string) $element['fieldname'];
if (empty($targetFieldName)) {
return parent::setup($element, $value, $group);
}
$customFields = FieldsHelper::getFields('com_content.article');
$targetField = null;
foreach ($customFields as $f) {
if ($f->name === $targetFieldName) {
$targetField = $f;
break;
}
}
if ($targetField) {
//Перезаписываем существующие атрибуты
$element['type'] = (string) $targetField->type;
$element['label'] = (string) $targetField->title;
$element['description'] = (string) $targetField->description;
// Переносим параметры из Custom Fields
if (!empty($targetField->fieldparams)) {
foreach ($targetField->fieldparams as $paramName => $paramValue) {
// Если параметра еще нет в XML, добавляем его
if (is_scalar($paramValue) && !isset($element[$paramName])) {
$element[$paramName] = (string) $paramValue;
}
// Обработка опций для списков (добавляем дочерние элементы, если их еще нет)
if ($paramName === 'options' && (is_array($paramValue) || is_object($paramValue)) && !isset($element->option)) {
if ((string) $element['type'] === 'list') {
$element['layout'] = 'joomla.form.field.list-fancy-select';
}
foreach ($paramValue as $option) {
$optionData = (array) $option;
$opt = $element->addChild('option', htmlspecialchars($optionData['name']));
$opt->addAttribute('value', $optionData['value']);
}
}
}
}
}
return parent::setup($element, $value, $group);
}
// Метод getInput нам не нужен, так как parent::setup() вызовет метод нужного нам типа
protected function getInput()
{
return '';
}
}
Что мы делаем и как это вообще работает? Мы создаем новый тип поля Customfield. При его генерации код достает из xml-представления поля атрибут fieldname и пытается найти среди созданных настраиваемых полей Joomla поле с таким же именем. Если это удается, то код достает все необходимые атрибуты из поля Joomla и генерирует select. Круто? Несомненно!
И еще обратите внимание на местоположение нового файла. Он создан в папке administrator, в компоненте com_content. Но создание такого файла не является хаком! Это именно новый файл с новым именем и Joomla о нем ничего бы и не знала, если бы не ее продуманная система поиска классов по именам. Обратите внимание, что название поля является частью названия файла класса. Файлы важно называть правильно, тогда и появляется вся эта магия.
Что ж, если все сделано правильно, в нашем новом типе пункта меню при создании вы увидите поля, совпадающие с созданными настраиваемыми полями и позволяющие выбирать их значения:

Выглядит эффектно, но основное колдовство еще впереди)
Шаг 3. Создаем файлы контроллера, модели и HtmlView
С интерфейсом создания страниц с фильтрами все готово, но на сайте пока ничего работать не будет. Joomla видит новое представление, но не знает как его обрабатывать. Мы должны научить ее:
- создать файл контроллера, который объяснит Joomla, какие файлы вызывать и что показывать
- создать файл модели, в который научит Joomla фильтровать материалы с учетом настраиваемых полей
- создать файл подготовки данных для макета, который, внезапно, подготовит данные для нового макета.
Звучит сложно? Да. В реальности 2 из 3 файлов практически пустые =)
3.1 Создаем файл контроллера FilterController:
/components/com_content/src/Controller/FilterController.php
Его содержимое:
<?php
namespace Joomla\Component\Content\Site\Controller;
defined('_JEXEC') or die;
use Joomla\Component\Content\Site\Controller\DisplayController as ContentDisplayController;
class FilterController extends ContentDisplayController
{
// Нам не нужно переопределять display(),
// так как родительский метод из DisplayController сделает всё за нас.
}
Как видим, здесь создается только пустой класс, расширяющий родительский класс контроллера. Добавлять в него ничего не нужно, но сам он должен существовать, чтобы Joomla корректно обрабатывала наш новый View.
3.2 Создаем файл подготовки данных для макета HtmlView:
components/com_content/src/View/Filter/HtmlView.php
Его содержимое:
<?php
namespace Joomla\Component\Content\Site\View\Filter;
defined('_JEXEC') or die;
use Joomla\Component\Content\Site\View\Category\HtmlView as CategoryView;
class HtmlView extends CategoryView
{
}
Этот файл также имеет пустой класс. Вся прелесть подхода: данные готовит родной класс CategoryView Joomla, а мы лишь «присасываемся» к нему и тянем все полезные методы. В случае, если Joomla обновиться (и добавятся, например, новые настройки в тип пункта меню Блог категории), наш View автоматически унаследует все эти изменения, как будто он родной для Joomla, а не приёмный =).
3.3 Создаем файл модели FilterModel:
components/com_content/src/Model/FilterModel.php
Его содержимое:
<?php
namespace Joomla\Component\Content\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormFactoryInterface;
use Joomla\Component\Content\Site\Model\CategoryModel;
class FilterModel extends CategoryModel
{
protected function populateState($ordering = 'a.ordering', $direction = 'ASC')
{
parent::populateState($ordering, $direction);
$app = Factory::getApplication();
$params = $app->getParams();
$filters = [];
$xmlPath = JPATH_SITE . '/components/com_content/tmpl/filter/default.xml';
if (file_exists($xmlPath)) {
// Загружаем XML
$xml = simplexml_load_file($xmlPath);
$customFields = $xml->xpath("//fieldset[@name='custom_filter']/field");
}
if ($customFields) {
foreach ($customFields as $field) {
// Проверяем тип
if ((string) $field['type'] === 'Customfield') {
$nameInXml = (string) $field['name'];
$joomlaFieldName = (string) $field['fieldname'];
// Получаем значение из параметров пункта меню
$value = $params->get($nameInXml);
if ($value !== null && $value !== '') {
$filters[$joomlaFieldName] = $value;
}
}
}
}
$this->setState('filter.custom_fields_data', $filters);
}
public function getItems()
{
$limit = $this->getState('list.limit');
if ($this->_articles === null && $category = $this->getCategory()) {
// Загружаем расширенную модель статей
$model = $this->bootComponent('com_content')->getMVCFactory()
->createModel('Filterarticles', 'Site', ['ignore_request' => true]);
// Передаем данные фильтра из текущей модели во внутреннюю
$model->setState('filter.custom_fields_data', $this->getState('filter.custom_fields_data'));
// Заполняем остальные стандартные состояния (копируем логику родителя)
$model->setState('params', Factory::getApplication()->getParams());
$model->setState('filter.category_id', $category->id);
$model->setState('filter.published', $this->getState('filter.published'));
$model->setState('filter.access', $this->getState('filter.access'));
$model->setState('filter.language', $this->getState('filter.language'));
$model->setState('filter.featured', $this->getState('filter.featured'));
$model->setState('list.ordering', $this->_buildContentOrderBy());
$model->setState('list.start', $this->getState('list.start'));
$model->setState('list.limit', $limit);
$model->setState('list.direction', $this->getState('list.direction'));
$model->setState('list.filter', $this->getState('list.filter'));
$model->setState('filter.tag', $this->getState('filter.tag'));
// Filter.subcategories indicates whether to include articles from subcategories in the list or blog
$model->setState('filter.subcategories', $this->getState('filter.subcategories'));
$model->setState('filter.max_category_levels', $this->getState('filter.max_category_levels'));
$model->setState('list.links', $this->getState('list.links'));
if ($limit >= 0) {
$this->_articles = $model->getItems();
if ($this->_articles === false) {
$this->setError($model->getError());
}
} else {
$this->_articles = [];
}
$this->_pagination = $model->getPagination();
}
return $this->_articles;
}
}
Здесь нам нужно переопределить 2 метода:
- populateState – чтобы доставал данные из xml-манифеста нового представления и выяснял, какие, собственно, поля нужно использовать для фильтрации
- getItems – этот метод в родительском классе отвечает за получение списка материалов. С ним нам нужно сделать пару модификаций: передать в него информацию о настраиваемых полях и вызвать еще одну модифицированную модель Filterarticles, которую мы создадим на следующем шаге.
3.4 Создаем файл модели FilterarticlesModel:
/components/com_content/src/Model/FilterarticlesModel.php
Его содержимое:
<?php
namespace Joomla\Component\Content\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Component\Content\Site\Model\ArticlesModel;
class FilterarticlesModel extends ArticlesModel
{
protected function getListQuery()
{
$query = parent::getListQuery();
$db = $this->getDatabase();
$filters = $this->getState('filter.custom_fields_data');
if (!empty($filters)) {
// 1. Получаем ID полей одним запросом
$fieldNames = array_keys($filters);
$fieldIdQuery = $db->getQuery(true)
->select($db->quoteName(['name', 'id']))
->from($db->quoteName('#__fields'))
->where($db->quoteName('name') . ' IN (' . implode(',', $db->quote($fieldNames)) . ')');
$db->setQuery($fieldIdQuery);
$fieldMap = $db->loadAssocList('name', 'id');
// 2. Создаем отдельный быстрый запрос только по таблице #__fields_values
$sub = $db->getQuery(true)
->select($db->quoteName('fv.item_id'))
->from($db->quoteName('#__fields_values', 'fv'))
->group($db->quoteName('fv.item_id'));
$conditions = [];
foreach ($filters as $fieldName => $value) {
if (empty($fieldMap[$fieldName])) continue;
$fId = (int) $fieldMap[$fieldName];
$valSql = is_array($value)
? $db->quoteName('fv.value') . ' IN (' . implode(',', $db->quote($value)) . ')'
: $db->quoteName('fv.value') . ' = ' . $db->quote($value);
$conditions[] = '(' . $db->quoteName('fv.field_id') . ' = ' . $fId . ' AND ' . $valSql . ')';
}
if (!empty($conditions)) {
$sub->where('(' . implode(' OR ', $conditions) . ')');
$sub->having('COUNT(DISTINCT ' . $db->quoteName('fv.field_id') . ') = ' . (int) count($conditions));
// 3. Выполняем выборку ID подходящих материалов
$db->setQuery($sub);
$targetIds = $db->loadColumn();
// 4. Модифицируем основной запрос
if (!empty($targetIds)) {
// Добавляем фильтрацию по найденным ID
$query->where($db->quoteName('a.id') . ' IN (' . implode(',', $targetIds) . ')');
} else {
// Если совпадений нет, заставляем запрос вернуть пустой результат
$query->where('1 = 0');
}
}
}
//echo '<pre>' . $query->dump() . '</pre>';
return $query;
}
}
Что это и зачем? Сейчас будет немного сложно – внимательно следите за руками!
- Выборка материалов категории делается SQL-запросом в базу данных
- Этот запрос собирается в методе getListQuery, который находится в модели /components/com_content/src/Model/ArticlesModel.php
- Мы не можем просто так переопределить этот метод в нашей модели components/com_content/src/Model/FilterModel.php, т.к. она является потомком CategoryModel, а не ArticlesModel
- Поэтому мы создаем еще одну новую модель FilterarticlesModel, которая является потомком ArticlesModel, и в нашей модели категории FilterModel, в переопределенном методе getItems вызываем именно FilterarticlesModel, а не ArticlesModel.
Этот трюк позволяет нам получить чистый доступ к методу getListQuery и модифицировать запрос на выборку материалов.
Также обратите внимание на код модифицированного метода getListQuery, а именно на то, как модифицируется SQL-запрос. Мы могли бы использовать классические наборы JOIN’ов для присоединения таблицы со значениями настраиваемых полей столько раз, сколько выбрано полей для фильтрации, но в случае больших таблиц этот метод начинает работать очень медленно. В предложенном коде используется всегда только один JOIN.
И второе – мы не просто присоединяем таблицу полей к таблице статей, которую создает Joomla по умолчанию, а делаем отдельный запрос на выборку по таблице полей, а затем полученные ID добавляем в основной запрос через WHERE IN. Этот вариант в 2 SQL-запроса работает ГОРАЗДО быстрее, чем вариант в один запрос с присоединением таблицы полей. Я не буду здесь объяснять почему, т.к. это может растянуться на еще одну такую же статью. Просто поверьте на слово).
После создания всех файлов, на странице нового View мы, наконец, сможем увидеть список отфильтрованных материалов с нормальной пагинацией. Но остается еще одно неприятное НО. О нем далее.
Шаг 4. Создаем плагин для регистрации нового View в роутере com_content
Всё уже работает, но на странице появятся предупреждения такого вида:
Warning: Undefined array key "filter" in \libraries\src\Component\Router\Rules\StandardRules.php on line 197
Warning: Attempt to read property "key" on null in e\libraries\src\Component\Router\Rules\StandardRules.php on line 199
Почему это происходит? Роутер com_content жестко прописан для работы с article, category, categories, archive и featured. Когда мы передаем view=filter, штатный обработчик не находит его в списке известных и не знает, как собрать/разобрать URL.
Чтобы исправить это без правки ядра, нам нужно «зарегистрировать» новое представление в роутере com_content. Поскольку мы не можем просто отредактировать src/Service/Router.php в ядре, напишем системный плагин.
4.1 Файл манифеста: plugins/system/contentfilter/contentfilter.xml
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - Content Filter Router Support</name>
<author>Wedal</author>
<creationDate>2025</creationDate>
<version>1.0.0</version>
<description>Регистрирует кастомное представление filter в роутере com_content</description>
<namespace path="src">Wedal\Plugin\System\ContentFilter</namespace>
<files>
<folder plugin="contentfilter">services</folder>
<folder>src</folder>
</files>
</extension>
4.2 Основной файл плагина: plugins/system/contentfilter/src/Extension/ContentFilter.php
<?php
namespace Wedal\Plugin\System\ContentFilter\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Router\SiteRouter;
class ContentFilter extends CMSPlugin implements SubscriberInterface
{
/**
* Подписываемся на событие инициализации приложения
*/
public static function getSubscribedEvents(): array
{
return [
'onAfterRoute' => 'registerContentFilterView',
];
}
/**
* Application object.
*
* @var CMSApplication
* @since 1.0
*/
protected $app;
/**
* Регистрируем представление 'filter' в роутере com_content
*/
public function registerContentFilterView(): void
{
if (!$this->app->isClient('site')) {
return;
}
try {
// $router = $this->app->getRouter();
$router = Factory::getContainer()->get(SiteRouter::class);
$contentRouter = $router->getComponentRouter('com_content');
if ($contentRouter instanceof RouterView) {
// Создаем конфигурацию для нашего представления
$filterView = new RouterViewConfiguration('filter');
$filterView->setKey('id');
// Регистрируем view в роутере
$contentRouter->registerView($filterView);
}
} catch (\Exception $e) {
}
}
}
4.3 Файл plugins/system/contentfilter/services/provider.php
<?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\CMS\Router\SiteRouter;
use Joomla\DI\ServiceProviderInterface;
use Wedal\Plugin\System\ContentFilter\Extension\ContentFilter;
use Joomla\Event\DispatcherInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$config = (array) PluginHelper::getPlugin('system', 'contentfilter');
$plugin = new ContentFilter($dispatcher, $config);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
В принципе, можно обойтись и без плагина, заглушив предупреждения, а для роутинга понадеятся на роутер для пунктов меню (ведь мы все равно создаем все страницы в нашем новом представлении через пункты меню), но лучше делать правильно. К тому же плагин я уже написал за вас =). В конце статьи будут все ссылки на файлы с кодом и плагин.
Ограничения данного решения
У данного подхода практически нет ограничений. Важно внимательно следить за расположением и именованием файлов и классов и вы получите практически неограниченные возможности по расширению функционала любого компонента Joomla.
Единственным, на мой взгляд, ограничением, является необходимость добавлять отдельно каждое поле в XML-манифест нашего нового View. Т.е если вы добавили новое настраиваемое поле в админке уже ПОСЛЕ реализации данного решения, и хотите фильтровать по нему, то нужно будет зайти в файл:
/components/com_content/tmpl/filter/default.xml
и добавить там его xml-описание по аналогии с другими полями. Это дело одной минуты, но требует понимания и доступа к коду.
Да, наверное, можно сделать более универсальный тип поля Customfield, но это потребует гораздо более сложного кода, поскольку поля есть для разных типов контента, разных групп. В моем представлении лучше сделать несколько отдельных альтернативных макетов со своими xml-манифестами и разными полями в них, чем пытаться создать универсальный комбайн.
Ссылки на скачивание
- Архив с файлами для создания нового View. Скопируйте его в корень сайта Joomla и все файлы разложатся по своим местам автоматически
- Плагин для регистрации view=filter в роутере. Установите, как обычное расширение и активируйте.
Заключение
Статья получилась достаточно большой и сложной. Я писал ее в том числе и для себя, т.к. этой техникой хоть и пользуюсь уже давно, часто забываю разные нюансы.
Данный подход еще раз демонстрирует насколько удивительной и гибкой является Joomla. Мы залезли в самое сердце компонента com_content, изменив его работу для отдельно взятого представления, при этом не модифицировав ни единого файла ядра, и не допустив ни одного хака.
Если вы хотите реализовать у себя на сайте такой или улучшенный фильтр, но подход кажется слишком сложным - можете обратиться ко мне за платной доработкой через раздел Контакты. Буду рад помочь.