Calendar 26.150.0–26.200.0: двойная бронь переговорки стала ошибкой, а форма бронирования больше не отдаёт чужую занятость

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

Ломающее обновление

Удалены или изменены публичные API — прикладной код может перестать работать.

Календарь вышел двумя версиями, 26.150.0 и 26.200.0. В 26.150.0 закрыли утечку в анонимном экшене бронирования CRM-формы, через который любой посетитель мог узнать занятость и имена сотрудников и ресурсов портала. В 26.200.0 занятость переговорок проверяется по умолчанию в CCalendar::SaveEvent, REST и AJAX, и модули, агенты и интеграции, которые раньше молча сохраняли пересечения, теперь получат исключение или ошибку.

Новое

Ловите конфликт переговорки в своём коде (26.200.0)

При пересечении CCalendar::SaveEvent() бросает Bitrix\Calendar\Rooms\OccupancyCheckerException. У исключения есть getOccupancyData(), isInvalidInterval() и константы ERROR_CODE (CALENDAR_LOCATION_OCCUPANCY_CONFLICT) и INVALID_INTERVAL_ERROR_CODE. Если ту же переговорку в этот момент сохраняет другой запрос, прилетит новый Rooms\OccupancyLockedException с getErrorCode(). Он наследует \RuntimeException, поэтому catch (SystemException $e) его пропустит.

        <?php declare(strict_types=1);

namespace Vendor\Rooms\Application\Service;

use Bitrix\Calendar\Rooms\Manager;
use Bitrix\Calendar\Rooms\OccupancyCheckerException;
use Bitrix\Calendar\Rooms\OccupancyLockedException;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;

final class RoomBookingService
{
    public function save(array $fields, int $userId): Result
    {
        $result = new Result();
        Loader::requireModule('calendar');

        try {
            $eventId = (int)\CCalendar::SaveEvent([
                'arFields' => $fields,
                'userId' => $userId,
                // с 26.200.0 null тоже значит 'Y', а 'N' сохранит пересечение с меткой в MEETING
                'checkLocationOccupancy' => 'Y',
            ]);
        } catch (OccupancyLockedException $e) {
            return $result->addError(new Error('Переговорную сейчас бронирует кто-то ещё, повторите позже', $e->getErrorCode()));
        } catch (OccupancyCheckerException $e) {
            if ($e->isInvalidInterval()) {
                return $result->addError(new Error($e->getMessage(), OccupancyCheckerException::INVALID_INTERVAL_ERROR_CODE));
            }

            $occupancy = $e->getOccupancyData();
            // в тексте исключения даты чужих броней, без view_full на переговорку их не показываем
            if (!Manager::canExposeOccupancyDates($occupancy)) {
                return $result->addError(new Error('Переговорная занята', OccupancyCheckerException::ERROR_CODE));
            }

            return $result->addError(new Error(
                $e->getMessage(),
                OccupancyCheckerException::ERROR_CODE,
                ['dates' => $occupancy['dates'] ?? [], 'truncated' => $occupancy['truncated'] ?? false],
            ));
        }

        if ($eventId <= 0) {
            return $result->addError(new Error(implode('; ', \CCalendar::GetAndClearErrors()), 'EVENT_NOT_SAVED'));
        }

        return $result->setData(['id' => $eventId]);
    }
}

    

Проверяйте серию разом и берите ту же блокировку (26.200.0)

Rooms\OccupancyChecker::checkBatch(array $events, array $excludedEventIds = []) проверяет несколько событий и загружает брони каждой переговорки один раз. Для массивов полей события есть обёртка CCalendarEvent::checkLocationOccupancyBatch(). У неё и у checkLocationOccupancy() появились флаги $throwOnConflict и $checkPermission. С $throwOnConflict = false метод вместо исключения возвращает true, если нашёл конфликт.

Rooms\OccupancyLock держит именованные блокировки БД с ключом calendar.room.occupancy.<roomId>. Статический resolveRoomIds($oldLocation, $newLocation) собирает отсортированные ID комнат из старого и нового LOCATION вместе со связанными переговорками. acquire(array $roomIds) захватывает их без ожидания и при неудаче бросает OccupancyLockedException, release() отпускает, деструктор вызывает его сам. Работает только на MySQL и PostgreSQL, на других подключениях acquire() с непустым списком комнат бросает OccupancyLockedException. Если ваш код меняет бронь переговорки, берите ту же блокировку, и календарь не запишет туда событие параллельно с вами.

Фильтруйте коды участников и показывайте лимит планировщика (26.200.0)

(new Planner\AccessCodeFilter($userId))->filter($codes) оставляет из кодов доступа UA, U<id>, DR<id>, группы SG<id>[_роль], которые пользователь видит, и видимые ему команды HR. Календарь прогоняет через него коды в планировщике и Живой ленте. Если ваш эндпоинт принимает коды участников от клиента, прогоните их через тот же фильтр.

