new

mFilter

Фильтр для MODX3 с поддержкой SEO-friendly URL
Автор дополнения
Николай Савин
Пакетов
20
Закачек
32 486
Обычно отвечает в течение суток
Автор дополнения
Пакетов
20
Закачек
32 486
Обычно отвечает в течение суток
Версия 1.4.2-beta1
Дата выпуска 10.05.2026
Загрузки 76
Просмотры 803
Внимание, этот компонент требует PHP 8.1 или выше!
Внимание, этот компонент требует MODX 3 или выше.



Возможности



  • Фасетная фильтрация — фильтрация по любым полям ресурсов, TV, опциям MiniShop3
  • SEO URL — человекочитаемые URL вида /catalog/brand_apple/color_black/
  • Кросс-фильтрация — подсчёт доступных значений с учётом активных фильтров
  • AJAX — обновление результатов без перезагрузки страницы
  • Headless API — REST API для интеграции с Vue, React, Svelte
  • SEO оптимизация — динамические title, description, H1, canonical
  • Словоформы — склонение названий фильтров в SEO-текстах
  • Интеграция — работает с MiniShop3, mSearch, pdoTools
  • Vue-интерфейс — современная админ-панель на Vue 3 + PrimeVue

Системные требования



  • MODX Revolution | 3.0.0+
  • PHP | 8.1+
  • MySQL | 5.7+ / MariaDB 10.3+

Зависимости


  • pdoTools 3.x — для работы сниппетов и шаблонизатора Fenom
  • VueTools — для административного интерфейса
  • MiniShop3 (опционально) — для фильтрации товаров

Быстрый старт


Добро пожаловать в документацию — docs.modx.pro/components/mfilter/quick-start

История изменений mFilter

Журнал версий компонента mFilter для MODX 3.x. Свежее сверху.

1.4.2-beta1

Исправления

  • mFilterForm: на пустом разделе форма больше не показывает значения из соседних категорий. Проверка !empty($baseIds) в сниппете трактовала пустой массив как «scope не задан» → каждый FilterType шёл по parents/depth и тянул значения из родительских категорий. Теперь — fallback на свой расчёт scope через новый Filter::getResourceIds(), и короткое замыкание на «нет значений» для пустого раздела.
  • mFilterNav: на пустом разделе сниппет больше не дампит весь mfl_slugs для запрошенного filterKey. Та же !empty([]) ловушка — при пустых baseIds suggestions не получались, hideEmpty молча пропускался, в вывод шли все исторические слаги. Добавлено различение трёх состояний baseIds: null / [] / непустой.
  • mFilterSitemap: карта сайта больше не содержит «мёртвых» URL для значений фильтров без реальных товаров в разделе. Старая реализация генерировала декартово произведение всех слагов из mfl_slugs без проверки пересечения с реальными товарами — sitemap раздувался 404-страницами, бот тратил crawl budget впустую.

Рефакторинг

  • mFilterNav теперь использует FacetIndexReader::batchGetTextValues() для индексированных текстовых ключей — один SQL вытягивает (value, count) для всех ключей сразу. Slug-строки подгружаются через preloadForFilters(). Не-индексированные ключи остаются на legacy-пути. На странице с 2–3 индексированными ключами вместо N×getByFilter + 1×getSuggestionsForIds теперь 1×batch SQL + 1×IN(...).
  • mFilterSitemap:
    • Single-filter URLs: значения берутся из FacetIndexReader::getTextValues() со scope от Filter::getResourceIds() — только реально существующие значения для раздела.
    • Two-filter URLs: новый метод FacetIndexReader::getCombinedTextValues() делает self-JOIN индекса по product_id — возвращает только реально сосуществующие пары (value1, value2). Декартово произведение для пар ушло.
    • Пустые разделы пропускаются до перебора слагов — для них вообще не делается SQL.
    • Cache key расширен cultureKey, tplWrapper, sitemapSchema, forceXML, return, depth (несколько pre-existing пропусков исправлены заодно).
    • Новый опциональный параметр сниппета depth (default 10) для scope.

