Skip to content
mFilter
mFilter
Фасетная фильтрация для MODX 3 с поддержкой SEO URL
  • MODX 3
  • PHP 8.1
  • Vue 3
  1. Компоненты
  2. mFilter
  3. Интерфейс админки
  4. SEO шаблоны

SEO шаблоны ​

SEO-шаблон — правило, по которому собираются title, H1, description и текст для подборки с применённым фильтром. Одно правило работает на всех значениях фильтра: SEO-шаблон для ключа made_in даёт заголовок и для «Германии», и для любой другой страны.

Создание SEO-шаблона ​

mFilter → SEO шаблоны → Добавить.

ПолеЧто задаёт
НазваниеТолько для списка в админке
СтраницаSEO-шаблон действует на этой странице. Пустое поле — глобальный SEO-шаблон, на всех страницах
Ключи фильтровКлючи, при которых SEO-шаблон срабатывает: made_in, color. Вводятся по одному, через Enter. Подсказки берутся из алиасов, ключ без алиасов — например, диапазон цены — впишите вручную
Шаблон H1Заголовок страницы
Шаблон TitleТег <title>
Шаблон DescriptionМета-описание
Шаблон текстаSEO-текст под выдачей
ПриоритетРазрешает ничью между SEO-шаблонами. Меньше — раньше. От 0 до 100. При равном приоритете порядок не гарантирован — задавайте разные
АктивенВыключенный SEO-шаблон не участвует в подборе

Незаполненное поле подобранного SEO-шаблона выводится пустым, а не значением по умолчанию. Заполняйте H1, Title и Description у каждого шаблона.

SEO-шаблон без ключей фильтров сохранить можно, но он не сработает никогда. «SEO-шаблона на всё» не существует: роль запасного варианта играет поведение без SEO-шаблона.

Как подбирается SEO-шаблон ​

SEO-шаблоны делятся на два вида. С заполненным полем «Страница» шаблон страничный — он срабатывает только на ней. С пустым — глобальный, годится для любой страницы каталога; в списке такие помечены меткой «Глобальный».

Ключи выбранных фильтров сравниваются с полем «Ключи фильтров» в четыре прохода. Первый подошедший SEO-шаблон и применяется:

  1. SEO-шаблоны этой страницы, у которых набор ключей совпал точно;
  2. SEO-шаблоны этой страницы, у которых совпал хотя бы один ключ;
  3. глобальные SEO-шаблоны с точным совпадением;
  4. глобальные SEO-шаблоны с совпадением хотя бы одного ключа.

Порядок проходов важнее приоритета: страничный SEO-шаблон обойдёт глобальный, а точное совпадение — частичное. Приоритет решает только, какой из SEO-шаблонов взять, когда в одном проходе подошли несколько.

Из-за частичного совпадения SEO-шаблон для made_in применится и к странице, где выбраны made_in и color. Чтобы у такой пары был свой заголовок, заведите SEO-шаблон с обоими ключами и той же привязкой к странице — точное совпадение выиграет у частичного. Глобальный шаблон на пару не обойдёт страничный шаблон для made_in: страничные проходы идут раньше.

Если SEO-шаблон не подобран ​

Заголовки собираются из названия страницы и выбранных значений:

  • H1 — «Название страницы - Значения»;
  • Title — то же плюс « | Название сайта»;
  • Description — описание страницы плюс те же значения.

Значения подставляются подписями: раздел каталога — пунктом меню, а если он пуст — заголовком, производитель MiniShop3 — именем, числовое значение TV — подписью из индекса.

Плейсхолдеры ​

Значения фильтров ​

Значение подставляется по ключу фильтра, без префикса. Именно по ключу, а не по полю, из которого фильтр берёт данные: у фильтра {"brand": {"field": "product_brand"}} в SEO-шаблоне пишется {$brand}. Доступны только ключи из латиницы, цифр и _: ключ с - или | в SEO-шаблоне не адресуется.

ПлейсхолдерЧто подставит
{$made_in}Все выбранные значения через запятую
{$made_in_first}Только первое значение
{$made_in_count}Сколько значений выбрано
{$vendor_id_raw}Значение до подстановки названия: у производителей, разделов и TV со списком ресурсов — идентификатор. Нужен для словоформ, заведённых на идентификатор: {$parent_raw.gen}. Диапазон и здесь одной строкой — «1000 — 5000»

Диапазон приходит одним значением, а не двумя границами: {$price} даст «1000 — 5000», а при одной границе — «от 1000» или «до 5000». Слова и тире берутся из лексикона mfl_seo_range_from, mfl_seo_range_to, mfl_seo_range_separator. Диапазон дат сворачивается так же, без числового форматирования.

Где фильтр хранит идентификатор, подставляется название — то же, что в заголовке без SEO-шаблона, а у TV — подпись значения:

