1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
googleapisync.php
См. документацию.
1<?
2namespace Bitrix\Calendar\Sync;
3
4use Bitrix\Calendar\Sync\Google\Helper;
5use Bitrix\Main\Config\Option;
6use Bitrix\Main\Context;
7use Bitrix\Main\Engine\UrlManager;
8use Bitrix\Main\Loader;
9use Bitrix\Main\Text\Emoji;
10use Bitrix\Main\Type;
11use Bitrix\Main\Localization\Loc;
12use Bitrix\Main\Type\Date;
13use \Bitrix\Main\Web;
14use Bitrix\Calendar\Util;
15use Bitrix\Calendar\Rooms;
16use CDavConnection;
17
23final class GoogleApiSync
24{
26 const ONE_DAY = 86400; //60*60*24;
27 const CHANNEL_EXPIRATION = 604800; //60*60**24*7
28 const CONNECTION_CHANNEL_TYPE = 'BX_CONNECTION';
29 const SECTION_CHANNEL_TYPE = 'BX_SECTION';
30 const SECTION_CONNECTION_CHANNEL_TYPE = 'SECTION_CONNECTION';
32 const SYNC_EVENTS_DATE_INTERVAL = '-1 months';
33 const DEFAULT_TIMEZONE = 'UTC';
34 const DATE_TIME_FORMAT = 'Y-m-d\TH:i:sP';
35 public const END_OF_DATE = "01.01.2038";
36 public const EXTERNAL_LINK = 'https://www.bitrix24.com/controller/google_calendar_push.php?target_host=';
37
41 private $syncTransport;
42 private $nextSyncToken = '',
43 $defaultTimezone = self::DEFAULT_TIMEZONE,
44 $userId = 0,
45 $calendarList = array(),
46 $defaultReminderData = array(),
47 $calendarColors = false,
48 $eventColors = false,
49 $eventMapping = array(
50 'DAV_XML_ID' => 'iCalUID',
51 'NAME' => 'summary',
52// 'DESCRIPTION' => 'description',
53 'CAL_DAV_LABEL' => 'etag'
54 );
58 private $connectionId;
62 private $nextPageToken = '';
63
70 public function __construct($userId = 0, $connectionId = 0)
71 {
72 if (!$userId)
73 {
74 $userId = \CCalendar::GetUserId();
75 }
76 $this->userId = $userId;
77 $this->connectionId = $connectionId;
78 $this->syncTransport = new GoogleApiTransport((int)$userId);
79 }
80
88 {
89 $this->syncTransport->stopChannel($channelId, $resourceId);
90
92 if (is_string($error))
93 {
95 return false;
96 }
97
98 return true;
99 }
100
108 {
109 $channel = $this->syncTransport->openCalendarListChannel($this->makeChannelParams($name, self::CONNECTION_CHANNEL_TYPE));
110 if (!$this->syncTransport->getErrors())
111 {
112 $channel['expiration'] = Type\DateTime::createFromTimestamp($channel['expiration']/1000);
113 return $channel;
114 }
115
117 if (is_string($error))
118 {
120 }
121
122 return [];
123 }
124
125 private function makeChannelParams($inputSecretWord, $type)
126 {
127 if (defined('BX24_HOST_NAME') && BX24_HOST_NAME)
128 {
129 $externalUrl = self::EXTERNAL_LINK . BX24_HOST_NAME;
130 }
131 else
132 {
133 $request = Context::getCurrent()->getRequest();
134 if (defined('SITE_SERVER_NAME') && SITE_SERVER_NAME)
135 {
136 $host = SITE_SERVER_NAME;
137 }
138 else
139 {
140 $host = Option::get('main', 'server_name', $request->getHttpHost());
141 }
142
143 $externalUrl = 'https://' . $host . '/bitrix/tools/calendar/push.php';
144 }
145
146 return [
147 'id' => $type.'_'.$this->userId.'_'.md5($inputSecretWord. time()),
148 'type' => 'web_hook',
149 'address' => $externalUrl,
150 'expiration' => (time() + self::CHANNEL_EXPIRATION) * 1000,
151 ];
152 }
153
160 public function startWatchEventsChannel($calendarId = 'primary')
161 {
162 $channel = $this->syncTransport->openEventsWatchChannel(
163 $calendarId,
164 $this->makeChannelParams($calendarId, self::SECTION_CHANNEL_TYPE)
165 );
166
167 if (!$this->syncTransport->getErrors())
168 {
169 $channel['expiration'] = Type\DateTime::createFromTimestamp($channel['expiration']/1000);
170 return $channel;
171 }
172
173 if (($error = $this->getTransportConnectionError()) && is_string($error))
174 {
176 }
177
178 return false;
179 }
180
185 public function testConnection()
186 {
187 $this->setColors();
188 if ($this->getTransportErrors())
189 {
190 return false;
191 }
192 return true;
193 }
194
199 public function getTransportErrors()
200 {
201 return $this->syncTransport->getErrors();
202 }
203
204 private function setColors()
205 {
206 if ($this->calendarColors === false || $this->eventColors === false)
207 {
208 $cacheTime = 86400 * 7;
209 $colorData = null;
210
211 if ($cacheTime)
212 {
213 $cache = \Bitrix\Main\Data\Cache::createInstance();
214 $cacheId = "google_colors";
215 $cachePath = 'googlecolors';
216
217 if ($cache->initCache($cacheTime, $cacheId, $cachePath))
218 {
219 $res = $cache->getVars();
220 $colorData = $res["colorData"];
221 }
222 }
223
224 if (!$cacheTime || empty($colorData))
225 {
226 $colorData = $this->syncTransport->getColors();
227 if ($cacheTime && isset($cache, $cacheId, $cachePath))
228 {
229 $cache->startDataCache($cacheTime, $cacheId, $cachePath);
230 $cache->endDataCache(array(
231 "colorData" => $colorData
232 ));
233 }
234 }
235
236 $this->calendarColors = array();
237 $this->eventColors = array();
238 if (is_array($colorData) && !empty($colorData['calendar']) && !empty($colorData['event']))
239 {
240 foreach ($colorData['calendar'] as $key => $color)
241 {
242 $this->calendarColors[$key] = $color;
243 }
244
245 foreach ($colorData['event'] as $key => $color)
246 {
247 $this->eventColors[$key] = $color;
248 }
249 }
250 }
251 }
252
258 private function getCalendarColor($colorId, $background = true)
259 {
260 $calendarColors = is_array($this->calendarColors) ? $this->calendarColors : array();
261 return $calendarColors[$colorId][($background ? 'background' : 'foreground')];
262 }
263
269 private function getEventColor($colorId, $background = true)
270 {
271 $eventColors = is_array($this->eventColors) ? $this->eventColors : array();
272 return $eventColors[$colorId][($background ? 'background' : 'foreground')];
273 }
274
281 {
282 $connectionError = $this->syncTransport->getErrorByCode('CONNECTION');
283 if ($connectionError)
284 {
285 return $connectionError['message'];
286 }
287
288 return [];
289 }
290
297 public function getCalendarItems(string $syncToken = null): array
298 {
299 $this->setColors();
300 $response = [];
301
302 if (empty($this->calendarList))
303 {
304 $response = $this->syncTransport->getCalendarList($this->getCalendarListParams($syncToken));
305 }
306
307 if ($response && !empty($response['items']))
308 {
309 foreach($response['items'] as $calendarItem)
310 {
311 $calendarItem['backgroundColor'] = $this->getCalendarColor($calendarItem['colorId']);
312 $calendarItem['textColor'] = $this->getCalendarColor($calendarItem['colorId'], true);
313 $this->calendarList[] = $calendarItem;
314 }
315
316 $this->nextSyncToken = $response['nextSyncToken'];
317 }
318
319
320 return $this->calendarList;
321 }
322
326 public function getNextSyncToken()
327 {
328 return $this->nextSyncToken;
329 }
330
337 public function getEvents(array $calendarData): array
338 {
339 $this->setColors();
340 $this->nextSyncToken = $calendarData['SYNC_TOKEN'] ?? '';
341 $this->nextPageToken = $calendarData['PAGE_TOKEN'] ?? '';
342
343 if (!empty($response = $this->runSyncEvents($calendarData['GAPI_CALENDAR_ID'])))
344 {
345 return $this->processResponseReceivingEvents($response);
346 }
347
348 return [];
349 }
350
356 public function getPrimaryId()
357 {
358 $calendar = $this->getCalendarById('primary');
359 return !empty($calendar) ? $calendar['id'] : '';
360 }
361
368 private function getCalendarById($calendarId)
369 {
370 $this->getCalendarItems();
371
372 foreach ($this->calendarList as $calendar)
373 {
374 if (($calendar['id'] == $calendarId) || (isset($calendar['primary']) && $calendarId == 'primary'))
375 {
376 return $calendar;
377 }
378 }
379 return array();
380 }
381
389 public function deleteEvent($eventId, $calendarId)
390 {
391 return $this->syncTransport->deleteEvent($eventId, urlencode($calendarId));
392 }
393
401 public function saveEvent($eventData, $calendarId, $parameters = []): ?array
402 {
403 $params['editInstance'] = $parameters['editInstance'] ?? false;
404 $params['originalDavXmlId'] = $parameters['originalDavXmlId'] ?? null;
405 $params['editParentEvents'] = $parameters['editParentEvents'] ?? false;
406 $params['editNextEvents'] = $parameters['editNextEvents'] ?? false;
407 $params['calendarId'] = $calendarId;
408 $params['instanceTz'] = $parameters['instanceTz'] ?? null;
409 $params['originalDateFrom'] = $eventData['ORIGINAL_DATE_FROM'] ?? null;
410 $params['gEventId'] = $eventData['G_EVENT_ID'] ?: str_replace('@google.com', '', $eventData['DAV_XML_ID']);
411 $params['syncCaldav'] = $parameters['syncCaldav'] ?? false;
412
413 $newEvent = $this->prepareToSaveEvent($eventData, $params);
414
415 $externalEvent = $this->sendToSaveEvent($newEvent, $params);
416
417 if ($externalEvent)
418 {
419 return [
420 'DAV_XML_ID' => $externalEvent['iCalUID'],
421 'CAL_DAV_LABEL' => $externalEvent['etag'],
422 'ORIGINAL_DATE_FROM' => $externalEvent['originalStartTime'] ? $eventData['ORIGINAL_DATE_FROM'] : null,
423 'G_EVENT_ID' => $externalEvent['id'],
424 ];
425 }
426
427 return null;
428 }
429
437 public function saveBatchEvents(array $events, string $gApiCalendarId, array $params): array
438 {
439 $responseFields = [];
440 $prepareEvents = [];
441
442 foreach ($events as $event)
443 {
444 $localEvent['gEventId'] = $event['gEventId'];
445 $partBody = $this->prepareToSaveEvent($event);
446 $localEvent['partBody'] = Web\Json::encode($partBody, JSON_UNESCAPED_SLASHES);
447 $prepareEvents[$event['ID']] = $localEvent;
448 }
449
450 $externalEvents = $this->syncTransport->sendBatchEvents($prepareEvents, $gApiCalendarId, $params);
451
452 if ($externalEvents)
453 {
454 foreach ($externalEvents as $key => $externalEvent)
455 {
456 $responseFields[$key]['DAV_XML_ID'] = $externalEvent['iCalUID'];
457 $responseFields[$key]['CAL_DAV_LABEL'] = $externalEvent['etag'];
458 $responseFields[$key]['G_EVENT_ID'] = $externalEvent['id'];
459 $responseFields[$key]['ORIGINAL_DATE_FROM'] = $externalEvent['originalStartTime'] ? $events[$key]['ORIGINAL_DATE_FROM'] : null;
460 }
461 }
462
463 return $responseFields;
464 }
465
466 public function updateLastResultConnection(string $lastResult): void
467 {
468 if (Loader::includeModule('dav') && !empty($this->connectionId))
469 {
470 CDavConnection::Update($this->connectionId, [
471 "LAST_RESULT" => $lastResult,
472 "SYNCHRONIZED" => ConvertTimeStamp(time(), "FULL"),
473 ]);
474 }
475 }
476
477 public function updateSuccessLastResultConnection(): void
478 {
479 $this->updateLastResultConnection("[200] OK");
480 }
481
487 private function prepareEvent($event): array
488 {
489 $returnData = [
490 'TZ_FROM' => $this->defaultTimezone,
491 'TZ_TO' => $this->defaultTimezone
492 ];
493
494 foreach ($this->eventMapping as $internalKey => $externalKey)
495 {
496 $returnData[$internalKey] = (isset($event[$externalKey]) ? $event[$externalKey] : '');
497 }
498
499 $returnData['iCalUID'] = $event['iCalUID'];
500 $returnData['DAV_XML_ID'] = $event['iCalUID'];
501 $returnData['G_EVENT_ID'] = $event['id'];
502
503 if (!empty($event['description']))
504 {
505 $description = str_replace("<br>", "\r\n", $event['description']);
506 $returnData["DESCRIPTION"] = trim(\CTextParser::clearAllTags($description));
507 }
508
509 if (empty($event['summary']))
510 {
511 $returnData['NAME'] = GetMessage('EC_T_NEW_EVENT');
512 }
513
514 if (!empty($event['transparency']) && $event['transparency'] == 'transparent')
515 {
516 $returnData['ACCESSIBILITY'] = 'free';
517 }
518 else
519 {
520 $returnData['ACCESSIBILITY'] = 'busy';
521 }
522
523 if (!empty($event['visibility']) && $event['visibility'] === 'private')
524 {
525 $returnData['PRIVATE_EVENT'] = true;
526 }
527 else
528 {
529 $returnData['PRIVATE_EVENT'] = false;
530 }
531
532 $returnData['OWNER_ID'] = $this->userId;
533 $returnData['CREATED_BY'] = $this->userId;
534 $returnData['CAL_TYPE'] = 'user';
535
536 if (!empty($event['start']['dateTime']) && !empty($event['end']['dateTime']))
537 {
538 $returnData['TZ_FROM'] = Util::isTimezoneValid($event['start']['timeZone']) ? $event['start']['timeZone'] : $this->defaultTimezone;
539 $returnData['TZ_TO'] = Util::isTimezoneValid($event['end']['timeZone']) ? $event['end']['timeZone'] : $this->defaultTimezone;
540
541 $eventStartDateTime = new Type\DateTime($event['start']['dateTime'], self::DATE_TIME_FORMAT, new \DateTimeZone($this->defaultTimezone));
542 $returnData['DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($eventStartDateTime->setTimeZone(new \DateTimeZone($returnData['TZ_FROM']))->format(Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
543
544 $eventStartDateTime = new Type\DateTime($event['end']['dateTime'], self::DATE_TIME_FORMAT, new \DateTimeZone($this->defaultTimezone));
545 $returnData['DATE_TO'] = \CCalendar::Date(\CCalendar::Timestamp($eventStartDateTime->setTimeZone(new \DateTimeZone($returnData['TZ_TO']))->format(Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
546 }
547
548 if (!empty($event['start']['date']))
549 {
550 $returnData['DATE_FROM'] = \CCalendar::Date(strtotime($event['start']['date']), false);
551 }
552
553 if (!empty($event['end']['date']))
554 {
555 if ($event['end']['date'] === $event['start']['date'])
556 {
557 $dateStr = strtotime($event['end']['date']);
558 }
559 else
560 {
561 $dateStr = strtotime($event['end']['date']) - self::ONE_DAY;
562 }
563
564 $returnData['DATE_TO'] = \CCalendar::Date($dateStr, false);
565 $returnData['DT_SKIP_TIME'] = 'Y';
566 }
567 else
568 {
569 $returnData['DT_SKIP_TIME'] = 'N';
570 }
571
572 $returnData['DATE_CREATE'] = \CCalendar::Date(strtotime($event['created']));
573
574 if (!empty($event['colorId']))
575 {
576 $returnData['COLOR'] = $this->getEventColor($event['colorId']);
577 $returnData['TEXT_COLOR'] = $this->getEventColor($event['colorId'], false);
578 }
579
580 $returnData['DATE_CREATE'] = \CCalendar::Date(time());
581 $returnData['status'] = $event['status'];
582 $returnData['hasMoved'] = "N";
583 $returnData['isRecurring'] = "N";
584
585 $exDates = [];
586
587 if ($event['recurrence'])
588 {
589 foreach ($event['recurrence'] as $recurrence)
590 {
591 if (preg_match('/(RRULE)/', $recurrence))
592 {
593 $rRuleData = preg_replace('/(RRULE\:)/', '', $recurrence);
594 $rRuleList = explode(';', $rRuleData);
595 $rRuleSet = [];
596 foreach ($rRuleList as $rRuleElement)
597 {
598 [$rRuleProp, $rRuleVal] = explode('=', $rRuleElement);
599 switch ($rRuleProp)
600 {
601 case 'FREQ':
602 if (in_array($rRuleVal, ['HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']))
603 {
604 $rRuleSet['FREQ'] = $rRuleVal;
605 }
606 break;
607 case 'COUNT':
608 $rRuleSet['COUNT'] = $rRuleVal;
609 break;
610 case 'INTERVAL':
611 $rRuleSet['INTERVAL'] = $rRuleVal;
612 break;
613 case 'BYDAY':
614 $rRuleByDay = array();
615 foreach(explode(',', $rRuleVal) as $day)
616 {
617 $matches = array();
618 if (preg_match('/((\-|\+)?\d+)?(MO|TU|WE|TH|FR|SA|SU)/', $day, $matches))
619 {
620 $rRuleByDay[$matches[1] === ''
621 ? $matches[3]
622 : $matches[1]] =
623 $matches[1] === ''
624 ? $matches[3]
625 : $matches[1];
626 }
627 }
628 if (!empty($rRuleByDay))
629 {
630 $rRuleSet['BYDAY'] = $rRuleByDay;
631 }
632 break;
633 case 'UNTIL':
634 try
635 {
636 $rRuleValDateTime = new Type\DateTime($rRuleVal, 'Ymd\THis\Z', new \DateTimeZone('UTC'));
637 $rRuleValDateTime->setTimeZone(new \DateTimeZone($returnData['TZ_TO']));
638 $untilDateTime = explode("T", $rRuleVal);
639
640 if ($untilDateTime[1] === "000000Z")
641 {
642 $rRuleValDateTime = $rRuleValDateTime->add("-1 day");
643 }
644
645 $rRuleSet['UNTIL'] = \CCalendar::Date(\CCalendar::Timestamp($rRuleValDateTime->format(Type\Date::convertFormatToPhp(FORMAT_DATETIME))) - 60, false, false);
646 }
647 catch(\Exception $e)
648 {
649 $rRuleSet['UNTIL'] = \CCalendar::Date(strtotime($rRuleVal), false, false);
650 }
651
652 break;
653 }
654 }
655 $returnData["RRULE"] = \CCalendarEvent::CheckRRULE($rRuleSet);
656 }
657 elseif (preg_match('/(\d{4}-?\d{2}-?\d{2})(Z)?/', $recurrence, $date))
658 {
659 if (!empty($date[1]))
660 {
661 $exDates[] = \CCalendar::Date(strtotime($date[1]), false);
662 }
663 }
664 }
665 }
666
667 if (!empty($event['recurringEventId']))
668 {
669 $returnData['isRecurring'] = "Y";
670 if ($event['status'] === 'cancelled')
671 {
672 $exDates[] = date(Date::convertFormatToPhp(FORMAT_DATE), strtotime(
673 !empty($event['originalStartTime']['dateTime'])
674 ? $event['originalStartTime']['dateTime']
675 : $event['originalStartTime']['date']
676 ));
677 }
678 elseif ($event['status'] === 'confirmed' && !empty($event['originalStartTime']))
679 {
680 $returnData['hasMoved'] = "Y";
681 $exDates[] = date(Date::convertFormatToPhp(FORMAT_DATE), strtotime(!empty($event['originalStartTime']['dateTime']) ? $event['originalStartTime']['dateTime'] : $event['originalStartTime']['date']));
682
683 if (!empty($event['originalStartTime']['dateTime']))
684 {
685 $originalTimeZone = Util::isTimezoneValid($event['originalStartTime']['timeZone']) ? $event['originalStartTime']['timeZone'] : $returnData['TZ_FROM'];
686 $eventOriginalDateFrom = new Type\DateTime($event['originalStartTime']['dateTime'], self::DATE_TIME_FORMAT, new \DateTimeZone($this->defaultTimezone));
687 $returnData['ORIGINAL_DATE_FROM'] = \CCalendar::Date(\CCalendar::Timestamp($eventOriginalDateFrom->setTimeZone(new \DateTimeZone($originalTimeZone))->format(Type\Date::convertFormatToPhp(FORMAT_DATETIME))));
688 }
689
690 if (!empty($event['originalStartTime']['date']))
691 {
692 $returnData['ORIGINAL_DATE_FROM'] = \CCalendar::Date(strtotime($event['originalStartTime']['date']), false);
693 }
694 }
695
696 $returnData['recurringEventId'] = $event['recurringEventId'];
697 }
698 if (!empty($exDates))
699 {
700 $returnData['EXDATE'] = implode(';', $exDates);
701 }
702
703 $returnData['REMIND'] = [];
704 if (!empty($event['reminders']['overrides']))
705 {
706 foreach ($event['reminders']['overrides'] as $remindData)
707 {
708 $remindTimeout = $remindData['minutes'];
709 $returnData['REMIND'][] = [
710 'type' => 'min',
711 'count' => $remindTimeout
712 ];
713 }
714 }
715 if (!empty($event['reminders']['useDefault']) && !empty($this->defaultReminderData) && $event['reminders']['useDefault'] === 1)
716 {
717 foreach ($this->defaultReminderData as $remindData)
718 {
719 $remindTimeout = $remindData['minutes'];
720 $returnData['REMIND'][] = [
721 'type' => 'min',
722 'count' => $remindTimeout
723 ];
724 }
725 }
726 if (!empty($event['location']))
727 {
728 $returnData['LOCATION'] = Rooms\Util::unParseTextLocation($event['location']);
729 }
730
731 if (!empty($event['sequence']))
732 {
733 $returnData['VERSION'] = (int)$event['sequence'] + Util::VERSION_DIFFERENCE;
734 }
735
736 return $returnData;
737 }
738
748 private function prepareToSaveEvent($eventData, $params = null): array
749 {
750 $newEvent = [];
751 $newEvent['summary'] = $eventData['NAME'];
752
753 if (!empty($eventData['ATTENDEES_CODES']) && is_string($eventData['ATTENDEES_CODES']))
754 {
755 $eventData['ATTENDEES_CODES'] = explode(",", $eventData['ATTENDEES_CODES']);
756 }
757
758 if (is_string($eventData['MEETING']))
759 {
760 $eventData['MEETING'] = unserialize($eventData['MEETING'], ['allowed_classes' => false]);
761 }
762 if (empty($eventData['MEETING']['LANGUAGE_ID']))
763 {
764 $eventData['MEETING']['LANGUAGE_ID'] = \CCalendar::getUserLanguageId((int)$eventData['OWNER_ID']);
765 }
766
767 if (isset($eventData['ATTENDEES_CODES']) && is_countable($eventData['ATTENDEES_CODES']) && count($eventData['ATTENDEES_CODES']) > 1)
768 {
769 $users = Util::getAttendees($eventData['ATTENDEES_CODES']);
770 $newEvent['description'] = Loc::getMessage('ATTENDEES_EVENT', null, $eventData['MEETING']['LANGUAGE_ID']).': '
771 . stripcslashes(implode(', ', $users))
772 . "\r\n"
773 . $eventData["DESCRIPTION"];
774 }
775 elseif (!empty($eventData['DESCRIPTION']) && is_string($eventData['DESCRIPTION']))
776 {
777 $newEvent['description'] = $eventData['DESCRIPTION'];
778 }
779
780 if (!empty($eventData['ACCESSIBILITY']) && $eventData['ACCESSIBILITY'] === 'busy')
781 {
782 $newEvent['transparency'] = 'opaque';
783 }
784 else
785 {
786 $newEvent['transparency'] = 'transparent';
787 }
788
789 if (!empty($eventData['LOCATION']['NEW']) && is_string($eventData['LOCATION']['NEW']))
790 {
791 $newEvent['location'] = \CCalendar::GetTextLocation($eventData['LOCATION']['NEW']);
792 }
793 elseif (!empty($eventData['LOCATION']) && is_string($eventData['LOCATION']))
794 {
795 $newEvent['location'] = \CCalendar::GetTextLocation($eventData['LOCATION']);
796 }
797
798 if (!empty($eventData['REMIND']))
799 {
800 $newEvent['reminders'] = $this->prepareRemind($eventData);
801 }
802
803 if ($eventData['DT_SKIP_TIME'] === "Y")
804 {
805 $newEvent['start']['date'] = Util::getDateObject($eventData['DATE_FROM'])
806 ->format('Y-m-d');
807 $newEvent['end']['date'] = Util::getDateObject($eventData['DATE_TO'])
808 ->add('+1 day')
809 ->format('Y-m-d');
810
811 if (!empty($eventData['G_EVENT_ID']))
812 {
813 $newEvent['start']['dateTime'] = null;
814 $newEvent['end']['dateTime'] = null;
815 $newEvent['start']['timeZone'] = null;
816 $newEvent['end']['timeZone'] = null;
817 }
818 }
819 else
820 {
821 $newEvent['start']['dateTime'] = Util::getDateObject($eventData['DATE_FROM'], false, $eventData['TZ_FROM'])
822 ->format(self::DATE_TIME_FORMAT);
823 $newEvent['start']['timeZone'] = Util::prepareTimezone($eventData['TZ_FROM'])->getName();
824
825 $newEvent['end']['dateTime'] = Util::getDateObject($eventData['DATE_TO'], false, $eventData['TZ_TO'])
826 ->format(self::DATE_TIME_FORMAT);
827 $newEvent['end']['timeZone'] = Util::prepareTimezone($eventData['TZ_TO'])->getName();
828
829 if (!empty($eventData['G_EVENT_ID']))
830 {
831 $newEvent['start']['date'] = null;
832 $newEvent['end']['date'] = null;
833 }
834 }
835
836 if (
837 !empty($eventData['RRULE'])
838 && is_array($eventData['RRULE'])
839 && isset($eventData['RRULE']['FREQ'])
840 && $eventData['RRULE']['FREQ'] !== 'NONE'
841 )
842 {
843 $newEvent['recurrence'] = $this->prepareRRule($eventData, $params['editNextEvents']);
844 }
845
846 if (isset($eventData['ORIGINAL_DATE_FROM']))
847 {
848 $newEvent['originalStartTime'] = Util::getDateObject($eventData['ORIGINAL_DATE_FROM'], false, $eventData['TZ_FROM'])
849 ->format(self::DATE_TIME_FORMAT);
850 }
851
852 if (isset($eventData['G_EVENT_ID']) && isset($eventData['RECURRENCE_ID']))
853 {
854 $newEvent['recurringEventId'] = $eventData['G_EVENT_ID'];
855 }
856
857 if ($params['syncCaldav'] || isset($eventData['DAV_XML_ID']))
858 {
859 $newEvent['iCalUID'] = $eventData['DAV_XML_ID'];
860 }
861
862 if (isset($eventData['G_EVENT_ID']))
863 {
864 $newEvent['id'] = $eventData['G_EVENT_ID'];
865 }
866
867 if (!empty($eventData['PRIVATE_EVENT']))
868 {
869 $newEvent['visibility'] = "private";
870 }
871 else
872 {
873 $newEvent['visibility'] = "public";
874 }
875
876 if (isset($eventData['VERSION']))
877 {
878 $newEvent['sequence'] = $eventData['VERSION'] - Util::VERSION_DIFFERENCE;
879 }
880
881 return $newEvent;
882 }
883
890 private function sendToSaveEvent($newEvent, $params)
891 {
892 if ($params['editInstance'] === true)
893 {
894 $eventOriginalStart = Util::getDateObject($params['originalDateFrom'], false, $params['instanceTz']);
895 $originalStart = $eventOriginalStart->format(self::DATE_TIME_FORMAT);
896 $externalId = $params['gEventId'] ?: $params['originalDavXmlId'];
897 $instance = $this->syncTransport->getInstanceRecurringEvent($params['calendarId'], $externalId, $originalStart);
898
899 $newEvent['originalStartTime'] = $originalStart;
900 $newEvent['recurringEventId'] = $params['originalDavXmlId'];
901
902 if (is_array($instance['items']))
903 {
904 return $this->syncTransport->updateEvent($newEvent, urlencode($params['calendarId']), $instance['items'][0]['id']);
905 }
906 }
907 elseif ($params['editParentEvents'] === true)
908 {
909 return $this->syncTransport->updateEvent($newEvent, urldecode($params['calendarId']), $params['gEventId']);
910 }
911 elseif ($params['syncCaldav'])
912 {
913 return $this->syncTransport->importEvent($newEvent, urlencode($params['calendarId']));
914 }
915 elseif (($params['gEventId']))
916 {
917 return $this->syncTransport->updateEvent($newEvent, urlencode($params['calendarId']), $params['gEventId']);
918 }
919 else
920 {
921 return $this->syncTransport->insertEvent($newEvent, urlencode($params['calendarId']));
922 }
923
924 return [];
925 }
926
931 public function createCalendar($calendar): ?array
932 {
933 $externalData = $this->syncTransport->insertCalendar($this->prepareCalendar($calendar));
934
935 return $externalData
936 ? ['GAPI_CALENDAR_ID' => $externalData['id']]
937 : null
938 ;
939 }
940
945 private function prepareCalendar($calendar): array
946 {
947 $returnData['summary'] = Emoji::decode($calendar['NAME']);
948 if (isset($calendar['EXTERNAL_TYPE']) && $calendar['EXTERNAL_TYPE'] === \CCalendarSect::EXTERNAL_TYPE_LOCAL)
949 {
950 IncludeModuleLangFile($_SERVER['DOCUMENT_ROOT'] . BX_ROOT . '/modules/calendar/classes/general/calendar.php');
951 $returnData['summary'] = Loc::getMessage('EC_CALENDAR_BITRIX24_NAME') . " " . $returnData['summary'];
952 }
953
954 return $returnData;
955 }
956
961 public static function getChannelOwner(string $channelId = null): ?int
962 {
963 if (empty($channelId))
964 {
965 return null;
966 }
967
968 $matches = [];
969 preg_match('/(' . self::CONNECTION_CHANNEL_TYPE . '|' . self::SECTION_CHANNEL_TYPE . ')_(\d+)_.+/', $channelId, $matches);
970
971 return !empty($matches) && (int)$matches[2] > 0
972 ? (int)$matches[2]
973 : null
974 ;
975 }
976
977 public function hasMoreEvents()
978 {
979 return !empty($this->nextPageToken);
980 }
981
985 private function hasExpiredSyncTokenError(): bool
986 {
987 return !empty($this->getExpiredSyncTokenError());
988 }
989
993 private function getExpiredSyncTokenError(): array
994 {
995 return array_filter($this->syncTransport->getErrors(), function ($error) {
996 return preg_match("/^\[(410)\][a-z0-9 _]*/i", $error['message']);
997 });
998 }
999
1004 private function processResponseReceivingEvents(array $response): array
1005 {
1006 $this->setSyncSettings($response);
1007
1008 return $this->getEventsList($response['items']);
1009 }
1010
1014 private function getRequestParamsWithSyncToken(): array
1015 {
1016 return [
1017 'pageToken' => $this->nextPageToken,
1018 'syncToken' => $this->nextSyncToken,
1019 'showDeleted' => 'true',
1020 ];
1021 }
1022
1026 private function getRequestParamsForFirstSync(): array
1027 {
1028 return [
1029 'pageToken' => $this->nextPageToken,
1030 'showDeleted' => 'true',
1031 'maxResults' => self::SYNC_EVENTS_LIMIT,
1032 'timeMin' => (new Type\DateTime())->add(self::SYNC_EVENTS_DATE_INTERVAL)->format(self::DATE_TIME_FORMAT),
1033 ];
1034 }
1035
1040 private function runSyncEvents($gApiCalendarId)
1041 {
1042 $response = !empty($this->nextSyncToken)
1043 ? $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsWithSyncToken())
1044 : $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsForFirstSync());
1045
1046 if (!$response && $this->hasExpiredSyncTokenError())
1047 {
1048 return $this->syncTransport->getEvents($gApiCalendarId, $this->getRequestParamsForFirstSync());
1049 }
1050
1051 return $response;
1052 }
1053
1058 private function getEventsList(iterable $events = null): array
1059 {
1060 if (empty($events))
1061 return [];
1062
1063 $eventsList = [];
1064 foreach ($events as $event)
1065 {
1066 $preparedEvent = $this->prepareEvent($event);
1067 $eventsList[$preparedEvent['G_EVENT_ID']] = $preparedEvent;
1068 }
1069
1070 return $eventsList;
1071 }
1072
1076 private function setSyncSettings(array $response = null): void
1077 {
1078 $this->nextPageToken = $response['nextPageToken'] ?? '';
1079 $this->nextSyncToken = $response['nextSyncToken'] ?? '';
1080 $this->defaultReminderData = $response['defaultReminders'] ?? $this->defaultReminderData;
1081 $this->defaultTimezone = Util::isTimezoneValid($response['timeZone']) ? $response['timeZone'] : $this->defaultTimezone;
1082 }
1083
1089 private function prepareRemind($eventData): array
1090 {
1091 $reminders = [];
1092 $reminders['useDefault'] = false;
1093 $reminders['overrides'] = [];
1094
1095 if (!is_iterable($eventData['REMIND']))
1096 {
1097 return [];
1098 }
1099
1100 foreach ($eventData['REMIND'] as $remindRule)
1101 {
1102 $minutes = $remindRule['count'];
1103 if ($remindRule['type'] === 'hour')
1104 {
1105 $minutes = 60 * $remindRule['count'];
1106 }
1107 elseif ($remindRule['type'] === 'day')
1108 {
1109 $minutes = 24 * 60 * $remindRule['count'];
1110 }
1111 elseif ($remindRule['type'] === 'daybefore')
1112 {
1113 $dateFrom = Util::getDateObject(
1114 $eventData['DATE_FROM'],
1115 $eventData['DT_SKIP_TIME'] === 'Y',
1116 $eventData['TZ_FROM']
1117 );
1118
1119 $remind = clone $dateFrom;
1120 if (method_exists($remind, 'setTime'))
1121 {
1122 $remind->setTime(0, 0, 0);
1123 }
1124 $remind->add("-{$remindRule['before']} days")->add("{$remindRule['time']} minutes");
1125
1126 if ($dateFrom->getTimestamp() < $remind->getTimestamp())
1127 {
1128 continue;
1129 }
1130
1131 $minutes = $this->calculateMinutes($dateFrom, $remind);
1132 }
1133 elseif ($remindRule['type'] === 'date')
1134 {
1135 $dateFrom = Util::getDateObject(
1136 $eventData['DATE_FROM'],
1137 $eventData['DT_SKIP_TIME'] === 'Y',
1138 $eventData['TZ_FROM']
1139 );
1140 $remind = Util::getDateObject(
1141 $remindRule['value'],
1142 $eventData['DT_SKIP_TIME'] === 'Y',
1143 $eventData['TZ_FROM']
1144 );
1145
1146 if ($dateFrom->getTimestamp() < $remind->getTimestamp())
1147 {
1148 continue;
1149 }
1150
1151 $minutes = $this->calculateMinutes($dateFrom, $remind);
1152 }
1153
1154 $reminders['overrides'][] = [
1155 'minutes' => $minutes,
1156 'method' => 'popup',
1157 ];
1158 }
1159
1160 return $reminders;
1161 }
1162
1168 private function calculateMinutes(Type\Date $dateFrom, Type\Date $remind): int
1169 {
1170 $diff = $dateFrom->getDiff($remind);
1171 $days = $diff->format('%d');
1172 $hours = $diff->format('%h');
1173 $minutes = $diff->format('%i');
1174
1175 return ((int)$days * 24 * 60) + ((int)$hours * 60) + (int)$minutes;
1176 }
1177
1184 private function prepareRRule($event, $editNextEvents): array
1185 {
1186 $rule = [];
1187 $parsedRule = \CCalendarEvent::ParseRRULE($event['RRULE']);
1188 $rRule = 'RRULE:';
1189 $rRule .= 'FREQ=' .$parsedRule['FREQ'];
1190 $rRule .= !empty($parsedRule['INTERVAL']) ? ';INTERVAL=' . $parsedRule['INTERVAL'] : '';
1191 if (!empty($parsedRule['BYDAY']))
1192 {
1193 if (is_string($parsedRule['BYDAY']))
1194 {
1195 $rRule .= ';BYDAY=' . $parsedRule['BYDAY'];
1196 }
1197 elseif (is_array($parsedRule['BYDAY']))
1198 {
1199 $rRule .= ';BYDAY=' . implode(",", $parsedRule['BYDAY']);
1200 }
1201 else
1202 {
1203 $rRule = '';
1204 }
1205 }
1206
1207 if (!empty($parsedRule['COUNT']))
1208 {
1209 $rRule .= ';COUNT=' . $parsedRule['COUNT'];
1210 }
1211 elseif (!empty($parsedRule['UNTIL']))
1212 {
1213 $tsTo = Util::getDateObject($parsedRule['UNTIL']);
1214 if ($event['DT_SKIP_TIME'] === "N" && $tsTo->getTimestamp() < (new Type\Date(self::END_OF_DATE, "d.m.Y"))->getTimestamp())
1215 {
1216 $tsTo->add('+1 day');
1217 }
1218 $rRule .= ';UNTIL='.$tsTo->format('Ymd\THis\Z');
1219 }
1220
1221 $rule[] = $rRule;
1222
1223 if (!empty($event['EXDATE']) && $editNextEvents !== true)
1224 {
1225 $exDates = explode(';', $event['EXDATE']);
1226 foreach ($exDates as $exDate)
1227 {
1228 if ($event['DT_SKIP_TIME'] === "Y")
1229 {
1230 $rule[] = 'EXDATE;VALUE=DATE:' . date('Ymd', strtotime($exDate));
1231 }
1232 else
1233 {
1234 $rule[] = 'EXDATE;TZID=UTC:'
1235 . date("Ymd", strtotime($exDate))
1236 . Util::getDateObject($event['DATE_FROM'], false, $event['TZ_FROM'])
1237 ->setTimeZone(new \DateTimeZone('UTC'))->format('\\THis\\Z');
1238 }
1239 }
1240 }
1241
1242 return $rule;
1243 }
1244
1249 public function deleteCalendar(string $gApiCalendarId): void
1250 {
1251 $this->syncTransport->deleteCalendar($gApiCalendarId);
1252 }
1253
1257 private function getCalendarListParams(string $syncToken = null): array
1258 {
1259 if ($syncToken === null)
1260 {
1261 return [];
1262 }
1263
1264 return [
1265 'showDeleted' => 'true',
1266 'showHidden' => 'true',
1267 'syncToken' => $syncToken,
1268 ];
1269 }
1270
1276 public function updateCalendar(string $gApiCalendarId, array $calendarData): void
1277 {
1278 $this->syncTransport->updateCalendar($gApiCalendarId, $this->prepareCalendar($calendarData));
1279 }
1280
1287 public function updateCalendarList(string $gApiCalendarId, array $section): array
1288 {
1289 return $this->syncTransport->updateCalendarList($gApiCalendarId, $this->prepareCalendarList($section));
1290 }
1291
1292
1297 private function prepareCalendarList(array $calendar): array
1298 {
1299 $parameters = [];
1300
1301 if (isset($calendar['COLOR']))
1302 {
1303 $parameters['backgroundColor'] = $calendar['COLOR'];
1304 $parameters['foregroundColor'] = '#ffffff';
1305 }
1306
1307 $parameters['selected'] = 'true';
1308
1309 return $parameters;
1310 }
1311
1315 public function getNextPageToken(): string
1316 {
1317 return $this->nextPageToken;
1318 }
1319}
$type
Определения options.php:106
const BX_ROOT
Определения bx_root.php:3
$resourceId
Определения push.php:24
if(empty( $fields)) foreach($fields as $field) $channelId
Определения push.php:23
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
Определения catalog_reindex.php:36
if(!is_object($USER)||! $USER->IsAuthorized()) $userId
Определения check_mail.php:18
const VERSION_DIFFERENCE
Определения helper.php:34
updateLastResultConnection(string $lastResult)
Определения googleapisync.php:466
saveBatchEvents(array $events, string $gApiCalendarId, array $params)
Определения googleapisync.php:437
__construct($userId=0, $connectionId=0)
Определения googleapisync.php:70
const CONNECTION_CHANNEL_TYPE
Определения googleapisync.php:28
getEvents(array $calendarData)
Определения googleapisync.php:337
stopChannel($channelId, $resourceId)
Определения googleapisync.php:87
getCalendarItems(string $syncToken=null)
Определения googleapisync.php:297
deleteEvent($eventId, $calendarId)
Определения googleapisync.php:389
const SYNC_EVENTS_DATE_INTERVAL
Определения googleapisync.php:32
const MAXIMUM_CONNECTIONS_TO_SYNC
Определения googleapisync.php:25
createCalendar($calendar)
Определения googleapisync.php:931
static getChannelOwner(string $channelId=null)
Определения googleapisync.php:961
deleteCalendar(string $gApiCalendarId)
Определения googleapisync.php:1249
const SECTION_CONNECTION_CHANNEL_TYPE
Определения googleapisync.php:30
startWatchCalendarList($name)
Определения googleapisync.php:107
startWatchEventsChannel($calendarId='primary')
Определения googleapisync.php:160
saveEvent($eventData, $calendarId, $parameters=[])
Определения googleapisync.php:401
updateCalendarList(string $gApiCalendarId, array $section)
Определения googleapisync.php:1287
updateCalendar(string $gApiCalendarId, array $calendarData)
Определения googleapisync.php:1276
static getAttendees(array $codeAttendees=null, string $stringWrapper='')
Определения util.php:272
static prepareTimezone(?string $tz=null)
Определения util.php:80
static isTimezoneValid(?string $timeZone)
Определения util.php:71
static getDateObject(string $date=null, ?bool $fullDay=true, ?string $tz='UTC')
Определения util.php:107
static createFromTimestamp($timestamp)
Определения datetime.php:246
static encode($data, $options=null)
Определения json.php:22
static clearAllTags($text)
Определения textparser.php:2358
$hours
Определения cron_html_pages.php:15
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$res
Определения filter_act.php:7
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
Определения .description.php:24
$host
Определения .description.php:9
$_SERVER["DOCUMENT_ROOT"]
Определения cron_frame.php:9
const FORMAT_DATETIME
Определения include.php:64
const FORMAT_DATE
Определения include.php:63
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
Определения tools.php:3778
GetMessage($name, $aReplace=null)
Определения tools.php:3397
$name
Определения menu_edit.php:35
$host
Определения mysql_to_pgsql.php:32
$event
Определения prolog_after.php:141
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$instance
Определения ps_b24_final.php:14
if(empty($signedUserToken)) $key
Определения quickway.php:257
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$background
Определения html.php:27
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$response
Определения result.php:21
$matches
Определения index.php:22
$error
Определения subscription_card_product.php:20