voximplant 26.800.100 Заметное

Voximplant 26.800.100: граф маршрутизации звонка читается из БД по белому списку классов

4 мин чтения
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

В описании voximplant 26.800.100 сказано, что входящие звонки сбрасывались во время распределения между сотрудниками и в голосовом меню. Код релиза поменялся в двух файлах, и оба изменения касаются графа маршрутизации звонка. Поле EXECUTION_GRAPH в CallTable лишилось флага 'serialized' => true и восстанавливает граф только из классов, перечисленных в новом Node::getSerializableClasses(). Публичный API только расширился, а вот собственные узлы маршрутизации, если вы их встраиваете, после обновления из БД не восстановятся.

Проверьте, нет ли у вас своих узлов маршрутизации

Раньше CallTable::getMap() описывал EXECUTION_GRAPH флагом 'serialized' => true. Теперь у поля два явных модификатора. save_data_modification вызывает serialize($value), а fetch_data_modification передаёт строку в новый CallTable::unserializeExecutionGraph($value), и тот разворачивает её с белым списком:

        return unserialize((string)$value, ['allowed_classes' => Node::getSerializableClasses()]);

    

В списке девять узлов из Bitrix\Voximplant\Routing (Root, SecurityCheck, Ivr, IvrAction, Queue, User, Pstn, Voicemail, Hangup) и класс очереди \Bitrix\Voximplant\Queue. Любой другой объект в графе при чтении станет __PHP_Incomplete_Class.

Это задевает код, который кладёт в граф маршрутизации собственных наследников Bitrix\Voximplant\Routing\Node. Граф с такими узлами больше не восстановится, а докблок getSerializableClasses() предупреждает, что без записи в списке звонок не продолжит маршрутизацию на следующем хите. Свой класс в список не добавить. Метод статический, событий и настроек для расширения у него нет. Поищите наследников Routing\Node в /local/ и в партнёрских модулях в /bitrix/modules/ до того, как ставить обновление.

Ещё два отличия заметит код, который читает это поле:

  • null и пустая строка теперь сразу дают null. Раньше Field::unserialize() отправлял '' в unserialize() и получал false;
  • isSerialized() у поля теперь возвращает false, хотя в колонке по-прежнему лежит результат serialize().

Схема БД не менялась. Колонка EXECUTION_GRAPH осталась text, пишет в неё тот же serialize(), так что миграция данных не нужна.

Как звонок обрывался на следующем хите

Каждый хит GetNextAction от облака приходит в controller_hit.php, и модуль поднимает звонок через Call::load() и CallTable::getByCallId(). Граф разворачивает fetch-модификатор поля, а Call принимает результат, только если получился instanceof Node. Если граф не восстановился, Router::getNextAction() строит новый. У новых узлов новые id, их выдаёт uniqid() в Node::__construct(), поэтому getCurrentNode() не находит сохранённый STAGE. Звонок завершается Command::HANGUP с 'CODE' => 500 и 'REASON' => 'No action found'. Сам router.php в этом релизе не менялся.

Распределение по сотрудникам в графе ведут узлы Queue и User, голосовое меню ведут Ivr и IvrAction. Сбросы из описания обновления с этой цепочкой сходятся.

Почему прежний флаг serialized не восстанавливал граф, из диффа не видно. В main 26.800.0 Field::unserialize() вызывает unserialize((string)$value) без allowed_classes и пропускает любые классы, а в снапшотах main с 26.650.100 по 26.800.0 это место не менялось. Видно только, что модуль перестал полагаться на общий флаг ORM и сам задаёт список классов.

Задайте allowed_classes в своих таблицах

Новые CallTable::unserializeExecutionGraph() и Node::getSerializableClasses() публичные, но для своего кода полезнее сам приём. Флагу serialized белый список классов не передать. Если ваша таблица хранит сериализованные объекты, сделайте как в voximplant и замените флаг парой модификаторов с белым списком. В объектном описании полей ту же пару вешает ObjectField: его модификаторы encode() и decode() вызывают колбэки из configureSerializeCallback() и configureUnserializeCallback().

        <?php declare(strict_types=1);

