main · Главный модуль 26.800.0 Безопасность

Main 26.800.0: закрыт SSRF через ftp://, а translit и выбор SMTP-отправителя работают иначе

1 мин чтения Устаревших API: 8

Обновление безопасности

Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.

Main 26.800.0 закрывает SSRF в CFile::MakeFileArray() через ссылки ftp:// и в RSS-гаджете, а заодно чистит ввод при инлайн-редактировании в CAdminUiList и в menu_edit.php. Сильнее по проектам ударят изменения поведения: CUtil::translit() оставляет на конце _, почта по-другому выбирает SMTP-отправителя и его лимит, а у DDL-билдеров миграций переименовали параметр. Дифф большой, 850 файлов (+30852/−19139), но примерно половина приходится на PNG, переведённые в WebP, и на JS, пересобранный после переписывания main.sidepanel и main.popup на TypeScript.

Что сломается

Уберите хвостовой _ из символьных кодов

С настройками по умолчанию (delete_repeat_replace = true) CUtil::translit() раньше молча выбрасывал последний символ строки, если того нет в таблице транслитерации. Теперь последний символ заменяется на replace_other, как и все остальные, если перед ним не стоит та же замена: из условия убрали $i != $len - 1. На обычных названиях это выглядит так:

        <?php declare(strict_types=1);

\CUtil::translit('Скидка 10%', 'ru');    // skidka_10_    (было skidka_10)
\CUtil::translit('Что нового?', 'ru');   // chto_novogo_  (было chto_novogo)
\CUtil::translit('Модуль (beta)', 'ru'); // modul_beta_   (было modul_beta)

    

Задето всё, что строит символьные коды и имена через CUtil::translit. Если хвост вам не нужен, срежьте его сами:

        <?php declare(strict_types=1);

$code = rtrim(\CUtil::translit($name, 'ru'), '_');

    

Там же переделали CFile::transformName(). Транслитерацию вынесли в новый protected static CFile::transliterate(string $name), и работает она только для физического имени файла и только при save_original_file_name = Y. ORIGINAL_NAME (первый элемент результата) больше не транслитерируется совсем, даже с translit_original_file_name = Y. Кто включал эту опцию ради латинского ORIGINAL_NAME, теперь получит имя в том виде, в каком файл загрузили. В safe_chars вместо одной точки стоит ._-(), так что дефисы и скобки в физическом имени сохраняются.

Проверьте переводы из translation_repository

Loc::loadLanguageFile() и loadCustomMessages() больше не перекодируют языковые файлы: вызовы Translation::getEncodings() и Encoding::convertEncoding() из них удалены. Раньше перекодировка работала, только если в .settings.php в секции translation включён convert_encoding и lang-файл лежит в translation_repository или developer_repository. Если у вас так и файлы там не в UTF-8, фразы теперь попадут в $MESS байт в байт и выведутся битыми символами. Файлы из /local/ и /bitrix/modules/ не перекодировались и раньше, для них ничего не меняется.

Поменялась и работа с константой BX_MESS_CODE. Раньше getMessage() при ней сразу возвращал код фразы. Теперь код подставляется вместо значения при загрузке lang-файла, поэтому код вместо текста вы увидите только у фраз, которые реально загружены из файлов.

Проверьте, через какой SMTP уходят письма

Больше всего изменений касается порталов, где один адрес подключён отправителем несколько раз, например разными сотрудниками.

Sender::getCustomSmtp($email) без identity теперь ищет отправителя через Sender\IdentityResolver::resolve(). Раньше бралась самая новая подтверждённая запись с SMTP (ORDER BY ID DESC). Теперь записи идут по возрастанию ID, и из нескольких совпадений выбирается первая публичная (IS_PUBLIC), а если публичной нет, самая старая. Если у дублей разные SMTP-настройки, после обновления письма могут пойти через другой сервер. Каждый неоднозначный выбор пишется в журнал событий с типом MAIL_SENDER_AMBIGUOUS, не чаще раза в сутки на адрес. Поищите эти записи после обновления, по ним видно, какие адреса стоит развести.

