GetEventsCommandHandler.php
| 1 | <?php |
| 2 | |
| 3 | namespace AmeliaBooking\Application\Commands\Booking\Event; |
| 4 | |
| 5 | use AmeliaBooking\Application\Commands\CommandHandler; |
| 6 | use AmeliaBooking\Application\Commands\CommandResult; |
| 7 | use AmeliaBooking\Application\Common\Exceptions\AccessDeniedException; |
| 8 | use AmeliaBooking\Application\Services\Booking\EventApplicationService; |
| 9 | use AmeliaBooking\Application\Services\User\ProviderApplicationService; |
| 10 | use AmeliaBooking\Application\Services\User\UserApplicationService; |
| 11 | use AmeliaBooking\Domain\Collection\Collection; |
| 12 | use AmeliaBooking\Domain\Common\Exceptions\AuthorizationException; |
| 13 | use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException; |
| 14 | use AmeliaBooking\Domain\Entity\Booking\Appointment\CustomerBooking; |
| 15 | use AmeliaBooking\Domain\Entity\Booking\Event\Event; |
| 16 | use AmeliaBooking\Domain\Entity\Booking\Event\EventPeriod; |
| 17 | use AmeliaBooking\Domain\Entity\Entities; |
| 18 | use AmeliaBooking\Domain\Entity\User\AbstractUser; |
| 19 | use AmeliaBooking\Domain\Factory\Booking\Event\EventPeriodFactory; |
| 20 | use AmeliaBooking\Domain\Services\Settings\SettingsService; |
| 21 | use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException; |
| 22 | use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\CustomerBookingRepository; |
| 23 | use AmeliaBooking\Infrastructure\Repository\Booking\Event\EventRepository; |
| 24 | use DateTimeZone; |
| 25 | use Exception; |
| 26 | |
| 27 | /** |
| 28 | * Class GetEventsCommandHandler |
| 29 | * |
| 30 | * @package AmeliaBooking\Application\Commands\Booking\Event |
| 31 | */ |
| 32 | class GetEventsCommandHandler extends CommandHandler |
| 33 | { |
| 34 | /** |
| 35 | * @param GetEventsCommand $command |
| 36 | * |
| 37 | * @return CommandResult |
| 38 | * |
| 39 | * @throws AccessDeniedException |
| 40 | * @throws InvalidArgumentException |
| 41 | * @throws QueryExecutionException |
| 42 | * @throws Exception |
| 43 | */ |
| 44 | public function handle(GetEventsCommand $command) |
| 45 | { |
| 46 | $result = new CommandResult(); |
| 47 | |
| 48 | /** @var SettingsService $settingsDS */ |
| 49 | $settingsDS = $this->container->get('domain.settings.service'); |
| 50 | /** @var EventRepository $eventRepository */ |
| 51 | $eventRepository = $this->container->get('domain.booking.event.repository'); |
| 52 | /** @var UserApplicationService $userAS */ |
| 53 | $userAS = $this->container->get('application.user.service'); |
| 54 | /** @var EventApplicationService $eventAS */ |
| 55 | $eventAS = $this->container->get('application.booking.event.service'); |
| 56 | /** @var ProviderApplicationService $providerAS */ |
| 57 | $providerAS = $this->container->get('application.user.provider.service'); |
| 58 | |
| 59 | |
| 60 | $params = $command->getField('params'); |
| 61 | |
| 62 | /** @var AbstractUser|null $user */ |
| 63 | $user = null; |
| 64 | |
| 65 | // Cabinet requests (source=cabinet-provider/customer) use page for *pagination*, |
| 66 | // not to signal a frontend booking-form context. Without this guard, sending |
| 67 | // page=1 from a provider JWT makes $isFrontEnd=true, which skips the auth block, |
| 68 | // clears provider scoping, and adds show=1 — exposing all published events. |
| 69 | $isCabinetPage = $command->getPage() === 'cabinet'; |
| 70 | |
| 71 | $isFrontEnd = isset($params['page']) && empty($params['group']) && !$isCabinetPage; |
| 72 | |
| 73 | $fetchBookings = !$isFrontEnd && ( |
| 74 | !isset($params['bookings']) || filter_var($params['bookings'], FILTER_VALIDATE_BOOLEAN) |
| 75 | ); |
| 76 | |
| 77 | $isCalendarPage = $isFrontEnd && (int)$params['page'] === 0; |
| 78 | |
| 79 | if (!$isFrontEnd) { |
| 80 | try { |
| 81 | $user = $command->getUserApplicationService()->authorization( |
| 82 | $isCabinetPage ? $command->getToken() : null, |
| 83 | $command->getCabinetType() |
| 84 | ); |
| 85 | } catch (AuthorizationException $e) { |
| 86 | $result->setResult(CommandResult::RESULT_ERROR); |
| 87 | $result->setData( |
| 88 | [ |
| 89 | 'reauthorize' => true |
| 90 | ] |
| 91 | ); |
| 92 | |
| 93 | return $result; |
| 94 | } |
| 95 | |
| 96 | if ($userAS->isAmeliaUser($user) && $userAS->isCustomer($user)) { |
| 97 | $params['customerId'] = $user->getId()->getValue(); |
| 98 | } |
| 99 | |
| 100 | if ($user && $user->getType() === AbstractUser::USER_ROLE_PROVIDER) { |
| 101 | $params['providers'] = [$user->getId()->getValue()]; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | if ($isFrontEnd && !empty($params['providers'])) { |
| 106 | $params['providers'] = array_values($params['providers']); |
| 107 | } |
| 108 | |
| 109 | if (isset($params['dates'][0])) { |
| 110 | $params['dates'][0] ? $params['dates'][0] .= ' 00:00:00' : null; |
| 111 | } |
| 112 | |
| 113 | if (isset($params['dates'][1])) { |
| 114 | $params['dates'][1] ? $params['dates'][1] .= ' 23:59:59' : null; |
| 115 | } |
| 116 | |
| 117 | if ($isFrontEnd) { |
| 118 | $params['show'] = 1; |
| 119 | |
| 120 | if (!empty($params['tag'])) { |
| 121 | $params['tag'] = str_replace('___', ' ', $params['tag']); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | if (isset($params['range']) && $isFrontEnd) { |
| 126 | if ($params['range'] === 'future') { |
| 127 | $startDate = date('Y-m-d 00:00:00'); |
| 128 | |
| 129 | // Date filter is selected on the frontend, |
| 130 | // so if the provided date is in the past, |
| 131 | // we can return empty result right away |
| 132 | if (!empty($params['dates'][0]) && $params['dates'][0] < $startDate) { |
| 133 | $result->setResult(CommandResult::RESULT_SUCCESS); |
| 134 | $result->setData( |
| 135 | [ |
| 136 | Entities::EVENTS => [], |
| 137 | 'count' => 0, |
| 138 | 'countTotal' => 0, |
| 139 | 'customersNoShowCount' => [] |
| 140 | ] |
| 141 | ); |
| 142 | return $result; |
| 143 | } |
| 144 | |
| 145 | // Date filter selected and date is in future or present. |
| 146 | // Intentionally discard any user-provided end date because a |
| 147 | // "future" widget has no upper bound. |
| 148 | if (!empty($params['dates'][0])) { |
| 149 | $params['dates'][1] = null; |
| 150 | } |
| 151 | |
| 152 | // Date filter not selected |
| 153 | if (empty($params['dates'][0])) { |
| 154 | $params['dates'][0] = $startDate; |
| 155 | $params['dates'][1] = null; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | if ($params['range'] === 'past') { |
| 160 | $endDate = date('Y-m-d 23:59:59', strtotime('-1 day')); |
| 161 | |
| 162 | // Date filter is selected on the frontend, |
| 163 | // so if the provided date is in the future, |
| 164 | // we can return empty result right away |
| 165 | if (!empty($params['dates'][0]) && $params['dates'][0] > $endDate) { |
| 166 | $result->setResult(CommandResult::RESULT_SUCCESS); |
| 167 | $result->setData( |
| 168 | [ |
| 169 | Entities::EVENTS => [], |
| 170 | 'count' => 0, |
| 171 | 'countTotal' => 0, |
| 172 | 'customersNoShowCount' => [] |
| 173 | ] |
| 174 | ); |
| 175 | return $result; |
| 176 | } |
| 177 | |
| 178 | // Date filter selected and date is in past |
| 179 | if (!empty($params['dates'][0])) { |
| 180 | $params['dates'][1] = $endDate; |
| 181 | } |
| 182 | |
| 183 | // Date filter not selected |
| 184 | if (empty($params['dates'][0])) { |
| 185 | $params['dates'][0] = null; |
| 186 | $params['dates'][1] = $endDate; |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | if (is_array($params['range'])) { |
| 191 | $startDate = !empty($params['range'][0]) ? $params['range'][0] . ' 00:00:00' : null; |
| 192 | $endDate = !empty($params['range'][1]) ? $params['range'][1] . ' 23:59:59' : null; |
| 193 | |
| 194 | // Date filter is selected on the frontend, |
| 195 | // so if the provided date is out of range, |
| 196 | // we can return empty result right away |
| 197 | if (!empty($params['dates'][0]) && ($params['dates'][0] < $startDate || $params['dates'][0] > $endDate)) { |
| 198 | $result->setResult(CommandResult::RESULT_SUCCESS); |
| 199 | $result->setData( |
| 200 | [ |
| 201 | Entities::EVENTS => [], |
| 202 | 'count' => 0, |
| 203 | 'countTotal' => 0, |
| 204 | 'customersNoShowCount' => [] |
| 205 | ] |
| 206 | ); |
| 207 | |
| 208 | return $result; |
| 209 | } |
| 210 | |
| 211 | // Date filter selected and date is in range |
| 212 | if (!empty($params['dates'][0])) { |
| 213 | $params['dates'][1] = $endDate; |
| 214 | } |
| 215 | |
| 216 | // Date filter not selected |
| 217 | if (empty($params['dates'][0])) { |
| 218 | $params['dates'][0] = $startDate; |
| 219 | $params['dates'][1] = $endDate; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | $criteria = [ |
| 225 | 'fetchEventsPeriods' => true, |
| 226 | 'fetchEventsTickets' => true, |
| 227 | 'fetchEventsTags' => $isFrontEnd, |
| 228 | 'fetchEventsProviders' => true, |
| 229 | 'fetchEventsOrganizer' => true, |
| 230 | 'fetchEventsImages' => true, |
| 231 | 'fetchBookings' => $fetchBookings, |
| 232 | 'fetchBookingsTickets' => $fetchBookings, |
| 233 | 'fetchBookingsCoupons' => $fetchBookings && $isCabinetPage, |
| 234 | 'fetchBookingsPayments' => $fetchBookings && $isCabinetPage, |
| 235 | 'fetchBookingsUsers' => $fetchBookings && $isCabinetPage, |
| 236 | 'fetchOccupancy' => !$fetchBookings, |
| 237 | ]; |
| 238 | |
| 239 | /** @var Collection $events */ |
| 240 | $events = $eventAS->getEventsByCriteria( |
| 241 | $params, |
| 242 | $criteria, |
| 243 | !empty($params['limit']) |
| 244 | ? $params['limit'] |
| 245 | : ($isFrontEnd ? $settingsDS->getSetting('general', 'itemsPerPage') : 10) |
| 246 | ); |
| 247 | |
| 248 | $selectedEventIds = []; |
| 249 | |
| 250 | if (!empty($params['idPopup']) && !$events->keyExists($params['idPopup'])) { |
| 251 | $selectedEventIds = [$params['idPopup']]; |
| 252 | } elseif (!empty($params['ids'])) { |
| 253 | $missingIds = array_values(array_diff(array_map('intval', $params['ids']), $events->keys())); |
| 254 | if (!empty($missingIds)) { |
| 255 | $selectedEventIds = $missingIds; |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | /** @var Collection $requestedEvents */ |
| 260 | $requestedEvents = !empty($selectedEventIds) ? $eventAS->getEventsByCriteria( |
| 261 | ['id' => $selectedEventIds, 'search' => !empty($params['search']) ? $params['search'] : null], |
| 262 | $criteria, |
| 263 | 0 |
| 264 | ) : new Collection(); |
| 265 | |
| 266 | $existingSeriesIds = []; |
| 267 | /** @var Event $existingEvent */ |
| 268 | foreach ($events->getItems() as $existingEvent) { |
| 269 | $seriesId = $existingEvent->getParentId() |
| 270 | ? $existingEvent->getParentId()->getValue() |
| 271 | : $existingEvent->getId()->getValue(); |
| 272 | $existingSeriesIds[$seriesId] = $existingEvent->getId()->getValue(); |
| 273 | } |
| 274 | |
| 275 | $replacedEventIds = []; |
| 276 | |
| 277 | foreach ($requestedEvents->getItems() as $event) { |
| 278 | if ($events->keyExists($event->getId()->getValue())) { |
| 279 | continue; |
| 280 | } |
| 281 | |
| 282 | $requestedSeriesId = $event->getParentId() |
| 283 | ? $event->getParentId()->getValue() |
| 284 | : $event->getId()->getValue(); |
| 285 | if (!empty($existingSeriesIds[$requestedSeriesId]) && !empty($params['skipRecurring'])) { |
| 286 | $replacedEventIds[$event->getId()->getValue()] = $existingSeriesIds[$requestedSeriesId]; |
| 287 | continue; |
| 288 | } |
| 289 | |
| 290 | $events->prependItem($event, $event->getId()->getValue(), true); |
| 291 | } |
| 292 | |
| 293 | $eventsArray = []; |
| 294 | |
| 295 | $customersNoShowCountIds = []; |
| 296 | |
| 297 | $noShowTagEnabled = $settingsDS->isFeatureEnabled('noShowTag'); |
| 298 | |
| 299 | /** @var Event $event */ |
| 300 | foreach ($events->getItems() as $event) { |
| 301 | // this would affect paging on frontend, should be done in the database? |
| 302 | if ($isFrontEnd && !$event->getShow()->getValue()) { |
| 303 | continue; |
| 304 | } |
| 305 | |
| 306 | if ( |
| 307 | ($isFrontEnd && $settingsDS->getSetting('general', 'showClientTimeZone')) || |
| 308 | $isCabinetPage || ($user && $user->getType() === AbstractUser::USER_ROLE_PROVIDER) |
| 309 | ) { |
| 310 | $timeZone = !empty($params['timeZone']) |
| 311 | ? $params['timeZone'] |
| 312 | : ($user && $user->getType() === Entities::PROVIDER ? $providerAS->getTimeZone($user) : 'UTC'); |
| 313 | |
| 314 | /** @var EventPeriod $period */ |
| 315 | foreach ($event->getPeriods()->getItems() as $period) { |
| 316 | $period->getPeriodStart()->getValue()->setTimezone(new DateTimeZone($timeZone)); |
| 317 | $period->getPeriodEnd()->getValue()->setTimezone(new DateTimeZone($timeZone)); |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | $eventsInfo = $eventAS->getEventInfo($event, $isFrontEnd); |
| 322 | |
| 323 | if ($isFrontEnd) { |
| 324 | $event->setBookings(new Collection()); |
| 325 | |
| 326 | /** @var EventPeriod $eventPeriod */ |
| 327 | foreach ($event->getPeriods()->getItems() as $key => $eventPeriod) { |
| 328 | /** @var EventPeriod $newEventPeriod **/ |
| 329 | $newEventPeriod = EventPeriodFactory::create( |
| 330 | array_merge( |
| 331 | $eventPeriod->toArray(), |
| 332 | ['zoomMeeting' => null] |
| 333 | ) |
| 334 | ); |
| 335 | |
| 336 | $event->getPeriods()->placeItem($newEventPeriod, $key, true); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | $ameliaUserId = $userAS->isAmeliaUser($user) && $user->getId() ? $user->getId()->getValue() : null; |
| 341 | |
| 342 | // Delete other bookings if user is customer |
| 343 | if ($userAS->isCustomer($user)) { |
| 344 | /** @var CustomerBooking $booking */ |
| 345 | foreach ($event->getBookings()->getItems() as $bookingKey => $booking) { |
| 346 | if ($booking->getCustomerId()->getValue() !== $ameliaUserId) { |
| 347 | $event->getBookings()->deleteItem($bookingKey); |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | if (!$isFrontEnd && $userAS->isCustomer($user) && $event->getBookings()->length() === 0) { |
| 353 | continue; |
| 354 | } |
| 355 | |
| 356 | /** @var CustomerBooking $booking */ |
| 357 | foreach ($event->getBookings()->getItems() as $booking) { |
| 358 | if ($noShowTagEnabled) { |
| 359 | $customersNoShowCountIds[] = $booking->getCustomerId()->getValue(); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | $eventArray = $event->toArray(); |
| 364 | |
| 365 | $eventArray['staff'] = array_map( |
| 366 | function ($provider) use ($providerAS) { |
| 367 | return [ |
| 368 | 'id' => $provider['id'], |
| 369 | 'firstName' => $provider['firstName'], |
| 370 | 'lastName' => $provider['lastName'], |
| 371 | 'picture' => $provider['pictureThumbPath'], |
| 372 | 'badge' => $providerAS->getBadge($provider['badgeId']) |
| 373 | ]; |
| 374 | }, |
| 375 | $eventArray['providers'] |
| 376 | ); |
| 377 | |
| 378 | // TODO - Redesign: Check if providers can be removed |
| 379 | // unset($eventArray['providers']); |
| 380 | |
| 381 | if (!empty($eventArray['organizerId'])) { |
| 382 | $eventArray['organizer'] = [ |
| 383 | 'id' => $eventArray['organizerId'], |
| 384 | 'firstName' => !empty($eventArray['organizer']) ? $eventArray['organizer']['firstName'] : '', |
| 385 | 'lastName' => !empty($eventArray['organizer']) ? $eventArray['organizer']['lastName'] : '', |
| 386 | 'picture' => !empty($eventArray['organizer']) ? $eventArray['organizer']['pictureThumbPath'] : '', |
| 387 | 'badge' => !empty($eventArray['organizer']) ? $providerAS->getBadge($eventArray['organizer']['badgeId']) : null |
| 388 | ]; |
| 389 | } |
| 390 | |
| 391 | usort( |
| 392 | $eventArray['gallery'], |
| 393 | function ($picture1, $picture2) { |
| 394 | return $picture1['position'] <=> $picture2['position']; |
| 395 | } |
| 396 | ); |
| 397 | |
| 398 | $eventsArray[] = array_merge($eventArray, $eventsInfo); |
| 399 | } |
| 400 | |
| 401 | $customersNoShowCount = []; |
| 402 | |
| 403 | if ($noShowTagEnabled && $customersNoShowCountIds) { |
| 404 | /** @var CustomerBookingRepository $bookingRepository */ |
| 405 | $bookingRepository = $this->container->get('domain.booking.customerBooking.repository'); |
| 406 | |
| 407 | $customersNoShowCount = $bookingRepository->countByNoShowStatus($customersNoShowCountIds); |
| 408 | } |
| 409 | |
| 410 | $eventsArray = apply_filters('amelia_get_events_filter', $eventsArray); |
| 411 | |
| 412 | do_action('amelia_get_events', $eventsArray); |
| 413 | |
| 414 | $result->setResult(CommandResult::RESULT_SUCCESS); |
| 415 | $result->setMessage('Successfully retrieved events'); |
| 416 | $result->setData( |
| 417 | [ |
| 418 | Entities::EVENTS => $eventsArray, |
| 419 | 'count' => !$isCalendarPage && empty($params['skipCount']) ? (int)sizeof($eventRepository->getFilteredIds($params, 0)) : null, |
| 420 | 'countTotal' => !$isCalendarPage && empty($params['skipCount']) ? (int)sizeof($eventRepository->getFilteredIds([], 0)) : null, |
| 421 | 'customersNoShowCount' => $customersNoShowCount ? array_values($customersNoShowCount) : [], |
| 422 | 'replacedEventIds' => !empty($replacedEventIds) ? $replacedEventIds : null, |
| 423 | ] |
| 424 | ); |
| 425 | |
| 426 | return $result; |
| 427 | } |
| 428 | } |
| 429 |