Landing 26.1300.200–26.1600.0: закрыты дыры с PHP и правами групп, поля редактора стали ES-классами
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
С прошлого разбора вышли четыре версии landing: 26.1300.200, 26.1400.0, 26.1500.0 и 26.1600.0. В каждой закрыты дыры, от сборки PHP-тега при импорте сайта до лишних прав у участников групп соцсети. Попутно в режиме Битрикс24 превью черновика переехало на маршрут раздела «Сайты», а поля редактора стали нативными ES-классами. Если у вас свои поля редактора, REST-интеграции с landing или сайты на landing24, прочитайте «Что сломается» до обновления. Предыдущие версии разобраны в Landing 26.1300.0–26.1300.50.
Новое
Решайте сами, кешировать ли динамический блок (26.1500.0)
Перед тем как взять общий HTML-кеш, Block::setDynamic() отправляет событие landing:OnBeforeDynamicBlockCache. В SOURCE_IDS лежат источники данных блока вида module:Source, а получают событие только модули этих источников. Хватит одного EventResult::ERROR, чтобы кеш для этого вывода не использовался. Пригодится, если ваш источник отдаёт выборку, которая зависит от посетителя.
Фильтр сверяет ID модуля обработчика, поэтому регистрируйте его через registerEventHandler(). У обработчика из addEventHandler() ID модуля нет, и событие до него не дойдёт.
<?php
declare(strict_types=1);
namespace Vendor\Promo\Landing;
use Bitrix\Main\Event;
use Bitrix\Main\EventResult;
final class DynamicCacheGuard
{
// источники модуля, чья выборка зависит от посетителя
private const PERSONAL_SOURCES = ['vendor.promo:PersonalOffers'];
public static function onBeforeDynamicBlockCache(Event $event): EventResult
{
$sourceIds = (array)$event->getParameter('SOURCE_IDS');
$type = array_intersect($sourceIds, self::PERSONAL_SOURCES) === []
? EventResult::SUCCESS
: EventResult::ERROR;
return new EventResult($type, null, 'vendor.promo');
}
}
// install/index.php модуля vendor.promo, метод InstallEvents()
\Bitrix\Main\EventManager::getInstance()->registerEventHandler(
'landing',
'OnBeforeDynamicBlockCache',
'vendor.promo',
\Vendor\Promo\Landing\DynamicCacheGuard::class,
'onBeforeDynamicBlockCache'
);
Выключайте проверку прав так, чтобы она включалась обратно (26.1500.0)
Шаблон $was = Rights::isOn(); Rights::setOff(); … if ($was) Rights::setOn(); ломался тихо. Составной isOn() возвращает false при выключенном глобальном режиме или LANDING_DISABLE_RIGHTS, и локальный флаг так и оставался выключенным. Новый Rights::isLocalOn() читает только локальный флаг, ядро перевело на него и на try/finally около двух десятков мест. Сделайте так же:
<?php
declare(strict_types=1);
namespace Vendor\Promo\Landing;
use Bitrix\Landing\Rights;
final class WithoutRights
{
public static function run(callable $action): mixed
{
$wasOn = Rights::isLocalOn();
Rights::setOff();
try {
return $action();
} finally {
if ($wasOn) {
Rights::setOn();
}
}
}
}
Новый Hook\Page\Cookies::getSelectedAgreementIdBySiteId() из 26.1600.0 снова вызывает setOff() и setOn() без запоминания состояния, то есть по схеме, которую вычищали в 26.1500.0.
Свой пункт в настройках сайта (26.1400.0)
Встройка LANDING_SETTINGS появилась в описании REST-скоупа landing, и её можно зарегистрировать через placement.bind. Пункт появится в боковом меню настроек сайта, приложение получит в опциях встройки SITE_ID и LID. Для сайтов VIBE пункты не выводятся.
Разбирайте чужой HTML своим парсером (26.1400.0)
new DOM\Document() из main берёт один парсер на процесс. С storePhpCode он прячет <? … ?> в комментарий-плейсхолдер и помнит исходник до конца процесса, так что другой документ с тем же комментарием получит этот PHP обратно (mantis 0257122). Landing\Html\DocumentFactory::createForUserHtml() создаёт документ с отдельным HtmlParser и выключенным storePhpCode. В своём модуле повторите приём: new DOM\HtmlParser(), storePhpCode = false, $document->setParser().
Доступные блоки без JS (26.1400.0)
Опция манифеста 'ariaLabelFromHref' => true у узла типа link подставляет href ссылки в её aria-label, так сделаны 15.social и 15.2.social_circles. HTML-блок на уровне санитайзера LOW пропускает семантические теги (nav, main, section, article и другие), role из белого списка и основные aria-*. Обработчики событий и script по-прежнему запрещены.
Для пользователей портала
- У Vibe (главной страницы портала) появились внутренние страницы по адресу
/pub/site/vibe/{siteId}/{путь}, их видят авторизованные пользователи с доступом к Vibe. Шаблонnews-detailдоступен типуvibe(26.1600.0). - Новая CRM-форма из редактора сайта получает согласие, которое подтверждается отправкой формы, если это поддерживает модуль crm (26.1600.0).
- Экспорт сайта перечисляет только приложения repo-блоков, и при импорте лишнего ставить не попросят (26.1500.0).
Что сломается
Превью черновика в режиме Битрикс24 (26.1300.200)
Если установлен bitrix24, crm или intranet, не задана LANDING_DISABLE_CLOUD и сайт в публичном скоупе, Landing::getPublicUrl() в режиме превью отдаёт /sites/site/<siteId>/preview/<landingId>/ вместо /pub/site/<id>/preview/<hash>/…. Такое превью требует авторизации и права на чтение страницы, а розданные подписанные ссылки черновик больше не показывают. На БУС без этих модулей всё по-старому. Ссылку на превью берите из Landing::createInstance(0)->getPublicUrl($landingId) при включённом Landing::setPreviewMode(true), потом верните прежний режим. Site\PortalPreview::buildUrl() условий режима не проверяет, и на БУС его ссылка ведёт на несуществующий маршрут.
REST и методы страниц
PublicAction\AiBlockудалён (26.1400.0). ЕгоcreateTemplate()иaddToLanding()были доступны через ajax-шлюз landing, замены в публичном API нет.landing.landing.addByTemplateчерез REST и ajax игнорируетPREPARE_BLOCKSиPREPARE_BLOCKS_DATA(26.1400.0). Из PHP поля принимаются, но в параметрах компонентов нескалярные значения и значения, начинающиеся с={, пропускаются. Параметры компонентов меняйте вторым шагом черезlanding.block.updateNodes(ключ ноды видаbitrix:search.title, значение{"attrs": {…}}). Пройдут только параметры изextraманифеста и только у не-repo блоков.landing.repo.registerиlanding.repo.unregisterбез контекста приложения требуют администратора портала либо роль сайтов с «Полным доступом», если тариф даёт расширенные права (26.1500.0). Вебхук обычного пользователя получитACCESS_DENIED. Выпустите его от администратора или вызывайте метод из приложения.landing.landing.updateпроверяетcanEdit()самой страницы, прав на сайт уже мало (26.1500.0).Landing::delete()проверяетcanDelete()страницы и блокировку до удаления блоков (26.1600.0).landing.landing.deleteтеперь удаляет и страницы из корзины или удалённого сайта. Если ваша интеграция ждала тут ошибку, проверьте её.Vibe\Vibe::delete()объявлен с: Result(26.1400.0). Наследник, переопределивший метод без типа, упадёт с fatal error. Допишите: \Bitrix\Main\Resultи возвращайте объект результата.Node\Component::saveNode()получилarray $additional = []и раскодирует строковые параметры, кроме html, черезhtmlspecialchars_decode()(26.1600.0). Отключается это через['decodeValues' => false]. Наследникам нужен новый параметр в сигнатуре.
Права на сайты групп (26.1600.0)
Rights::getOperations() прогонял карту операций группы ['read' => bool, 'edit' => bool, …] через array_values(), а hasAccessForSite() искал в ней нестрогим in_array(). В PHP 'delete' == true, и одно разрешённое действие открывало все остальные. Теперь сравнение строгое. Участники групп, которые с одним правом настраивали, публиковали или удаляли сайт группы, этого больше не смогут.
Поля редактора на ES-классах (26.1500.0)
BX.Landing.UI.Field.Text и ещё несколько полей собраны без Babel, и вызов BX.Landing.UI.Field.Text.apply(this, arguments) бросает TypeError. Ядро наступило на это само. По коду в 26.1500.0 не создаются поле видео-вставки и поля Unit. Их починили в 26.1600.0 через Reflect.construct, так что с 26.1500.0 обновляйтесь сразу дальше. Свои поля переведите на class extends или на тот же приём:
class VendorPhoneField extends BX.Landing.UI.Field.Text
{
constructor(data)
{
super({ ...data, textOnly: true });
BX.Dom.addClass(this.layout, 'vendor-phone-field');
}
}
// если нужна старая функция-конструктор
BX.Landing.UI.Field.VendorPhone = function(data)
{
var self = Reflect.construct(BX.Landing.UI.Field.Text, [data], new.target || BX.Landing.UI.Field.VendorPhone);
self.mask = '+7 (___) ___-__-__';
return self;
};
BX.Landing.UI.Field.VendorPhone.prototype = Object.create(BX.Landing.UI.Field.Text.prototype);
BX.Landing.UI.Field.VendorPhone.prototype.constructor = BX.Landing.UI.Field.VendorPhone;
Шаблон landing24 и путь vibe
- landing24 (26.1400.0) больше не запрещает масштабирование во viewport, первой в
<body>выводит ссылку «Перейти к содержимому» и рисует кольцо фокуса:focus-visible. Сайты со своим CSS фокуса или копией шаблона проверьте. С 26.1500.0 хлебные крошки обёрнуты в<nav>, и селектор по прямому родителюul.landing-breadcrumbможет отвалиться. - На портале с bitrix24, crm или intranet
landing.pubразбирает путьvibeиvibe/…как маршрут Vibe и при неудаче отдаёт 404 (26.1600.0). В этом режиме первый сегмент пути означает сайт, поэтому по коду перестанет открываться сайт с кодомvibeпо адресу/pub/site/vibe/…. Ссылайтесь на него по ID или смените код.
Что закрыли
- Импорт сайта (26.1300.200, mantis 257039): подменённые id блоков в архиве собирали php-тег из санитизированного контента. С 26.1500.0 такой блок пропускается, а в лог уходит
LANDING_IMPORT_PHP_TAG. - Общий DOM-парсер (26.1400.0, mantis 0257122) и подстановка
#TITLE#в PHP-параметр компонента. UrlChecker при сбое внешнего сервиса больше не записывает ссылку чистой навсегда. - Подсветка поиска в
landing.pub(26.1500.0) вставляла фразы изqв регулярку безpreg_quote, а контент не-repo блоков потом исполняется черезeval. - Права: настройки чужой страницы в
landing.landing_edit(26.1500.0). Остальные проверки прав описаны в «Что сломается».
Deprecated
File::markAssetRebuilded()заменён наFile::markAssetRebuilt(), старый метод вызывает новый (26.1400.0).Assets\ResourceCollection::getPathes()заменён наgetPaths(), а параметр$pathesвremove()стал$paths. Вызовы с именованным аргументом сломаются (26.1400.0).
Мелочи и находки
Site\PreviewUrlпрожил две версии и удалён в 26.1300.200. Класс был final и использовался только внутри модуля,getPortalOrigin()иwithRequestPort()переехали вSite\PortalPreviewс прежними сигнатурами.- Ключ фразы
LANDING_HOOK_SPEED_TTILEисправлен наLANDING_HOOK_SPEED_TITLE(26.1400.0), переопределение старого ключа в своих lang-файлах перестанет действовать. landing.startв SEF-режиме при первом обращении сам добавляет вb_site_templateстроку с условием на маршрут превью (26.1300.200).
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\Landing\File::markAssetRebuilded()
|
Deprecated |
Bitrix\Landing\File::markAssetRebuilt()
|
Bitrix\Landing\Assets\ResourceCollection::getPathes()
|
Deprecated |
Bitrix\Landing\Assets\ResourceCollection::getPaths()
|
Bitrix\Landing\Site\PreviewUrl
|
Удалено |
Bitrix\Landing\Site\PortalPreview
|
Bitrix\Landing\PublicAction\AiBlock
|
Удалено | — |