*/ private array $handled = []; public function __construct( private BookingRepository $bookings, private AvailabilityRepository $availability, private EnrollmentRepository $enrollments, private PaymentService $payments, private GuardianRepository $links, private GuardianService $guardians, ) {} public function register(): void { // `delete_user` fires before the row goes, which is what lets the lookups // below still find the account's bookings and children. `wpmu_delete_user` // is the multisite equivalent for a user removed from the network entirely. add_action( 'delete_user', [ $this, 'releaseBookings' ] ); add_action( 'wpmu_delete_user', [ $this, 'releaseBookings' ] ); } /** * Release everything the account had booked ahead of it, then remove any * children that only existed to be booked for. */ public function releaseBookings( int $userId ): void { if ( $userId <= 0 || isset( $this->handled[ $userId ] ) ) { return; } $this->handled[ $userId ] = true; $this->release( $userId ); $this->removeChildren( $userId ); } /** * Cancel one account's upcoming lessons and active enrolments, freeing the * slot and voiding the pending payment behind each. */ private function release( int $studentId ): void { // Upcoming and not already cancelled — the only bookings that are still // holding anything. foreach ( $this->bookings->findUpcomingForStudent( $studentId ) as $lesson ) { $this->bookings->updateStatus( (int) $lesson->id, Lesson::STATUS_CANCELLED ); $this->availability->release( $lesson->slotId ); $this->payments->voidPending( $lesson->paymentId ); } foreach ( $this->enrollments->findByStudent( $studentId ) as $enrollment ) { if ( Enrollment::STATUS_ACTIVE !== $enrollment->status ) { continue; } $this->enrollments->updateStatus( (int) $enrollment->id, Enrollment::STATUS_CANCELLED ); $this->payments->voidPending( $enrollment->paymentId ); } } /** * Delete every child linked to a departing guardian, releasing what each was * holding first. Each child is marked handled *before* it is deleted, so the * `delete_user` this fires re-enters and returns without redoing the release. */ private function removeChildren( int $guardianId ): void { foreach ( $this->links->findByGuardian( $guardianId ) as $link ) { $childId = $link->studentId; if ( $childId <= 0 || $childId === $guardianId || isset( $this->handled[ $childId ] ) ) { continue; } $this->handled[ $childId ] = true; $this->release( $childId ); $this->links->delete( $guardianId, $childId ); $this->guardians->deleteUser( $childId ); } } }