С лимитами тоже по-новому:

  • Sender::getEmailLimit($email) без identity возвращает минимальный положительный лимит среди подтверждённых записей адреса. Раньше брался лимит самой новой подтверждённой записи, у которой он вообще задан, даже если это 0. С identity берётся лимит только найденной записи.
  • Sender::updateSender() больше не раздаёт OPTIONS.smtp.limit всем записям адреса через setEmailLimit(), а из контроллера Bitrix\Main\Controller\Mail\Sender удалён prepareLimits(). Форма main.mail.confirm пишет лимит только в редактируемую запись через новый Sender::setSenderLimit() и removeEmailLimit() больше не вызывает.

Обработчик Sender::applyCustomSmtp() на main:OnBeforePhpMail стал строже. Если в Mail\Context задан identity, а адрес в From не совпадает с адресом найденного отправителя, он возвращает EventResult::ERROR с кодом MAIL_SENDER_ADDRESS_MISMATCH, и bxmail() отдаёт false. Проверка срабатывает только там, где identity уже передают. При Context::setAutomaticCustomSmtpEnabled(false) кастомный SMTP вообще не подбирается.

В Mail\Internal\SenderTable у поля EMAIL появился save-модификатор Address::normalizeEmail() (mb_strtolower(trim())). Адрес приводится к нижнему регистру на любой записи, включая addMulti и updateMulti. Ещё переопределены update() и delete(): каждый вызов делает дополнительный SELECT текущей строки и сбрасывает кеши. Массовое обновление отправителей в цикле получит по лишнему запросу на строку.

Действие main.mail.sender.getAvailableSenders теперь отдаёт UserSenderDataProvider::getUserAvailableSenderIdentities() вместо Sender::prepareUserMailboxes(). Дедупликации там нет, и записи с одинаковым отображаемым значением остаются в списке. Если вы строите по нему свой выпадающий список, готовьтесь к одинаковым строкам.

Прошлая правка почты в main вышла в 26.750.0, там чинили плейсхолдеры с точкой в шаблонах.

Замените ссылки на удалённые PNG

Часть картинок удалили, на их место пришли WebP:

  • /bitrix/panel/main/images/icons-sprite-14.png, adm-security-banner-warning-icon.png, composite/composite_home.png;
  • /bitrix/js/main/core/images/panel/top-panel-sprite-2.png;
  • /bitrix/js/main/phonenumber/flag/sprite-{16,24,32}.png;
  • картинки чек-листа и ассеты imageeditor.

Своя CSS со ссылками на эти пути сломается. Найти их можно так:

        grep -rnE 'icons-sprite-14\.png|adm-security-banner-warning-icon\.png|composite_home\.png|top-panel-sprite-2\.png|phonenumber/flag/sprite-' local/

    

Переименуйте mysqlIndexName и поправьте наследников DDL-билдеров

В Bitrix\Main\DB\Ddl\Builder\IndexesTrait у addIndex(), addUniqueIndex() и addFulltextIndex() параметр $mysqlIndexName переименован в $indexName. Позиционные вызовы вроде $table->addIndex('ix_vendor_log_user', ['USER_ID']) работают как раньше (на PostgreSQL имя в миграциях затем нормализует IndexNameProcessor, о нём ниже), а вызов с именованным аргументом mysqlIndexName: упадёт с Error про неизвестный параметр.

Во всех DDL-билдерах и колонках (AlterTableBuilder, IndexesTrait, AbstractColumn, AbstractIntColumn и других) возвращаемый тип self заменили на static. Наследник, который переопределяет такой метод с : self, станет несовместим с родителем, и PHP откажется загружать класс.

По мелочи в том же слое:

  • AlterTableBuilder: dropIndexNameResolver получает второй аргумент $hintName. Если резолвер вернул null, drop пропускается, а раньше в этом случае бралось имя-подсказка.
  • PgsqlRenderer::renderAlterColumnLines() получил параметр ?ColumnState $state = null, а индексы в PostgreSQL теперь создаются под переданным именем.
  • Migration\Tools\SpyTable::__construct() получил обязательный параметр IndexNameProcessor $indexNameProcessor.

