PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Services/BookingService.php +52 -7 2.4.0 → 2.5.0 View file →
@@ -55,9 +55,11 @@
55 55 }
56 56
57 57 do_action('fluent_booking/before_booking', $bookingData, $calendarSlot);
58 58
59 - $booking = Booking::create($bookingData);
59 + $booking = Helper::dbTransaction(function () use ($bookingData) {
60 + return Booking::create($bookingData);
61 + });
60 62
61 63 self::attachHosts($booking, $calendarSlot);
62 64 self::updateParentInfo($booking, $bookingIds);
63 65 self::updateMetas($booking, $bookingData, $guests, $customFieldsData, $calendarSlot);
@@ -452,10 +454,8 @@
452 454 }
453 455
454 456 public static function generateBookingICS(Booking $booking)
455 457 {
456 - $author = $booking->getHostDetails(false);
457 -
458 458 // Initialize the ICS content
459 459 $icsContent = "BEGIN:VCALENDAR\r\n";
460 460 $icsContent .= "VERSION:2.0\r\n";
461 461 $icsContent .= "PRODID:-//FluentBooking//Fluent Booking//EN\r\n";
@@ -463,9 +463,57 @@
463 463 // PUBLISH = plain "add to calendar" event. METHOD:REQUEST makes it an iTIP
464 464 // invitation bound to the ATTENDEE, which Google Calendar then rejects/mishandles.
465 465 $icsContent .= "METHOD:PUBLISH\r\n";
466 466
467 - $icsContent .= "BEGIN:VEVENT\r\n";
467 + foreach (self::getIcsBookings($booking) as $icsBooking) {
468 + $icsContent .= self::generateIcsEvent($icsBooking);
469 + }
470 +
471 + // Close the VCALENDAR component
472 + $icsContent .= "END:VCALENDAR\r\n";
473 +
474 + return $icsContent;
475 + }
476 +
477 + /**
478 + * A recurring or multiple-time booking is stored as one row per time, with
479 + * the last row as the parent the guest lands on. Its export carries every
480 + * confirmed time in the set, one VEVENT each, so an occurrence that was
481 + * cancelled or is still awaiting confirmation stays out of the calendar.
482 + */
483 + private static function getIcsBookings(Booking $booking)
484 + {
485 + if ($booking->parent_id) {
486 + return [$booking];
487 + }
488 +
489 + // Additional guests on a group booking are linked the same way, each
490 + // with their own email; their bookings are not this guest's to export.
491 + $childBookings = Booking::with(['calendar', 'calendar_event', 'booking_meta'])
492 + ->where('parent_id', $booking->id)
493 + ->where('email', $booking->email)
494 + ->whereIn('status', ['scheduled', 'completed'])
495 + ->get()
496 + ->all();
497 +
498 + if (!$childBookings) {
499 + return [$booking];
500 + }
501 +
502 + $bookings = array_merge($childBookings, [$booking]);
503 +
504 + usort($bookings, function ($first, $second) {
505 + return strtotime($first->start_time) - strtotime($second->start_time);
506 + });
507 +
508 + return $bookings;
509 + }
510 +
511 + private static function generateIcsEvent(Booking $booking)
512 + {
513 + $author = $booking->getHostDetails(false);
514 +
515 + $icsContent = "BEGIN:VEVENT\r\n";
468 516 $icsContent .= "STATUS:CONFIRMED\r\n";
469 517 $icsContent .= "UID:" . md5($booking->hash) . "\r\n"; // Unique ID for the event
470 518 $icsContent .= "DTSTAMP:" . gmdate('Ymd\THis\Z') . "\r\n"; // Required by RFC5545; Google rejects ICS without it
471 519
@@ -484,11 +532,8 @@
484 532 $organizerEmail = sanitize_email($author['email']) ?: $author['email'];
485 533 $icsContent .= "ORGANIZER;CN=\"" . self::escapeIcsText($author['name']) . "\":mailto:" . $organizerEmail . "\r\n";
486 534
487 535 $icsContent .= "END:VEVENT\r\n";
488 -
489 - // Close the VCALENDAR component
490 - $icsContent .= "END:VCALENDAR\r\n";
491 536
492 537 return $icsContent;
493 538 }
494 539