CCalendarPlanner::prepareData теперь отдаёт plannerLimitExceeded, maxPlannerUsers, plannerPeopleCount и plannerLimitPeopleCount, а для своего интерфейса есть попап BX.Calendar.Controls.PlannerLimitHint (опции bindElement и maxPlannerUsers, методы show(), close() и destroy(), который снимает обработчик resize с window). С limitExceededPolicy => CCalendarPlanner::LIMIT_EXCEEDED_POLICY_OMIT_USERS при превышении лимита пользователи в ответ не попадают вовсе.

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

Пересечение больше не сохранится молча (26.200.0)

Раньше CCalendarEvent::Edit(), а через него CCalendar::SaveEvent() и SaveEventEx(), проверял занятость, только если checkLocationOccupancy === true. Теперь null означает 'Y', bool превращается в 'Y' или 'N', а любое другое значение даёт ArgumentException. С 'N' событие сохранится, но конфликт останется в MEETING меткой LOCATION_OCCUPANCY_OVERRIDE с TIMESTAMP.

Проверки нет для CAL_TYPE location, дочерних копий события и источников синхронизации: google, google_api_oauth, icloud, office365, caldav, caldav_google_oauth, exchange. На всём портале её выключает опция isOccupancyCheckerEnabled = N модуля calendar.

Заодно появилась проверка прав. Её не выключают ни опция isOccupancyCheckerEnabled, ни 'N'. Без view_full на переговорку SaveEvent бросит OccupancyCheckerException с фразой EC_MR_RESERVE_ERR_BUSY. Её отключает checkPermission => false, а если передан ключ checkLocationOccupancyPermission, то любое его значение, кроме true. Права проверяются у текущего пользователя (CCalendar::GetUserId(), обычно $USER), а не у userId из параметров SaveEvent. Агент или обработчик, который бронирует переговорку от имени сотрудника, может упасть, даже если у сотрудника доступ есть. Если права вы уже проверили сами, передайте checkLocationOccupancyPermission => false.

Разберите новые ошибки REST и AJAX (26.200.0)

calendar.event.add и calendar.event.update проверяют занятость по умолчанию, выключает проверку новый параметр doCheckOccupancy=N. Значение, кроме Y и N, вернёт HTTP 400 с текстом «Недопустимое значение параметра „doCheckOccupancy“». Новые коды ошибок:

  • CALENDAR_LOCATION_OCCUPANCY_CONFLICT с additional.occupancy.dates (даты в UTC в формате ISO, только если у вызывающего есть view_full на переговорку) и additional.occupancy.truncated;
  • CALENDAR_LOCATION_OCCUPANCY_INVALID_INTERVAL;
  • CALENDAR_LOCATION_OCCUPANCY_LOCKED.

Все три приходят со статусом CRestServer::STATUS_OK, то есть HTTP 200, так что смотрите на поле error в теле ответа.

calendar.event.update для встреч (is_meeting: 'Y') раньше сохранял событие от имени пользователя из параметра host, теперь от имени того, кто вызывает метод.

У AJAX calendar.api.calendarentryajax.editEntry параметр doCheckOccupancy по умолчанию стал Y (был N), другое значение даёт edit_entry_invalid_argument. Новые коды: CALENDAR_LOCATION_OCCUPANCY_INVALID_INTERVAL и CALENDAR_LOCATION_OCCUPANCY_LOCKED. Конфликт у неповторяющегося события теперь приходит кодом edit_entry_location_busy, раньше это была пара edit_entry_location_busy_recurrence и edit_entry_location_repeat_busy.

Соберите обработчики команд с новым аргументом (26.200.0)

В конструкторах CreateEventCommand и UpdateEventCommand из Bitrix\Calendar\Application\Command параметр bool $checkLocationOccupancy заменён на string $occupancyCheckMode, метод isCheckLocationOccupancy() удалён, вместо него getOccupancyCheckMode(). CreateEventHandler и UpdateEventHandler требуют в конструкторе Core\Event\LegacyLocationAvailabilityCheckerInterface, через который подменяется проверка доступности старых переговорок на инфоблоке. new CreateEventHandler() без аргумента упадёт с ArgumentCountError. Передайте штатную реализацию: new CreateEventHandler(new \Bitrix\Calendar\Rooms\LegacyLocationAvailabilityChecker()).

Не вызывайте ExcludeInstance без пользователя (26.200.0)

CCalendarEvent::ExcludeInstance($eventId, $excludeDate) теперь возвращает Main\Result, раньше не возвращал ничего. Методу нужен текущий пользователь с правом ACTION_EVENT_EDIT и на событие, и на мастер серии. В агенте или CLI без $USER придёт ошибка exclude_recursion_date_access_denied. Дата должна строго совпадать с форматом CCalendar::Date. AJAX excludeRecursionDate отвечает ошибками exclude_recursion_date_*.

Планировщик показывает занятость первых 50 участников (26.200.0)

CCalendar::GetMaxPlannerUsers() по умолчанию возвращает 50 (константа DEFAULT_MAX_PLANNER_USERS), раньше лимита не было. На большой встрече планировщик покажет занятость первых 50 участников, хоста поставит первым вне лимита и выведет подсказку «В событии много участников...». Лимит хранится в опции maxPlannerUsers модуля calendar. Из JS-расширения calendar.planner удалены settings.maxPlannerUsers и статический Planner.getMaxPlannerUsers().