Изменилось и поведение Migration\Table. dropIndex() без списка колонок в dev-режиме бросает исключение 1104, иначе операция пропускается с записью в лог. modifyColumn по отсутствующей колонке пропускается. Все операции идут в режиме master-only, SQL выполняется через $connection->queryExecute(), а при установке и удалении модуля ошибка превращается в исключение 1199.

CModule::installMigrations() и uninstallMigrations() теперь ловят Migration\Exception и возвращают его ошибкой в Result. Если ваш установщик оборачивал их в try/catch, catch больше не сработает: проверяйте isSuccess().

Не разбирайте ключи ORM-коллекции

Bitrix\Main\ORM\Objectify\Collection::sysSerializePrimaryKey() сменил формат внутренних ключей _objects. Одиночный ключ теперь проходит через json_encode(), составной превращается в JSON-объект с именами полей (раньше был JSON-список значений). Эти ключи видны снаружи в foreach, если первичный ключ строковый или составной:

        <?php declare(strict_types=1);

foreach ($collection as $key => $object) {
    // строковый ключ 'ru':                  было ru,       стало "ru" (с кавычками)
    // составной ключ ID = 1, LANG_ID = 'ru': было [1,"ru"], стало {"ID":1,"LANG_ID":"ru"}
    $primary = $object->primary; // так надёжнее, чем разбирать $key
}

    

С целочисленным ID ключи прежние.