ФильтрЧто хранитсяЧто подставится
Производитель MiniShop3, {$vendor_id}12«Acme»
Раздел каталогаИдентификатор разделаНазвание раздела
Категория товаровИдентификатор категорииНазвание категории
TV со списком ресурсовИдентификатор страницыНазвание страницы
Другой TV, где значение — идентификаторИдентификаторПодпись из поля «Возможные значения», например из привязки @SELECT name, id FROM …
TV, у значения которого нет подписиИдентификаторСам идентификатор

Поля страницы и сайта ​

ПлейсхолдерЧто подставит
{$pagetitle}, {$longtitle}, {$menutitle}, {$introtext}, {$id}, {$alias}…Любое поле ресурса под своим именем
{$<имя TV>}Значение TV этой страницы — обработанное, с параметрами вывода. Только TV, привязанные к шаблону страницы
{$site_name}Название сайта — единственная системная настройка, доступная в SEO-шаблоне

Запись через точку

SEO-шаблоны, написанные по прежней версии документации, обращались к полям через точку: {$resource.pagetitle}, {$filters.vendor_id}. Такая запись работает и даёт то же, что {$pagetitle} и {$vendor_id}. Уровень вложенности — один: {$filters.vendor_id.gen} останется в заголовке как текст, падеж пишется как {$vendor_id.gen}. {$resource} и {$filters} целиком подставляются пустым местом.

Имена берутся в таком порядке: сначала поля ресурса, затем TV, затем значения фильтров. Последние записываются поверх, поэтому ключ фильтра, совпадающий с именем поля или TV — description, parent, id, — перекроет его.

Числа найденных товаров в SEO-шаблоне нет

Плейсхолдера с числом найденных товаров в SEO-шаблоне нет. Число выставляет сниппет mFilter в плейсхолдер mfilter.total (имя меняется параметром &totalVar), а выводит чанк обёртки — строкой «Найдено: N». В шаблоне ресурса читайте его ниже вызова сниппета через {$_modx->getPlaceholder('mfilter.total')}: через $_pls оно не видно, см. Плейсхолдеры.

Модификаторы ​

Модификатор пишется через точку после ключа: {$made_in.gen}.

Падежи ​

МодификаторПадеж
.nomИменительный
.genРодительный
.datДательный
.accВинительный
.insТворительный
.preПредложный

Множественное число — те же имена с суффиксом: .gen_plural, .dat_plural и так далее.

Формы берутся из Словоформ. Если слова там нет, подставится исходное значение — без ошибки и без записи в лог.

У производителей, разделов и категорий товаров склоняется название: словоформа нужна на «Acme», а не на 12. Пакетная генерация таких словоформ не создаст — она берёт слова из значений алиасов, а у этих фильтров там идентификаторы. Заведите их вручную.

Склоняется одно значение. Если выбрано несколько, {$color} — это строка «красный, синий», словоформы для неё нет, и {$color.gen} выведет её в именительном падеже. Для падежа берите первое значение: {$color_first.gen}.

ЗаписьЧто выведет
{$color}«красный, синий»
{$color.gen}«красный, синий» — падеж не применился
{$color_first.gen}«красного»

Обработка текста ​

МодификаторЧто делает
.lower, .upperНижний и верхний регистр
.ucfirst, .lcfirstПервая буква заглавная или строчная
.stripУбирает HTML-теги
.trimУбирает пробелы по краям
.escapeЭкранирует кавычки и угловые скобки

Неизвестный модификатор ошибки не вызывает — значение подставится как есть. Поэтому опечатка в {$made_in.gne} выглядит как «падеж не применился».

Условия ​

Блок {if $ключ}…{/if} выводит содержимое, только когда значение непустое. Пустыми считаются и 0, и "0" — это касается логических фильтров и {$ключ_count}. В условии пишется только имя, без модификатора: {if $color.gen} останется в тексте как есть.

{$pagetitle} {if $color}цвета {$color.gen}{/if}

Ветка {else} выводится, когда значение пустое. Она нужна SEO-шаблону, который подобран по частичному совпадению ключей: SEO-шаблон для color и made_in применится и к странице, где выбрана только страна.

{$pagetitle} {if $color}цвета {$color.gen}{else}всех цветов{/if}

Внутри блока работают те же плейсхолдеры и модификаторы, блоки можно вкладывать друг в друга. Незакрытый блок или второй {else} в одном блоке выводятся как текст — так ошибку видно в заголовке.

Пробелы

Перед сборкой парсер заменяет подряд идущие пробелы и переносы строк одним пробелом, а края всего текста обрезает. Пробелы внутри ветки остаются как написаны: {$pagetitle}{if $color} — {$color}{/if} даст «Диваны — синий». Разметку многострочного SEO-текста это не портит, но пустые строки между абзацами исчезнут — разделяйте абзацы тегами.

Примеры SEO-шаблонов ​