CalendarAjax::updatePlannerAction больше не учитывает add_cur_user_to_list и ownerId, хостом по умолчанию становится hostId из запроса или текущий пользователь.

Своя форма бронирования получит пустой ответ (26.150.0)

Анонимный экшен calendar.api.resourcebookingajax.getFillFormData (без фильтров Authentication и Csrf) брал settingsData и fieldName прямо из запроса. Так аноним получал занятость с ID событий, имена любых пользователей и ресурсов и настройки любого пользовательского поля. Теперь на запрос с settingsData экшен отвечает [] и, если задан LOG_FILENAME, пишет в лог «legacy request: settingsData present». Новые параметры: sourceType (поддерживается только crm-webform), formId, sec и fieldCode, окно дат по-прежнему задают from и to. Пользователей и ресурсы экшен берёт из SETTINGS_DATA поля веб-формы, когда код безопасности формы верный и форма активна. Пустой ответ придёт и при одной границе окна дат, дате, которую не удалось разобрать, окне длиннее 63 дней или to < from.

Ответ урезан белым списком. Сверху остались timezoneOffset, timezoneOffsetLabel, fieldSettings, usersAccessibility, resourcesAccessibility, userIndex, workTimeStart и workTimeEnd, в записях занятости только dateFrom, dateTo и fullDay без id события. В fieldSettings девять ключей, SELECTED_USERS и SETTINGS больше не приходят. Поле бронирования вне CRM-формы и старые закешированные бандлы resourcebooking на внешних сайтах получат пустые данные.

В редакторе формы несохранённое поле бронирования показывает уведомление «Предпросмотр доступности появится после сохранения формы...» вместо ошибки [UF_NOT_FOUND]. Парную правку получил landing 26.1300.100. Панель настроек формы передаёт CRM-форме объект identification, без которого превью в редакторе получило бы пустой ответ.

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

  • Уведомления об изменении и отмене встречи уходят и тогда, когда правит делегат с правом EDIT на секцию календаря хоста (26.200.0).
  • CCalendar::GetAccessNames подставляет LOGIN или EMAIL приглашённым, которые раньше отображались сырым кодом вроде «U123» (26.200.0).
  • iCloud-синхронизация резервирует UUID события до PUT и больше не плодит дубли при повторах (26.200.0).
  • Если вы наследовали Rooms\OccupancyChecker, обновите переопределение protected getEventsForCheck(): у родителя появился параметр ?array $limitEvent = null, и наследник со старой сигнатурой упадёт с Fatal error о несовместимом объявлении. У getExistingEvents сигнатура прежняя, но логика переработана (26.200.0).

Прошлый разбор календаря — 26.125.100.

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

Символ Статус Чем заменять
Bitrix\Calendar\Application\Command\CreateEventCommand::isCheckLocationOccupancy() Удалено Bitrix\Calendar\Application\Command\CreateEventCommand::getOccupancyCheckMode()
Bitrix\Calendar\Application\Command\UpdateEventCommand::isCheckLocationOccupancy() Удалено Bitrix\Calendar\Application\Command\UpdateEventCommand::getOccupancyCheckMode()
BX.Calendar.Planner.getMaxPlannerUsers() Удалено —

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

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

Подписаться

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

calendar 26.125.100 Безопасность Свежее

Calendar 26.125.100: карточка события без полного доступа больше не выдаёт хоста, участников и раздел

В Calendar 26.125.100 карточка события и планировщик перестали отдавать пользователю без полного доступа хоста, автора, участников, данные раздела, ссылки и пользовательские поля. Режет данные новый класс `Bitrix\Calendar\View\EventViewData`, а ответы экшенов `getViewEventSlider`, `getCalendarViewSl…

5 мин
calendar 26.100.0 Безопасность Свежее

Calendar 26.100.0: почтовые приглашения и calendar.section.update берут хоста, тип и владельца из базы

В Calendar 26.100.0 REST-метод `calendar.section.update` берёт тип и владельца секции из базы, а обработчик входящих iCal-приглашений ищет событие только среди встреч получателя и при обновлении встречи читает её хоста из базы. В участники встречи теперь можно звать команды из модуля `humanresources…

4 мин
calendar 26.125.0 Рутинное Свежее

Calendar 26.125.0: компактная форма события вне календаря

Экшен `calendar.api.calendarajax.getStandaloneCompactFormData` собирает секции для формы заново. Обычному пользователю он гарантирует секцию для сохранения и при необходимости создаёт личный календарь. Коллаберу отдаёт только секции его коллабов с правом записи, экстранет-пользователю и коллаберу бе…

1 мин

calendar 26.0.100: почему у людей пропадали встречи при синхронизации с Google и iCloud

Маленький релиз с большими последствиями — исправлена потеря данных при импорте календаря. Если внешний сервис отдавал неполную страницу событий, Битрикс считал недостающие экземпляры удалёнными и вычищал их у себя. Публичного API изменение не трогает, но поставить его стоит всем, у кого работает си…

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

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

на связи

пишет…

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

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

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

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

Войти