Пройдитесь по мелким контрактам

  • CUserCounterPage::setNewEvent() удалён вместе с защищёнными getMinMax(), getUserIdOption() и setUserIdOption(), его вызов убран из CAllUserCounter. checkSendCounter() больше не вызывается из CMain::RunFinalActionsInternal(): он зарегистрирован как background job в Application::initializeBackgroundJobs() и работает без обёртки $DB->StartUsingMasterOnly(), а проверка CUserCounter::CheckLiveMode() переехала внутрь метода.
  • Константы START_EXEC_EVENTS_1 и START_EXEC_EVENTS_2 теперь float (microtime(true)), а не строка "usec sec". START_EXEC_EVENTS_2 определяется после обработчиков OnAfterEpilog, а не до них. Если вы считаете по ним время, уберите разбор строки.
  • JS BX.Main.PopupManager: handlePopupDestroy, handlePopupShow и handlePopupClose стали приватными (#), handleOnAfterInit стал стрелочной функцией-свойством. Снаружи первые три больше не вызвать.
  • В .settings.php модуля секция rest сменила defaultNamespace на \Bitrix\Main\Infrastructure\Rest\Controller, а прежний \Bitrix\Main\Rest\V3\Controller перенесён в namespaces.
  • InitFilterEx() сначала берёт значение фильтра из глобальной переменной $$name и только потом из $_REQUEST. На старой админской странице глобальная переменная с именем поля фильтра перебьёт то, что пришло в запросе.

Какие дыры закрыли

CFile::MakeFileArray() для путей ftp:// и ftps:// теперь резолвит хост через Web\IpAddress::createByUri(), отклоняет приватные адреса и подставляет в URL полученный IP. До этого от SSRF был защищён только http(s) через HttpClient::setPrivateIp(false). Гаджет rssreader создаёт HttpClient с privateIp => false, так что URL ленты тоже больше не уведёт запрос на внутренний адрес.

Остальное касается ввода:

  • CAdminUiList при инлайн-редактировании кастомных полей с именами вида a[b][c] отбрасывает элементы, у которых сегмент ключа начинается с ~ или = (в комментарии ядра это «маркеры сырого SQL»). Запись идёт только в FIELDS[$id] текущей строки, а сегменты FIELDS и id, присланные клиентом, игнорируются.
  • public/menu_edit.php проверяет check_bitrix_sessid() в самом начале обработки POST. Параметры пункта меню [2], [3] и [4], включая условие показа, больше не десериализуются из POST-поля additional_params_N и берутся из существующего файла меню.
  • main.mail.confirm/ajax.php приводит ID отправителя к int до проверки владельца. Прежний setEmailLimit($email, …) менял лимит у всех подтверждённых записей с тем же адресом, в том числе у чужих, теперь лимит пишется только в редактируемую запись.
  • Mail\Tracking принимает URL от обработчика нового события OnMailEventMailClickRedirect только при том же origin (схема, хост, порт) и учитывает протокол-относительные формы //, /\ и \\.

Deprecated

CHTTP::Download() помечен на замену Bitrix\Main\Web\HttpClient, CHTTP::PrepareData() на http_build_query():

        <?php declare(strict_types=1);

use Bitrix\Main\Application;
use Bitrix\Main\Web\HttpClient;

// было: (new \CHTTP())->Download($url, $path);
$http = new HttpClient(['socketTimeout' => 10, 'streamTimeout' => 30, 'privateIp' => false]);
$saved = $http->download($url, Application::getDocumentRoot() . '/upload/tmp/price.xml');

// было: CHTTP::PrepareData($data);
$query = http_build_query($data);

    

В JS у PopupManager на выпил пошли _popups и _currentPopup, вместо них getPopups() и getCurrentPopup(). Остальные строки в списке deprecated (PopupWindow, PopupMenuWindow, PopupMenuItem, кнопки в пользу BX.UI.Button, SliderEvent.getSliderPage(), SliderManager.getLastOpenPage() и getCurrentPage()) старые. Пометки стояли и в прежних .js, при переходе на TypeScript их перенесли в main.popup.bundle.d.ts и side-panel.bundle.d.ts.

Новое

Подмените содержимое файла, не меняя ID

Bitrix\Main\File\ContentReplacer::replace(int $fileId, int $replacementId, ?int $expectedReplacementId, string $owner): Result меняет содержимое, которое отдаётся по ID файла, и оставляет сам ID прежним. Инфоблоки, UF и HL-блоки продолжают ссылаться на $fileId. Связь лежит в существующей таблице b_file_version (ORIGINAL_ID хранит стабильный ID, VERSION_ID текущую замену), и CFile::GetByID() выбирает строку версии с ORDER BY ID DESC LIMIT 1.

Перед вызовом учтите:

  • замену сохраняете заранее, например через CFile::SaveFile(), и её ID должен быть больше $fileId;
  • $expectedReplacementId работает как оптимистичная блокировка: null при первом назначении, дальше ID текущей замены, при расхождении придёт FILE_REPLACEMENT_CONFLICT;
  • $owner — идентификатор вашего сервиса, [a-zA-Z0-9_.-]{1,100}, и проверкой прав он не служит. Связь с чужим versionOwner метод не трогает и тоже возвращает FILE_REPLACEMENT_CONFLICT;
  • блокировка $connection->lock('b_file', 0) берётся без ожидания, занято — FILE_REPLACEMENT_LOCKED. Работа идёт в useMasterOnly(true), и докблок требует вызывать метод вне внешней транзакции;
  • прежняя замена удаляется через CFile::Delete(), исходный $fileId не удаляется никогда. Если процесс упал между шагами, следующий replace() сначала дочистит висящий obsoleteFileId;
  • цепочки запрещены (isValidGraph()), все участвующие ID должны образовывать независимые пары один-к-одному.
        <?php declare(strict_types=1);

use Bitrix\Main\File\ContentReplacer;

$replacementId = (int)\CFile::SaveFile($uploadedFile, 'vendor.pricelist');

$result = (new ContentReplacer())->replace(
    fileId: $priceListFileId,
    replacementId: $replacementId,
    expectedReplacementId: $lastReplacementId, // null при первой замене
    owner: 'vendor.pricelist',
);

if ($result->isSuccess()) {
    $lastReplacementId = $replacementId; // сохраните у себя для следующего вызова
} else {
    $code = $result->getErrors()[0]->getCode(); // FILE_REPLACEMENT_CONFLICT, _LOCKED, _INVALID, _WRITE_FAILED
}

    

Повторный вызов с той же заменой вернёт успех с changed = false в getData(). Для тестов и переопределения открыты protected updateReplacement() и deleteObsoleteFile().

Отправьте письмо от конкретного ящика и узнайте причину отказа

Bitrix\Main\Mail\Sender\Identity — иммутабельная ссылка на отправителя, без адреса и без запросов к БД. Создаётся через Identity::fromMailbox(int $mailboxParentId, string $mailboxModuleId = 'mail'), fromSender(int $senderId) или fromMailboxAndSender(...). CEvent::SendImmediate(), Mail\Event::sendImmediate() и handleEvent() получили параметры ?Identity $senderIdentity и ?Mail\Context $context, так что отправителя можно задать явно: \CEvent::SendImmediate('SALE_NEW_ORDER', 's1', $fields, senderIdentity: Identity::fromMailbox($mailboxId)). Отложенная очередь CEvent::Send() не сохраняет ни identity, ни context.

Mail::sendResult(array $mailParams): Result отправляет письмо и возвращает причину отказа: MAIL_SEND_CANCELED, MAIL_SEND_NOT_ALLOWED, MAIL_SEND_FAILED или ошибку из контекста вроде MAIL_SENDER_ADDRESS_MISMATCH. Mail::send() теперь вызывает его внутри.

        <?php declare(strict_types=1);

use Bitrix\Main\Mail\Context;
use Bitrix\Main\Mail\Mail;
use Bitrix\Main\Mail\Sender\Identity;

$context = (new Context())->setSenderIdentity(Identity::fromMailbox($mailboxId));

$result = Mail::sendResult([
    'TO' => $clientEmail,
    'SUBJECT' => 'Счёт готов',
    'BODY' => 'Счёт №123 можно оплатить до пятницы.',
    'HEADER' => ['From' => 'sales@example.com'],
    'CHARSET' => 'UTF-8',
    'CONTENT_TYPE' => 'text',
    'CONTEXT' => $context,
]);

foreach ($result->getErrors() as $error) {
    // MAIL_SENDER_ADDRESS_MISMATCH: From не совпадает с адресом ящика
    $logger->warning($error->getCode() . ': ' . $error->getMessage());
}

    

Свой обработчик OnBeforeMailSend может отменить отправку своей ошибкой: вернуть EventResult::ERROR с параметром ['error' => Error], и она дойдёт до вызывающего кода через sendResult().

Ещё по почте:

  • Mail\Context: setSenderIdentity(), setAutomaticCustomSmtpEnabled(?bool), setSendingError(?Error) и парные геттеры;
  • Mail\Address::normalizeEmail(?string) — единая каноническая форма адреса, Sender\AddressFilter::applyEmails(Query $query, array $emails) вешает на запрос нормализованный фильтр (пустой список превращается в 1 = 0);
  • IdentityResolver::resolveForOwner(int $ownerId, string $email) ищет запись конкретного владельца, у Sender появились resolveSenderId(), resolveIdentityForOwner(), setSenderLimit(), clearSenderCache(), clearIdentitySenderCache();
  • UserSenderDataProvider::getUserAvailableSenders() получил параметр bool $preserveIdentities = false;
  • событие main:OnMailEventMailClickRedirect вызывается перед редиректом по трекинговой ссылке. Обработчик может вернуть ['url' => ...], но новый URL примут только с тем же origin. Модуль sender уже вешает на него свой ExternalUrlSanitizer.

Подсветите коды фраз прямо на странице

Loc::setMessageDecorator(?callable $decorator) ставит callback вида ($code, $raw, $s, $sourceFile, $language), который вызывается на каждый getMessage() до подстановки $replace. Loc::getMessageSourceFile(string $code, ?string $language = null) возвращает lang-файл фразы, но путь запоминается только для файлов, загруженных после установки декоратора. Поэтому ставить его надо рано, в init.php, иначе вместо пути получите null.

        <?php declare(strict_types=1);

use Bitrix\Main\Config\Configuration;
use Bitrix\Main\Context;
use Bitrix\Main\Localization\Loc;

// local/php_interface/init.php: подсветка только на стенде с debug = true
$debug = Configuration::getValue('exception_handling')['debug'] ?? false;

if ($debug && Context::getCurrent()->getRequest()->getQuery('lang_debug') === 'Y') {
    Loc::setMessageDecorator(
        static fn ($code, ?string $raw, ?string $s, ?string $file, $lang): ?string
            => $s === null ? null : sprintf('%s [%s @ %s]', $s, $code, $file ?? '?'),
    );
}

    

Катите миграции модуля на отдельное подключение

CModule::configureMigrationContext(Migration\Context $context) — protected-хук установщика, который вызывается перед прогоном install/migrations. Вместе с Migration\Context::setConnection() он отправляет таблицы модуля из tables.php на отдельное подключение, а обработчики событий и агенты по-прежнему пишутся в основную БД. Методы Tools\Database::* получили параметр ?Connection, в Context добавили isCurrentDevUpdater().

        <?php declare(strict_types=1);

use Bitrix\Main\Application;
use Bitrix\Main\UpdateSystem\Migration\Context;

class vendor_stats extends CModule
{
    public $MODULE_ID = 'vendor.stats';

    protected function configureMigrationContext(Context $context): void
    {
        // подключение 'stats' описано в секции connections файла .settings.php
        $context->setConnection(Application::getConnection('stats'));
    }
}

    

Migration\IndexNameProcessor::process() в PostgreSQL приводит имя индекса к виду ix_|ux_|ixf_ + таблица + назначение, имена длиннее 63 символов режет до 50 и добавляет 12 символов sha256, а в текущем dev-апдейтере проверяет формат имени (исключение 1108). В DDL появились Column\ColumnState, ColumnStateData, enum ColumnAutoincrementType (Identity, SequenceDefault, None) и AlterTableData::getColumnState(). При расширении автоинкрементной колонки рендерер PostgreSQL генерирует ALTER SEQUENCE … AS bigint. Проверка сайта теперь требует в PostgreSQL standard_conforming_strings = on.

Посмотрите на REST v3 и мелочи интерфейса

В Bitrix\Main\Infrastructure\Rest\Controller лежат REST v3-контроллеры main, наследники Bitrix\Rest\V3\Controller\RestController с DTO, мапперами и сервисами: Like (scope rating), Smile и SmileSet (smile), User\Agreement и User\Consent (userconsent), User\History и User\History\Fields (user), Userfieldconfig (userfieldconfig). Старые фильтры переводит Support\LegacyQueryConverter.

В интерфейсе main-ui-select получил параметр enableSearch: поиск для одиночного выбора включается, если в списке не меньше 10 пунктов, и шаблоны main.field.enum включают его для списков с DISPLAY = UI. Компонент main.mail.form напоминает о забытом вложении (опция main:mail_form_attachment_reminder, по умолчанию Y), понимает общие подписи из модуля mail и запоминает выбор подписи. У поля from появились параметры senderIdName и mailboxIdName, они выводят скрытые input с ID отправителя и ящика.

БД

Новых таблиц нет. В install/mysql/install.sql и install/pgsql/install.sql у b_main_mail_sender появился индекс по (PARENT_ID, PARENT_MODULE_ID) для поиска отправителя по ящику: IX_B_MAIN_MAIL_SENDER_PARENT_ID и ix_b_main_mail_sender_parent_id_parent_module_id.

b_main_mail_sender.EMAIL теперь хранится в нижнем регистре. Старые строки дочищает степпер Bitrix\Main\Update\SenderEmailNormalization, его можно приостановить опцией main:sender_email_normalization_stop = Y, а строки, которые не удалось записать, попадают в журнал с типом MAIL_SENDER_NORMALIZATION_FAILED. Где степпер ставится в очередь, в коде модуля не видно.

ContentReplacer работает на существующей b_file_version (PK ORIGINAL_ID, UNIQUE по VERSION_ID, META типа text) и пишет в META ключи versionOwner и obsoleteFileId.

Мелочи и находки

  • ContentReplacer в «Управлении сайтом» пока ниоткуда не вызывается, поиск по bitrix/modules находит только сам класс. Комментарий в нём ссылается на «существующие» связи с ключом versionOwner, но в этой версии таких нет.
  • perfmon/classes/general/keeper.php до сих пор разбирает START_EXEC_EVENTS_* через explode(' ', …). Пока perfmon не обновят, с включённым монитором производительности будет предупреждение о несуществующем ключе массива, а интервал теперь измеряет обработчики OnAfterEpilog вместо отправки счётчиков.
  • Напоминание о вложении работает на регулярках из lang-файла, куда нарочно заложены опечатки: влажени, прекреп, прекрип, прлага.
  • В старом Sender::add() обработчик OnBeforeEventSend регистрировался только при непустой $smtpConfig, но в методе эта переменная не определялась, так что код не срабатывал никогда. Теперь письмо с кодом подтверждения уходит с Context::setAutomaticCustomSmtpEnabled(false).
  • SenderEmailNormalization сравнивает адреса в PHP. По комментарию в коде, в MySQL из-за collation условие EMAIL <> LOWER(EMAIL) ничего не находит.
  • Готовится тип пользовательского поля rich_text, создать его нельзя, пока выключен флаг Bitrix\UI\Config\Feature\RichTextUserFieldFlag. В UI\Copyright заодно добавили Mermaid, KaTeX, Marked, DOMPurify, Cytoscape.js и Floating UI, комментарии указывают на ui/install/js/ui/mermaid и ui/install/js/ui/floating-ui.
  • main.mail.form проверяет class_exists('\Bitrix\Mail\Service\SharedSignature\SignatureResolver') под будущую версию модуля mail, а в комментариях ссылается на внутреннюю спецификацию («AC-010», «API-02 contract»).
  • CAdminList::IsDefaultFilter() читает $_REQUEST['set_default'] без ??, так что без этого параметра PHP 8 выдаст предупреждение.
  • Main перестаёт опираться на CUpdateSystem: Migration\Files по умолчанию использует \CUpdateClient::class, UpdateServerDataParser вызывает CUpdateClient::ParseServerData, Migration\Table::writeLog() пишет через CUpdateClient::AddMessage2Log.

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
CHTTP::Download() Deprecated Bitrix\Main\Web\HttpClient
CHTTP::PrepareData() Deprecated http_build_query()
BX.Main.PopupManager._popups Deprecated BX.Main.PopupManager.getPopups()
BX.Main.PopupManager._currentPopup Deprecated BX.Main.PopupManager.getCurrentPopup()
CUserCounterPage::setNewEvent() Удалено —
BX.Main.PopupManager.handlePopupDestroy() Удалено —
BX.Main.PopupManager.handlePopupShow() Удалено —
BX.Main.PopupManager.handlePopupClose() Удалено —

Читайте дальше

main 26.750.0 Заметное Свежее

Main 26.750.0: точка в имени плейсхолдера больше не ломает почтовые шаблоны

Main 26.750.0 меняет три файла ядра, +16/−5 строк, и заметная правка среди них одна (вторая, в лицензионном `ActivationSystem`, служебная). Но сидит она в месте, где из почтового шаблона генерируется PHP-код, так что посмотреть на неё стоит всем, у кого в шаблонах есть плейсхолдеры с точкой в имени,...

5 мин
main 26.700.0 Заметное

Main 26.700.0: в ядре появились фича-флаги

Битрикс выкатил main 26.700.0 — и это тот редкий случай, когда в ядре появляется целая новая подсистема: фича-флаги `Bitrix\Main\Config\Feature` с двумя таблицами в БД. Плюс несколько тихих поведенческих изменений, которые стоит проверить, если у вас не-UTF-8 сайт, кастомные фильтры пользователей ил...

4 мин
main 26.650.0 Заметное

main 26.650.0: сброс счётчиков ленты пачкой и превью своих ссылок без похода наружу

Два изменения в ядре, оба — про лишнюю работу, которую система делала на ровном месте. Счётчики живой ленты научились сбрасываться одним вызовом вместо цикла, а превью ссылок перестало ходить HTTP-запросом на собственный портал. Ломающих удалений нет: все правки сигнатур — добавление аргументов с де...

3 мин
voximplant 26.800.0 Ломающее Свежее

Voximplant 26.800.0: attachRecord и finish отказывают локальным адресам, searchCrmEntities отдаёт один лид

В voximplant 26.800.0 у четырёх REST-методов внешней телефонии поменялось поведение, хотя сигнатуры остались прежними. `telephony.externalCall.attachRecord` и `telephony.externalCall.finish` с `RECORD_URL` больше не скачивают записи с приватных адресов и не ходят через прокси. `searchCrmEntities` от...

4 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти