AbstractQrCodeApplicationService.php
9 months ago
QrCodeApplicationService.php
9 months ago
StarterQrCodeApplicationService.php
9 months ago
QrCodeApplicationService.php
255 lines
| 1 | <?php |
| 2 | |
| 3 | namespace AmeliaBooking\Application\Services\QrCode; |
| 4 | |
| 5 | use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException; |
| 6 | use AmeliaBooking\Domain\Entity\Booking\Event\CustomerBookingEventTicket; |
| 7 | use AmeliaBooking\Domain\Entity\Booking\Event\EventPeriod; |
| 8 | use AmeliaBooking\Domain\Services\DateTime\DateTimeService; |
| 9 | use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException; |
| 10 | use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException; |
| 11 | use AmeliaBooking\Infrastructure\Repository\Booking\Event\EventTicketRepository; |
| 12 | use AmeliaBooking\Infrastructure\Repository\Location\LocationRepository; |
| 13 | use AmeliaBooking\Infrastructure\Services\QrCode\QrCodeInfrastructureService; |
| 14 | use Interop\Container\Exception\ContainerException; |
| 15 | |
| 16 | /** |
| 17 | * Class QrCodeApplicationService |
| 18 | * |
| 19 | * @package AmeliaBooking\Application\Services\QrCode |
| 20 | */ |
| 21 | class QrCodeApplicationService extends AbstractQrCodeApplicationService |
| 22 | { |
| 23 | /** |
| 24 | * @param array $eventData |
| 25 | * @param array $booking |
| 26 | * @param string $ticketCode |
| 27 | * |
| 28 | * @return array |
| 29 | * |
| 30 | * @throws NotFoundException |
| 31 | * @throws QueryExecutionException |
| 32 | * @throws ContainerException |
| 33 | * @throws InvalidArgumentException |
| 34 | */ |
| 35 | public function createQrCodeEventTickets($eventData, $booking, $ticketCode = ''): array |
| 36 | { |
| 37 | /** @var QrCodeInfrastructureService $qrService */ |
| 38 | $qrService = $this->container->get('infrastructure.qrcode.service'); |
| 39 | |
| 40 | $locale = is_string($booking['info']) ? json_decode($booking['info'], true)['locale'] : ''; |
| 41 | $qrCodeItems = []; |
| 42 | $qrJson = $booking['qrCodes']; |
| 43 | $qrArr = is_string($qrJson) ? json_decode($qrJson, true) : (is_array($qrJson) ? $qrJson : []); |
| 44 | |
| 45 | if (is_array($qrArr)) { |
| 46 | foreach ($qrArr as $qr) { |
| 47 | if (!$ticketCode || hash_equals($qr['ticketManualCode'], $ticketCode)) { |
| 48 | $qrData = $qr; |
| 49 | if (!empty($qr['qrCodeData'])) { |
| 50 | $eventTranslations = $eventData['translations'] ? json_decode($eventData['translations'], true) : null; |
| 51 | $qrData['bookingId'] = $booking['id']; |
| 52 | $qrData['eventName'] = $eventTranslations['name'][$locale] ?: $eventData['name']; |
| 53 | $qrData['eventStartDateTime'] = $eventData['periods'][0]['periodStart']; |
| 54 | |
| 55 | if ( |
| 56 | $qrData['type'] === 'ticket' && |
| 57 | array_key_exists('eventTicketId', $qrData) && |
| 58 | isset($booking['ticketsData']) |
| 59 | ) { |
| 60 | foreach ($eventData['customTickets'] as $ticket) { |
| 61 | if ($ticket['id'] === $qrData['eventTicketId']) { |
| 62 | $ticketTranslations = $ticket['translations'] ? json_decode($ticket['translations'], true) : null; |
| 63 | $ticketName = $ticketTranslations[$locale] ?: $ticket['name']; |
| 64 | $qrData['eventTicketName'] = $ticketName; |
| 65 | break; |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | if (isset($eventData['customLocation']) && $eventData['customLocation']) { |
| 71 | $qrData['eventLocation'] = $eventData['customLocation']; |
| 72 | } |
| 73 | if (isset($eventData['location'])) { |
| 74 | $qrData['eventLocation'] = $eventData['location']; |
| 75 | } |
| 76 | if (isset($eventData['locationId'])) { |
| 77 | /** @var LocationRepository $locationRepository */ |
| 78 | $locationRepository = $this->container->get('domain.locations.repository'); |
| 79 | $location = $locationRepository->getById($eventData['locationId']); |
| 80 | if ($location) { |
| 81 | $locationTranslations = $location->getTranslations() ? json_decode($location->getTranslations()->getValue(), true) : null; |
| 82 | $locTranslation = $locationTranslations['name'][$locale] ?? $location->getName()->getValue(); |
| 83 | $qrData['eventLocation'] = $location->getAddress()->getValue() ?: $locTranslation; |
| 84 | } |
| 85 | } |
| 86 | if ($ticketCode) { |
| 87 | return $qrService->generateQrCode($qrData); |
| 88 | } |
| 89 | $qrCodeItems[] = $qrService->generateQrCode($qrData); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | return $qrCodeItems; |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Create QR code data array for a booking and event |
| 100 | * |
| 101 | * @param $event |
| 102 | * @param $booking |
| 103 | * @return array |
| 104 | * @throws InvalidArgumentException |
| 105 | * @throws ContainerException |
| 106 | * @throws NotFoundException |
| 107 | * @throws QueryExecutionException |
| 108 | */ |
| 109 | public function createQrCodeEventData($event, $booking): array |
| 110 | { |
| 111 | $qrCodes = []; |
| 112 | $qrNumberData = $this->getNumberOfQrCodes($booking, $event); |
| 113 | |
| 114 | if ($qrNumberData['number'] > 0 && $event->getId()) { |
| 115 | // Common timestamp for generation moment (UTC ISO8601) |
| 116 | $generatedAt = DateTimeService::getNowDateTimeObjectInUtc()->format('Y-m-d H:i:s'); |
| 117 | |
| 118 | $bookingIdVal = $booking->getId()->getValue(); |
| 119 | $eventIdVal = $event->getId()->getValue(); |
| 120 | $customerIdVal = $booking->getCustomerId() ? $booking->getCustomerId()->getValue() : ''; |
| 121 | |
| 122 | // Booking-level |
| 123 | if ($qrNumberData['number'] > 1) { |
| 124 | $bookingManualCode = $this->generateManualCode([ |
| 125 | 'bookingId' => $bookingIdVal, |
| 126 | 'eventId' => $eventIdVal, |
| 127 | 'customerId' => $customerIdVal, |
| 128 | 'generatedAt' => $generatedAt, |
| 129 | ]); |
| 130 | |
| 131 | $bookingQrData = 'type: booking | bookingId:' . $bookingIdVal . ' | ticketManualCode:' . $bookingManualCode; |
| 132 | |
| 133 | $qrCodes[] = array_merge([ |
| 134 | 'type' => 'booking', |
| 135 | 'eventName' => $event->getName() ? $event->getName()->getValue() : '', |
| 136 | 'ticketManualCode' => $bookingManualCode, |
| 137 | 'qrCodeData' => $bookingQrData, |
| 138 | 'generatedAt' => $generatedAt, |
| 139 | ], ['dates' => $this->qrCodeDateFlag($event)]); |
| 140 | } |
| 141 | |
| 142 | // Person / ticket level |
| 143 | for ($i = 1; $i <= $qrNumberData['number']; $i++) { |
| 144 | $ticketIdForPerson = $qrNumberData['ticketIds'][$i - 1] ?? null; |
| 145 | $codePayload = [ |
| 146 | 'bookingId' => $bookingIdVal, |
| 147 | 'eventId' => $eventIdVal, |
| 148 | 'customerId' => $customerIdVal, |
| 149 | 'ticketIndex' => $i, |
| 150 | 'generatedAt' => $generatedAt, |
| 151 | ]; |
| 152 | |
| 153 | if ($ticketIdForPerson) { |
| 154 | $codePayload['ticketId'] = $ticketIdForPerson; |
| 155 | } |
| 156 | |
| 157 | $manualTicketCode = $this->generateManualCode($codePayload); |
| 158 | |
| 159 | $ticketQrData = 'type: ticket | bookingId:' . $bookingIdVal . ' ticketManualCode:' . $manualTicketCode; |
| 160 | |
| 161 | $entry = [ |
| 162 | 'type' => 'ticket', |
| 163 | 'eventName' => $event->getName() ? $event->getName()->getValue() : '', |
| 164 | 'ticketManualCode' => $manualTicketCode, |
| 165 | 'qrCodeData' => $ticketQrData, |
| 166 | 'generatedAt' => $generatedAt, |
| 167 | ]; |
| 168 | |
| 169 | if ($ticketIdForPerson) { |
| 170 | /** @var EventTicketRepository $eventTicketRepository */ |
| 171 | $eventTicketRepository = $this->container->get('domain.booking.event.ticket.repository'); |
| 172 | $ticket = $eventTicketRepository->getById($ticketIdForPerson); |
| 173 | $entry['eventTicketId'] = $ticketIdForPerson; |
| 174 | if ($ticket && $ticket->getName()) { |
| 175 | $entry['eventTicketName'] = $ticket->getName()->getValue(); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | $qrCodes[] = array_merge($entry, ['dates' => $this->qrCodeDateFlag($event)]); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | return $qrCodes; |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Determine number of QR codes to generate for a booking, and build a sequence of ticket ids if applicable |
| 188 | * |
| 189 | * @param $booking |
| 190 | * @param $event |
| 191 | * @return array ['number' => int, 'ticketIds' => array|null] |
| 192 | */ |
| 193 | private function getNumberOfQrCodes($booking, $event): array |
| 194 | { |
| 195 | $personsForQr = 0; |
| 196 | $ticketIdSequence = []; |
| 197 | if ($booking->getTicketsBooking() && $event->getCustomPricing()->getValue()) { |
| 198 | /** @var CustomerBookingEventTicket $ticketBooking */ |
| 199 | foreach ($booking->getTicketsBooking()->getItems() as $ticketBooking) { |
| 200 | $ticketPersons = ($ticketBooking->getPersons() ? $ticketBooking->getPersons()->getValue() : 0); |
| 201 | $personsForQr += $ticketPersons; |
| 202 | // Build a flat sequence of ticket ids, one per person, to map each QR to a ticket |
| 203 | if ($ticketPersons && $ticketBooking->getEventTicketId()) { |
| 204 | for ($ti = 0; $ti < $ticketPersons; $ti++) { |
| 205 | $ticketIdSequence[] = $ticketBooking->getEventTicketId()->getValue(); |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | } else { |
| 210 | $personsForQr = $booking->getPersons() ? $booking->getPersons()->getValue() : 0; |
| 211 | } |
| 212 | |
| 213 | $qrCodesNumberData['number'] = $personsForQr; |
| 214 | $qrCodesNumberData['ticketIds'] = $ticketIdSequence ?: null; |
| 215 | |
| 216 | return $qrCodesNumberData; |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Create an array of event dates (Y-m-d) as keys, with false values, for QR code date flagging |
| 221 | * |
| 222 | * @param $event |
| 223 | * @return array |
| 224 | */ |
| 225 | private function qrCodeDateFlag($event): array |
| 226 | { |
| 227 | $dates = []; |
| 228 | /** @var EventPeriod $p */ |
| 229 | foreach ($event->getPeriods()->getItems() as $p) { |
| 230 | $start = (clone $p->getPeriodStart()->getValue())->setTime(0, 0, 0); |
| 231 | $end = (clone $p->getPeriodEnd()->getValue())->setTime(0, 0, 0); |
| 232 | while ($start <= $end) { |
| 233 | $dates[$start->format('Y-m-d')] = false; |
| 234 | $start->modify('+1 day'); |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | return $dates; |
| 239 | } |
| 240 | |
| 241 | /** |
| 242 | * Generate a short manual code from data array |
| 243 | * |
| 244 | * @param array $data |
| 245 | * @return string |
| 246 | */ |
| 247 | private function generateManualCode($data): string |
| 248 | { |
| 249 | $raw = hash('sha256', json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true); |
| 250 | $b64 = rtrim(strtr(base64_encode($raw), '+/', 'AZ'), '='); |
| 251 | |
| 252 | return substr($b64, 0, 10); |
| 253 | } |
| 254 | } |
| 255 |