calendar 26.0.100: почему у людей пропадали встречи при синхронизации с Google и iCloud
Маленький релиз с большими последствиями — исправлена потеря данных при импорте календаря. Если внешний сервис отдавал неполную страницу событий, Битрикс считал недостающие экземпляры удалёнными и вычищал их у себя. Публичного API изменение не трогает, но поставить его стоит всем, у кого работает синхронизация.
В чём была проблема
Импорт устроен так: получили от Google или iCloud список экземпляров повторяющегося события, сравнили с локальным, чего нет в ответе — удалили. Логика верная ровно при одном условии: ответ полный.
А ответ полный не всегда. При начальной синхронизации данные приходят страницами, и одна страница — это не весь набор. Экземпляр, который лежит на следующей странице, для процедуры сравнения выглядит точно так же, как удалённый у вендора: его нет в ответе. Результат — встреча исчезает из календаря пользователя, хотя её никто не удалял.
Комментарий в новом коде описывает ровно это: на неполном или обрезанном ответе отсутствующие экземпляры могли просто не попасть в страницу, а не быть удалёнными; пропуск очистки нужен, чтобы не терять данные и сохранить EventConnections для следующего полного прохода.
Как исправили
В DTO EventListResponse (и у Google, и у iCloud) появилось поле public bool $isPartial = false. Процедуры импорта проставляют его по ответу вендора, а AbstractEventImportProcessor::removeDeprecatedInstances() при isPartialResponse просто выходит, ничего не удаляя.
Ключевое разграничение — явные удаления применяются всегда. Google, присылающий событию статус cancelled, и iCloud, отвечающий 404, — это утверждение «этого события нет», ему верят при любом флаге. Подавляется только вывод удаления из факта отсутствия в списке.
Условия выставления флага стоит знать точно, потому что они не «всегда на всякий случай»:
- Google:
isPartial = trueна начальной синхронизации (syncTokenотсутствует), если в запросе былpageTokenили в ответе естьnextPageToken. Одностраничный полный снимок — безpageTokenна входе и безnextPageTokenна выходе — очистку не отключает. Инкрементальная синхронизация (когдаsyncTokenесть) не затрагивается вообще. - iCloud: аналогичный флаг на DTO;
ICloudEventSynchronizerперестал безусловно выставлятьsyncTokenизnextSyncTokenна каждом ответе.
То есть защита включается ровно там, где ответ действительно может быть неполным, и не мешает нормальной очистке в остальных случаях.
Заодно поправлена логика удаления
Роль master/owner против guest теперь берётся из локального события в $this->map, а не из внешнего. Причина техническая и показательная: у внешнего события getParentId() всегда null, так что определить по нему, экземпляр это или мастер, невозможно. Экземпляр повторения ищется через мастера и getSameInstance().
Что делать
- Ставить, если у вас включена синхронизация с Google Calendar или iCloud. Это исправление потери пользовательских данных, а не улучшение.
- Если пользователи жаловались на «пропали встречи после подключения календаря» — вот объяснение, и после обновления повторяться не должно.
- Пишете свою синхронизацию с внешним источником — здесь хороший общий урок: отсутствие записи в ответе не равно её удалению, пока вы не уверены, что ответ полный.