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