Новый публичный API

  • Filter::getResourceIds(int $resourceId, int $depth = 10): int[] — возвращает все ID товаров раздела с тем же scope, что и Filter::apply() (parents/depth/published/deleted/msCategoryMember secondaries), но без фильтров и пагинации. Для пустого раздела возвращает []. Используется в mFilterForm/mFilterNav/mFilterSitemap, когда mFilter-сниппета на странице нет.
  • FacetIndexReader::getCombinedTextValues(string $key1, string $key2, ?array $productIds = null): array — пары значений двух фильтров, реально встречающиеся вместе у товаров. Один SQL self-JOIN по mfl_facet_index_text. Используется sitemap'ом, годится для любой аналитики «какие пары значений сосуществуют».
  • FilterHandler::isEmptyScope(array $context): bool — guard для headless/API-вызовов, которые явно ставят filtered_ids или resource_ids в []. Пропускает per-FilterType SQL и возвращает пустой envelope.
  • FilterHandler::buildEmptyFiltersData() / buildEmptySuggestions() — внутренние хелперы, формирующие envelope с пустыми values, точно совпадающий по форме с реальными getFiltersData()/getSuggestions().

Хардeнинг (по результатам ревью)

  • Cache key в mFilterNav теперь содержит сигнатуру $baseIds (count + crc32 от отсортированных ID). Без неё разные scope могли коллизить на одной записи кэша при cache=1.
  • mFilterNav: ранний выход на пустом scope пишет пустую строку в кэш — постоянно-пустые секции больше не дёргают getResourceIds() на каждом запросе.
  • mFilterNav: sort_order сохранён для индексированного пути через map per-key (только когда sortBy=sort_order).
  • mFilterNav: режим showCount=0 hideEmpty=0 сохранён — в нём весь filterKeyList идёт по slug-driven пути без scope resolve. Историческая «SEO-облако-как-есть» семантика не тронута.
  • Все $modx->log(1, ...) приведены к modX::LOG_LEVEL_ERROR ради единообразия с правилом проекта.

1.4.1-beta1

Новые возможности

Автосинхронизация индекса фасетов — закрывает известное ограничение из 1.4.0, ручная пересборка после редактирования товаров через MODX/MS3 больше не нужна. Три слоя покрывают типичные пути правок:

  1. Плагин на OnDocFormSave / OnResourceDelete — инкрементально обновляет индекс для сохранённого/удалённого товара (5–20 мс на товар, незаметно для UX). Фильтр по class_key пропускает сохранения статических страниц при установленном MS3.
  2. Плагин на msOnAfterImport — запускает полную пересборку после CSV-импорта MS3 (через Scheduler если установлен, иначе синхронный fallback с предупреждением в логе).
  3. Новая recurring Scheduler-задача mfl_sync_facet_index (+5 минут) — догоняет вызовы $resource->save() без процессора и SQL-импорты, обновляющие editedon. Читает last_sync_at из MflCache, пересобирает батчами по 5000 ID, удаляет soft-deleted строки. На простаивающих сайтах ничего не делает, лог не засоряет.

Дополнительно:

  • Новый метод FacetIndexBuilder::syncByEditedon(?int $sinceTimestamp = null) — инкрементальная синхронизация. Используется новой Scheduler-задачей и доступен из своего кода.
  • Новый метод FacetIndexBuilder::shouldIndexClassKey(?string $classKey) — хелпер гейтинга операций по товарам, ограничивает работу msProduct'ами при установленном MS3.
  • Задача rebuildFacetIndex.php теперь принимает параметр mode=full|sync — универсальный обработчик для on-demand вызовов.

Что по-прежнему требует ручной пересборки

  • Прямой SQL UPDATE без editedon=NOW() — sync такого не видит
  • DELETE FROM modx_site_content через сырой SQL — нет deletedon-строки для детекции

