Documentgenerator 26.200.0: плейсхолдеры в атрибутах DOCX больше не вычищаются, у DocxXml новые сигнатуры
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
Генератор документов 26.200.0 заполняет DOCX обходом DOM вместо регулярки по всему XML, и плейсхолдеры в атрибутах разметки, кроме descr и name у картинок, теперь доходят до готового документа как текст. У Bitrix\DocumentGenerator\Body\DocxXml удалён protected-метод и сменились сигнатуры ещё двух, а установщик модуля переехал с install.sql на миграции main. В коробочный Битрикс24 версия вышла 2 сентября 2026 года, так указано в официальном канале «Битрикс24 changelog».
Проверьте готовые документы по своим DOCX-шаблонам
До 26.200.0 DocxXml::replacePlaceholders() прогонял preg_replace_callback по всему XML документа, а новая версия ищет плейсхолдеры только в текстовых узлах //w:t и в атрибутах descr и name у //w:drawing//wp:docPr. Плейсхолдер в descr или name заменяется пустой строкой.
Раньше clearPlaceholdersInAttributes() вырезал из XML плейсхолдеры, которых нет ни в w:t, ни среди картинок. В 26.200.0 тело метода сведено к return;, а сам он помечен @deprecated Attribute cleanup is not part of the supported DOCX placeholder surface. Вызов из normalizeContent() остался, замены у метода нет. Такие плейсхолдеры остаются в готовом документе как текст и уезжают пользователю вместе с файлом.
DocxXml::getPlaceholders() отдаёт только плейсхолдеры картинок и текстовых узлов, без остальных совпадений регулярки по XML.
Сгенерируйте по документу из каждого своего DOCX-шаблона и посмотрите, что осталось в фигурных скобках, например так: unzip -p contract.docx 'word/*.xml' | grep -o '{[^{}]*}'. Первыми проверяйте шаблоны, где плейсхолдеры стоят вне обычного текста.
Обновите наследников DocxXml
Если в /local/ есть класс, унаследованный от Bitrix\DocumentGenerator\Body\DocxXml, проверьте в нём три метода.
replaceRealByTemporaryPlaceholers(array $placeholdersMatches, DocxXml $document) удалён, опечатку в имени учитывайте при поиске. Вызов через parent:: упадёт с fatal error, а ваше переопределение станет мёртвым кодом, потому что модуль этот метод больше не вызывает. Вместо него есть replaceWhitelistedPlaceholdersByTemporaryPlaceholders(DocxXml $document, bool $includeImageAttributes = true). Готовые совпадения регулярки он не принимает и ищет плейсхолдеры в DOM сам.
processContentWithTemporaryPlaceholders() потерял четвёртый параметр bool $isPurgeEmptyValue = true. Лишний позиционный аргумент PHP отбросит без ошибки, так что сломается это тихо. Режим остался один. Фигурные скобки, которые не являются временными плейсхолдерами, остаются как есть, а временный плейсхолдер без ключа в $values заменяется пустой строкой.
getPlaceholders(): array теперь объявлен в самом DocxXml. У родителя Xml::getPlaceholders() типа возврата нет, поэтому переопределение без : array в наследнике DocxXml до 26.200.0 работало, а на новой версии даёт fatal error о несовместимой сигнатуре. Хватит дописать тип:
<?php declare(strict_types=1);
namespace Vendor\Docs\Body;
use Bitrix\DocumentGenerator\Body\DocxXml;
final class ContractDocxXml extends DocxXml
{
public function getPlaceholders(): array
{
$placeholders = parent::getPlaceholders();
// ваша доработка списка
return $placeholders;
}
}
Для наследников в DocxXml появились protected-методы новой схемы: getTextPlaceholderNames(), getPlaceholderNamesFromXml(string $content), replaceNodeValuePlaceholdersByTemporaryPlaceholders() и getTemporaryPlaceholder(). К ним добавились константы TEMPORARY_PLACEHOLDER_CONTEXT_TEXT и TEMPORARY_PLACEHOLDER_CONTEXT_DOCPR_ATTRIBUTE.
Проверьте поля, исправленные вручную
В Bitrix\DocumentGenerator\Document сравнение ручного значения с вычисленным вынесли в private isOverrideKept(), его используют getValue() и getExternalValues(true). Скаляры и Value-объекты сравниваются как строки с normalizeValue($computedRaw, true). В docblock объяснено, что так Money, Date и похожие значения перестанут всегда считаться изменёнными.
Ручную правку поля, у которого вычисленное значение является массивом, getValue() раньше не применял никогда, потому что условие требовало !is_array($this->values[$name]). Теперь применяет, если она отличается от вычисленного значения (сравнение через !=). Поле-массив с ручной правкой после обновления начнёт отдавать эту правку. Ещё setFields() стал пропускать элементы, которые не являются массивами.
Уберите из своих скриптов install.sql модуля
Файлы install/db/mysql/install.sql, uninstall.sql и их pgsql-копии удалены. documentgenerator::InstallDB() и UnInstallDB() вызывают $this->installMigrations() и $this->uninstallMigrations($dropTables), это protected-методы CModule из main. В main 26.250.100 их ещё нет, в main 26.650.0 уже есть. Скрипты, которые читали install.sql модуля напрямую, перестанут работать.
Схема не менялась. Те же 15 таблиц, от b_documentgenerator_template до b_documentgenerator_actualize_queue, описаны в install/migrations/tables.php через CreateTableBuilder с прежними полями и индексами. В migration_config.json задан defaultTableName: b_documentgenerator_template и соответствие install/components → components, install/js → js.
Обработчики событий и агенты переехали из install/index.php в install/migrations/events.php и agents.php. Обработчиков три: main:onNumberGeneratorsClassesCollect через registerCompatible, rest:OnRestServiceBuildDescription и pull:OnGetDependentModule с sort 800. При удалении модуля в режиме ModuleUninstall вызов register превращается в unregister.
Агентов тоже три, все непериодические: Driver::installDefaultRoles() с интервалом 150, Service\ActualizeQueue::process(5) и Driver::installDefaultTemplatesForCurrentRegion() с интервалом 300. Первый запуск раньше задавался явно (+150 с, без задержки и +300 с). Теперь задержку выбирает Migration\Agent::doAdd() из main: 60 с при установке модуля, 15 минут при установке дистрибутива, 10 минут в облаке.
Мелочи и находки
- Временный плейсхолдер раньше выглядел как
{SystemHtmlValues<N>}, гдеNбралось изrandom_int(200, 10000). Новый выглядит как{SystemDocxValue<hex>}сbin2hex(random_bytes(16))и проверкой, что такого плейсхолдера ещё нет ни в карте, ни в тексте документа. - Старый код склеивал XPath-запрос с текстом плейсхолдера (
$matches[0]). Новый делает один запрос//w:t[text()[contains(.,"{")]]и разбирает значения узлов регуляркой. - В размножаемых блоках после каждого
processMultiplyingBlock()добавленsaveContent(), а имена полей внутри блока берутся из DOM черезgetPlaceholderNamesFromXml()с кешем по контенту. Для пустой картинки (emptyили' ') узлыw:drawingудаляются из блока, раньше на место плейсхолдера картинки подставлялся маркер{__SystemEmptyImage}. Docx::setNodes()существовал и раньше, а сохранять изменения начал только сейчас. Он нормализует внутренний документ, кладёт его контент обратно в zip и обновляет$this->content. Его вызывает анонимайзер модуля disk. Методsave()вdisk/lib/integration/anonymizer/docxprovider.phpсначала зовётsetNodes(), а потомprocess(). Внутри цикла по внутренним документам послеaddContentToZip()добавлен ещё$this->zip->close(), хотяaddContentToZip()сама делаетclose()иopen(). Повторногоopen()до следующей итерации нет.- Имена агентов в
install/migrations/agents.phpначинаются с\.Migration\Agent::doAdd()в режимеisDevMode()отвергает такие имена исключением 201 «Agent name should not start with backslash», ноCModule::installMigrations()dev-режим не включает. Если переводите свой модуль на миграции, пишите имена агентов без ведущего слеша. Controller\Template::add()пропускает список доступа через privatefilterAccessCodes(), который отбрасывает нестроковые и пустые коды и убирает дубли. Если после фильтра ничего не осталось, старые права шаблона не удаляются. Раньше непустой массив мусора приводил кdeleteByTemplateIds()и попыткамadd().- Компонент
documentgenerator.templatesпередаёт фильтр региона в URL слайдера как['REGION' => [$region['CODE']]]вместо ключа'REGION[]'.
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\DocumentGenerator\Body\DocxXml::clearPlaceholdersInAttributes()
|
Deprecated | — |
Bitrix\DocumentGenerator\Body\DocxXml::replaceRealByTemporaryPlaceholers()
|
Удалено |
Bitrix\DocumentGenerator\Body\DocxXml::replaceWhitelistedPlaceholdersByTemporaryPlaceholders()
|