*/ private array $deferred_events = []; public function __construct() { $this->repo = new RsvTimetableReservationRepository(); } public function get_all(): array { return $this->repo->get_all(); } public function get(int $id) : RsvTimetableReservation { return $this->repo->get($id); } private function time_of_day_minutes(DateTime $utc_dt): int { $dt = (clone $utc_dt)->setTimezone(wp_timezone()); return (int) $dt->format('G') * 60 + (int) $dt->format('i'); } public function check_availability(int $timetable_id, DateTime $start_utc, DateTime $end_utc): bool { $overlapping_capacity = (new RsvTimetableCapacityRepository()) ->get_overlapping_capacity($timetable_id, $start_utc, $end_utc); if (count($overlapping_capacity) === 0) { return false; } $start_min = $this->time_of_day_minutes($start_utc); $end_min = $this->time_of_day_minutes($end_utc); if ((int) $overlapping_capacity[0]->start_time > $start_min) { return false; } $max = (int) $overlapping_capacity[0]->end_time; foreach ($overlapping_capacity as $cap) { if ((int) $cap->start_time > $max) { return false; } $max = max($max, (int) $cap->end_time); } if ($max < $end_min) { return false; } $capacity = (int) $overlapping_capacity[0]->capacity; $reservations = $this->repo->get_overlapping($timetable_id, $start_utc, $end_utc); return count($reservations) < $capacity; } public function is_reservation_valid(RsvTimetableReservation $reservation): bool { return $this->check_availability( $reservation->timetable_id, $reservation->start_utc, $reservation->end_utc, ); } private function create_confirmation(int $reservation_id, int $timetable_reservation_id): string { $code = wp_generate_password(32, false); $this->repo->insert_confirmation($reservation_id, $timetable_reservation_id, $code); return $code; } private function set_confirmed_state(string $code, bool $state): int { $confirmation = $this->repo->get_confirmation($code); if ($confirmation === null) { throw new InvalidArgumentException('Confirmation code not found.'); } $this->repo->set_confirmed((int) $confirmation['timetable_reservation_id'], $state); $this->repo->delete_confirmation($code); $reservation_service = new RsvReservationService(); $reservation_service->confirmation_state_changed($confirmation['reservation_id']); return $confirmation['timetable_reservation_id']; } public function accept(string $code): int { return $this->set_confirmed_state($code, true); } public function refuse(string $code): int { return $this->set_confirmed_state($code, false); } public function has_pending_confirmation(int $reservation_id): bool { return $this->repo->has_pending_confirmation($reservation_id); } private function get_confirmation_code(int $reservation_id): string { $code = $this->repo->get_confirmation_code($reservation_id); if ($code === null) { throw new InvalidArgumentException('No pending confirmation for this reservation.'); } return $code; } public function accept_by_reservation_id(int $reservation_id): void { $this->set_confirmed_state($this->get_confirmation_code($reservation_id), true); } public function refuse_by_reservation_id(int $reservation_id): void { $this->set_confirmed_state($this->get_confirmation_code($reservation_id), false); } // TODO: Add requires_confirmation parameter public function create(int $reservation_id, RsvTimetableReservation $reservation): int { if (!$this->is_reservation_valid($reservation)) { throw new InvalidArgumentException(); } $capacity = (new RsvTimetableCapacityRepository())->get_overlapping_capacity( $reservation->timetable_id, $reservation->start_utc, $reservation->end_utc, ); $needs_confirmation = array_filter($capacity, fn($c) => (bool) $c->requires_confirmation); $insert_array = $reservation->to_array(); $insert_array['reservation_id'] = $reservation_id; $insert_array['is_confirmed'] = empty($needs_confirmation); $id = $this->repo->insert($insert_array); if (!empty($needs_confirmation)) { $maintainer_email = (new RsvTimetableRepository())->get_maintainer_email($reservation->timetable_id); if ($maintainer_email) { $code = $this->create_confirmation($reservation_id, $id); $this->deferred_events[] = new RsvTimetableReservationPendingEvent( $reservation_id, $reservation, $code, $maintainer_email ); } } else { $reservation_service = new RsvReservationService(); $reservation_service->confirmation_state_changed($reservation_id); } $this->deferred_events[] = new RsvTimetableReservationCreatedEvent($reservation); return $id; } /** * Dispatch (and clear) the events buffered by create(). Callers must invoke * this *after* committing the transaction that wraps create(). */ public function flush_deferred_events(): void { $events = $this->deferred_events; $this->deferred_events = []; foreach ($events as $event) { RsvEventDispatcher::dispatch($event); } } public function get_by_timetable(int $timetable_id, ?int $limit = null, int $skip = 0): array { return $this->repo->get_by_timetable($timetable_id, $limit, $skip); } public function count_by_timetable(int $timetable_id): int { return $this->repo->count_by_timetable($timetable_id); } public function get_reservations_on_date(int $timetable_id, DateTime $date_utc): array { return $this->repo->get_reservations_on_date($timetable_id, $date_utc); } }