PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / src / Application / Services / Notification / SMSNotificationService.php
ameliabooking / src / Application / Services / Notification Last commit date
AbstractNotificationService.php 4 months ago AbstractWhatsAppNotificationService.php 5 months ago ApplicationNotificationService.php 4 months ago AppointmentNotificationService.php 2 months ago BasicWhatsAppNotificationService.php 8 months ago EmailNotificationService.php 1 week ago NotificationHelperService.php 8 months ago SMSAPIService.php 4 months ago SMSNotificationService.php 1 week ago
SMSNotificationService.php
552 lines
1 <?php
2
3 /**
4 * @copyright © Melograno Ventures. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace AmeliaBooking\Application\Services\Notification;
9
10 use AmeliaBooking\Domain\Services\Logger\LoggerInterface;
11 use AmeliaBooking\Application\Services\Helper\HelperService;
12 use AmeliaBooking\Application\Services\Placeholder\PlaceholderService;
13 use AmeliaBooking\Domain\Collection\Collection;
14 use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
15 use AmeliaBooking\Domain\Entity\Entities;
16 use AmeliaBooking\Domain\Entity\Notification\Notification;
17 use AmeliaBooking\Domain\Entity\Notification\NotificationLog;
18 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
19 use AmeliaBooking\Domain\Services\Settings\SettingsService;
20 use AmeliaBooking\Domain\ValueObjects\String\NotificationStatus;
21 use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException;
22 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
23 use AmeliaBooking\Infrastructure\Repository\Notification\NotificationLogRepository;
24 use AmeliaBooking\Infrastructure\Repository\Notification\NotificationSMSHistoryRepository;
25 use Exception;
26 use Slim\Exception\ContainerException;
27 use Slim\Exception\ContainerValueNotFoundException;
28
29 /**
30 * Class SMSNotificationService
31 *
32 * @package AmeliaBooking\Application\Services\Notification
33 */
34 class SMSNotificationService extends AbstractNotificationService
35 {
36 /** @var bool */
37 private $sentSmsLowEmail = false;
38
39 /** @noinspection MoreThanThreeArgumentsInspection */
40 /**
41 * @param array $appointmentArray
42 * @param Notification $notification
43 * @param bool $logNotification
44 * @param int|null $bookingKey
45 * @param null $allBookings
46 * @param array $invoice
47 * @throws InvalidArgumentException
48 * @throws NotFoundException
49 * @throws QueryExecutionException
50 * @throws Exception
51 */
52 public function sendNotification(
53 $appointmentArray,
54 $notification,
55 $logNotification,
56 $bookingKey = null,
57 $allBookings = null,
58 $invoice = []
59 ) {
60 /** @var \AmeliaBooking\Application\Services\Settings\SettingsService $settingsAS */
61 $settingsAS = $this->container->get('application.settings.service');
62 /** @var PlaceholderService $placeholderService */
63 $placeholderService = $this->container->get("application.placeholder.{$appointmentArray['type']}.service");
64 /** @var HelperService $helperService */
65 $helperService = $this->container->get('application.helper.service');
66
67 $data = $placeholderService->getPlaceholdersData(
68 $appointmentArray,
69 $bookingKey,
70 'sms',
71 null,
72 $allBookings,
73 false,
74 $notification->getName()->getValue()
75 );
76
77 $isCustomerPackage = isset($appointmentArray['isForCustomer']) && $appointmentArray['isForCustomer'];
78
79 if ($appointmentArray['type'] === Entities::PACKAGE) {
80 if (!empty($appointmentArray['recurring'][0]['booking']['info']) && $isCustomerPackage) {
81 $info = $appointmentArray['recurring'][0]['booking']['info'];
82
83 $infoArray = json_decode($info, true);
84
85 if (!empty($infoArray['phone'])) {
86 $appointmentArray['customer']['phone'] = $infoArray['phone'];
87 }
88 } else {
89 $info = $isCustomerPackage ? json_encode($appointmentArray['customer']) : null;
90 }
91 } else {
92 $info = $bookingKey !== null ? $appointmentArray['bookings'][$bookingKey]['info'] : null;
93 }
94
95 $notificationContent = $helperService->getBookingTranslation(
96 $helperService->getLocaleFromBooking($info),
97 $notification->getTranslations() ? $notification->getTranslations()->getValue() : null,
98 'content'
99 ) ?: $notification->getContent()->getValue();
100
101 $text = $placeholderService->applyPlaceholders($notificationContent, $data);
102
103 $users = $this->getUsersInfo(
104 $notification->getSendTo()->getValue(),
105 $appointmentArray,
106 $bookingKey,
107 $data
108 );
109
110 foreach ($users as $user) {
111 if (!empty($user['phone_country'])) {
112 $allowedCountries = apply_filters('amelia_whitelists_sms_countries', []);
113 if (!empty($allowedCountries) && !in_array(strtoupper($user['phone_country']), $allowedCountries)) {
114 continue;
115 }
116 }
117 if ($user['phone']) {
118 if (!$isCustomerPackage && !empty($data['providersAppointments'][$user['id']])) {
119 $text = $placeholderService->applyPlaceholders(
120 $notificationContent,
121 array_merge(
122 $data,
123 ['cart_appointments_details' => $data['providersAppointments'][$user['id']]]['cart_appointments_details']
124 )
125 );
126 }
127
128 $reParsedData = !$isCustomerPackage ?
129 $placeholderService->reParseContentForProvider(
130 $appointmentArray,
131 '',
132 $text,
133 $user['id']
134 ) : [
135 'body' => $text,
136 ];
137
138 try {
139 $this->saveAndSend(
140 $notification,
141 $user,
142 $appointmentArray,
143 $reParsedData,
144 $logNotification,
145 $user['phone']
146 );
147
148 $additionalPhoneNumbers = $settingsAS->getBccSms();
149
150 foreach ($additionalPhoneNumbers as $phoneNumber) {
151 $this->saveAndSend(
152 $notification,
153 null,
154 $appointmentArray,
155 $reParsedData,
156 $logNotification,
157 $phoneNumber
158 );
159 }
160 } catch (QueryExecutionException $e) {
161 $this->container->getLoggerService()->channel(LoggerInterface::CHANNEL_NOTIFICATION)->error(
162 'Failed to send SMS notification',
163 [
164 'exception' => $e,
165 'notificationType' => $notification->getName()->getValue(),
166 'userId' => $user['id'] ?? null,
167 'appointmentId' => $appointmentArray['id'] ?? null,
168 'bookingKey' => $bookingKey,
169 ]
170 );
171 }
172 }
173 }
174 }
175
176
177 private function sendSmsBalanceLowEmail()
178 {
179 /** @var SettingsService $settingsService */
180 $settingsService = $this->container->get('domain.settings.service');
181 /** @var SMSAPIService $smsApiService */
182 $smsApiService = $this->container->get('application.smsApi.service');
183
184 $smsLowEmail = $settingsService->getSetting('notifications', 'smsBalanceEmail');
185 if ($smsLowEmail && $smsLowEmail['enabled']) {
186 try {
187 $userResponse = $smsApiService->getUserInfo();
188 if (
189 !empty($userResponse) &&
190 $userResponse->status === 'OK' &&
191 !empty($userResponse->user) &&
192 $userResponse->user->balance <= $smsLowEmail['minimum']
193 ) {
194 /** @var EmailNotificationService $notificationService */
195 $notificationService = $this->container->get('application.emailNotification.service');
196 $notificationService->sendSmsBalanceLowEmail($smsLowEmail['email']);
197
198 $this->sentSmsLowEmail = true;
199 }
200 } catch (\Exception $e) {
201 }
202 }
203 }
204
205 /**
206 * @throws ContainerValueNotFoundException
207 * @throws QueryExecutionException
208 * @throws InvalidArgumentException
209 */
210 public function sendUndeliveredNotifications()
211 {
212 /** @var NotificationLogRepository $notificationLogRepository */
213 $notificationLogRepository = $this->container->get('domain.notificationLog.repository');
214
215 /** @var NotificationSMSHistoryRepository $notificationsSMSHistoryRepo */
216 $notificationsSMSHistoryRepo = $this->container->get('domain.notificationSMSHistory.repository');
217
218 /** @var Collection $undeliveredNotifications */
219 $undeliveredNotifications = $notificationLogRepository->getUndeliveredNotifications('sms');
220
221 /** @var SMSAPIService $smsApiService */
222 $smsApiService = $this->container->get('application.smsApi.service');
223
224 /** @var NotificationLog $undeliveredNotification */
225 foreach ($undeliveredNotifications->getItems() as $undeliveredNotification) {
226 try {
227 $data = json_decode($undeliveredNotification->getData()->getValue(), true);
228
229 if ($history = $notificationsSMSHistoryRepo->getItemById($data['historyId'])) {
230 $apiResponse = $smsApiService->send(
231 $history['phone'],
232 $data['body'],
233 AMELIA_ACTION_URL . '/notifications/sms/history/' . $data['historyId']
234 );
235
236 if ($apiResponse->status === 'OK') {
237 $this->updateSmsHistory($data['historyId'], $apiResponse);
238
239 $notificationLogRepository->updateFieldById(
240 $undeliveredNotification->getId()->getValue(),
241 1,
242 'sent'
243 );
244 }
245 }
246 } catch (Exception $e) {
247 }
248 }
249 }
250
251 /**
252 * @throws QueryExecutionException
253 * @throws InvalidArgumentException
254 * @throws Exception
255 */
256 public function sendBirthdayGreetingNotifications()
257 {
258 /** @var Collection $notifications */
259 $notifications = $this->getByNameAndType('customer_birthday_greeting', $this->type);
260
261 /** @var Notification $notification */
262 $notification = $notifications->getItem($notifications->keys()[0]);
263
264 // Check if notification is enabled and it is time to send notification
265 if (
266 $notification->getStatus()->getValue() === NotificationStatus::ENABLED &&
267 $notification->getTime() &&
268 DateTimeService::getNowDateTimeObject() >=
269 DateTimeService::getCustomDateTimeObject($notification->getTime()->getValue())
270 ) {
271 /** @var NotificationLogRepository $notificationLogRepo */
272 $notificationLogRepo = $this->container->get('domain.notificationLog.repository');
273 /** @var NotificationSMSHistoryRepository $notificationsSMSHistoryRepo */
274 $notificationsSMSHistoryRepo = $this->container->get('domain.notificationSMSHistory.repository');
275 /** @var SMSAPIService $smsApiService */
276 $smsApiService = $this->container->get('application.smsApi.service');
277 /** @var PlaceholderService $placeholderService */
278 $placeholderService = $this->container->get('application.placeholder.appointment.service');
279 /** @var SettingsService $settingsService */
280 $settingsService = $this->container->get('domain.settings.service');
281
282 $customers = $notificationLogRepo->getBirthdayCustomers($this->type);
283
284 $companyData = $placeholderService->getCompanyData();
285
286 $customersArray = $customers->toArray();
287
288 foreach ($customersArray as $bookingKey => $customerArray) {
289 $data = [
290 'customer_email' => $customerArray['email'],
291 'customer_first_name' => $customerArray['firstName'],
292 'customer_last_name' => $customerArray['lastName'],
293 'customer_full_name' => $customerArray['firstName'] . ' ' . $customerArray['lastName'],
294 'customer_phone' => $customerArray['phone'],
295 'customer_id' => $customerArray['id'],
296 ];
297
298 /** @noinspection AdditionOperationOnArraysInspection */
299 $data += $companyData;
300
301 $text = $placeholderService->applyPlaceholders(
302 $notification->getContent()->getValue(),
303 $data
304 );
305
306 if ($data['customer_phone']) {
307 try {
308 $historyId = $notificationsSMSHistoryRepo->add(
309 [
310 'notificationId' => $notification->getId()->getValue(),
311 'userId' => $data['customer_id'],
312 'text' => $text,
313 'phone' => $data['customer_phone'],
314 'alphaSenderId' => $settingsService->getSetting('notifications', 'smsAlphaSenderId'),
315 ]
316 );
317
318 $smsData = apply_filters(
319 'amelia_manipulate_sms_data',
320 [
321 'text' => $text,
322 'to' => $data['customer_phone']
323 ]
324 );
325
326 $apiResponse = null;
327
328 if (empty($smsData['skipSending'])) {
329 $apiResponse = $smsApiService->send(
330 $smsData['to'],
331 $smsData['text'],
332 AMELIA_ACTION_URL . '/notifications/sms/history/' . $historyId
333 );
334 }
335
336 if ($apiResponse && $apiResponse->status === 'OK') {
337 $this->updateSmsHistory($historyId, $apiResponse);
338
339 $logNotificationId = $notificationLogRepo->add(
340 $notification,
341 $data['customer_id']
342 );
343
344 if ($logNotificationId) {
345 $notificationLogRepo->updateFieldById($logNotificationId, 1, 'sent');
346 }
347 }
348 } catch (QueryExecutionException $e) {
349 }
350 }
351 }
352 }
353 }
354
355 /** @noinspection MoreThanThreeArgumentsInspection */
356 /**
357 * @param Notification $notification
358 * @param array $user
359 * @param array $appointmentArray
360 * @param array $reParsedData
361 * @param bool $logNotification
362 * @param string $sendTo
363 *
364 * @return void
365 *
366 * @throws QueryExecutionException
367 * @throws InvalidArgumentException
368 */
369 private function saveAndSend($notification, $user, $appointmentArray, $reParsedData, $logNotification, $sendTo)
370 {
371
372 /** @var NotificationLogRepository $notificationsLogRepository */
373 $notificationsLogRepository = $this->container->get('domain.notificationLog.repository');
374 /** @var NotificationSMSHistoryRepository $notificationsSMSHistoryRepo */
375 $notificationsSMSHistoryRepo = $this->container->get('domain.notificationSMSHistory.repository');
376 /** @var SettingsService $settingsService */
377 $settingsService = $this->container->get('domain.settings.service');
378 /** @var SMSAPIService $smsApiService */
379 $smsApiService = $this->container->get('application.smsApi.service');
380
381 if ($user && !empty($appointmentArray['isRetry'])) {
382 /** @var Collection $sentNotifications */
383 $sentNotifications = $notificationsLogRepository->getSentNotificationsByUserAndEntity(
384 $user['id'],
385 'sms',
386 $appointmentArray['type'],
387 $appointmentArray['type'] === Entities::PACKAGE ?
388 $appointmentArray['packageCustomerId'] : $appointmentArray['id']
389 );
390
391 if ($sentNotifications->length()) {
392 return;
393 }
394 }
395
396 $historyId = $notificationsSMSHistoryRepo->add(
397 [
398 'notificationId' => $notification->getId()->getValue(),
399 'userId' => $user ? $user['id'] : null,
400 'appointmentId' =>
401 $appointmentArray['type'] === Entities::APPOINTMENT ? $appointmentArray['id'] : null,
402 'eventId' =>
403 $appointmentArray['type'] === Entities::EVENT ? $appointmentArray['id'] : null,
404 'packageCustomerId' => $appointmentArray['type'] === Entities::PACKAGE ?
405 $appointmentArray['packageCustomerId'] : null,
406 'text' => $reParsedData['body'],
407 'phone' => $user ? $user['phone'] : $sendTo,
408 'alphaSenderId' => $settingsService->getSetting('notifications', 'smsAlphaSenderId')
409 ]
410 );
411
412 $logNotificationId = null;
413
414 if ($logNotification) {
415 $logNotificationId = $notificationsLogRepository->add(
416 $notification,
417 $user ? $user['id'] : null,
418 $appointmentArray['type'] === Entities::APPOINTMENT ? $appointmentArray['id'] : null,
419 $appointmentArray['type'] === Entities::EVENT ? $appointmentArray['id'] : null,
420 $appointmentArray['type'] === Entities::PACKAGE ? $appointmentArray['packageCustomerId'] : null,
421 json_encode(
422 [
423 'subject' => '',
424 'body' => $reParsedData['body'],
425 'icsFiles' => [],
426 'historyId' => $historyId,
427 ]
428 )
429 );
430 }
431
432
433
434 $data = [
435 'sendTo' => $sendTo,
436 'body' => $reParsedData['body'],
437 'historyId' => $historyId,
438 'logNotificationId' => $logNotificationId,
439 ];
440
441 if ($this->getSend()) {
442 $this->sendSms($data);
443 } else {
444 $this->addPreparedNotificationData($data);
445
446 if ($data['logNotificationId']) {
447 $notificationsLogRepository->updateFieldById((int)$data['logNotificationId'], 1, 'sent');
448 }
449 }
450 }
451
452 /**
453 * @param int $historyId
454 * @param mixed $apiResponse
455 * @throws QueryExecutionException
456 */
457 public function updateSmsHistory($historyId, $apiResponse)
458 {
459 /** @var NotificationSMSHistoryRepository $notificationsSMSHistoryRepo */
460 $notificationsSMSHistoryRepo = $this->container->get('domain.notificationSMSHistory.repository');
461
462 if (in_array($apiResponse->message->status, ['failed', 'undelivered'], true)) {
463 $this->container->getLoggerService()->channel(LoggerInterface::CHANNEL_NOTIFICATION)->warning(
464 'SMS provider reported delivery failure',
465 [
466 'historyId' => $historyId,
467 'status' => $apiResponse->message->status,
468 ]
469 );
470 }
471
472 $notificationsSMSHistoryRepo->update(
473 $historyId,
474 [
475 'logId' => $apiResponse->message->logId,
476 'status' => $apiResponse->message->status,
477 'price' => $apiResponse->message->price,
478 'dateTime' => DateTimeService::getNowDateTimeInUtc(),
479 'segments' => $apiResponse->message->segments
480 ]
481 );
482 }
483
484 /**
485 * @param $data array
486 *
487 * @return void
488 * @throws QueryExecutionException
489 */
490 protected function sendSms($data)
491 {
492 /** @var NotificationLogRepository $notificationsLogRepository */
493 $notificationsLogRepository = $this->container->get('domain.notificationLog.repository');
494 /** @var SMSAPIService $smsApiService */
495 $smsApiService = $this->container->get('application.smsApi.service');
496
497 $smsData = apply_filters(
498 'amelia_manipulate_sms_data',
499 [
500 'text' => $data['body'],
501 'to' => $data['sendTo']
502 ]
503 );
504
505 $data = array_merge(
506 $data,
507 [
508 'text' => $data['body'],
509 'to' => $data['sendTo']
510 ],
511 is_array($smsData) ? $smsData : []
512 );
513
514 if (!empty($data['skipSending'])) {
515 return;
516 }
517
518 $apiResponse = $smsApiService->send(
519 $data['to'],
520 $data['text'],
521 AMELIA_ACTION_URL . '/notifications/sms/history/' . $data['historyId']
522 );
523
524 if ($apiResponse->status === 'OK') {
525 $this->updateSmsHistory($data['historyId'], $apiResponse);
526
527 if ($data['logNotificationId']) {
528 $notificationsLogRepository->updateFieldById((int)$data['logNotificationId'], 1, 'sent');
529 }
530
531 if (!$this->sentSmsLowEmail) {
532 $this->sendSmsBalanceLowEmail();
533 }
534 } else {
535 if ($data['logNotificationId']) {
536 $notificationsLogRepository->updateFieldById((int)$data['logNotificationId'], 0, 'sent');
537 }
538 }
539 }
540
541 /**
542 * @return void
543 * @throws QueryExecutionException
544 */
545 public function sendPreparedNotifications()
546 {
547 foreach ($this->getPreparedNotificationData() as $item) {
548 $this->sendSms($item);
549 }
550 }
551 }
552