17 private const RENEW_LIMIT = 5;
18 private const CREATE_LIMIT = 2;
19 private const CLEAR_LIMIT = 6;
20 private const CHECK_LIMIT = 10;
35 $pushesDb = PushTable::getList([
42 'limit' => self::RENEW_LIMIT,
45 while ($row = $pushesDb->fetch())
48 if ($row[
'ENTITY_TYPE'] === self::TYPE_CONNECTION)
50 $connectionIds[] = (int)$row[
'ENTITY_ID'];
53 if ($row[
'ENTITY_TYPE'] === self::TYPE_SECTION)
55 $sectionIds[] = (int)$row[
'ENTITY_ID'];
59 if (!empty($pushRows))
65 if (!empty($sectionIds))
67 $sectionResult = $DB->query(
"SELECT * FROM b_calendar_section WHERE ID IN (" . implode(
',', $sectionIds) .
")");
68 while($row = $sectionResult->fetch())
70 $sections[$row[
'ID']] = $row;
71 if (!empty($row[
'CAL_DAV_CON']) && !in_array((
int)$row[
'CAL_DAV_CON'], $connectionIds,
true))
73 $connectionIds[] = (int)$row[
'CAL_DAV_CON'];
78 if (!empty($connectionIds))
80 $connectionResult = $DB->query(
"SELECT * FROM b_dav_connections WHERE ID IN (" . implode(
',', $connectionIds) .
")");
81 while($row = $connectionResult->fetch())
83 $connections[$row[
'ID']] = $row;
87 foreach ($pushRows as $row)
90 if ($row[
'ENTITY_TYPE'] === self::TYPE_CONNECTION && !empty($connections[$row[
'ENTITY_ID']]))
92 $connectionData = $connections[$row[
'ENTITY_ID']];
93 if (is_string($connectionData[
'LAST_RESULT']) &&
self::isAuthError($connectionData[
'LAST_RESULT']))
97 $googleApiConnection =
new GoogleApiSync($connectionData[
'ENTITY_ID'], $connectionData[
'ID']);
98 $googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']);
100 $channelInfo = $googleApiConnection->startWatchCalendarList($connectionData[
'NAME']);
102 elseif ($row[
'ENTITY_TYPE'] === self::TYPE_SECTION && !empty($sections[$row[
'ENTITY_ID']]))
104 $section = $sections[$row[
'ENTITY_ID']];
107 (!empty($connectionData)
108 && is_string($connectionData[
'LAST_RESULT'])
109 && self::isAuthError($connectionData[
'LAST_RESULT'])
111 || self::isVirtualCalendar($section[
'GAPI_CALENDAR_ID'], $section[
'EXTERNAL_TYPE'])
117 $connectionData = $connections[$section[
'CAL_DAV_CON']];
118 $googleApiConnection =
new GoogleApiSync($section[
'OWNER_ID'], $section[
'CAL_DAV_CON']);
119 $googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']);
121 $channelInfo = $googleApiConnection->startWatchEventsChannel($section[
'GAPI_CALENDAR_ID']);
130 && isset($channelInfo[
'id'], $channelInfo[
'resourceId'])
131 && isset($googleApiConnection)
134 $googleApiConnection->updateSuccessLastResultConnection();
136 'ENTITY_TYPE' => $row[
'ENTITY_TYPE'],
137 'ENTITY_ID' => $row[
'ENTITY_ID'],
138 'CHANNEL_ID' => $channelInfo[
'id'],
139 'RESOURCE_ID' => $channelInfo[
'resourceId'],
140 'EXPIRES' => $channelInfo[
'expiration'],
141 'NOT_PROCESSED' =>
'N'
147 if (count($pushRows) < 4)
149 \CAgent::removeAgent(
"\\Bitrix\\Calendar\\Sync\\GoogleApiPush::renewWatchChannels();",
"calendar");
159 $googleApiConnection =
new GoogleApiSync($userId, $connectionId);
161 $sectionIds = self::getNotVirtualSectionIds($localSections);
163 $pushChannels = PushTable::getList([
165 '=ENTITY_TYPE' => self::TYPE_SECTION,
166 '=ENTITY_ID' => $sectionIds,
169 $inactiveSections = array_flip($sectionIds);
171 while($row = $pushChannels->fetch())
174 $tsExpires = strtotime($row[
'EXPIRES']);
176 if ($now > $tsExpires)
183 || self::isAuthError(self::getLastResultBySectionId((
int)$row[
'ENTITY_ID']))
186 unset($inactiveSections[$row[
'ENTITY_ID']]);
190 $googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']);
192 unset($inactiveSections[$row[
'ENTITY_ID']]);
194 $localCalendarIndex = array_search($row[
'ENTITY_ID'], array_column($localSections,
'ID'));
195 if ($localCalendarIndex !==
false)
197 $channelInfo = $googleApiConnection->startWatchCalendarList($localSections[$localCalendarIndex][
'GAPI_CALENDAR_ID']);
203 'ENTITY_TYPE' => $row[
'ENTITY_TYPE'],
204 'ENTITY_ID' => $row[
'ENTITY_ID']
207 'CHANNEL_ID' => $channelInfo[
'id'],
208 'RESOURCE_ID' => $channelInfo[
'resourceId'],
209 'EXPIRES' => $channelInfo[
'expiration'],
210 'NOT_PROCESSED' =>
'N'
217 if (is_array($localSections) && is_array($inactiveSections) && $googleApiConnection instanceof
GoogleApiSync)
219 self::startChannelForInActiveSections($localSections, $inactiveSections, $googleApiConnection);
234 $pushOptionEnabled = \COption::getOptionString(
'calendar',
'sync_by_push',
false);
235 if (!$pushOptionEnabled && !\CCalendar::isBitrix24())
241 if(!Loader::includeModule(
'dav'))
246 $davConnections = \CDavConnection::getList(
249 'ACCOUNT_TYPE' => Google\Helper::GOOGLE_ACCOUNT_TYPE_API,
253 [
'nTopCount' => self::CREATE_LIMIT]
257 $pushConnectionIds = [];
258 while($row = $davConnections->fetch())
261 if (!self::isConnectionError($row[
'LAST_RESULT']))
263 $lastId = $row[
'ID'];
264 $connections[] = $row;
265 $pushConnectionIds[] = $row[
'ID'];
269 if(!empty($connections))
271 $result = PushTable::getList(
274 '=ENTITY_TYPE' => self::TYPE_CONNECTION,
275 '=ENTITY_ID' => $pushConnectionIds,
281 while($row = $result->fetch())
283 $pushChannels[$row[
'ENTITY_ID']] = $row;
286 foreach($connections as $davConnection)
288 if(isset($pushChannels[$davConnection[
'ID']]))
293 $googleApiConnection =
new GoogleApiSync($davConnection[
'ENTITY_ID'], $davConnection[
'ID']);
294 $channelInfo = $googleApiConnection->startWatchCalendarList($connections[
'NAME']);
295 if($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
298 $googleApiConnection->updateSuccessLastResultConnection();
300 'ENTITY_TYPE' => self::TYPE_CONNECTION,
301 'ENTITY_ID' => $davConnection[
'ID'],
302 'CHANNEL_ID' => $channelInfo[
'id'],
303 'RESOURCE_ID' => $channelInfo[
'resourceId'],
304 'EXPIRES' => $channelInfo[
'expiration'],
305 'NOT_PROCESSED' =>
'N'
311 if($lastId == $start)
313 \CAgent::removeAgent(
"\\Bitrix\\Calendar\\Sync\\GoogleApiPush::createWatchChannels(".$start.
");",
"calendar");
314 \CAgent::removeAgent(
"\\Bitrix\\Calendar\\Sync\\GoogleApiPush::createWatchChannels(0);",
"calendar");
327 public static function stopChannel(array $row =
null, $ownerId = 0): void
331 if ($row[
'ENTITY_TYPE'] === self::TYPE_SECTION)
333 if (Loader::includeModule(
'dav'))
335 $connectionData = \CDavConnection::getById($row[
'ENTITY_ID']);
338 $ownerId = $connectionData[
'ENTITY_ID'];
342 if ($ownerId > 0 && isset($connectionData) && !self::isConnectionError($connectionData[
'LAST_RESULT']))
344 $googleApiConnection =
new GoogleApiSync($ownerId, $row[
'ENTITY_ID']);
345 $googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']);
349 if ($row[
'ENTITY_TYPE'] === self::TYPE_SECTION)
351 $section = \CCalendarSect::getById($row[
'ENTITY_ID']);
354 $ownerId = $section[
'OWNER_ID'];
358 if (Loader::includeModule(
'dav') && !empty($section[
'CAL_DAV_CON']))
360 $connectionData = \CDavConnection::getById($section[
'CAL_DAV_CON']);
363 if ($ownerId > 0 && isset($connectionData) && !self::isConnectionError($connectionData[
'LAST_RESULT']))
365 $googleApiConnection =
new GoogleApiSync($ownerId, $section[
'CAL_DAV_CON']);
366 $googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']);
380 return !empty($lastResult) && preg_match(
"/^\[(4\d\d)\][a-z0-9 _]*/i", $lastResult);
383 public static function isAuthError(
string $lastResult =
null): bool
385 return !empty($lastResult) && preg_match(
"/^\[(401)\][a-z0-9 _]*/i", $lastResult);
390 return !empty($lastResult) && preg_match(
"/^\[(410)\][a-z0-9 _]*/i", $lastResult);
399 return !empty($error)
401 "/^\[404\] Channel \'[a-z0-9 _]*\' not found for project \'[a-z0-9 _]*\'/i",
437 if ($entityType === self::TYPE_SECTION)
440 "SELECT s.*, c.LAST_RESULT as LAST_RESULT
441 FROM b_calendar_section s
442 LEFT JOIN b_dav_connections c
443 ON s.CAL_DAV_CON = c.ID
444 WHERE s.ID=" . $entityId
446 if ($section = $r->fetch())
448 if (self::isAuthError($section[
'LAST_RESULT']))
456 if (empty($tokens[
'nextSyncToken']))
461 \CCalendarSect::edit(
465 'ID' => $section[
'ID'],
466 'SYNC_TOKEN' => $tokens[
'nextSyncToken'],
467 'PAGE_TOKEN' => $tokens[
'nextPageToken'],
473 \CCalendar::clearCache();
476 elseif ($entityType === self::TYPE_CONNECTION)
478 $r = $DB->query(
"SELECT * FROM b_dav_connections WHERE ID=" . $entityId);
479 if ($connection = $r->fetch())
482 self::isAuthError($connection[
'LAST_RESULT'])
483 || !Loader::includeModule(
'dav')
490 \CCalendar::clearCache();
508 if (!Loader::includeModule(
'dav'))
513 $davConnectionsDb = \CDavConnection::getList(
516 'ACCOUNT_TYPE' => Google\Helper::GOOGLE_ACCOUNT_TYPE_API,
517 '>ID' => $lastIdConnection,
520 [
'nTopCount' => self::CHECK_LIMIT]
523 while ($davConnection = $davConnectionsDb->fetch())
525 if (self::isAuthError($davConnection[
'LAST_RESULT']))
530 $connections[$davConnection[
'ID']] = $davConnection;
531 $connectionIds[] = $davConnection[
'ID'];
532 $lastIdConnection = $davConnection[
'ID'];
535 if (!empty($connectionIds))
537 self::checkPushConnectionChannel($connectionIds, $connections);
538 self::checkPushSectionChannel($connectionIds, $connections);
550 private static function checkPushConnectionChannel(array $connectionIds, array $connections): void
552 $existedConnectionChannels = [];
553 $pushConnectionChannelsDb = PushTable::getList(
556 '=ENTITY_TYPE' => self::TYPE_CONNECTION,
557 '=ENTITY_ID' => $connectionIds
562 while ($row = $pushConnectionChannelsDb->fetch())
565 if (!empty($connections[$row[
'ENTITY_ID']]))
567 $connectionData = $connections[$row[
'ENTITY_ID']];
568 if (!self::isConnectionError($connectionData[
'LAST_RESULT']))
570 $existedConnectionChannels[] = $row[
'ENTITY_ID'];
574 $googleApiConnection =
new GoogleApiSync($connectionData[
'ENTITY_ID'], $connectionData[
'ID']);
575 if ($googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']))
578 $channelInfo = $googleApiConnection->startWatchCalendarList($connectionData[
'NAME']);
581 if (is_string($googleApiConnection->getTransportConnectionError()))
586 if ($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
588 $existedConnectionChannels[] = $row[
'ENTITY_ID'];
589 $googleApiConnection->updateSuccessLastResultConnection();
591 'ENTITY_TYPE' => $row[
'ENTITY_TYPE'],
592 'ENTITY_ID' => $row[
'ENTITY_ID'],
593 'CHANNEL_ID' => $channelInfo[
'id'],
594 'RESOURCE_ID' => $channelInfo[
'resourceId'],
595 'EXPIRES' => $channelInfo[
'expiration'],
596 'NOT_PROCESSED' =>
'N'
603 $missedChannelConnections = array_diff($connectionIds, $existedConnectionChannels);
604 if (!empty($missedChannelConnections))
606 foreach ($missedChannelConnections as $missedConnection)
608 if (self::isAuthError($missedConnection[
'LAST_RESULT']))
614 $connectionData = $connections[$missedConnection];
615 $googleApiConnection =
new GoogleApiSync($connectionData[
'ENTITY_ID'], $connectionData[
'ID']);
616 $channelInfo = $googleApiConnection->startWatchCalendarList($connectionData[
'NAME']);
617 if ($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
619 $googleApiConnection->updateSuccessLastResultConnection();
621 'ENTITY_TYPE' => self::TYPE_CONNECTION,
622 'ENTITY_ID' => $connectionData[
'ID'],
623 'CHANNEL_ID' => $channelInfo[
'id'],
624 'RESOURCE_ID' => $channelInfo[
'resourceId'],
625 'EXPIRES' => $channelInfo[
'expiration'],
626 'NOT_PROCESSED' =>
'N'
631 $error = $googleApiConnection->getTransportConnectionError();
632 if (is_string($error))
634 $googleApiConnection->updateLastResultConnection($error);
646 private static function checkPushSectionChannel(array $connectionIds, array $connections): void
648 $existedSectionChannels = [];
652 $sectionsDb = Internals\SectionTable::getList(
655 'CAL_DAV_CON' => $connectionIds,
663 while ($section = $sectionsDb->fetch())
665 $sections[$section[
'ID']] = $section;
666 $sectionIds[] = $section[
'ID'];
669 if (!empty($sectionIds))
671 $pushSectionChannelsDb = PushTable::getList(
674 '=ENTITY_TYPE' => self::TYPE_SECTION,
675 '=ENTITY_ID' => $sectionIds
680 while ($row = $pushSectionChannelsDb->fetch())
683 if (!empty($sections[$row[
'ENTITY_ID']]))
685 $section = $sections[$row[
'ENTITY_ID']];
687 if (self::isVirtualCalendar($section[
'GAPI_CALENDAR_ID'], $section[
'EXTERNAL_TYPE']))
692 if (!self::isConnectionError($connections[$section[
'CAL_DAV_CON']][
'LAST_RESULT']))
694 $existedSectionChannels[] = $row[
'ENTITY_ID'];
698 $googleApiConnection =
new GoogleApiSync($section[
'OWNER_ID'], $section[
'CAL_DAV_CON']);
699 if ($googleApiConnection->stopChannel($row[
'CHANNEL_ID'], $row[
'RESOURCE_ID']))
702 $channelInfo = $googleApiConnection->startWatchEventsChannel($section[
'GAPI_CALENDAR_ID']);
704 if (is_string($googleApiConnection->getTransportConnectionError()))
709 if ($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
711 $existedSectionChannels[] = $row[
'ENTITY_ID'];
712 $googleApiConnection->updateSuccessLastResultConnection();
714 'ENTITY_TYPE' => $row[
'ENTITY_TYPE'],
715 'ENTITY_ID' => $row[
'ENTITY_ID'],
716 'CHANNEL_ID' => $channelInfo[
'id'],
717 'RESOURCE_ID' => $channelInfo[
'resourceId'],
718 'EXPIRES' => $channelInfo[
'expiration'],
719 'NOT_PROCESSED' =>
'N'
726 $missedChannelSections = array_diff($sectionIds, $existedSectionChannels);
727 if (!empty($missedChannelSections))
729 foreach ($missedChannelSections as $missedSection)
732 $connectionData = $connections[$sections[$missedSection][
'CAL_DAV_CON']];
733 $section = $sections[$missedSection];
735 self::isAuthError($connectionData[
'LAST_RESULT'])
736 || self::isVirtualCalendar($section[
'GAPI_CALENDAR_ID'], $section[
'EXTERNAL_TYPE'])
742 $googleApiConnection =
new GoogleApiSync($connectionData[
'ENTITY_ID'], $connectionData[
'ID']);
743 $channelInfo = $googleApiConnection->startWatchEventsChannel($section[
'GAPI_CALENDAR_ID']);
744 if ($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
746 $googleApiConnection->updateSuccessLastResultConnection();
749 'ENTITY_ID' => $missedSection,
750 'CHANNEL_ID' => $channelInfo[
'id'],
751 'RESOURCE_ID' => $channelInfo[
'resourceId'],
752 'EXPIRES' => $channelInfo[
'expiration'],
753 'NOT_PROCESSED' =>
'N'
756 PushTable::add($row);
760 $error = $googleApiConnection->getTransportConnectionError();
761 if (is_string($error))
763 $googleApiConnection->updateLastResultConnection($error);
777 PushTable::delete([
'ENTITY_TYPE' => $row[
'ENTITY_TYPE'],
'ENTITY_ID' => $row[
'ENTITY_ID']]);
785 private static function isVirtualCalendar(?
string $gApiCalendarId, ?
string $externalType): bool
787 return preg_match(
'/(holiday.calendar.google.com)/', $gApiCalendarId)
788 || preg_match(
'/(group.v.calendar.google.com)/', $gApiCalendarId)
789 || preg_match(
'/(@virtual)/', $gApiCalendarId)
790 || preg_match(
'/(_readonly)/', $externalType)
791 || preg_match(
'/(_freebusy)/', $externalType);
800 private static function startChannelForInActiveSections(
801 array $localSections,
802 array $inactiveSections,
803 GoogleApiSync $googleApiConnection
806 foreach ($localSections as $section)
808 if (self::isVirtualCalendar($section[
'GAPI_CALENDAR_ID'], $section[
'EXTERNAL_TYPE']))
813 if (isset($inactiveSections[$section[
'ID']]))
815 if (($push = self::getPush(self::TYPE_SECTION, $section[
'ID'])) && self::isValid($push))
820 $channelInfo = $googleApiConnection->startWatchEventsChannel($section[
'GAPI_CALENDAR_ID']);
821 if ($channelInfo && isset($channelInfo[
'id'], $channelInfo[
'resourceId']))
825 "ENTITY_TYPE" => self::TYPE_SECTION,
826 'ENTITY_ID' => $section[
'ID']
829 $googleApiConnection->updateSuccessLastResultConnection();
831 'ENTITY_TYPE' => self::TYPE_SECTION,
832 'ENTITY_ID' => $section[
'ID'],
833 'CHANNEL_ID' => $channelInfo[
'id'],
834 'RESOURCE_ID' => $channelInfo[
'resourceId'],
835 'EXPIRES' => $channelInfo[
'expiration'],
836 'NOT_PROCESSED' =>
'N'
841 $error = $googleApiConnection->getTransportConnectionError();
842 if (is_string($error))
844 $googleApiConnection->updateLastResultConnection($error);
855 private static function getLastResultBySectionId(
int $sectionId): ?string
859 $strSql =
"SELECT c.ID as CONNECTON_ID, c.LAST_RESULT, s.ID as SECTION_ID
860 FROM b_dav_connections c
861 INNER JOIN b_calendar_section s
863 .
" WHERE s.CAL_DAV_CON = c.ID";
865 $connectionDb = $DB->Query($strSql);
866 if ($connection = $connectionDb->Fetch())
868 return $connection[
'LAST_RESULT'];
878 private static function getNotVirtualSectionIds(array $localSections): array
881 foreach ($localSections as $section)
884 if (self::isVirtualCalendar($section[
'GAPI_CALENDAR_ID'], $section[
'EXTERNAL_TYPE']))
889 $sectionIds[] = (int)$section[
'ID'];
904 $pushResultDb = PushTable::getByPrimary([
905 'ENTITY_TYPE' => self::TYPE_CONNECTION,
909 return $pushResultDb->fetch();
922 && isset($push[
'NOT_PROCESSED'])
926 $strSql =
"UPDATE b_calendar_push"
927 .
" SET NOT_PROCESSED = '" . Google\Dictionary::PUSH_STATUS_PROCESS[
'block'] .
"'"
928 .
" WHERE ENTITY_TYPE = '" . $type .
"' AND ENTITY_ID = " . $entityId .
";";
944 $strSql =
"UPDATE b_calendar_push"
945 .
" SET NOT_PROCESSED = 'N'"
946 .
" WHERE ENTITY_TYPE = '" . $type .
"' AND ENTITY_ID = " . $entityId .
";";
967 && isset($push[
'NOT_PROCESSED'])
971 $strSql =
"UPDATE b_calendar_push"
972 .
" SET NOT_PROCESSED = '" . Google\Dictionary::PUSH_STATUS_PROCESS[
'unprocessed'] .
"'"
973 .
" WHERE ENTITY_TYPE = '" . $type .
"' AND ENTITY_ID = " . $entityId .
";";
983 public static function getPush(
string $type,
int $entityId): ?array
987 $strSql =
"SELECT * FROM b_calendar_push"
988 .
" WHERE ENTITY_TYPE = '" . $type .
"' AND ENTITY_ID = " . $entityId .
";";
989 $pushDb = $DB->Query($strSql);
991 if ($push = $pushDb->Fetch())
999 private static function isValid(?array $push): bool
1007 $tsExpires = strtotime($push[
'EXPIRES']);
1009 return !($now > $tsExpires);