namespace Vendor\Dialer\Model;

use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\ObjectField;
use Vendor\Dialer\Scenario\Scenario;
use Vendor\Dialer\Scenario\Schedule;
use Vendor\Dialer\Scenario\Step;

class ScenarioTable extends DataManager
{
    // все классы графа: корень, узлы и объекты, которые узлы держат в свойствах
    private const GRAPH_CLASSES = [Scenario::class, Step::class, Schedule::class];

    public static function getTableName(): string
    {
        return 'vendor_dialer_scenario';
    }

    public static function getMap(): array
    {
        return [
            (new IntegerField('ID'))
                ->configurePrimary()
                ->configureAutocomplete(),
            (new ObjectField('GRAPH'))
                ->configureSerializeCallback(
                    static fn (?Scenario $graph): ?string => $graph === null ? null : serialize($graph)
                )
                ->configureUnserializeCallback(
                    static fn (?string $value): ?Scenario => self::restoreGraph($value)
                ),
        ];
    }

    public static function restoreGraph(?string $value): ?Scenario
    {
        if ($value === null || $value === '') {
            return null;
        }

        $graph = unserialize($value, ['allowed_classes' => self::GRAPH_CLASSES]);

        return $graph instanceof Scenario ? $graph : null;
    }
}

    

В GRAPH_CLASSES должны попасть все классы, которые встречаются в графе, включая классы вложенных объектов. Voximplant держит в своём списке \Bitrix\Voximplant\Queue, хотя это не узел. Узел Routing\Queue создаёт этот объект через \Bitrix\Voximplant\Queue::createWithId() в конструкторе и хранит в свойстве $queue. Без записи в списке $this->queue стал бы __PHP_Incomplete_Class, и Queue::getNextAction() упал бы с TypeError ещё до touchUser() и toArray(), потому что getUsersToInvite() принимает только \Bitrix\Voximplant\Queue. Проверка instanceof Scenario в restoreGraph() такую ошибку не заметит: корень восстановится, а вложенный __PHP_Incomplete_Class упадёт при первом вызове метода или передаче в типизированный параметр.

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

  • Белый список заодно работает как харднинг. Раньше колонка разворачивалась unserialize() без ограничений, теперь произвольный объект из БД не создастся. Комментарии в коде объясняют список только маршрутизацией, про безопасность в них ничего нет.
  • У lib/model/call.php и lib/routing/node.php сняли бит исполнения (100755 → 100644).

Предыдущий релиз модуля разобран отдельно: voximplant 26.800.0.

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

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

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 мин
voximplant 26.700.0 Ломающее Свежее

Voximplant 26.700.0: оценку качества связи убрали, у notifyAdmins() появился тип

Модуль телефонии voximplant 26.700.0 вышел в коробочный Битрикс24 11 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Из карточки звонка убрали оценку качества связи вместе с её JS-событиями, у `Im::notifyAdmins()` появился тип параметра, а публичные константы `SipStatusInfor…

3 мин
ui 26.693.0 Заметное Свежее

UI 26.693.0: загрузчик называет причину отказа, а вьювер прячет кнопку скачивания

В ui 26.693.0 загрузчик файлов перед сохранением ещё раз проверяет временный файл и при отказе отдаёт текст ошибки из `CFile::CheckFile`, а при превышении дисковой квоты кладёт в ошибку причину `disk_quota_exceeded`. Вьювер теперь прячет кнопку скачивания у отдельного файла, а `FeaturePromoter.show(…

3 мин
socialnetwork 26.250.0 Рутинное Свежее

Socialnetwork 26.250.0: комментарий о том, как дочерние чаты проекта попадают в папку «Все»

В socialnetwork 26.250.0 код не менялся, проверять после обновления нечего. В двух внутренних обработчиках, `SendChildAttachedMessage` и `SendChildDetachedMessage`, появился одинаковый docblock о том, как дочерний чат проекта попадает в папку «Все» мессенджера. Push `chatUpdate` только убирает строк…

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

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

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

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

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

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

Войти