new

MigxPageConfigurator

Интеграция вёрстки и управление контентом
Версия 2.5.29-rc
Дата выпуска 11.06.2026
Загрузки 22
Просмотры 3 515
Внимание, этот компонент несовместим с MODX 3.
НЕ РАБОТАЕТ С MODX 3 Пока
<MigxPageConfigurator

Вступление

Компонент предназначен для повышения гибкости работы с контентом сайта. Позволяет ускорить интеграцию вёрстки с Modx Revolution.

Основные возможности:

  1. Автоматическое создание элементов сайта: шаблоны, ТВ.
  2. Автоматическая расстановка в вёрстке плейсхолдеров, вызовов сниппетов, чанков.
  3. Автоматическое создание файлов чанков и секций.
  4. Автоматическое заполнение контентом админки сайта.
  5. Централизованное редактирование вёрстки.
  6. Редактирование контента прямо на фронте (компонент mpcVisualEditor).
  7. Многоязычность через файлы лексиконов с переключением языка на лету.
  8. Встроенная ленивая загрузка изображений.
  9. Удобное управление контактами из админки.
  10. Декларативное управление настройками и сущностями из консоли (CLI).


Начало работы

Чтобы работать с компонентом было комфортнее рекомендую:

  1. Прочитать документацию на сайте https://docs.modx.pro (краткая справка и список изменений — в папке core/components/migxpageconfigurator/docs)
  2. Ознакомиться с примерами сущностей, которыми оперирует компонент, в папке core/components/migxpageconfigurator/examples

MPC 2.0

Служебная информация

К служебной информации можно отнести любые части шаблонов, которые есть на всех страницах и не зависят от ресурса (например фавикон, логотип, метрики и т.д.).
Служебная информация записывается в системные настройки или в настройки созданные с помощью компонента ClientConfig.
Если требуется записать настройку в конкретный контекст необходимо добавить атрибут data-mpc-ctx с указанием контекста или без значения, если нужно записать данные в текущий контекст.
Для обозначения служебной информации в шаблоне используется атрибут data-mpc-info с указанием ключа служебной информации. Из коробки доступны любые системные настройки.
Для добавления собственных ключей необходимо отредактировать конфигурацию MIGX с именем mpc_service_info.
Для служебной информации доступен вывод по условию. Чтобы задать условие используйте атрибут data-mpc-if, если не указывать условие — условием будет плейсхолдер поля.

Работа с секциями

Каждый шаблон состоит из секций. Секция может быть любым html тегом, но, как правило, это тег section или div.
Чтобы определить секцию нужно указать атрибут data-mpc-section с указанием ключа секции. Ключ секции должен содержать только латинские буквы, цифры и знаки подчёркивания (например test, main, header). Также для секции необходимо указать имя в атрибуте data-mpc-name. Имя необходимо для контент-менеджера, чтобы он по нему мог понять, что находится внутри. Если секция используется в нескольких шаблонах или несколько раз в одном шаблоне, то все копии следует отметить атрибутом data-mpc-copy, в значении рекомендую указывать название или путь к шаблону, в котором находится оригинал секции. Для копий необязательно использовать ту же разметку, что для оригинала.

Например это оригинал:

<section id="{$id}" data-mpc-section="third" data-mpc-name="Оригинал секции">
     <div class="container">
        <h1 data-mpc-field="title">Секция с простыми полями</h1>
        <h2 data-mpc-field="subtitle">SubTitle</h2>
        <div data-mpc-if="$content" data-mpc-field="content">
            <p>Paragraph 1</p>
            <p>Paragraph 2</p>
            <p>Paragraph 3</p>
        </div>
     </div>
</section>

Тогда копия может быть такой:

<section id="{$id}" data-mpc-section="third" data-mpc-name="Оригинал секции" data-mpc-copy="test.tpl">
    <span data-mpc-field="title">Другой заголовок</span>
    <span data-mpc-field="subtitle">Другой подзаголовок</span>
</section>

Каждая секция состоит из набора полей, который определяется указанием атрибутов data-mpc-field.

Статичные секции. Секция может быть статической, т.е. отображаться с одинаковым контентом на разных страницах сайта. Чтобы сделать секцию статической, добавьте ей флаг data-mpc-static (без значения). Статичную секцию можно сделать обычной, а обычную статичной для отдельных ресурсов или для всех ресурсов с конкретным шаблоном. У статичных секций плейсхолдеры отложенные (символ
##
вместо
{
), значения каскадятся от ресурса.

Работа с полями секции

Поля бывают элементарные (img, picture, video, audio, title, subtitle, content, btn_text) и списочные (list_of_lists, list_images, list_triple, list_triple_pictures и т.д.). Разница между ними в том, что списочные поля состоят из других списочных и элементарных полей.
Начиная с mpc 2.5.0 имя поля может быть любым (произвольная латиница с цифрами и подчёркиванием), а не только из набора зарезервированных — тип такого поля задаётся атрибутом data-mpc-ftype (см. ниже).

DEPRECATED — медиа-списки (list_images, list_pictures, list_videos, list_audios): спец-имена для «массива однотипных медиа без ul/li». Появились, когда нужно было расставить картинки в разных местах, где
{foreach}
не подходил. Нарезаются ФИКСИРОВАННЫМИ слотами (
$list_images[0]
,
[1]
, …), а не циклом, поэтому число элементов задаётся на нарезке и не меняется динамически (в т.ч. из фронт-редактора). С появлением произвольных имён полей секции (mpc 2.5.0) надобность отпала — вместо медиа-списка заводите нужное число обычных img/picture-полей с разными именами. Новые шаблоны на медиа-списки не делать; существующие продолжают работать.

Условный вывод. Для всех полей доступен условный вывод — для этого укажите полю атрибут data-mpc-if с указанием условия без оператора if.
Если атрибуту data-mpc-if не указать значение, то в качестве условия будет взят плейсхолдер поля. Например из такого шаблона:

<h2 data-mpc-field="subtitle" data-mpc-if>SubTitle</h2>

получим вот такой результат:

{if $subtitle} <h2>SubTitle</h2> {/if}

Limit и offset. Для списочных полей также доступно указание limit (data-mpc-lim) и offset (data-mpc-off).
Например такой шаблон:

<ul data-mpc-field="list_of_lists" data-mpc-lim="1" data-mpc-off="1">
    <li data-mpc-item="">
        <h5 data-mpc-field-1="title">Title1</h5>
        <ul  data-mpc-field-1="list_triple_img">
            <li data-mpc-item-1>
                <h5 data-mpc-field-2="title">Title12</h5>
                <h6 data-mpc-field-2="subtitle">Subtitle12</h6>
                <p data-mpc-field-2="content">Content12</p>
                <img data-mpc-nolazy data-mpc-field-2="img" src="https://i.pinimg.com/736x/80/7d/2b/807d2b9987f0d35a1036b1597c3deb74.jpg" width="50" height="50" alt="Радуга">
            </li>
        </ul>
    </li>
    <li data-mpc-item="">
        <h5 data-mpc-field-1="title">Title12</h5>
        <ul data-mpc-field-1="list_triple_img">
            <li data-mpc-item-1>
                <h5 data-mpc-field-2="title">Title122</h5>
                <h6 data-mpc-field-2="subtitle">Subtitle122</h6>
                <p data-mpc-field-2="content">Content22</p>
                <img data-mpc-field-2="img" src="https://i.pinimg.com/736x/f3/d9/2d/f3d92dcd1cd6f66daa196bfd255ac41d.jpg" width="50" height="50" alt="Радуга">
            </li>
        </ul>
    </li>
</ul>

преобразуется в такой чанк:

<ul>
    {foreach $list_of_lists as $item1 index=$i1 last=$l1}
        {set $c1 = 0}
        {if $i1 >= 1 AND $c1 < 1}
            {set $c1 = sum($c1, 1)}
            <li>
                <h5>{$item1.title}</h5>
                <ul>
                    {foreach $item1.list_triple_img as $item2 index=$i2 last=$l2}
                        <li>
                            <h5>{$item2.title}</h5>
                            <h6>{$item2.subtitle}</h6>
                            <p>{$item2.content}</p>
                            <img src="{$item2.img[0].src}" width="{$item2.img[0].width}" height="{$item2.img[0].height}" alt="{$item2.img[0].alt}">
                        </li>
                    {/foreach}
                </ul>
            </li>
        {/if}
    {/foreach}
</ul>

А пользователь увидит только второй элемент.

Максимум записей в списке. На контейнере списка можно ограничить число записей, которые контент-менеджер сможет добавить, атрибутом data-mpc-max с числом. Это пишется в migx-конфиг (maxRecords).

Имя, тип и подпись поля

Для автоматической генерации правильного поля в админке полю/TV можно дать дополнительные атрибуты:

  • data-mpc-ftype — тип поля (имя MODX input-типа: listbox, number, date, checkbox, option и т.д.). Определяет также мультиопционность.
  • data-mpc-fcap — подпись (caption) поля в админке.
  • data-mpc-fdesc — описание поля в админке.
  • data-mpc-values — список опций для listbox/option в формате migx
    Подпись==key||Подпись2==key2
    или выборкой
    @SELECT ...
    .

Например:

<select data-mpc-field="size" data-mpc-ftype="listbox" data-mpc-fcap="Размер" data-mpc-values="Маленький==s||Большой==l"></select>


Поля ресурса и TV

Кроме полей секции компонент умеет работать напрямую с полями текущего ресурса и его TV.

  • data-mpc-rfield — вывод поля ресурса. В значении — имя поля (pagetitle, longtitle, introtext и т.д.). На нарезке превращается в
    {$resource.pagetitle}
    (или ключ лексикона при включённой многоязычности), а контент грабится прямо в нативную колонку ресурса.
  • data-mpc-tv — вывод TV ресурса. В значении — имя TV; если TV ещё нет, она будет создана автоматически (тип берётся из data-mpc-ftype, подпись/описание — из data-mpc-fcap/data-mpc-fdesc). Превращается в
    {$resource.tvs.subtitle}
    .
  • data-mpc-res — флаг на поддереве: помечает, что внутри данные ЧУЖОГО ресурса. rfield/tv внутри такого блока каттер и грабер не трогают — контент туда пишет разработчик сам.
  • data-mpc-rid + data-mpc-table — сменить источник значения поля: при data-mpc-table отличном от config поле читается из колонки ресурса с id из data-mpc-rid.

Например:

<h1 data-mpc-rfield="pagetitle">Заголовок страницы</h1>
<div data-mpc-tv="subtitle" data-mpc-ftype="textarea" data-mpc-fcap="Подзаголовок">Текст</div>


Вставка элементов: сниппеты и чанки

В разметке можно сразу размещать вызовы сниппетов и подключение чанков.

  • data-mpc-snippet — заменяет элемент на вызов сниппета. Значение
    "имяСниппета|пресет"
    (пресет опционален). Префикс
    !
    — некэшируемый вызов.
  • data-mpc-chunk — имя файла-чанка (используется вместе с data-mpc-include/data-mpc-parse); также помечает вложенный чанк для нарезки в отдельный файл.
  • data-mpc-include — флаг: подключить чанк из файла (
    {include "file:..."}
    ). Имя файла берётся из соседнего data-mpc-chunk.
  • data-mpc-parse — как include, но через parseChunk с параметрами; само значение атрибута — массив параметров (Fenom-литерал), имя чанка — из data-mpc-chunk.
  • data-mpc-attr — «отложенный» атрибут: строка
    data-mpc-attr="attr=val"
    целиком заменяется на
    attr=val
    . Нужен, чтобы Fenom/спецсимволы в атрибуте дошли до рендера невырезанными.

Примеры:

<div data-mpc-snippet="pdoResources|news"></div>
<div data-mpc-include data-mpc-chunk="header.tpl"></div>
<div data-mpc-parse="['cls' => 'card']" data-mpc-chunk="card.tpl"></div>
<a data-mpc-attr="href={$resource.uri}">Ссылка</a>


Дополнительные атрибуты вывода

  • data-mpc-unwrap — флаг: вывести только содержимое (плейсхолдер/вызов), отбросив сам элемент-обёртку.
  • data-mpc-symbol — переопределить первый символ Fenom-тега. По умолчанию
    {
    , для статичных секций
    ##
    . Пример:
    data-mpc-symbol="##"
    .
  • data-mpc-nolazy — флаг: отключить ленивую загрузку для конкретного изображения/фона.


Разметка текста в полях

По умолчанию любое значение поля при записи очищается от HTML (strip_tags). Какие теги разрешено сохранять — задаётся в системной настройке mpc_allowed_tags (через запятую). Пусто — вырезаются все теги.
Эта же настройка управляет тулбаром визуального редактора: кнопка форматирования показывается только для разрешённого тега. Чтобы появились кнопки ссылки/картинки, добавьте в настройку a и img. Дополнительные разрешённые атрибуты к безопасным дефолтам задаются в настройке mpcve_allowed_attrs.
Для разметки контента рекомендуется ограничиться набором: strong, em, u, s, ul/li, ol/li, blockquote, code, kbd, a.

Многоязычность (лексиконы)

Компонент умеет хранить значения полей не в самих полях, а в файлах лексиконов — это даёт перевод контента и переключение языка на лету без перенарезки.

  • mpc_use_lexicons — главный переключатель: при включённом значения пишутся ключом в БД + переводом в файл лексикона.
  • mpc_default_language — язык-источник (по умолчанию ru), из него берутся плейсхолдеры при синхронизации остальных языков.
  • mpc_available_languages — все языки сайта (через запятую). При нарезке набор ключей всех неосновных языков приводится к текущему: новые ключи добавляются со значением-плейсхолдером, удалённые выкидываются.
  • data-mpc-lexicon — на секции задаёт префикс ключей лексикона (он же мерж-ключ секции); пустое значение → имя секции. Различает оригинал и копию секции.
  • data-mpc-translate — только для контактов: переопределяет список переводимых под-полей контакта (CSV), перекрывая настройку mpc_contact_lexicon_fields.


Работа с контактами и другой публичной информацией

Контакты сохраняются в ТВ с именем contacts у ресурса с шаблоном Контакты. Всё это задаётся в системных настройках.
Для добавления контактов используется атрибут data-mpc-contact, где нужно указать тип контакта и расположение. Доступные типы:

  1. phone — телефон
  2. mail — email
  3. address — адрес
  4. social — соц. сеть
  5. map — карта
  6. worktime — время работы
  7. requisite — реквизит
  8. messenger — мессенджер

Контакты группируются по значению. Один контакт может иметь несколько мест размещения на странице (например в шапке и в подвале).
Расположение — это набор латинских символов, цифр и знака подчёркивания (например header и footer).
Так же для контакта можно указать ключ в атрибуте data-mpc-key. Ключ нужен для обращения к конкретному контакту. Ключ может содержать только латиницу, цифры и нижнее подчёркивание. Если ключ не указать, он будет сгенерирован автоматически. Ключ невозможно изменить из админки.

Данные контакта следует размещать в html элементах с атрибутами data-mpc-cfield. Доступны следующие поля контакта:

  1. caption — подпись
  2. attributes — любые другие данные, например иконка

Важно: из-за особенностей работы компонента не используйте знак +, его можно заменить на %2B, но только не в контактах.
Так же в контактах не допускается использовать svg, эти теги просто не будут заменены на плейсхолдеры.

Генерация миниатюр

Компонент умеет генерировать миниатюры изображений с помощью сниппета pThumb. Сниппет устанавливается отдельно.
Вы можете указать свой в системной настройке mpc_thumb_snippet.
В системной настройке mpc_common_thumb_params можно указать параметры генерации миниатюр, ширина и высота подставятся из соответствующих атрибутов.
Если оставить эту настройку пустой, миниатюры генерироваться не будут. Отключить генерацию миниатюр для отдельного изображения можно добавив ему атрибут data-mpc-nothumb.
Через атрибут data-mpc-thumb можно задать индивидуальные параметры для конкретного изображения.
ВАЖНО: изображения в списках считаются одним целым, поэтому атрибуты data-mpc-nothumb и data-mpc-thumb следует указывать первому элементу, а применены они будут ко всем.
Для фоновых изображений (поле bg_img), которые заданы с помощью атрибута style, также доступна генерация миниатюр. При этом ширину и высоту следует указывать в атрибуте style.
ВАЖНО: каждое свойство должно заканчиваться знаком «;», иначе значение не будет считано.
Верная запись выглядит так:

<div class="container" data-mpc-field="bg_img" style="background-image: url('https://i.pinimg.com/736x/2b/d7/27/2bd7274a962e509da7dd8ed5b27549f7.jpg');height: 500px;width:1920px;"></div>


Разворачивание SVG

Если в системной настройке указано значение атрибута и этот же атрибут указан тегу img, то при загрузке страницы тег img будет заменён на SVG из файла.
Картинки с атрибутом из системной настройки mpc_expand_attr игнорируются скриптом, который отвечает за ленивую загрузку.

Загрузка картинок

Если путь к картинке начинается с http и в системной настройке mpc_images_path указан путь к папке, то изображения будут загружены в эту папку при обработке шаблона.
К пути будет добавлено значение атрибута data-mpc-section, т.е. если в системной настройке указан путь /assets/images/ и картинки будут находиться в секции
data-mpc-section="first"
, то загружены они будут в папку /assets/images/first/.

Как добавить поле в секцию и самостоятельно указать плейсхолдер?

Стандартные механизмы генерации плейсхолдеров достаточно универсальны, но всё же не покрывают 100% задач. Кроме того, кому-то может быть удобнее и привычнее расставлять плейсхолдеры и писать вызовы самостоятельно. В этом случае для создания полей в админке нужно внутри секции перечислить все необходимые поля, добавив им атрибут data-mpc-remove:

<section id="{$id}" data-mpc-section="first" data-mpc-name="Секция с простыми полями">
    <span data-mpc-field="title" data-mpc-remove>Title</span>
    <div class="container">
        <h1>{$title}</h1>
    </div>
</section>

ВАЖНО: если вы обращаетесь к глобальным массивам
$_GET
,
$_SESSION
,
$_COOKIE
или используете другие плейсхолдеры, которые будут доступны только непосредственно перед отдачей страницы пользователю, то начинать запись следует с
##
вместо
{
. Например:

<h1>Купить грибы в ##$.get.city}</h1>
##'msProducts' | snippet: ['parents' => 0, 'resource' => $.session.resources]} <!-- этот вызов полностью будет произведён перед отдачей на фронт -->
##'msProducts' | snippet: ['parents' => 0, 'resource' => $.session.resources, 'title' => '{$title}' ]} <!-- а в этот вызов будет передан параметр title, значение которому будет присвоено на этапе пререндера -->


Визуальный редактор (mpcVisualEditor)

Размеченные компонентом страницы можно редактировать прямо на фронте — отдельным компонентом mpcVisualEditor. Редактор находит поля по тем же data-mpc-* маркерам и сохраняет правки по каждому полю отдельно.
Чтобы маркеры остались в готовых чанках (иначе редактору не за что зацепиться), на нарезке должна быть включена системная настройка mpc_edit_mode. Сам редактор подключается на фронт только когда одновременно
mpcve_active=1
И
mpc_edit_mode=1
.
Для боевого деплоя mpc_edit_mode выключают и делают перенарезку — в файлы попадает чистый HTML без служебных атрибутов.

Управление из консоли (CLI)

Компонент умеет декларативно приводить админку к описанному в проектных манифестах состоянию — без ручного клика в админке. Тонкая обёртка — console/mpc, доступны группы команд: resources, plugins, configs, settings, clientconfig, packages, cut, cache, lexicon.
Подробности, флаги и формат манифестов — в core/components/migxpageconfigurator/console/README.md.

Системные события

mpcOnGetSectionFieldsValues — позволяет изменить получаемые из шаблона данные. Параметры:
  • sectionKey — ключ секции, значение атрибута data-mpc-section
  • fieldsValues — массив значений полей секции
  • section — DOMElement секции

mpcOnHandleContact — позволяет изменить контактные данные. Параметры:
  • contact — массив контактных данных, доступен как
    $contact[0]

mpcOnBeforeDownloadFile — позволяет изменить имя файла перед загрузкой медиа (картинки/видео/аудио/прочее). Параметры:
  • fileName — имя файла (без расширения); вернуть новое через
    returnedValues['fileName']
  • extension — расширение файла
  • type — тип медиа (images/videos/audios/others)
  • downloadPath — путь к папке загрузки внутри источника
  • Grabber — экземпляр загрузчика

mpcOnBeforeRender — перед рендером ресурса. Параметры: resourceData (можно подменить через
returnedValues['resourceData']
), Render.

mpcOnBeforeParseConfig — перед разбором конфига секций. Параметры: sections (подмена через
returnedValues['sections']
), Render.

mpcOnGetSectionHtml — после сборки HTML секции при рендере. Параметры: section, html (подмена через
returnedValues['html']
), Render.

mpcOnGetNewHtml — при формировании нового HTML поля на нарезке. Параметры: fieldHTMLNew (подмена через
returnedValues['fieldHTMLNew']
), PlaceholderProcessor.

mpcOnFieldSave — после сохранения значения поля (в т.ч. из визуального редактора). Параметры: resourceId, address (уровень/адрес поля).

mpcOnGetLexiconKey — при вычислении ключа лексикона. Параметры: sectionLexiconPrefix, lexiconKey (подмена через
returnedValues['lexiconKey']
), fieldName, Grabber.

mpcOnImportLexiconValue — при импорте значения лексикона. Параметры: value (подмена через
returnedValues['value']
).

mpcOnGetResourceIdentifier — при вычислении идентификатора ресурса для ключей лексикона. Параметры: rid (подмена через
returnedValues['rid']
), Grabber.

mpcOnAddCellToExcel, mpcOnBeforeSaveExcel — хуки экспорта лексиконов в XLSX.

2.5.29-rc

Переключение языка, лексиконизация полей ресурса/TV из редактора, защита pagetitle.

  • Переключение языка: имя cookie (mpc_lang_cookie_name) и домен (mpc_lang_cookie_domain) вынесены в системные настройки; JS-селектор и PHP теперь ставят cookie на один домен — фикс конфликта двух cookie на домене/поддомене. Список языков парсится с trim.
  • Поля ресурса: pagetitle перенесён в protected (дефолты mpc_editable/protected_resource_fields) — через data-mpc-rfield больше не перезаписывается.
  • Лексиконизация data-mpc-rfield/data-mpc-tv из редактора: при лексиконах в колонку/TV пишется ключ mpc_resource(tv), значение — в словарь (без per-resource гейта).
  • Новый сервис LexiconSync: синхронизация ключей между языками + список непереведённых (.pending) — единый код для нарезки (LexiconManager::syncOtherLanguages) и редактора (FieldWriter). Перевод снимает ключ с pending, новый оригинал распространяется плейсхолдером по языкам.
  • data-mpc-fcap/data-mpc-fdesc теперь работают и для TV (подпись/описание создаваемого TV).
  • data-mpc-copy на чанке: файл не нарезается, только подключение (include/parse) — переиспользование чанка без зависимости от порядка/режима нарезки.
  • Фон (bg_img): регулярка замены url() понимает любые кавычки/без и пробелы (раньше только одинарные при выключенном lazyload).
  • Событие mpcOnFieldSave зарегистрировано (вызывалось, но не было в events.php); рассинхрон mpcOnBeforeDownloadImage → mpcOnBeforeDownloadFile исправлен.
  • Каскад контентных полей ресурса от «типа страницы»: пустое поле наследует значение типа (whitelist mpc_editable_resource_fields).
  • mpc_download_paths: подпапка по типу медиа относительно mpc_media_path. setup.options: + cookie языка / путь пресетов / лексиконные настройки.
  • mpcVE: mpcve_active связан с mpc_edit_mode (предупреждение в лог, если редактор включён, но edit_mode выкл).

2.5.28-rc

Группировка системных настроек по областям + полная русификация.

  • Все 49 системных настроек разложены по группам (area): пути, медиа, лексиконы, контакты, поля ресурса, общие — с префиксом mpc_ и русскими заголовками групп (areampc*), чтобы не путались с областями ядра/других пакетов.
  • Лексикон настроек доведён: у каждой настройки есть название и описание (ru/en); заполнены ранее пустые описания (lazyload_enabled/expand_enabled/use_lexicons/available_languages/default_language/allowed_tags/allow_modx_tags).

2.5.27-rc

Чистка настроек и ретайр механизма create/.

  • Аудит системных настроек: набор приведён к реально используемому в коде; на свежих установках лишние удаляются резолвером 6removesettings.php (image_extensions, service_info_tv_name, copy_config_tv_name, images_path, mime_to_ext, resource_lexicon_keys_path, path_to_create, thumb_format).
  • mpc_mime_to_ext (большой JSON в настройке) вынесена в файл elements/media/mime_to_ext.json; настройка теперь mpc_mime_to_ext_path (путь относительно core/). Убран дублировавшийся хардкод-JSON из Grabber.
  • Ретайр mpc_resource_lexicon_keys_path + файла resource_lexicon_keys.inc.php + ветки $_rlang в LexiconManager::createLexicons — роль закрыта автогенерацией ключей mpcresource* (ResourceFieldGrabber).
  • mpc_exclude_fields_path объявлена в settings.php; Render читает путь относительно core/ (единый паттерн с mime/lexicon).
  • mpc_thumb_format ретайрнута — была мёртвой (формат идёт из f= в mpc_common_thumb_params); убрано чтение в Cutter.
  • Ретайр механизма create/: удалены Mpc::manageElement + процессоры, console/mgr_elems.php, CLI-команда elements, настройка mpc_path_to_create, examples/create/. Чем закрыто: TV → авто-провижн data-mpc-tv; ресурсы → resources apply; сниппеты → файловые; плагины → новая CLI-группа plugins.
  • CLI: группа events переименована в plugins и расширена — PluginsApply (заменил EventsApply) создаёт/обновляет плагин (код из файла, категория, static) и синхронизирует события. Манифест console/examples/plugins.example.php. Старый list-формат событий поддержан для совместимости.

2.5.26-rc

Фикс: дефолт настройки mpc_thumb_snippet.

  • Установочный дефолт mpc_thumb_snippet исправлен pThumbmpcThumb (встроенная обёртка над pThumb с проверкой существования файла). Прежнее значение указывало напрямую на pThumb в обход обёртки, расходилось с поставляемым сниппетом snippet.mpcthumb.php и тестами. Затрагивает только свежие установки. Файл: _build/elements/settings.php.

2.5.25-rc

Фикс: ресурсные лексиконы статичной секции перебивали page-types.

  • Фикс каскада лексиконов. При сохранении ресурса (OnDocFormSave) секция, помеченная статичной, оставляла свои ключи в ресурсном lexicon-файле; на рендере resource-уровень перебивает type/page-types (Mpc::getLexiconFilenames грузит ресурсный файл последним) → значение «прилипало» к ресурсу после перевода секции в статику, игнорируя page-types. Теперь manageResourceLexicons вычищает ключи статичной секции (по её префиксу, array_diff_key симметрично filterByPrefix) из ресурсного массива перед перезаписью файла. Существующая порча лечится пересохранением ресурса. Полная перенарезка (mgr_tpl) багом не страдала. Файл: Plugins/OnDocFormSave.php.

2.5.24-rc

Setup-модалка при установке + кнопки копирования секций + чистка legacy.

  • Модалка параметров при установке (build setup-options): использовать лексиконы, доступные языки, язык по умолчанию, путь к манифестам mpc CLI. Значения пишутся в системные настройки (resolvers/Asetupoptions.php, только при свежей установке).
  • Кнопки «Дополнить / Перезаписать / Очистить секции» в тулбаре MIGX-грида mpc_config (замена TV-галочки copy_sections) — без патча vendored-migx. Грид обновляется без перезагрузки. FieldWriter::copySectionsFromType + processors/resource/copysections.
  • mpc CLI: путь к манифестам в настройке mpc_manifests_path (резолв от core/), дефолты имён (apply без аргумента), обёртка console/mpc.
  • TV img и copy_sections больше не создаются при установке (не нужны).
  • Удалена настройка mpc_copy_config_tv_name (обслуживала убранную галочку).
  • Фикс getContextSettingValue: context-настройка теперь override с fallback на глобальную (раньше затирала её на false) — глобальные настройки/модалка подхватываются на фронте.
  • mpc CLI: работа с контекстными настройками (modContextSetting, per-key 'context') и ClientConfig (cgSetting/cgContextValue); settings/clientconfig list с фильтрами --namespace/--context/--group/--key, табличный вывод.
  • Таблица mpc_tracked_fields теперь создаётся при установке через xPDO-схему (класс mpcTrackedField + 0schema-резолвер) вместо ленивого CREATE TABLE.
  • Плагин: привязка OnBeforeDocFormSave добавлена в build-декларацию (нужна для админ-аудита правок ресурса).

2.5.23-rc

Сброс resource-кэша после нарезки (mgr_tpl) — переводы подхватываются без ручной очистки.

Баг: после mgr_tpl.php переводы не появлялись на сайте, пока вручную не сбросишь кэш из админки. Причина: грабер сбрасывает только lexicon_topics (LexiconManager::createLexicons), но НЕ resource-кэш — закэшированные страницы держат старые РЕЗОЛВНУТЫЕ значения лексикона (рендер выполняет {key|lexicon} и кэширует HTML). Mpc::process чистил кэш только при mpc_dev_mode, да и то лишь parsed-файлы (Render::clearCache), а не MODX resource-кэш.

  • Mpc::process в конце зовёт новый refreshSiteCache(): cacheManager->refresh(['lexicon_topics' => [], 'resource' => ['contexts' => []]]) — симметрично точечной записи редактора (FieldWriter::afterSave, который и так подхватывал переводы сразу). Контекст — инициализированный в mgr_tpl ($argv[1], по умолчанию web).
  • Mpc::process — единственный вызов из console/mgr_tpl.php, так что сброс срабатывает ровно на пути перенарезки.
  • Регрессий нет: 290/290 тестов зелёные.

2.5.22-rc

Условный рендер булевых медиа-атрибутов + preload как строка (для редактора video/audio mpcVE).

HTML-boolean-атрибуты (controls/autoplay/loop/muted) включены самим ФАКТОМ присутствия — controls="0" всё равно включает контролы. Каттер рендерил их подстановкой значения (controls="{$rec.controls}"), из-за чего из фронт-редактора их нельзя было ВЫКЛЮЧИТЬ. Теперь рендерим условно.

  • Cutter\PlaceholderProcessor: булевы атрибуты (BOOL_ATTRS) в setAttributes помечаются сентинелом @@MPCBOOL@@, новый renderBoolAttrs превращает его в {if $rec.attr}attr{/if} (firstSymbol { для обычных секций / ## для static → резолвится на final-пассе). Так атрибут выводится только при truthy-значении; 0 его не печатает. Вызов — в конце setMediaPlaceholder (булевы только на главном video/audio; source/img их не имеют). Лексиконизация/подстановка прочих атрибутов не затронута.
  • Grabber\FieldValueExtractor::getMediaValue: preload теперь грабится как string (перечислимый auto|metadata|none), а не boolean — иначе значение терялось (1/0) и рендер preload="{$rec.preload}" давал мусор. Булевы (controls/autoplay/loop/muted) остаются 1/0 (presence) — совместимо с условным рендером ({if 1} → есть, {if 0} → нет), регрессии первого рендера нет.
  • ВАЖНО: чтобы булев атрибут стал управляемым из редактора, он должен присутствовать в разметке на нарезке (иначе {if} не сгенерён — нечего включать/выключать).
  • Тесты: PlaceholderProcessorTest::testSetPlaceholdersVideoBooleanAttrsConditionalRender (+условный рендер, нет сентинела/value-подстановки) — 290/290 зелёные.

2.5.21-rc

ЕДИНОЕ решение «лексиконизировать ли поле» для каттера, грабера и редактора.

Решение размазывалось и расходилось: config-поля каттер/грабер гейтили по content-type (shouldLexiconize/in_array), а rfield/TV — только по useLexicons + exclude, БЕЗ content-type. Итог: image-TV получал | lexicon (каттер) и ключ в колонке (грабер), хотя image нет в mpc_translated_content. Редактор же вообще решал по эвристике «есть ли ключ у соседей». Теперь ВСЕ идут через LexiconManager::shouldLexiconize($contentType, $field[, $parent]) (content-type ∈ mpc_translated_content + exclude_lexicons).

  • LexiconManager::contentTypeForTag($tag) — ЕДИНЫЙ маппинг тег→content-type (img/source/picture→image, video→video, audio→audio, иначе text). Один источник для каттера и грабера.
  • Cutter::replaceResourceMarkers (rfield/TV): | lexicon ставится только если shouldLexiconize(contentTypeForTag(tag), name). Было !isExcluded(name) без content-type.
  • ResourceFieldGrabber::lexiconize (rfield/TV): тот же гейт. image-TV при настройке без image → путь в колонке, не ключ; exclude (pagetitle) сохранён.
  • FieldWriter (редактор): решение тоже каноничное (shouldLexiconize) вместо прежней эвристики fieldUsesLexiconKeys (удалена). content-type: config — inputTVtype поля из mpc_base; TV — modTemplateVar.type; rfield — text. idx-less цепочка parentFieldName (как каттер); ключ для записи — idx-ful (getLexiconKeyForPath). Гейт применён и к rfield/TV-веткам редактора.
  • ВАЖНО: секции/ресурсы, нарезанные при ДРУГОЙ настройке mpc_translated_content, рассинхронизированы с новым решением — нужна перенарезка (mgr_tpl). Отметить в документации пакета.
  • Тесты: LexiconManagerTest (+contentTypeForTag), ResourceFieldGrabberTest (+image-TV не лексиконится; tdc-проп в фикстурах), FieldWriterTest (rfield/tv/скаляр гейтятся настройкой) — 289/289 (Unit+Snapshot) зелёные.

2.5.20-rc

Лексикон-ключи вложенных полей: единая КОНСТРУКЦИЯ parentFieldName (грабер+редактор).

  • Раньше формат ЛИСТА ключа был единым (LexiconManager::getLexiconKey), но КОНСТРУКЦИЯ parentFieldName для вложенности (цепочка {field}/{field}_{idx} по уровням) жила только в грабере (ContentParser), а редактор (FieldWriter::makeLexiconKey) на вложенных полях бейлил в '' → значение писалось ЛИТЕРАЛОМ в конфиг → чанк {$field|lexicon} рендерил ПУСТО. Итог: у нового вложенного элемента картинка сохранялась (запись-ветка ключи генерит), а скалярные поля (title/subtitle/content) — нет.
  • Конструкция вынесена в LexiconManager: appendLexiconParent($parent,$field,$idx) — один сегмент цепочки (грабер зовёт инкрементально при рекурсии); getLexiconKeyForPath($prefix,$path,$fieldName) — сворачивает путь и форматирует листом через getLexiconKey (редактор зовёт). Теперь ОБА идут через одну функцию — ключи не разъезжаются.
  • ContentParser::parseHTML переключён на appendLexiconParent (вывод побайтово тот же — Snapshot-тесты грабера зелёные). FieldWriter::makeLexiconKeygetLexiconKeyForPath (вложенность любой глубины; убран бейл на path>1).
  • ПОБОЧНО исправлен ключ строк списка при idx>0: было {prefix}_{field}_{name}_{idx} (неверно), стало {prefix}_{field}_{idx}_{name}_{idx} как у грабера (реальный лексикон: third_list_of_lists_1_title_1). Тест testNewRowFieldGetsLexiconKey приведён к верной схеме.
  • FieldWriter::fieldUsesLexiconKeys РАСШИРЕН: ищет лексикон-ключ поля во ВСЕХ экземплярах списка (по всем индексам всех уровней, новый рекурсивный listFieldHasLexiconKey), а не только у соседей в КОНКРЕТНОЙ строке-списке. Без этого у НОВОГО верхнего элемента его вложенный список целиком пуст (deep-clone) → соседей-ключей нет → поле не лексиконизировалось → значение писалось литералом → после перезагрузки чанк {$f|lexicon} рендерил ПУСТО («значения сохраняются, но после reload пустые»). Тест testNewNestedFieldGetsLexiconKeyFromOtherInstances.
  • Тесты: LexiconManagerTest +2, FieldWriterTest +1 — 286/286 (Unit+Snapshot) зелёные.

2.5.19-rc

Row-операции по вложенным спискам (path) + deep-clone новой строки.

  • ConfigFieldWriter::mutateRows — поддержка ВЛОЖЕННЫХ списков: address.path = путь спуска к строке-контейнеру [{field,idx},…] (НЕ включает целевой список), parentField = имя целевого списка в этой строке. Переиспользует mutateAtPath (декод JSON-строк по уровням). Для top-level списков path пуст (прежнее поведение). Новый descentPath() берёт путь ТОЛЬКО из address.path (для row-ops parentField+idx — не путь: idx тут операнд $fn). add/delete/move работают на любой глубине.
  • ConfigFieldWriter::addRow — новая строка теперь deep-clone образца (blankRow/blankRows): скаляры → '', ВЛОЖЕННЫЕ списки → структура каждой строки СОХРАНЯЕТСЯ рекурсивно, значения чистятся. ВАЖНО: вложенный список внутри строки ContentParser хранит НАТИВНЫМ массивом (json-кодируется только верхний уровень поля), поэтому deep-clone ловит и нативные массивы строк-объектов, и JSON-строки (после правок через mutateAtPath). Иначе ветка is_array→[] затирала дочерний список нового элемента → «row not found in path for field img» при сохранении вложенного img. Прочие нативные массивы (sources, пустые) → []. looksLikeRows() детектит JSON-строку строк-объектов.
  • Тесты ConfigFieldWriterTest: +6 (deep-clear строковой и нативной структуры, nested add/delete/move по path, ошибка пути, регрессия «img нового вложенного элемента») — 20/20 зелёные.

2.5.18-rc

Read-API лексикона: дамп карты key→value для показа ЗНАЧЕНИЙ в редакторе.

  • LexiconWriter::all(identifier) — полная карта key=>value лексикона ресурса (через LexiconManager::getLexicons). Нужна визуальному редактору (mpcVE), чтобы панель скрытых полей показывала значение, а не ключ (иначе сохранение без правки записало бы сам ключ в значение — порча).
  • FieldWriter::readLexicons(level, resourceId) — карта лексикона уровня (resource|global) для фронта; пусто, если mpc_use_lexicons выкл. Резолвит ресурс уровня (resolveLevelResource) + идентификатор файла. Read-only, без записи.

2.5.17-rc

Write-API: глубокий лексикон-мерж media-записи с sources (picture/video/audio).

  • FieldWriter::writeConfigField: record-значение с вложенным img (JSON-строка) и/или массивом sourcesmergeMediaRecord (а не плоский mergeRecordWithLexicon). Рекурсивно обрабатывает лексиконизируемые листья: img.src/alt/title, sources[k].srcset/src, верхнеуровневые src/poster (video/audio). Существующий ключ → пишем значение в лексикон и сохраняем ключ; НОВЫЙ source/лист → генерим ключ единой getLexiconKey ({prefix}_{field}_source[_idx], _poster, …). Если новое значение РАВНО ключу — фронт прислал ключ (лист не меняли) → лексикон не трогаем (защита от затирания неизменённых источников отрендеренным thumb-URL). Хелперы: isMediaWithSources, mergeMediaRecord, mergeImgKeys, resolveLeafKey. Тест testMergeMediaRecordPictureSources.
  • Версия пакета 2.5.162.5.17.

2.5.16-rc

Write-API: новая media-запись (картинка добавленной строки) получает лексикон-ключи.

  • FieldWriter::writeConfigField: для record-значения теперь различаем существующую запись (есть ключи → mergeRecordWithLexicon, как было) и НОВУЮ (recordHasLexiconKeys=false → newRecordWithLexiconKeys). Новая media-запись: для лексиконизируемых под-полей (src/srcset/alt/title) генерим ключи через единую getLexiconKey (idx=0 → без суффикса), пишем значения в лексикон, в запись кладём ключи; width/height — литералом. Без lexicon_prefix секции → литерал. Нужно для загрузки картинки в только что добавленную строку media-списка (иначе src="{…|lexicon}" → ''). Хелперы: sectionPrefix, recordHasLexiconKeys, newRecordWithLexiconKeys. Тест testNewMediaRecordGetsLexiconKeys.
  • Версия пакета 2.5.152.5.16.

2.5.15-rc

Рефакторинг: генерация лексикон-ключа — ЕДИНАЯ функция (грабер + редактор).

  • LexiconManager::getLexiconKeypublic static: единый источник формата ключа. Раньше редактор (FieldWriter::makeLexiconKey, 2.5.14) дублировал формат и РАСХОДИЛСЯ с грабером — клеил суффикс даже для idx=0, тогда как грабер для idx=0 суффикс НЕ добавляет (gallery_list_images vs ошибочный …_0). На 1-перенарезке это рассинхронило бы ключи редактора и грабера. Теперь makeLexiconKey строит только options и зовёт общий getLexiconKey. Вложенные новые поля (путь >1) ключ пока не генерят (редко). Тест testGetLexiconKeyFormat фиксирует формат (idx=0 → без суффикса).
  • Версия пакета 2.5.142.5.15.

2.5.14-rc

Фикс: новое лексиконизируемое поле (напр. поле добавленной строки) сохраняется как ключ.

  • FieldWriter::writeConfigField: при useLexicons, если поле НОВОЕ (текущее значение пустое/не-ключ) и реально лексиконится, редактор теперь ГЕНЕРИТ ключ лексикона (формат как у грабера: {prefix}_{parentField}_{field}_{idx}, prefix из lexicon_prefix секции), пишет значение в лексикон ресурса, в конфиг кладёт КЛЮЧ. Раньше писался литерал → чанк {$field | lexicon} возвращал '' (модификатор на промахе ключа отдаёт ''), и значение «пропадало» после перезагрузки. Лексиконизируем только если поле лексиконится — сигнал: у соседних строк того же списка лежат ключи (fieldUsesLexiconKeys); иначе (литералы/нет соседей/top-level) — пишем литералом. Re-edit «застрявшего» литерала чинит его (заменяет на ключ). Тесты: +2 (testNewRowFieldGetsLexiconKey/…StaysLiteralWhenNotLexiconized).
  • Версия пакета 2.5.132.5.14.

2.5.13-rc

Write-API: запись/чтение поля строки ВЛОЖЕННОГО списка (путь любой глубины).

  • ConfigFieldWriter::setValue/getValue: адрес теперь поддерживает path — массив сегментов [{field, idx}, …] от секции к строке. Поле уровня 2 лежит на 2 уровня глубже (cfg[sec][L1][i][L2][j][field]) — раньше односегментный адрес писал в top-level поле (затирал одноимённое поле секции). Навигация mutateAtPath декодирует JSON-строки строк на каждом уровне. parentField+idx (1 уровень) — частный случай пути (back-compat). Тесты: +3 (set/get по пути, ошибка индекса).
  • Версия пакета 2.5.122.5.13.

2.5.12-rc

Write-API: структурные операции над строками списков (add/delete/move) для редактора.

  • ConfigFieldWriter: addRow/deleteRow/moveRow (PURE, через mutateRows) — манипуляции массивом строк поля-списка в mpc_config. addRow добавляет пустую строку по структуре первой (MIGX_id=max+1); deleteRow/moveRow — splice + reindex. Значения строк (вкл. лексикон-ключи) едут ВМЕСТЕ со строкой → переводы не теряются при перестановке/удалении (orphan-ключ удалённой строки безвреден, чистится перенарезкой). Тесты: +4 (testAddRow…/testDeleteRow…/testMoveRow…).
  • FieldWriter::writeRowOp(address) — обёртка: резолв уровня + load/save mpc_config + диспетч add|delete|move. Адрес: level/resourceId/section/parentField/op + idx (delete) / fromIdx,toIdx (move).
  • Версия пакета 2.5.112.5.12.

2.5.11-rc

Фикс: lexicon-мерж без updContent не тащит обратно ключи удалённых полей (orphan).

  • LexiconManager::createLexicons (preserve-ветка): сохраняем значение существующего перевода ТОЛЬКО для ключей, которые ещё есть в текущей нарезке (поле живо). Раньше array_merge($lexicons, $existing) возвращал в файл ключи удалённых полей — orphan-перевод оставался и при повторном добавлении поля маскировал новое значение (поле test_2 удалили→добавили с новым значением, а в лексикон писалось старое). Теперь foreach по ключам нарезки: живое поле → значение сохраняем; удалённое → ключ выпадает; новое → значение из шаблона.
  • NB: уже «застрявший» orphan для ЖИВОГО поля чистится разовой перенарезкой с 1 (или delete-поля + перенарезка) — отличить устаревший orphan от правки админа по состоянию файла нельзя.
  • Тест переименован/уточнён: testCreateLexiconsPreservesLiveDropsOrphanWithoutOverwrite.
  • Версия пакета 2.5.102.5.11.

2.5.10-rc

Фикс: значения лексиконов в файле не перезаписываются без updContent (дополняет 2.5.9).

  • LexiconManager::createLexicons($allLexicons, bool $overwrite = true): без updContent существующие переводы в lexicon-файле НЕ трогаются — дописываются только новые ключи (новых полей); значения админа сохраняются (array_merge($lexicons, $existing) — existing побеждает). С updContent — прежнее поведение (шаблон перезаписывает, whitelist resource_lexicon_keys сохраняется). Раньше array_merge($tmp, $lexicons) всегда давал template-значениям перезаписать файл → переводы затирались при mgr_tpl без 1.
  • Вызовы: SectionProcessor передаёт updContent; Grabber::createLexicons по умолчанию уважает $this->updContent. Контакты (ContactUpdater) уже гейтятся (handleContacts выходит при !updContent) — без изменений.
  • Тесты: testCreateLexiconsPreservesExistingWithoutOverwrite, testCreateLexiconsOverwritesWithFlag.
  • Версия пакета 2.5.92.5.10.

2.5.9-rc

Перенарезка без updContent = умный МЕРЖ конфига (а не «не трогать»). Уточняет 2.5.6.

  • SectionProcessor: значения секций теперь МЕРЖАТСЯ со старым конфигом (resource И static), а не перезаписываются/пропускаются. mergeSectionFields(grabbed, existing, reserved, overwrite):
    • без updContent: служебные ключи (MIGX_id/id/section_name/…) — свежие из шаблона; контент-поле, что уже есть в конфиге → СОХРАНЯЕМ правку админа; новое поле → его контент из шаблона; поле, ушедшее из шаблона → удаляется; admin-поля настроек/стилей вне шаблона (hide_section/position/props…) — сохраняются.
    • с updContent (1): шаблон перезаписывает контент (admin-поля настроек/стилей вне шаблона всё равно сохраняются).
  • Resource-конфиг теперь пишется ВСЕГДА (мерж со существующим по MIGX_formname), а не только при updContent. Static-блоки (updateStaticSectionValues) — мерж вместо голой замены. 2.5.6 (просто гейт по updContent) этим заменён.
  • Тесты: mergeSectionFields (+2: preserve/overwrite), indexConfigBySection.
  • Версия пакета 2.5.82.5.9.

2.5.8-rc

Фикс: поля настроек/стилей mpc_base (inline_styles/class_names/…) не дублируются в контент-таб.

  • SectionProcessor::getSectionFields: data-mpc-field с именем, которое уже есть в mpc_base в НЕ-контентном табе (настройки/стили: inline_styles, class_names, css_file_path, props, hide_section, position…), больше НЕ добавляется в контент-таб секции — иначе появлялся дубль определения. Значение поля по-прежнему грабится (ContentParser), определение остаётся одно — в своём табе mpc_base. Реализация: collectReservedFieldNames (имена нон-контентных табов) + skip в getSectionFields. Тест testCollectReservedFieldNames. Дополняет фикс 2.5.7 (там проверялся только контент-таб).
  • Версия пакета 2.5.72.5.8.

2.5.7-rc

Фикс: имя поля из mpc_base приоритетнее ftype (без дубликата определения).

  • SectionProcessor::makeFieldDef: проверка «имя уже есть в mpc_base → берём его определение» теперь БЕЗУСЛОВНА (раньше — только если не задан data-mpc-ftype). Если поле с таким именем есть в mpc_base, ftype игнорируется (имя приоритетнее) — не плодим дубликат/конфликт. fcap/fdesc по-прежнему переопределяют подпись/описание. Тест: SectionProcessorTest::testNameInBaseBeatsFtype.
  • Версия пакета 2.5.62.5.7.

2.5.6-rc

Фикс: перенарезка БЕЗ updContent больше не перезаписывает контент статик-секций.

  • SectionProcessor::grabSection: граб значений статик-секции в конфиг статик-блоков (updateStaticSectionValues) теперь гейтится updContent — как и resource-уровень (там запись mpc_config гейтилась, а статик-ветка sbpSectionValues — нет, поэтому mgr_tpl web temp.tpl без 1 перезаписывал контент общих секций). Без updContent: схема/вёрстка обновляются (createSectionConfig), значения сохраняются (sbp пишется обратно без изменений). С 1 — как раньше.
  • Версия пакета 2.5.52.5.6.

2.5.5-rc

Фикс фатала при перенарезке с лексиконами и нестандартным именем lexicon-файла.

  • LexiconManager::getResourceIdentifierById: $q->prepare() мог вернуть false (SQL не подготовился — напр. mpc_lexicon_filename_field указывает на несуществующую колонку modResource), а код сразу звал $q->stmt->execute() → «Call to a member function execute() on bool» (фатал на mgr_tpl/перенарезке). Теперь проверяем is_object($q->stmt) перед execute; при неудаче — фолбэк на числовой id + LOG_LEVEL_ERROR с подсказкой проверить настройку. Тест: LexiconManagerTest::testGetResourceIdentifierByIdFallsBackWhenPrepareFails. Латентный баг (не из 2.5.x), всплыл при включённых лексиконах с lexiconFilenameField != id.
  • Версия пакета 2.5.42.5.5.

2.5.4-rc

Публичный read-API конфига для редактора (mpcVE): правка СКРЫТЫХ полей.

  • FieldWriter::readConfig(level, resourceId) — read-only: возвращает декодированный mpc_config уровня (resource/global/type). Нужен mpcVE для панели скрытых полей: поля, вырезанные из страницы (data-mpc-remove) или вспомогательные (напр. resources), DOM-маркера не имеют, но лежат в конфиге. Переиспользует resolveLevelResource + имя TV (единый источник правды с записью); запись скрытого поля идёт прежним путём writeConfigField (address с parentField/idx для под-полей строк). Тесты: FieldWriterTest (+3: decode секций, пустой конфиг, нет ресурса).
  • Версия пакета 2.5.32.5.4.

2.5.3-rc

Лексиконизация TV-полей (data-mpc-tv) — по аналогии с rfield, замыкает цепочку перевода TV (грабер → каттер → write-API). Отменяет ограничение из 2.5.1-rc «tv не трогается».

  • Грабер (ResourceFieldGrabber): TV-ветка теперь лексиконится так же, как rfield — в TV-значение пишется КЛЮЧ mpc_resource_tv_, текст уходит в файл лексикона ресурса. Гейты те же: useLexicons + !isExcluded (поле из excludeLexiconFields → прямое значение в TV, без ключа). Метод lexiconize() получил параметр keyPrefix (rfield → mpc_resource_, tv → mpc_resource_tv_).
  • Отдельный неймспейс ключа mpc_resource_tv_ (а не общий mpc_resource_) — чтобы rfield и TV с ОДИНАКОВЫМ именем не делили один ключ лексикона (иначе перевод одного затирал бы другой). Тест testRfieldAndTvSameNameUseDistinctKeys фиксирует это.
  • Каттер (replaceResourceMarkers): получил параметр lexiconKeyPrefix; вызов для tv теперь с lexiconize=true и префиксом mpc_resource_tv_ → рендерит {'mpc_resource_tv_' | lexicon} (без лексиконов / для excluded — прямое {$resource.tvs.}).
  • Write-API (FieldWriter::writeTv): зеркало writeResourceField — в лексиконном режиме, если у TV есть ключ mpc_resource_tv_, правка пишется в ЛЕКСИКОН (колонку/TV-значение не трогаем); иначе прямой setTVValue (degradation, как у rfield). mpcVE-сторона (роутинг type:'tv' → writeTv) уже была — фронт-редактирование TV теперь лексикон-aware без изменений в пакете mpcVE.
  • LexiconManager/LexiconWriter/Render — без изменений (утилиты переиспользованы; $resource.tvs уже в скоупе рендера).
  • Тесты (+5, всего 255): ResourceFieldGrabberTest (+3: tv-лексикон вкл, tv-exclude, rfield/tv без коллизии), CutterSnapshotTest (+1: tv → mpc_resource_tv_ lexicon-форма), FieldWriterTest (+1: writeTv в лексикон при наличии ключа). Применяется ПЕРЕНАРЕЗКОЙ.
  • Версия пакета 2.5.22.5.3.

2.5.2-rc

Единый exclude для всех лексиконизируемых полей (включая rfield). Отменяет решение из 2.5.1-rc «отдельный exclude для rfield не вводим».

  • excludeLexiconFields теперь действует и на поля ресурса (data-mpc-rfield), а не только на config-секции. Раньше rfield-путь лексиконизации (грабер ResourceFieldGrabber::lexiconize + каттер replaceResourceMarkers) гейтился ТОЛЬКО на useLexicons и игнорировал список исключений — поэтому лексиконились ВСЕ rfield, включая pagetitle, который MODX читает из колонки напрямую (`/меню/крошки/дерево/поиск) и показывал бы там ключ вместо значения. Теперь оба места консистентно спрашиваютLexiconManager::isExcluded()(новый публичный фасад над приватнымisFieldExcluded): исключённое поле → грабер пишет в колонку ЗНАЧЕНИЕ (не ключ), каттер рендерит прямое{$resource.}(не| lexicon). Гейты rfield-лексиконизации:useLexicons+!isExcluded` (защищённые alias/uri/template по-прежнему отфильтрованы грабером раньше).
  • pagetitle добавлен в дефолтный excludeLexiconFields (services/exclude_lexicons.inc.php). Прочие нативные поля (menutitle/longtitle/description/…) — в project-specific exclude-файл (mpc_exclude_lexicons_filename) по необходимости (часто они как раз переводимы — в дефолт не кладём).
  • Тесты: ResourceFieldGrabberTest (+testExcludedRfieldNotLexiconized: колонка=значение, лексикон пуст), CutterSnapshotTest (+testCutterExcludesNativeFieldsFromLexicon: pagetitle→{$resource.pagetitle}, content→| lexicon). Применяется ПЕРЕНАРЕЗКОЙ.
  • Версия пакета 2.5.12.5.2.

2.5.1-rc

Под фронт-редактирование (mpcVE): лексикон-aware запись полей, лексиконизация полей ресурса, кросс-ресурсные поля. Оставшийся роадмап редактора — core/components/mpcvisualeditor/docs/TODO.md.

  • Лексикон-aware FieldWriter::writeConfigField: в лексиконном режиме поле mpc_config хранит КЛЮЧ, а текст — в lexicon-файле. Теперь при записи через write-API: если ТЕКУЩЕЕ значение поля — ключ лексикона, новое значение пишется в лексикон (LexiconWriter), а ключ в конфиге сохраняется; для медиа-записи ([{src,alt,title,…}]) мерж по под-полям (лексиконные src/alt/title → их ключи обновляют лексикон, не-лексиконные width/height → литералом). Не-лексиконные значения и mpc_use_lexicons=0 → прямая запись в конфиг (прежнее поведение, деградирует само). Снята прежняя заглушка lexiconized → not implemented. Фронт менять не нужно (текстовый и img-редакторы шлют литералы — бэкенд сам разруливает по наличию ключа).
  • Новый LexiconWriter (services/custom/Handlers): точечный read-modify-write одной записи lexicon-файла {core}/{mpc_lexicon_path}/{culture}/{identifier}.inc.php (сохраняет остальные ключи; идентификатор/чтение/санитайз — из LexiconManager; culture = $_COOKIE['mpc_lang'] ?: cultureKey; cache-refresh best-effort). Тесты: LexiconWriterTest (has/set/escape/preserve), FieldWriterTest (+merge/isRecordValue).
  • Лексиконизация полей ресурса (data-mpc-rfield). Раньше rfield по дизайну НЕ лексиконились (нативная колонка, рендер {$resource.field}) — поэтому не переводились. Теперь при mpc_use_lexicons=1 цепочка замкнута в 3 местах: (1) грабер (ResourceFieldGrabber) кладёт в КОЛОНКУ ресурса КЛЮЧ mpc_resource_ (как config-поля хранят ключ в mpc_config), а значение — в лексикон-файл ресурса (в админке поле показывает ключ, не дубль значения). NB: нативные потребители колонки (MODX-`/меню/крошки/поиск) увидят ключ — для full-mpc сайтов, где заголовок рендерится через rfield, это ок; иначе заголовки рендерить через| lexicon; (2) каттер (handleResourceFields/replaceResourceMarkers) рендерит rfield как{'mpcresource' | lexicon}(без рантайм-фолбэка — как config-поля: есть лексикон →| lexicon, нет →{$resource.field}при выключенных лексиконах); (3)FieldWriter::writeResourceFieldпишет правку в лексикон-ключmpcresourceтекущего языка (колонку не трогает). Лексиконятся ВСЕ rfield (динамически из разметки; защищённые alias/uri/template отфильтрованы грабером, отдельный exclude для rfield не вводим).tvне трогается. Тесты:ResourceFieldGrabberTest(+2: лексикон вкл/выкл). Применяется ПЕРЕНАРЕЗКОЙ. Нюанс: нативная колонка не обновляется правкой перевода (MODX-меню/крошки берутpagetitle` напрямую) — фронт-контент через каттер показывает перевод.
  • Кросс-ресурсные поля: data-mpc-res="{$id}" на ОБЁРТКЕ (поля ресурса, выводимого сниппетом — превью/аннотация статьи в листинге) → правка пишется в ТОТ ресурс. (1) strip-regex: +res. (2) Каттер (replaceResourceMarkers): если у data-mpc-rfield/data-mpc-tv есть data-mpc-res на самом элементе ИЛИ предке (hasAttribute+closest; DiDom closest не включает self) → каттер контент НЕ переписывает (continue), только маркеры остаются для редактора. handleResourceFields бежит по всему html ДО извлечения item-чанков, поэтому без этого он ломал бы авторскую разметку item-чанка сниппета — а рендер чужого ресурса (reslexicons + | lexicon, фолбэк) пишет автор, каттер этого не знает. (3) FieldWriter::writeResourceField — авто-роутинг цели: лексикон только если у целевого ресурса есть ключ mpc_resource_ (mpc-управляемый), иначе колонка (обычная статья из сниппета). (4) Грабер (ResourceFieldGrabber): поля data-mpc-rfield/data-mpc-tv ВНУТРИ обёртки data-mpc-res на грабе ИГНОРИРУЮТСЯ (isCrossResource через closest) — это разметка для редактора, их значения принадлежат другому ресурсу; иначе {$id}-разметка затирала бы поля текущего ресурс-типа. VE-часть (фронт читает data-mpc-res) — в пакете mpcVE. Применяется перенарезкой.
  • Тонкости (далее): поле, ПУСТОЕ при нарезке (нет ключа) и заполняемое в редакторе, пишется литералом (генерация нового ключа — отдельно); контактные/каскадные уровни проверить на сайте; рендер чужого ресурса через сниппет (reslexicons + | lexicon + фолбэк) пишет автор item-чанка.
  • Версия пакета 2.5.02.5.1.

2.5.0-rc

Фича «произвольные имена полей секции»: произвольное data-mpc-field имя теперь видно и редактируется в админке (S1 + S1.5 + S2 + GC). Фронтовая часть (mpcVisualEditor читает data-mpc-ftype для точного типа редактора) — в пакете mpcVE отдельно.

  • [S1] Произвольные имена полей секции, видимые/редактируемые в админке (clone-by-type). Раньше каттер (SectionProcessor::getSectionFieldsdeleteUndueFields) оставлял в конфиге секции ТОЛЬКО поля, заранее описанные в mpc_base — поле с произвольным data-mpc-field именем выкидывалось и в migx-форме админки не появлялось. Теперь имя разрешается в определение по ТИПУ-прототипу: тип задаётся data-mpc-ftype (имя прототипа в mpc_base), иначе дефолт по элементу (медиа-теги img/picture/video/audio и фон через inline style → свой тип img/picture/video/audio/bg_img; прочее → text). Существующие прототипы (img, list_*, picture, video, audio, bg_img) клонируются из mpc_base с переименованием поля (sub-config configs/виджет сохраняются — списки/медиа/checkbox работают «из коробки»); скаляры text/textarea/richtext/number/checkbox(Да==1) синтезируются в коде. caption = data-mpc-fcap либо «{caption прототипа} (имя)»; description = data-mpc-fdesc. Если имя само совпало с прототипом и ftype не задан — берётся как есть (прежнее поведение). Новые authoring-атрибуты data-mpc-ftype|fcap|fdesc добавлены в strip-regex каттера. mpc_base НЕ менялся (чистка до реестра типов + миграция — S2; фронт читает ftype — S3). Тест SectionProcessorTest (pure makeFieldDef, +10 кейсов). Напоминание: применяется ПЕРЕНАРЕЗКОЙ секций (per-section конфиги пересобираются).
  • [S1.5] Динамические списки из произвольных имён. Если data-mpc-field="X" — структурный список (есть data-mpc-item), X нет в mpc_base и не задан ftype, каттер собирает row-конфиг migx ДИНАМИЧЕСКИ из полей строки (data-mpc-field-{уровень}, union по строкам с dedupe по имени) тем же makeFieldDef и сохраняет в modx_migx под детерминированным именем mpc_auto__ (перенарезка апдейтит тот же конфиг); поле-список ссылается на него через configs. Генерится formtabs + columns (картинки → this.renderImage, вложенные списки show_in_grid=0) + дефолтный extended. Вложенные списки (data-mpc-field-2 в data-mpc-item-1 и глубже) — РЕКУРСИВНО, что закрывает прежнее ограничение 2-го уровня. Предопределённые mpc_list_* НЕ трогаются: имена из mpc_base и ftype идут штатным путём (ничего на рабочем сайте не ломается). Медиа-списки формата «повторяющиеся теги без ul/li» (list_images и т.п.) — на прежних прототипах. GC осиротевших авто-конфигов: каждый mpc_auto_* помечается владельцем (extended.mpc_owner = имя секции); при нарезке секции удаляются её mpc_auto_*, не пересозданные в текущий проход (поле-список удалили/переименовали). Маркер-владелец исключает коллизии префиксов имён секций; чужие секции не трогаются (нарезка одной секции чистит только свои). Тесты: SectionProcessorTest (+4 кейса: autoListName, listFieldDef, buildColumns).
  • [S2] В mpc_base ДОБАВЛЕНЫ прототипы скалярных типов: text (Текстовое поле), textarea (Текстовая область), richtext (Форматированный текст), checkbox (Флажок, Да==1), number (Число) — content-таб, pos 50–54. Ничего не удалялось (старые именованные поля целы → рабочий сайт не ломается). Теперь ftype="text|textarea|richtext|checkbox|number" клонирует реальный прототип из mpc_base (синтез в коде остаётся фолбэком, если прототипа нет). Имена без mpc_-префикса (чистый ftype="text"); MigxConfigMerger (union) добавит их в БД при апгрейде, кастомные поля сохранит. Для применения сида к БД БЕЗ переустановки — новая консольная команда console/mgr_configs.php sync (грузит сид, мержит через MigxConfigMerger, сохраняет; как резолвер 2migxconfigs).
  • Попутно: исправлен бутстрап console/mgr_configs.php (MODX_CORE_PATH определялся через str_replace('/','\\') → бэкслэши, ломал require на Linux; приведён к dirname(__FILE__,4).'/' как в mgr_tpl.php).
  • Версия пакета 2.4.172.5.0.

2.4.17-rc

  • Фикс регрессии 2.4.16: нормализатор quoteSnippetParamValues ломал валидные выражения-параметры в скобках. Значение вида 'input' => ('hero_bg_img' | lexicon) (mpcThumb, PlaceholderProcessor:524) начинается с (, а нормализатор: (1) не считал ( за выражение и (2) читал значение до первой , без учёта вложенности/кавычек → обрезал и оборачивал ('hero_bg_img' в кавычки → '('hero_bg_img'Unexpected token 'hero_bg_img' на парсинге parsed/.tpl. Исправлено: readValueEnd читает ПОЛНОЕ значение с учётом вложенных () [] {} и строковых литералов; normalizeValue квотирует ТОЛЬКО простое голое слово (^[\p{L}\p{N}_.\/-]+$ — резолв eager-плейсхолдера), а выражения ((…), $…, {…}, @…), уже квотированное, числа/булево/null оставляет как есть; вложенные массивы нормализуются рекурсивно. Тесты: RenderTest (+4 кейса: скобочное выражение с внутренними кавычками, скобки+голое слово, выражение с операторами ?:, и пр.). Перенарезка не требуется — фикс на рендере.

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