Страна производства ​

Ключи фильтров: made_in

Title: Товары из {$made_in.gen} — купить в {$site_name}
H1: Товары из {$made_in.gen}
Description: Товары из {$made_in.gen} в наличии. Доставка по всей России.

Страна и цвет ​

Ключи фильтров: made_in, color

Title: Товары из {$made_in.gen} {$color.gen} цвета | {$site_name}
H1: Товары из {$made_in.gen}, {$color.gen} цвета

Диапазон цен ​

Ключи фильтров: price

Title: {$pagetitle} {$price} рублей
H1: {$pagetitle}, {$price}

Индексация ​

Поля «не индексировать» у SEO-шаблона нет: страницы закрываются системными настройками.

НастройкаПо умолчаниюЧто делает
mfilter.seo_noindex_filteredНетЗакрывает все отфильтрованные страницы разом
mfilter.seo_max_filters2Закрывает страницу, где выбрано больше указанного числа фильтров. 0 — без ограничения
mfilter.seo_max_values1Закрывает страницу, где у одного фильтра выбрано больше указанного числа значений. 0 — без ограничения

Диапазон считается одним фильтром, хотя в адрес попадает двумя границами. Значения же считаются внутри каждого ключа отдельно, а у границы значение одно — поэтому seo_max_values диапазон не закрывает даже при лимите в единицу.

Индивидуальная запись снимает закрытие: страница, для которой её написали, индексируется, даже если попала под лимиты. Настройка mfilter.seo_noindex_filtered сильнее — она закрывает страницу и при заполненной записи.

Canonical ​

Адрес вычисляется, в SEO-шаблоне его не задают.

  • Открытая для индексации страница получает canonical на саму себя — отфильтрованный адрес без номера страницы, сортировки и лимита;
  • закрытая — canonical на страницу без фильтров.

Адрес всегда абсолютный.

Программный доступ ​

php
$mfilter = $modx->services->get('mfilter');
$seoBuilder = $mfilter->getSeoBuilder();

// Значения настоящие: у производителя — id; диапазон — двумя ключами
$filters = ['made_in' => ['Германия'], 'price|min' => ['1000'], 'price|max' => ['5000']];

$seoData = $seoBuilder->build($resourceId, $filters);
// [
//     'h1' => 'Товары из Германии',
//     'title' => 'Товары из Германии — купить в Название сайта',
//     'description' => 'Товары из Германии в наличии...',
//     'text' => '<p>SEO текст...</p>',
//     'canonical' => 'https://site.ru/catalog/made_in--germaniya/',
//     'noindex' => false
// ]

Для несуществующего ресурса build() вернёт пустой массив.

МетодЧто делает
shouldNoindex(array $filters): boolПопадает ли набор фильтров под лимиты индексации
getCanonical(int $resourceId, array $filters): stringCanonical для набора фильтров
clearCache(): voidСбрасывает кэш загруженных SEO-шаблонов

Вывод на фронтенде ​

Плейсхолдеры mfilter.seo.* ставит сниппет mFilter — всегда, а без фильтров в них заголовок, описание и адрес самого ресурса. Читайте их в шаблоне ресурса ниже вызова сниппета через $_modx->getPlaceholder(): через $_pls видны только значения, которые плагин считает заранее на SEO-адресе. Пример для <head> и <body>, где вызов mFilter стоит в начале шаблона, — в Плейсхолдерах.

Маркеры для AJAX

Атрибуты data-mfilter-h1 и data-mfilter-seo-text (или классы h1.mfilter-h1 и .mfilter-seo-text) обязательны: без них JS не найдёт элементы при AJAX-фильтрации, и они обновятся только после F5. Класс mfilter-h1 срабатывает только на теге <h1> — на другом теге нужен атрибут. Меты title, description, canonical, robots в <head> JS обновляет по стандартным селекторам — маркеры не нужны.

Если страница открыта для индексации, скрипт после фильтрации переписывает любой <meta name="robots"> с noindex на index, follow — и выставленный вручную тоже. Учитывайте это, если закрываете раздел своим robots.

После обновления JS вызывает событие mfilter:seoUpdate — его можно слушать, если на странице есть свои элементы с SEO-данными:

js
document.addEventListener('mfilter:seoUpdate', (e) => {
    console.log(e.detail.seoData.h1, e.detail.seoData.title);
});

Когда SEO-шаблон не нужен ​

SEO-шаблон описывает правило для всех значений сразу. Если текст нужен под одну конкретную подборку — свой заголовок и описание для определённого бренда, — правило заводить не обязательно: такая страница настраивается поштучно на вкладке Индивидуальные SEO.

Одно другому не мешает. Заполненные поля индивидуальной записи перекрывают SEO-шаблон, незаполненные берутся из него: правило закрывает массу страниц, а руками пишется то, ради чего стоит сесть за текст.