Хардeнинг (по результатам ревью PR #13)

  • Убран SELECT по msProductData.editedon — такой колонки в MS3 нет, recurring task падал бы каждые 5 минут с Unknown column
  • Soft-delete теперь детектится по deletedon (MODX 3 Delete-процессор не трогает editedon)
  • Baseline last_sync_at сдвинут на −1 секунду, чтобы записи, закоммиченные в ту же секунду, что и наш SELECT, не терялись навсегда

1.4.0-beta1

Главное

Денормализованный индекс фасетов (mfl_facet_index_text / mfl_facet_index_num), заменяющий JOIN'ы к msProductData / msProductOption / modTemplateVarResource на лету. На каталоге 30k применение фильтра ускорилось с ~30с до ~1.7с, расчёт suggestion'ов — с сотен мс до единиц. Каталоги 200k+, ранее неюзабельные, теперь работают за секунды.

Новые возможности

  • Прозрачное чтение — обработчики FilterType сами определяют индексированные ключи (hasIndexedTextKey / hasIndexedNumKey) и переключаются между индексом и fallback'ом на исходные таблицы. Существующие конфиги наборов фильтров работают без изменений.
  • Бесшовная миграция — при upgrade резолвер ставит задачу mfl_rebuild_facet_index в очередь Scheduler. Пока пересборка не выполнилась — фильтрация продолжает работать через fallback, простоев нет. Без Scheduler — одна кнопка в админке.
  • Админка — новая вкладка «Обслуживание» со статусом индекса, количеством записей, временем последней пересборки, ручной пересборкой (напрямую или через Scheduler), управлением кэшем. Старый прогрев перенесён в сворачиваемый блок с пометкой Legacy.
  • Кнопка «Переиндексация» в шапке — единая, заменяет несколько прежних. Подсвечивается жёлтым, пока индекс не построен.
  • Инкрементальная пересборка при сохранении набора — пересобирается только изменённый ключ, а не весь каталог. Изменения определяются через рекурсивный ksort + json_encode (порядок ключей не важен).

Производительность

  • Batch SQL для текстовых suggestion'ов — при ≥2 индексированных текстовых фильтрах их значения собираются одним GROUP BY вместо N отдельных сканов. ~4× быстрее на многофильтровых страницах.
  • *`COUNT()вместоCOUNT(DISTINCT)`** в чтениях из индекса — безопасно благодаря уникальности PRIMARY KEY.
  • Filter::applyToIds использует raw PDO fetch вместо getIterator() — экономит 2–3 секунды на выборках 30k+ строк.

Инфраструктура

  • Новая Scheduler-задача mfl_rebuild_facet_index для фоновой полной пересборки.
  • Builder обнуляет PHP-таймауты (set_time_limit(0), ignore_user_abort(true)) на каждом фильтре; безопасен для пересборок через HTTP.
  • Новый эндпойнт /maintenance/status возвращает блок facet_index с количеством строк, временем последней пересборки и флагом pending-run.

Хардeнинг (по результатам ревью)

  • Количество строк после INSERT IGNORE читается из возвращаемого значения PDO::exec() — убран лишний SELECT ROW_COUNT().
  • REGEXP для числовых значений принимает опциональный знак и запятую как десятичный разделитель; REPLACE перед CAST не даёт '1,5' молча превратиться в 1. Мусор вида '1.2.3.4' отбраковывается.
  • Whitelist ^[a-zA-Z0-9_]+$ для имени поля в FacetIndexBuilder — defense-in-depth, конфиг доступен только админам, но backticks не защищают от payload вида ; DROP TABLE; --.
  • Ошибки PDO prepare/execute в Filter::applyToIds теперь логируют errorInfo + SQL вместо тихого пустого результата.
  • WarmupTab во вкладке «Обслуживание» больше не монтируется при свёрнутом Panel — экономит запрос к /warmup-configs при каждом открытии вкладки.

Известные ограничения

  • OnDocFormSave пока не пересобирает facet-индекс — индекс не обновляется автоматически при редактировании товара через админку. Закрыто в 1.4.1-beta1.
  • Удаление набора фильтров не чистит индекс — orphan-строки накапливаются до следующей полной пересборки. Косметика, на корректность не влияет.
  • Race condition в buildAll() при долгой пересборке (окно между TRUNCATE и INSERT) — отслеживается в issue #12. В типичных FPM-конфигурациях практически не проявляется.

1.3.3-beta1

Исправления

  • URL Patterns: parse_regex теперь автогенерируется из url_pattern, если пустой. Раньше для паттерна dvigatel--{value} нужно было вручную писать regex, иначе URL отдавал 404 при прямом переходе. Поддерживаются плейсхолдеры {key}, {value}, {values}.
  • Hide_key паттерны исключены из автогенерации — их url_pattern (например {value}) дал бы слишком жадный /^(.+)$/, перехватывающий любой сегмент. Hide_key продолжают идти через hasSlug() валидацию.

1.3.2-beta1

Новые возможности

  • Canonical URL handling — фильтрованные URL без trailing slash теперь возвращают 404 по умолчанию (настраивается через mfilter.non_canonical_url_handling: 404 | 301 | off). Предотвращает SEO-дубли.
  • DOM-событие mfilter:contentLoaded диспатчится после обновления результатов с detail: container, append, total, instance — для интеграции внешнего JS на новых карточках товаров.

Изменения

  • Дефолт mfilter.url_separator изменён с _ на --. На существующих установках значение сохраняется при upgrade ('settings' => false в build config); затрагивает только новые установки.

1.3.1-beta1

Исправления

  • loadMore: ошибка в user success callback больше не сбрасывает page counter (emit вынесен из try/catch)
  • loadMore: boundary check предотвращает запросы за последнюю страницу
  • loadMore: автоскрытие кнопки при загрузке всех элементов (через data-mfilter-load-more)
  • Range sliders: десятичные значения отображаются корректно (17.6 больше не округляется до 18)
  • Range sliders: автоопределение шага по диапазону (~100 шагов, «красивые» числа)
  • Range sliders: кастомный шаг через data-step на slider-элементе
  • Range inputs: step="any" в tpl снимает HTML5-блокировку submit с десятичными значениями
  • Range inputs: дефолтные (неизменённые) значения больше не попадают в URL при submit
  • syncFormWithState: range-фильтры не помечаются как user-changed, если совпадают с дефолтами
  • initPaginationState: глобальный DOM-поиск wrapper'а, когда форма и результаты — отдельные элементы

Новые возможности

  • Empty results state: элемент .mfilter-empty авто-показывается/скрывается по total (содержимое контролируется шаблоном)

1.3.0-beta1

Новые возможности

  • Select-фильтры: option с count=0 получают атрибут disabled через suggestions
  • Range-фильтры: десятичные числа в SEO URL (например, price_99.50_500)
  • JS: syncFormWithState() при инициализации — инпуты формы синхронизируются с URL-состоянием при загрузке страницы

Улучшения

  • Удалён legacy JS-стек: mfilter.core.js, mfilter.api.js, mfilter.ui.js (−2515 строк). FilterUI.js — единственная реализация UI.
  • Убран hardcoded whitelist isRangeFilter() из SlugParser и UrlBuilder — любой URL-сегмент key_number_number теперь трактуется как range.
  • Regex SlugParser поддерживает ключи с цифрами (price2, area3).
  • Дефолтная сортировка не попадает в URL (сравнение с конфиг-дефолтом, а не хардкодом 'menuindex').
  • Точка входа mfilter.js: убран MFilterCore fallback, только MFilterUI.
  • Резолвер resolve.frontend_assets.php: автообновление системной настройки при upgrade, если сторонних файлов не обнаружено.

Исправления

  • Range-инпуты без слайдера: data-user-changed ставится при ручном вводе, фикс игнорирования значений при submit
  • Утечка пагинации pdoPage: pageNavVar сменён с пустой строки на неиспользуемый плейсхолдер (pdoPage трактовал '' как дефолт)
  • JS parseFilterPath: разделитель range берётся из urlSep вместо хардкода '_'
  • JS resetFilterCounts: option.value !== '' вместо falsy-проверки (фикс option с value="0")
  • JS syncFormWithState: корректная обработка range-инпутов (по name) и select-элементов

Ломающие изменения

  • window.MFilterCore удалён — используйте window.MFilterUI
  • window.MFilterAPI удалён — функционал в core/ApiClient.js и core/FilterAPI.js
  • Плейсхолдер {$results} удалён из outer-чанка — используйте {$rows}
  • Legacy JS-файлы удалены — если шаблон вручную подключал mfilter.core.js/mfilter.api.js/mfilter.ui.js, удалите эти ` теги. Ассеты регистрируются автоматически через настройкуmfilter.frontend_assets`.

1.2.3-beta1

Улучшения

  • JS API: loadMore() теперь эмитит полный цикл событий (beforeSubmit, success, error, afterSubmit) с флагом append: true для отличия от обычного submit
  • JS API: instance.state теперь содержит pageCount и total, синхронизируются с ответом сервера после каждого запроса
  • JS API: SSR-инициализация pageCount/total из атрибутов data-page-count и data-total на wrapper-элементе
  • Дефолтный tplOuter: добавлены data-page-count и data-total для SSR-инициализации состояния

Исправления

  • Админка: дерево ресурсов в редакторе набора фильтров не авто-чекает родителя при выборе всех потомков (отключён PrimeVue propagateUp). Родитель чекается только прямым кликом
  • Админка: снятие чекбокса с дочернего ресурса не меняет состояние родителя
  • Админка: кнопка «Выбрать всех потомков» выбирает только потомков, не сам узел

Внимание: если используете кастомный tplOuter, добавьте в wrapper-элемент data-page-count="{$pageCount}" data-total="{$total}" для SSR-инициализации пагинации. Без этих атрибутов pageCount/total появятся только после первого AJAX-запроса.


1.2.2-beta1

Исправления

  • Поддержка msCategoryMember: товары из вторичных MS3-категорий теперь учитываются в counts фильтров, пагинации и листинге товаров
  • Централизованы хелперы родительской фильтрации в AbstractFilterType (buildParentSqlCondition, getProductIdsByParents)
  • Исправлена OR-группировка в xPDO-запросах — unpublished/deleted товары не утекают через вторичные категории
  • In-memory кэш для getSecondaryProductIds() — нет повторных DB-запросов в одном request'е
  • Удалены дубли getProductIdsByParents() из VendorsFilterType, BooleanFilterType, NumberFilterType
  • TvIndexer: индексация TV покрывает товары из вторичных категорий

1.2.1-beta1

Улучшения

  • Задача mfl_warmup стала рекуррентной (каждые 50 минут) — кэш с дефолтным TTL 3600с никогда не протухает
  • Удалён legacy warm_suggestions через HTTP-запросы из rebuildCache (заменён на mfl_warmup)

Code review fixes

  • autoCreateConfig: in-memory guard предотвращает повторные DB-запросы на cache miss в одном request'е
  • warmConfig: try/finally гарантирует восстановление $modx->resource при exception
  • warmConfig: depth берётся из параметров конфига, а не из хардкода
  • WarmupManager: все timestamps через gmdate() (UTC consistency)
  • SnippetCallParser: добавлены скобки для читаемости, документированы ограничения parsePhpArray
  • WarmupConfigController: whitelist sortBy для предотвращения SQL-инъекций

1.2.0-beta1

Новые возможности

  • Подсистема warmup: настраиваемый прогрев кэша baseIds и фасетных counts
  • Админка: новая вкладка «Прогрев» для управления конфигурациями
  • Парсер вызова сниппета: поддержка Fenom, MODX-тегов и JSON для извлечения параметров
  • Автосоздание конфигов при первом посещении страницы (cache miss)
  • Scheduler-задача mfl_warmup: фоновый прогрев через cron
  • Кнопка «Запустить через Scheduler» в админке для фонового выполнения

Модели

  • Новая таблица mfl_warmup_configs: конфигурации с element, params, cache_key_hash
  • Новая таблица mfl_warmup_config_resources: many-to-many привязка конфигов к страницам

Интеграция

  • Сниппет mFilter проверяет warmup-кэш перед вызовом ElementRunner::getIds()
  • WarmupKeyBuilder: детерминистский MD5-хеш только от параметров, влияющих на выборку (дефолты исключаются)
  • WarmupManager: CRUD, чтение/запись кэша, выполнение прогрева, авто-создание на cache miss
  • API: 10 эндпойнтов для управления конфигами (/warmup-configs/*)

Улучшения

  • Filter::getSuggestionsForIds() кэширует результаты в MflCache (включает прогрев фасетных counts)
  • Warmup также прогревает значения фильтров (getFilters) для mFilterForm — устраняет 13–19с холодного старта
  • Отдельная Scheduler-задача mfl_warmup (раньше переиспользовалась mfl_rebuild_cache с путаными параметрами)
  • Cache expiration через UTC (gmdate) — фикс преждевременной инвалидации при разных timezone у cron и web
  • TvIndexer::indexResource() индексирует только TV, используемые в фильтрах, а не все TV ресурса

Исправления

  • Удалено отладочное логирование из ColorsFilterType

1.1.5-beta1

Улучшения

  • Cache invalidation: кэш значений фильтров (MflCache) теперь автоматически очищается при изменении конфигурации набора

Исправления

  • Suggestion counts для Vendors и Boolean типов показывали завышенные числа после AJAX-фильтрации (отсутствовал scope-limit resource_ids)
  • Имя вендора отображалось как заголовок ресурса в выбранных фильтрах и крошках (vendor_id резолвился как ID ресурса MODX, а не как ID вендора MS3)
  • Сортировка в lazy-loading таблицах админки (Slugs, WordForms)

1.1.4-beta1

Улучшения

  • SSR-профилировщик: debug-панель показывает метрики производительности на первой загрузке страницы (не только после AJAX)

Исправления

  • Падение при удалении пакета: класс EncryptedVehicle не находился, так как MODX удаляет namespace-директории до обработки vehicle. Резолвер делает fallback на загрузку из директории распакованного пакета
  • Удаление пакета блокировалось расшифровкой: EncryptedVehicle::uninstall() больше не падает при отсутствии ключа, идёт по cleanup через резолвер
  • Ошибка «Could not load class MflPageConfig» при установке (модель удалена в 1.1.0, но миграционный резолвер на неё ссылался)
  • Warning «Constant COMPONENT_NAME already defined» при установке

1.1.3-beta1

Исправления

  • Дублирование SQL JOIN к msProductData в Filter::apply(): NumberFilterType добавлял innerJoin('Data'), потом apply() добавлял leftJoin('Data') → MySQL «Not unique table/alias» и пустые результаты для slider-фильтров
  • Конфликт алиасов TV-фильтров: DefaultFilterType, DateFilterType, MonthFilterType, YearFilterType, DayFilterType использовали фиксированный алиас 'TVR' для TV-JOIN'ов — два TV-фильтра одновременно ломали SQL. Теперь уникальные алиасы TVR_{field}
  • Пустой hash в mfilter.outer.tpl: Fenom {var} присваивание с ?: и $_pls[] молча возвращает пустоту. Замена на прямой вывод {$_pls['hash']}

1.1.2-beta1

Исправления

  • SQL-ошибка в Filter::apply(): колонки Data.vendor нет в msProductData, заменено на Data.vendor_id
  • MsProductSource::getKnownFields(): vendorvendor_id под актуальную схему MS3
  • Детектор полей SourceHandler: vendorvendor_id
  • Удалён дубль vendor из полей сортировки (уже есть vendor_id)

1.1.1-beta1

Исправления

  • API: getFilterSetService()getFilterSetManager() в FilterController
  • API: getFilterTypeRegistry()getFilterTypesRegistry() в FilterController
  • FilterController обрабатывал FilterSet-массив как xPDO-объект ($filterSet->get()$filterSet[])
  • Задача rebuildCache: неверный формат аргумента для Filter::getFilters() (массив вместо int)
  • Задача rebuildCache: использование void-результата rebuildRouterCache() как массива
  • Удалён dead-код в BooleanFilterType::getValueCounts()
  • Очищен PHPStan baseline: удалены 6 устаревших записей для удалённых файлов/методов

1.1.0-beta

Новые возможности

  • mFilterCrumbs: сниппет хлебных крошек с фильтр-сегментами и SEO-разметкой (Schema.org, JSON-LD)
  • mFilterSelected: компонент отображения выбранных фильтров
  • mFilterSitemap: сниппет и Scheduler-задача для генерации sitemap
  • mFilterNav: SEO-навигация по фильтрам
  • Headless API для интеграции SPA/фронтенд-фреймворков
  • Кастомные заголовки фильтров
  • Полная поддержка опций MS3 в фильтрах
  • TV Index: нормализация значений для фильтрации
  • Интеграция с mSearch (морфологический поиск)
  • Шифрование пакета (защита от копирования)
  • Резолвер метрик для анонимной статистики установок
  • Mass operations для словоформ
  • Автодополнение при выборе страницы в SEO-редакторе
  • Переключатель шаблонов (grid/list) и lazy-loading дерева ресурсов
  • Профилировщик производительности

Улучшения

  • Удалён deprecated сервис MflPageConfig (миграция на FilterSetManager)
  • JS: кастомный URL-разделитель из системных настроек
  • Рефакторинг настроек: mfl_*mfilter.*
  • SEO: логика canonical URL для фильтрованных страниц
  • SEO: рефакторинг noindex-настроек
  • SEO: человекочитаемые метки для parent и vendor_id
  • Админка: фикс фильтра ключа в списке слагов
  • mFilterForm: автоопределение текущего ресурса
  • Улучшены типы фильтров: vendors, parents, colors, date
  • Строгая валидация URL: 404 для несуществующих значений
  • CSS-изоляция для совместимости с ExtJS
  • Автоподключение фронтенд-ассетов (паттерн MS3)
  • Автоустановка зависимостей при установке пакета
  • Рефакторинг админки: статус в шапке, удалена вкладка Tools

Исправления

  • Зарезервированные слова MySQL 8
  • Совместимость xPDO-паттернов
  • Обработка date-фильтров
  • Парсинг TV
  • Инициализация вывода шаблона (tpl)
  • Алиасы и генератор
  • limit в AJAX-URL ответа

1.0.0-beta1

Первый публичный релиз.

  • SEO-friendly URL для фильтрованных страниц
  • Множество типов фильтров: default, number, boolean, parents, date, vendors, colors
  • Поддержка русских словоформ через morpher.ru API
  • Vue 3 админка с PrimeVue
  • AJAX-фильтрация с History API
  • Адаптивная мобильная боковая панель фильтров
  • Многозонное обновление контента
  • Настраиваемые URL-паттерны на фильтр
  • Автогенерация слагов
  • SEO-шаблоны с плейсхолдерами и склонениями
  • Конфигурация фильтров на уровне страницы
  • Управление кэшем и инвалидация
  • Интеграция с MODX Scheduler для фоновых задач

Последние обсуждения в сообществе MODX.pro