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