Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
googleapitransport.php
1<?
3
13use CCalendar;
14use CSocServGoogleOAuth;
15use CSocServGoogleProxyOAuth;
16use Exception;
17
25{
26 private const API_BASE_URL = Google\Helper::GOOGLE_SERVER_PATH_V3;
27 protected const SERVICE_NAME = 'google';
28 private $client;
29 private $errors;
30 private $currentMethod = '';
34 private $oAuth;
38 protected $requestLogger;
39
49 public function __construct($userId)
50 {
51 if (!Loader::includeModule('socialservices'))
52 {
53 throw new SystemException("Can not include module \"SocialServices\"! " . __METHOD__);
54 }
55
56 $this->client = new Web\HttpClient();
57 if (RequestLogger::isEnabled())
58 {
59 $this->requestLogger = new RequestLogger((int)$userId, self::SERVICE_NAME);
60 }
61
62 if (CSocServGoogleProxyOAuth::isProxyAuth())
63 {
64 $oAuth = new CSocServGoogleProxyOAuth($userId);
65 }
66 else
67 {
68 $oAuth = new CSocServGoogleOAuth($userId);
69 }
70
71 $oAuth->getEntityOAuth()->addScope(
72 [
73 'https://www.googleapis.com/auth/calendar',
74 'https://www.googleapis.com/auth/calendar.readonly',
75 ]
76 );
77 $oAuth->getEntityOAuth()->setUser($userId);
78 if ($oAuth->getEntityOAuth()->GetAccessToken())
79 {
80 $this->client->setHeader('Authorization', 'Bearer ' . $oAuth->getEntityOAuth()->getToken());
81 $this->client->setHeader('Content-Type', 'application/json');
82 $this->client->setHeader('Referer', $this->getDomain());
83 unset($oAuth);
84 }
85 else
86 {
87 $this->errors[] = array("code" => "NO_ACCESS_TOKEN", "message" => "No access token found");
88 }
89 }
90
99 public function openCalendarListChannel($channelInfo): array
100 {
101 $this->currentMethod = __METHOD__;
102
103 return $this->doRequest(
105 self::API_BASE_URL. '/users/me/calendarList/watch',
106 Web\Json::encode($channelInfo, JSON_UNESCAPED_SLASHES)
107 );
108 }
109
119 public function openEventsWatchChannel($calendarId, $channelInfo): array
120 {
121 $this->currentMethod = __METHOD__;
122
123 return $this->doRequest(
125 self::API_BASE_URL . '/calendars/' . urlencode($calendarId) . '/events/watch',
126 Web\Json::encode($channelInfo, JSON_UNESCAPED_SLASHES)
127 );
128 }
129
139 public function stopChannel($channelId, $resourceId): array
140 {
141 $this->currentMethod = __METHOD__;
142
143 return $this->doRequest(
145 self::API_BASE_URL . '/channels/stop',
146 Web\Json::encode(['id' => $channelId, 'resourceId' => $resourceId], JSON_UNESCAPED_SLASHES)
147 );
148 }
149
158 private function doRequest($type, $url, $requestParams = '')
159 {
160 $this->errors = $response = [];
161
163 {
164 throw new ArgumentException('Bad request type');
165 }
166
167 $this->client->query($type, $url, ($requestParams ?: null));
168
169 //Only "OK" response is acceptable.
170 if ($this->client->getStatus() === 200)
171 {
172 $contentType = $this->client->getHeaders()->getContentType();
173
174 if ($contentType === 'multipart/mixed')
175 {
176 $response = $this->multipartDecode($this->client->getResult());
177 }
178 else
179 {
180 try
181 {
182 $response = Web\Json::decode($this->client->getResult());
183 }
184 catch (ArgumentException $exception)
185 {
186 $response = null;
187 }
188 }
189 }
190 else
191 {
192 try
193 {
194 $error = Web\Json::decode($this->client->getResult());
195 $this->errors[] = ["code" => "CONNECTION", "message" => "[" . $error['error']['code'] . "] " . $error['error']['message']];
196 }
197 catch (ArgumentException $exception)
198 {
199 foreach($this->client->getError() as $code => $error)
200 {
201 $this->errors[] = ["code" => $code, "message" => $error];
202 }
203 }
204 }
205
206 if ($this->requestLogger)
207 {
208 $this->requestLogger->write([
209 'requestParams' => $requestParams,
210 'url' => $url,
211 'method' => $type,
212 'statusCode' => $this->client->getStatus(),
213 'response' => $this->prepareResponseForDebug($response),
214 'error' => $this->prepareErrorForDebug(),
215 ]);
216 }
217
218 return $response;
219 }
220
228 public function deleteEvent($eventId, $calendarId)
229 {
230 $this->currentMethod = __METHOD__;
231 return $this->doRequest(Web\HttpClient::HTTP_DELETE, self::API_BASE_URL . '/calendars/' . $calendarId . '/events/' . $eventId);
232 }
233
242 public function patchEvent($patchData, $calendarId, $eventId)
243 {
244 $this->currentMethod = __METHOD__;
245 $requestBody = Web\Json::encode($patchData, JSON_UNESCAPED_SLASHES);
246 return $this->doRequest(Web\HttpClient::HTTP_PUT, self::API_BASE_URL . '/calendars/' . $calendarId . '/events/' . $eventId, $requestBody);
247 }
248
257 public function updateEvent($eventData, $calendarId, $eventId)
258 {
259 $this->currentMethod = __METHOD__;
260 $requestBody = Web\Json::encode($eventData, JSON_UNESCAPED_SLASHES);
261 return $this->doRequest(Web\HttpClient::HTTP_PUT, self::API_BASE_URL . '/calendars/' . $calendarId . '/events/' . $eventId, $requestBody);
262 }
263
271 public function insertEvent($eventData, $calendarId)
272 {
273 $this->currentMethod = __METHOD__;
274 $requestBody = Web\Json::encode($eventData, JSON_UNESCAPED_SLASHES);
275 return $this->doRequest(Web\HttpClient::HTTP_POST, self::API_BASE_URL . '/calendars/' . $calendarId . '/events/', $requestBody);
276 }
277
285 public function importEvent($eventData, $calendarId)
286 {
287 $this->currentMethod = __METHOD__;
288 $requestBody = Web\Json::encode($eventData, JSON_UNESCAPED_SLASHES);
289 return $this->doRequest(Web\HttpClient::HTTP_POST, self::API_BASE_URL . '/calendars/' . $calendarId . '/events/import', $requestBody);
290 }
291
297 public function getCalendarList(array $requestParameters = null): ?array
298 {
299 $this->currentMethod = __METHOD__;
300
301 $url = self::API_BASE_URL . '/users/me/calendarList';
302 $url .= empty($requestParameters) ? '' : '?' . preg_replace('/(%3D)/', '=', http_build_query($requestParameters));
303
304 return $this->doRequest(Web\HttpClient::HTTP_GET, $url);
305 }
306
312 public function getColors()
313 {
314 $this->currentMethod = __METHOD__;
315 return $this->doRequest(Web\HttpClient::HTTP_GET, self::API_BASE_URL . '/colors');
316 }
317
325 public function getEvents($calendarId, $requestParams = array())
326 {
327 $this->currentMethod = __METHOD__;
328 $requestParams = array_filter($requestParams);
329 $url = self::API_BASE_URL . '/calendars/' . urlencode($calendarId) . '/events';
330 $url .= empty($requestParams) ? '' : '?' . preg_replace('/(%3D)/', '=', http_build_query($requestParams));
331 return $this->doRequest(Web\HttpClient::HTTP_GET, $url);
332 }
333
339 public function getErrors()
340 {
341 return $this->errors;
342 }
343
350 public function getErrorsByCode($code)
351 {
352 return array_filter($this->errors, function($error) use ($code)
353 {
354 return $error['code'] == $code;
355 });
356 }
357
364 public function getErrorByCode($code)
365 {
366 if (!is_array($this->errors))
367 {
368 return [];
369 }
370
371 $errorsByCode = array_filter($this->errors, function($error) use ($code)
372 {
373 return $error['code'] == $code;
374 });
375
376 if (!empty($errorsByCode))
377 {
378 return end($errorsByCode);
379 }
380
381 return [];
382 }
383
392 public function getInstanceRecurringEvent($calendarId, $eventId, $originalStart)
393 {
394 $this->currentMethod = __METHOD__;
395
396 $requestParameters = ['originalStart' => $originalStart];
397 $requestParameters = array_filter($requestParameters);
398 $url = self::API_BASE_URL . '/calendars/' . urlencode($calendarId) . '/events/' . urlencode($eventId) . '/instances/';
399 $url .= empty($requestParameters) ? '' : '?' . preg_replace('/(%3D)/', '=', http_build_query($requestParameters));
400
401 return $this->doRequest(Web\HttpClient::HTTP_GET, $url);
402 }
403
410 public function insertCalendar($calendarData)
411 {
412 $this->currentMethod = __METHOD__;
413 $requestBody = Web\Json::encode($calendarData, JSON_UNESCAPED_SLASHES);
414 return $this->doRequest(Web\HttpClient::HTTP_POST, self::API_BASE_URL . '/calendars/', $requestBody);
415 }
416
417 public function sendBatchEvents($body, $calendarId, $params)
418 {
419 $url = "https://www.googleapis.com/batch/calendar/v3/";
420 $requestBody = $this->prepareMultipartMixed($body, $calendarId, $params);
421 return $this->doRequest(Web\HttpClient::HTTP_POST, $url, $requestBody);
422 }
423
430 protected function prepareMultipartMixed($postData, $calendarId, $params)
431 {
432 if (is_array($postData))
433 {
434 $boundary = 'BXC'.md5(rand().time());
435 $this->client->setHeader('Content-type', 'multipart/mixed; boundary='.$boundary);
436
437 $data = '';
438
439 foreach ($postData as $key => $value)
440 {
441 $data .= '--'.$boundary."\r\n";
442
443 if (is_array($value))
444 {
445 $contentId = '<item'.$key.':'.$key.'>';
446
447 if (is_array($value))
448 {
449 $data .= 'Content-Type: application/http'."\r\n";
450 $data .= 'Content-ID: '.$contentId."\r\n\r\n";
451
452 if (!empty($value['gEventId']))
453 {
454 $data .= $params['method'].' /calendar/v3/calendars/'.$calendarId.'/events/'.$value['gEventId']."\r\n";
455 }
456 else
457 {
458 $data .= 'POST /calendar/v3/calendars/'.$calendarId.'/events'."\r\n";
459 }
460
461 $data .= 'Content-type: application/json'."\r\n";
462
463 $data .= 'Content-Length: '.mb_strlen($value['partBody'])."\r\n\r\n";
464 $data .= $value['partBody'];
465 $data .= "\r\n\r\n";
466 }
467 }
468 }
469
470 $data .= '--'.$boundary."--\r\n";
471 $postData = $data;
472 }
473
474 return $postData;
475 }
476
482 public function multipartDecode($response): array
483 {
484 $events = [];
485
486 $boundary = $this->client->getHeaders()->getBoundary();
487
488 $response = str_replace("--$boundary--", "--$boundary", $response);
489 $parts = explode("--$boundary\r\n", $response);
490
491 foreach ($parts as $key => $part)
492 {
493 $part = trim($part);
494 if (!empty($part))
495 {
496 $partEvent = explode("\r\n\r\n", $part);
497 $data = $this->getMetaInfo($partEvent[1]);
498
499 if ($data['status'] === 200)
500 {
501 $id = $this->getId($partEvent[0]);
502 if ($id === null)
503 {
504 continue;
505 }
506
507 try
508 {
509 $event = Web\Json::decode($partEvent[2]);
510 }
511 catch(Exception $exception)
512 {
513 continue;
514 }
515
516 $event['etag'] = $data['etag'];
517 $events[$id] = $event;
518 }
519 else
520 {
521 AddMessage2Log('Event sync error. ID: ' . $this->getId($partEvent[0]));
522 }
523 }
524 }
525
526 return $events;
527 }
528
529 private function getMetaInfo($headers): array
530 {
531 $data = [];
532 foreach (explode("\n", $headers) as $k => $header)
533 {
534 if($k === 0)
535 {
536 if(preg_match('#HTTP\S+ (\d+)#', $header, $find))
537 {
538 $data['status'] = (int)$find[1];
539 }
540 }
541 elseif(mb_strpos($header, ':') !== false)
542 {
543 [$headerName, $headerValue] = explode(':', $header, 2);
544 if(mb_strtolower($headerName) === 'etag')
545 {
546 $data['etag'] = trim($headerValue);
547 }
548 }
549 }
550
551 return $data;
552 }
553
558 private function getId ($headers): ?int
559 {
560 $id = null;
561 foreach (explode("\n", $headers) as $k => $header)
562 {
563 if(mb_strpos($header, ':') !== false)
564 {
565 [$headerName, $headerValue] = explode(':', $header, 2);
566 if(mb_strtolower($headerName) === 'content-id')
567 {
568 $part = explode(':', $headerValue);
569 $id = rtrim($part[1], ">");
570 }
571 }
572 }
573
574 return (int)$id;
575 }
576
582 public function deleteCalendar(string $calendarId): array
583 {
584 $this->currentMethod = __METHOD__;
585 return $this->doRequest(Web\HttpClient::HTTP_DELETE, self::API_BASE_URL . '/calendars/' . $calendarId . '');
586 }
587
591 private function getDomain(): string
592 {
593 if (CCalendar::isBitrix24())
594 {
595 return 'https://bitrix24.com';
596 }
597
598 if (defined('BX24_HOST_NAME') && BX24_HOST_NAME)
599 {
600 return "https://" . (string)BX24_HOST_NAME;
601 }
602
603 $server = Application::getInstance()->getContext()->getServer();
604
605 return "https://" . (string)$server['HTTP_HOST'];
606 }
607
614 public function updateCalendar(string $calendarId, $calendarData): array
615 {
616 $this->currentMethod = __METHOD__;
617 $requestBody = Web\Json::encode($calendarData, JSON_UNESCAPED_SLASHES);
618
619 return $this->doRequest(Web\HttpClient::HTTP_PUT, self::API_BASE_URL . '/calendars/' . $calendarId, $requestBody);
620 }
621
628 public function updateCalendarList(string $calendarId, $calendarData): array
629 {
630 $this->currentMethod = __METHOD__;
631
632 $url = self::API_BASE_URL . '/users/me/calendarList/' . $calendarId;
633 $url .= '?' . preg_replace('/(%3D)/', '=', http_build_query(['colorRgbFormat' => "True"]));
634
635 $requestBody = Web\Json::encode($calendarData, JSON_UNESCAPED_SLASHES);
636
637 return $this->doRequest(Web\HttpClient::HTTP_PUT, $url, $requestBody);
638 }
639
644 private function prepareResponseForDebug($response): string
645 {
646 if (!$response || !is_array($response))
647 {
648 return '';
649 }
650
651 $result = '';
652
653 foreach ($response as $key => $value)
654 {
655 if (is_string($value))
656 {
657 $result .= "{$key}:{$value}; ";
658 }
659 elseif (is_array($value))
660 {
661 $result .= "{$key}:";
662 foreach ($value as $valueKey => $valueValue)
663 {
664 $result .= "{$valueKey}:{$valueValue}, ";
665 }
666 $result .= "; ";
667 }
668 }
669
670 return $result;
671 }
672
676 private function prepareErrorForDebug(): string
677 {
678 if (!$this->errors || !is_array($this->errors))
679 {
680 return '';
681 }
682
683 $result = '';
684 foreach ($this->errors as $error)
685 {
686 $result .= $error['code'] . " " . $error['message'] . "; ";
687 }
688
689 return $result;
690 }
691}
sendBatchEvents($body, $calendarId, $params)
getEvents($calendarId, $requestParams=array())
updateCalendar(string $calendarId, $calendarData)
prepareMultipartMixed($postData, $calendarId, $params)
updateCalendarList(string $calendarId, $calendarData)
getCalendarList(array $requestParameters=null)
updateEvent($eventData, $calendarId, $eventId)
patchEvent($patchData, $calendarId, $eventId)
getInstanceRecurringEvent($calendarId, $eventId, $originalStart)
openEventsWatchChannel($calendarId, $channelInfo)