Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
ednaruimhpx.php
1<?php
2
4
15
17{
18 public const ID = 'ednaruimhpx';
19
20 protected const MESSAGE_TYPE = "whatsapp";
21 protected const DEFAULT_PRIORITY = "normal";
22 protected const CONTENT_TEXT = "text";
23
24 private const ENDPOINT_OPTION = 'connector_endpoint';
25 private const LOGIN_OPTION = 'login';
26 private const PASSWORD_OPTION = 'password';
27 private const SUBJECT_OPTION = 'subject_id';
28
29 public static function isSupported()
30 {
31 return defined('MESSAGESERIVICE_ALLOW_IMHPX') && MESSAGESERIVICE_ALLOW_IMHPX === true;
32 }
33
34 public function getId()
35 {
36 return static::ID;
37 }
38
39 public function getName()
40 {
41 return 'Edna.ru IMHPX';
42 }
43
44 public function getShortName()
45 {
46 return 'Edna.ru IMHPX';
47 }
48
49 public function isRegistered()
50 {
51 return defined('MESSAGESERIVICE_ALLOW_IMHPX') && MESSAGESERIVICE_ALLOW_IMHPX === true;
52 }
53
54 public function canUse()
55 {
56 return $this->isRegistered();
57 }
58
59 public function getFromList()
60 {
61 $id = $this->getOption(self::SUBJECT_OPTION);
62 return [
63 [
64 'id' => $id,
65 'name' => $id,
66 ],
67 ];
68 }
69
70
71 public function register(array $fields): Result
72 {
73 $this->setOption(static::ENDPOINT_OPTION, $fields['connector_endpoint']);
74 $this->setOption(static::LOGIN_OPTION, $fields['login']);
75 $this->setOption(static::PASSWORD_OPTION, $fields['password']);
76 $this->setOption(static::SUBJECT_OPTION, $fields['subject_id']);
77
78 return new Result();
79 }
80
81 public function getOwnerInfo()
82 {
83 return [
84 self::ENDPOINT_OPTION => $this->getOption(self::ENDPOINT_OPTION),
85 self::LOGIN_OPTION => $this->getOption(self::LOGIN_OPTION),
86 self::PASSWORD_OPTION => $this->getOption(self::PASSWORD_OPTION),
87 self::SUBJECT_OPTION => $this->getOption(self::SUBJECT_OPTION),
88 ];
89 }
90
91 public function getCallbackUrl(): string
92 {
93 return parent::getCallbackUrl();
94 }
95
96 public function getExternalManageUrl()
97 {
98 // TODO: Implement getExternalManageUrl() method.
99 }
100
101 public function sendMessage(array $messageFields): Sender\Result\SendMessage
102 {
103 $result = new Sender\Result\SendMessage();
104 if (!$this->canUse())
105 {
106 return $result->addError(new Error('Service is unavailable'));
107 }
108
109 $body = $this->makeBodyOutgoingMessage($messageFields);
110
111 $requestResult = $this->callExternalMethod($body);
112 if (!$requestResult->isSuccess())
113 {
114 $result->addErrors($requestResult->getErrors());
115
116 return $result;
117 }
118
119 $response = $requestResult->getHttpResponse();
120 $this->processServiceResponse($response, $result);
121
122 return $result;
123 }
124
125 public function getMessageStatus(array $messageFields)
126 {
127 // TODO: Implement getMessageStatus() method.
128 }
129
130 public static function resolveStatus($serviceStatus): ?int
131 {
132 switch ($serviceStatus)
133 {
134 case 'read':
135 case 'sent':
136 return MessageStatus::SENT;
137 case 'enqueued':
139 case 'delayed':
141 case 'delivered':
143 case 'undelivered':
145 case 'failed':
146 case 'cancelled':
147 case 'expired':
148 case 'no-match-template':
150 default:
151 return mb_strpos($serviceStatus, 'error') === 0 ? MessageStatus::ERROR : MessageStatus::UNKNOWN;
152 }
153 }
154
155 protected function makeBodyOutgoingMessage(array $messageFields): string
156 {
157 $messageText = $messageFields['MESSAGE_BODY'];
158 $messageText = htmlspecialcharsbx($messageText);
159
160 $smsBlock = "<sms>
161 <subject>Bitrix24</subject>
162 <priority>{$this->getPriority()}</priority>
163 <content>{$messageText}</content>
164 <sendTimeoutSeconds>60</sendTimeoutSeconds>
165 <validityPeriodMinutes>30</validityPeriodMinutes>
166 </sms>";
167
168 $address = static::normalizePhoneNumberForOutgoing($messageFields['MESSAGE_TO']);
169
170 $ownerInfo = $this->getOwnerInfo();
171 $login = $ownerInfo[static::LOGIN_OPTION];
172 $password = $ownerInfo[static::PASSWORD_OPTION];
173 $mailbox = $ownerInfo[static::SUBJECT_OPTION];
174
175 $template = <<<XML
176<?xml version="1.0" encoding="UTF-8" standalone="no"?>
177<consumeInstantMessageRequest>
178 <header>
179 <auth>
180 <login>{$login}</login>
181 <password>{$password}</password>
182 </auth>
183 </header>
184 <payload>
185 <instantMessageList>
186 <instantMessage clientId="{$messageFields['ID']}">
187 <address>{$address}</address>
188 <subject>{$mailbox}</subject>
189 <priority>{$this->getPriority()}</priority>
190 <instantMessageType>{$this->getMessageType()}</instantMessageType>
191 <contentType>text</contentType>
192 <content>
193 <text>{$messageText}</text>
194 </content>
195 $smsBlock
196 </instantMessage>
197 </instantMessageList>
198 </payload>
199</consumeInstantMessageRequest>
200XML;
201
202 return $template;
203 }
204
205 protected function callExternalMethod(string $body): Sender\Result\HttpRequestResult
206 {
207 $httpClient = new HttpClient([
208 "socketTimeout" => $this->socketTimeout,
209 "streamTimeout" => $this->streamTimeout,
210 'waitResponse' => true,
211 ]);
212 $httpClient->setHeader('User-Agent', 'Bitrix24');
213 $httpClient->setHeader('Content-type', 'text/xml');
214
215 $result = new Sender\Result\HttpRequestResult();
216 $result->setHttpRequest(new DTO\Request([
217 'method' => HttpClient::HTTP_POST,
218 'uri' => $this->getServiceEndpoint(),
219 'headers' => method_exists($httpClient, 'getRequestHeaders') ? $httpClient->getRequestHeaders()->toArray() : [],
220 'body' => $body
221 ]));
222
223 $body = Encoding::convertEncoding($body, SITE_CHARSET, 'utf-8');
224 if (!$httpClient->query(HttpClient::HTTP_POST, $this->getServiceEndpoint(), $body))
225 {
226 $result->setHttpResponse(new DTO\Response([
227 'error' => Sender\Util::getHttpClientErrorString($httpClient)
228 ]));
229 $httpError = $httpClient->getError();
230 $errorCode = array_key_first($httpError);
231 $result->addError(new Error($httpError[$errorCode], $errorCode));
232 return $result;
233 }
234
235 $httpResponse = new DTO\Response([
236 'statusCode' => $httpClient->getStatus(),
237 'headers' => $httpClient->getHeaders()->toArray(),
238 'body' => $httpClient->getResult(),
239 ]);
240 $result->setHttpResponse($httpResponse);
241
242 return $result;
243 }
244
269 public function processServiceResponse(DTO\Response $response, Sender\Result\SendMessage $result): void
270 {
271 if ($response->statusCode !== 200)
272 {
273 $result->addError(new Error("Response status code is {$response->statusCode}", 'WRONG_SERVICE_RESPONSE_CODE'));
274 return;
275 }
276 $parseResult = $this->parseXml($response->body);
277 if (!$parseResult->isSuccess())
278 {
279 $result->addError(
280 new Error(
281 'XML parse error: ' . implode('; ', $parseResult->getErrorMessages()),
282 'XML_PARSE_ERROR'
283 )
284 );
285 return;
286 }
287
289 $instantMessageResponse = $parseResult->getData()['root'];
290
291 // hack to convert SimpleXMLElement to array
292 $instantMessageResponse = Json::decode(Json::encode($instantMessageResponse));
293
294 // response structure
295 if (
296 !isset($instantMessageResponse['payload'])
297 || (!isset($instantMessageResponse['payload']['code'])
298 && !isset($instantMessageResponse['payload']['instantMessageList'])
299 )
300 )
301 {
302 $result->addError(new Error('Wrong xml response structure', 'SERVICE_RESPONSE_PARSE_ERROR'));
303 return;
304 }
305
306 if ($instantMessageResponse['payload']['code'] !== 'ok')
307 {
308 $result->setStatus(\Bitrix\MessageService\MessageStatus::ERROR);
309 $result->addError(new Error($instantMessageResponse['payload']['code'], $instantMessageResponse['payload']['code']));
310 return;
311 }
312
313 foreach ($instantMessageResponse['payload']['instantMessageList'] as $instantMessage)
314 {
315 if ($instantMessage['code'] === 'ok' && isset($instantMessage['@attributes']['providerId']))
316 {
317 $result->setExternalId($instantMessage['@attributes']['providerId']);
318 $result->setAccepted();
319 }
320 else
321 {
322 $result->setStatus(\Bitrix\MessageService\MessageStatus::ERROR);
323 $result->addError(new Error('', $instantMessage['code']));
324 }
325 // we expect only one message response here
326 return;
327 }
328
329 $result->addError(new Error('Could not find message status in response', 'SERVICE_RESPONSE_PARSE_ERROR'));
330 }
331
353 public function processIncomingRequest(string $incomingRequestBody): DTO\Response
354 {
355 $response = new DTO\Response([
356 'statusCode' => 200
357 ]);
358 $parseResult = $this->parseIncomingRequest($incomingRequestBody);
359 if (!$parseResult->isSuccess())
360 {
361 $response->statusCode = 400;
362 $response->body = 'Parse error';
363
364 return $response;
365 }
366
368 $statusUpdateList = $parseResult->getData();
369 foreach ($statusUpdateList as $statusUpdate)
370 {
371 $message = Message::loadByExternalId(static::ID, $statusUpdate->externalId);
372 if ($message && $statusUpdate->providerStatus != '')
373 {
374 $message->updateStatusByExternalStatus($statusUpdate->providerStatus);
375 }
376 }
377
378 return $response;
379 }
380
402 public function parseIncomingRequest(string $incomingRequestBody): Result
403 {
404 $result = new Result();
405 $parseResult = $this->parseXml($incomingRequestBody);
406 if (!$parseResult->isSuccess())
407 {
408 return $result->addErrors($parseResult->getErrors());
409 }
410
412 $incomingRequest = $parseResult->getData()['root'];
413
414 // incoming messages are not supported yet
415 if ($incomingRequest->getName() != 'provideInstantMessageDlvStatusResponse')
416 {
417 return $result;
418 }
419
420 // hack to convert SimpleXMLElement to array
421 $incomingRequest = Json::decode(Json::encode($incomingRequest));
422 if (
423 !isset($incomingRequest['payload'])
424 || (!isset($incomingRequest['payload']['code'])
425 && !isset($incomingRequest['payload']['instantMessageList'])
426 )
427 )
428 {
429 return $result->addError(new Error('Wrong XML structure'));
430 }
431
432 $statusUpdateList = [];
433
434 // If response contains only one message delivery report - <instantMessage> element will contain the report
435 // If response contains more than on message delivery report - <instantMessage> element will contain array of reports
436 $instantMessageList = $incomingRequest['payload']['instantMessageList']['instantMessage'];
437 if (!is_array($instantMessageList))
438 {
439 //empty list
440 return $result->setData($statusUpdateList);
441 }
442
443 if (Collection::isAssociative($instantMessageList))
444 {
445 $instantMessageList = [$instantMessageList];
446 }
447
448 foreach ($instantMessageList as $instantMessage)
449 {
450 $statusUpdateList[] = new DTO\StatusUpdate([
451 'externalId' => (int)$instantMessage['@attributes']['providerId'],
452 'providerStatus' => $instantMessage['instantMessageDlvStatus']['dlvStatus'],
453 'deliveryStatus' => static::resolveStatus($instantMessage['instantMessageDlvStatus']['dlvStatus']),
454 'deliveryError' => $instantMessage['instantMessageDlvStatus']['dlvError']
455 ]);
456 }
457
458 return $result->setData($statusUpdateList);
459 }
460
466 protected function parseXml(string $xmlString): Result
467 {
468 $result = new Result();
469
470 if ($xmlString === '')
471 {
472 return $result->addError(new Error('Empty XML'));
473 }
474
475 libxml_use_internal_errors(true);
476 libxml_clear_errors();
477 $parsedBody = simplexml_load_string($xmlString);
478 $parseErrors = libxml_get_errors();
479
480 if (!empty($parseErrors))
481 {
483 foreach ($parseErrors as $parseError)
484 {
485 $result->addError(new Error($parseError->message, $parseError->code));
486 }
487 return $result;
488 }
489
490 $result->setData([
491 'root' => $parsedBody
492 ]);
493 return $result;
494 }
495
496 protected function getPriority()
497 {
498 return $this::DEFAULT_PRIORITY;
499 }
500
501 protected function getMessageType()
502 {
503 return $this::MESSAGE_TYPE;
504 }
505
506 public function getServiceEndpoint(): string
507 {
508 return $this->getOption(static::ENDPOINT_OPTION);
509 }
510
511 public static function normalizePhoneNumberForOutgoing(string $phoneNumber): string
512 {
513 // remove +
514 if (mb_strpos($phoneNumber, '+') === 0)
515 {
516 return mb_substr($phoneNumber, 1);
517 }
518
519 return $phoneNumber;
520 }
521}
static loadByExternalId(string $senderId, string $externalId, ?string $from=null)
Definition message.php:71
getOption($optionName, $defaultValue=null)
static normalizePhoneNumberForOutgoing(string $phoneNumber)
static getHttpClientErrorString(HttpClient $httpClient)
Definition util.php:24