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/TimeSlotService.php +217 -133 1.5.01 → 2.5.0 View file →
@@ -4,9 +4,8 @@
4 4
5 5 use FluentBooking\App\Models\Booking;
6 6 use FluentBooking\App\Models\Calendar;
7 7 use FluentBooking\App\Models\CalendarSlot;
8 -use FluentBooking\App\Models\Availability;
9 8 use FluentBooking\Framework\Support\Arr;
10 9 use FluentBooking\Framework\Support\DateTime;
11 10
12 11 class TimeSlotService
@@ -16,14 +15,11 @@
16 15 protected $calendar;
17 16
18 17 protected $hostId = null;
19 18
20 - protected $groupedSlots = [];
21 -
22 19 public function __construct(Calendar $calendar, CalendarSlot $calendarSlot)
23 20 {
24 21 $this->hostId = null;
25 - $this->groupedSlots = [];
26 22 $this->calendar = $calendar;
27 23 $this->calendarSlot = $calendarSlot;
28 24 }
29 25
@@ -37,24 +33,22 @@
37 33 $ranges = $this->getCurrentDateRange($fromDate, $toDate);
38 34
39 35 $bookedSlots = $this->getBookedSlots([$fromDate, $toDate], 'UTC', $isDoingBooking);
40 36
41 - $ranges = $this->maybeBookingFrequencyLimitRanges($ranges, $bookedSlots);
42 - $ranges = $this->maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration);
37 + $ranges = $this->maybeBookingFrequencyLimitRanges($ranges);
38 + $ranges = $this->maybeBookingDurationLimitRanges($ranges, $duration);
43 39
44 40 $cutOutTime = DateTimeHelper::getTimestamp() + $this->calendarSlot->getCutoutSeconds();
45 41
46 - $maxBookingTime = strtotime($this->calendarSlot->getMaxBookableDateTime($fromDate, $timeZone, 'Y-m-d H:i:s'));
42 + $maxBookingTime = $this->getMaxBookingTimestamp($fromDate, $toDate, $timeZone);
47 43
48 44 $timezoneInfo = $this->getTimezoneInfo();
49 45
50 46 $rangedSlots = $this->getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo);
51 47
52 - if (!$this->groupedSlots) {
53 - return $rangedSlots;
54 - }
48 + $rangedSlots = $this->maybeBookingPerDayLimitSlots($rangedSlots, $bookedSlots, $duration);
55 49
56 - return $this->adjustGroupedSlots($ranges, $rangedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo);
50 + return $rangedSlots;
57 51 }
58 52
59 53 protected function getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo, $rangedSlots = [], $hostId = null)
60 54 {
@@ -84,10 +78,8 @@
84 78 if (!$availableSlots) {
85 79 continue;
86 80 }
87 81
88 - $currentBookedSlots = $bookedSlots[$date] ?? [];
89 -
90 82 $isToday = $date === $todayDate;
91 83
92 84 $isLastDay = $date === $lastDate;
93 85
@@ -92,8 +84,10 @@
92 84 $isLastDay = $date === $lastDate;
93 85
94 86 $validSlots = [];
95 87
88 + $validDstDateSlots = [];
89 +
96 90 foreach ($availableSlots as $start) {
97 91 $end = gmdate('H:i', strtotime($start) + $period); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
98 92 $endDate = $start < $end ? $date : gmdate('Y-m-d', strtotime($date) + 86400); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
99 93
@@ -103,20 +97,28 @@
103 97 ];
104 98
105 99 $slot = $this->maybeDayLightSavingSlot($slot, $dstTime, $scheduleTimezone);
106 100
101 + $slotDate = gmdate('Y-m-d', strtotime($slot['start'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
102 +
103 + $currentBookedSlots = $bookedSlots[$slotDate] ?? [];
104 +
107 105 if ($isToday && strtotime($slot['start']) < $cutOutTime) {
108 106 continue;
109 107 }
110 108
111 109 if ($isLastDay && strtotime($slot['end']) > $maxBookingTime) {
112 - continue;
110 + break;
113 111 }
114 112
115 113 $isSlotAvailable = $this->isSlotAvailable($slot, $currentBookedSlots, $bufferTime, $hostId);
116 114
117 115 if ($isSlotAvailable) {
118 - $validSlots[] = $slot;
116 + if ($slotDate != $date) {
117 + $validDstDateSlots[] = $slot;
118 + } else {
119 + $validSlots[] = $slot;
120 + }
119 121 }
120 122 }
121 123
122 124 if ($validSlots) {
@@ -122,8 +124,14 @@
122 124 if ($validSlots) {
123 125 $currentSlots = $rangedSlots[$date] ?? [];
124 126 $rangedSlots[$date] = $this->mergeAndSortSlots($currentSlots, $validSlots);
125 127 }
128 +
129 + if ($validDstDateSlots) {
130 + $slotDate = gmdate('Y-m-d', strtotime($validDstDateSlots[0]['start'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
131 + $currentSlots = $rangedSlots[$slotDate] ?? [];
132 + $rangedSlots[$slotDate] = $this->mergeAndSortSlots($currentSlots, $validDstDateSlots);
133 + }
126 134 }
127 135
128 136 return $rangedSlots;
129 137 }
@@ -161,54 +169,8 @@
161 169
162 170 return true;
163 171 }
164 172
165 - protected function adjustGroupedSlots($ranges, $rangedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo)
166 - {
167 - list($scheduleTimezone, $dstTime) = $timezoneInfo;
168 -
169 - $todayDate = gmdate('Y-m-d'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
170 -
171 - $lastDate = end($ranges);
172 -
173 - $addedDates = [];
174 -
175 - foreach ($this->groupedSlots as $slot) {
176 - $slot = $this->maybeDayLightSavingSlot($slot, $dstTime, $scheduleTimezone);
177 -
178 - $date = gmdate('Y-m-d', strtotime($slot['start']));
179 - if ($todayDate == $date && strtotime($slot['start']) < $cutOutTime) {
180 - continue;
181 - }
182 -
183 - if ($lastDate == $date && strtotime($slot['end']) > $maxBookingTime) {
184 - continue;
185 - }
186 -
187 - if (!isset($rangedSlots[$date])) {
188 - $rangedSlots[$date] = [];
189 - $rangedSlots[$date][] = $slot;
190 - continue;
191 - }
192 -
193 - $addedDates[$date] = $date;
194 -
195 - $rangedSlots[$date][] = $slot;
196 - }
197 -
198 - foreach ($addedDates as $date) {
199 - $dateSlots = $rangedSlots[$date];
200 -
201 - usort($dateSlots, function ($a, $b) {
202 - return strtotime($a['start']) - strtotime($b['start']);
203 - });
204 -
205 - $rangedSlots[$date] = $dateSlots;
206 - }
207 -
208 - return $rangedSlots;
209 - }
210 -
211 173 public function isSpotAvailable($fromTime, $toTime, $duration = null, $hostId = null)
212 174 {
213 175 $this->hostId = $hostId;
214 176
@@ -214,36 +176,54 @@
214 176
215 177 $fromTimeStamp = strtotime($fromTime);
216 178 $toTimeStamp = strtotime($toTime);
217 179
180 + $duration = $this->calendarSlot->getDuration($duration);
181 +
182 + list($scheduleTimezone, $dstTime) = $this->getTimezoneInfo();
183 +
184 + $toEndTime = $this->maybeDayLightSavingTime($toTime, $dstTime, $scheduleTimezone);
185 +
186 + // Start from the requested time, not the DST-shifted one: the shift can cross into the next UTC day and drop that day's bookings.
218 187 $fromTime = gmdate('Y-m-d 00:00:00', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
219 - $toTime = gmdate('Y-m-d 23:59:59', $toTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
188 + $toTime = gmdate('Y-m-d 23:59:59', strtotime($toEndTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
220 189
221 - $duration = $this->calendarSlot->getDuration($duration);
222 -
223 190 $slots = $this->getDates($fromTime, $toTime, $duration, true);
224 191
225 192 $fromDate = gmdate('Y-m-d', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
226 193 $toDate = gmdate('Y-m-d', $toTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
227 -
194 +
228 195 $availableSlots = $slots[$fromDate] ?? [];
229 -
196 +
230 197 if ($fromDate != $toDate) {
231 198 $availableSlots = array_merge($availableSlots, $slots[$toDate] ?? []);
232 199 }
233 200
201 + return $this->isSlotExists($availableSlots, $fromTimeStamp, $toTimeStamp);
202 + }
203 +
204 + protected function isSlotExists($availableSlots, $fromTimeStamp, $toTimeStamp)
205 + {
206 + // Slot generation only checks notice on today and the horizon on the last day.
207 + if ($fromTimeStamp < strtotime($this->calendarSlot->getMinBookableDateTime()) ||
208 + $toTimeStamp > $this->getMaxBookingTimestamp(null, gmdate('Y-m-d H:i:s', $toTimeStamp), 'UTC') // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
209 + ) {
210 + return false;
211 + }
212 +
234 213 $left = 0;
235 214 $right = count($availableSlots) - 1;
236 215
237 216 while ($left <= $right) {
238 217 $mid = $left + (($right - $left) >> 1);
218 + $slot = $availableSlots[$mid];
239 219
240 - $midStartTime = strtotime($availableSlots[$mid]['start']);
241 - $midEndTime = strtotime($availableSlots[$mid]['end']);
220 + $slotStartTime = strtotime($slot['start']);
221 + $slotEndTime = strtotime($slot['end']);
242 222
243 - if ($fromTimeStamp == $midStartTime && $toTimeStamp == $midEndTime) {
244 - return true;
245 - } elseif ($fromTimeStamp > $midStartTime) {
223 + if ($fromTimeStamp == $slotStartTime && $toTimeStamp == $slotEndTime) {
224 + return $slot;
225 + } elseif ($fromTimeStamp > $slotStartTime) {
246 226 $left = $mid + 1;
247 227 } else {
248 228 $right = $mid - 1;
249 229 }
@@ -262,9 +242,9 @@
262 242 $endDate = gmdate('Y-m-t 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
263 243 }
264 244
265 245 $currentDate = strtotime($startDate);
266 - $endDate = strtotime($endDate);
246 + $endDate = strtotime($endDate) + 1; // add 1s in case end is 23:59:59
267 247 $oneDay = 24 * 60 * 60;
268 248
269 249 $dateArray = [];
270 250
@@ -296,13 +276,20 @@
296 276
297 277 $hostIds = $this->calendarSlot->getHostIds($this->hostId);
298 278 $status = ['pending', 'reserved', 'approved', 'scheduled', 'completed'];
299 279
280 + // Single indexed start_time range: widen the lower bound by max booking duration to catch overlaps.
281 + $maxDurationMinutes = (int) apply_filters('fluent_booking/max_booking_duration_minutes', DAY_IN_SECONDS / MINUTE_IN_SECONDS, $this->calendarSlot);
282 +
283 + $rangeLowerBound = gmdate('Y-m-d H:i:s', strtotime($dateRange[0]) - $maxDurationMinutes * MINUTE_IN_SECONDS); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
284 +
300 285 $bookings = Booking::with(['calendar_event'])
301 286 ->whereHas('hosts', function ($query) use ($hostIds) {
302 287 $query->whereIn('user_id', $hostIds);
303 288 })
304 - ->whereBetween('start_time', $dateRange)
289 + ->where('start_time', '>=', $rangeLowerBound)
290 + ->where('start_time', '<=', $dateRange[1])
291 + ->where('end_time', '>=', $dateRange[0])
305 292 ->orderBy('start_time', 'ASC')
306 293 ->whereIn('status', $status)
307 294 ->get()
308 295 ->groupBy('group_id');
@@ -372,9 +359,8 @@
372 359
373 360 $eventIdAdded = false;
374 361 foreach ($rangedItems as $date => $slot) {
375 362 if ($isGroupBooking && $remaining && $this->calendarSlot->id == $booking->event_id) {
376 - $this->groupedSlots[] = $slot;
377 363 if ($eventIdAdded) {
378 364 $slot['event_id'] = null;
379 365 }
380 366 $eventIdAdded = true;
@@ -438,9 +424,9 @@
438 424 $start += $interval;
439 425 }
440 426
441 427 if ($slot['end'] == '24:00' && $start < $end) {
442 - $daySlots = $this->handleNextDaySlot($daySlots, $items, $end, $start, $interval, $period, $day, $days);
428 + $daySlots = $this->handleNextDaySlot($daySlots, $items, $start, $end, $interval, $period, $day, $days);
443 429 }
444 430 }
445 431 if ($daySlots) {
446 432 $formattedSlots[$day] = $daySlots;
@@ -464,22 +450,22 @@
464 450 }
465 451
466 452 protected function handleNextDaySlot($daySlots, &$items, $start, $end, $interval, $period, $day, $days)
467 453 {
468 - $nextDayIndex = array_search($day, $days) + 1;
454 + $nextDayIndex = (array_search($day, $days) + 1) % count($items);
469 455
470 456 if (isset($days[$nextDayIndex])) {
471 457 $nextDay = $items[$days[$nextDayIndex]];
472 458
473 459 if ($nextDay && $nextDay[0]['start'] == '00:00') {
474 - $nextDayEnd = strtotime($nextDay[0]['end']);
460 + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']);
475 461 $reserveTime = $end - $start;
476 462
477 - while ($period - $reserveTime <= $nextDayEnd) {
463 + while ($period - $reserveTime <= $nextDayEndTime) {
478 464 $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
479 465 $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
480 466 $daySlots[] = $startTime;
481 -
467 +
482 468 if ($nextDayStart < $startTime) {
483 469 $items[$days[$nextDayIndex]][0]['start'] = $nextDayStart;
484 470 break;
485 471 }
@@ -549,12 +535,12 @@
549 535 if (isset($overrideSlots[$nextDayIndex])) {
550 536 $nextDay = $overrideSlots[$nextDayIndex];
551 537
552 538 if ($nextDay && $nextDay[0]['start'] == '00:00') {
553 - $nextDayEnd = strtotime($nextDay[0]['end']);
539 + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']);
554 540 $reserveTime = $end - $start;
555 541
556 - while ($period - $reserveTime <= $nextDayEnd) {
542 + while ($period - $reserveTime <= $nextDayEndTime) {
557 543 $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
558 544 $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
559 545 $formattedSlots[] = $startTime;
560 546
@@ -592,20 +578,20 @@
592 578 {
593 579 $this->hostId = $hostId;
594 580
595 581 $event = $this->calendarSlot;
596 - $calendar = $this->calendar;
597 582 $duration = $event->getDuration($duration);
598 583
599 - $startDate = $this->adjustStartDate($startDate, $timeZone);
584 + $adjustedDate = $this->adjustStartDate($startDate, $timeZone);
600 585
601 586 $isDisplaySpots = $event->is_display_spots;
602 587 $isMultiGuest = $event->isMultiGuestEvent();
603 - $endDate = $event->getMaxBookableDateTime($startDate, $timeZone);
588 + $isMultiBooking = $event->isAdditionalGuestEnabled();
589 + $endDate = $event->getMaxBookableDateTime($adjustedDate, $timeZone);
604 590 $startDate = $event->getMinBookableDateTime($startDate, $timeZone);
605 591
606 592 $maxBooking = false;
607 - if ($isDisplaySpots && $isMultiGuest) {
593 + if ($isMultiGuest && ($isDisplaySpots || $isMultiBooking)) {
608 594 $maxBooking = $event->getMaxBookingPerSlot();
609 595 }
610 596
611 597 if (strtotime($startDate) > strtotime($endDate)) {
@@ -672,9 +658,9 @@
672 658 }
673 659 }
674 660
675 661 $convertedSpots = array_map(function ($spots) {
676 - return array_values($spots);
662 + return array_values(ksort($spots) ? $spots : $spots);
677 663 }, $convertedSpots);
678 664
679 665 return $convertedSpots;
680 666 }
@@ -732,9 +718,9 @@
732 718
733 719 return $rangeArray;
734 720 }
735 721
736 - private function maybeBookingFrequencyLimitRanges($ranges, $bookedSlots)
722 + private function maybeBookingFrequencyLimitRanges($ranges)
737 723 {
738 724 if (!$ranges) {
739 725 return $ranges;
740 726 }
@@ -789,39 +775,12 @@
789 775 }
790 776 }
791 777 }
792 778 }
793 -
794 - // Per Day Booking Frequency Limit Hanlder
795 - if (!empty($keyedFrequenceyLimits['per_day'])) {
796 - $perDayLimit = $keyedFrequenceyLimits['per_day'];
797 - foreach ($ranges as $rangeIndex => $rangeDate) {
798 - if (!isset($bookedSlots[$rangeDate])) {
799 - continue;
800 - }
801 -
802 - $dayBooked = array_filter($bookedSlots[$rangeDate], function ($slot) {
803 - return Arr::get($slot, 'event_id') == $this->calendarSlot->id;
804 - });
805 -
806 - if (!$dayBooked) {
807 - continue;
808 - }
809 -
810 - if (count($dayBooked) >= $perDayLimit) {
811 - unset($ranges[$rangeIndex]);
812 - }
813 -
814 - if (!$ranges) {
815 - return [];
816 - }
817 - }
818 - }
819 -
820 779 return $ranges;
821 780 }
822 781
823 - private function maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration)
782 + private function maybeBookingDurationLimitRanges($ranges, $duration)
824 783 {
825 784 if (!$ranges) {
826 785 return $ranges;
827 786 }
@@ -868,35 +827,153 @@
868 827 }
869 828 }
870 829 }
871 830 }
831 + return $ranges;
832 + }
872 833
873 - // Per Day Booking Frequency Limit Hanlder
874 - if (!empty($keyedLimits['per_day'])) {
875 - $perDayLimit = $keyedLimits['per_day'];
876 - foreach ($ranges as $rangeIndex => $rangeDate) {
877 - if (!isset($bookedSlots[$rangeDate])) {
878 - continue;
834 + protected function maybeBookingPerDayLimitSlots($rangesSlots, $bookedSlots, $duration)
835 + {
836 + $isDurationEnabled = !!Arr::get($this->calendarSlot->settings, 'booking_duration.enabled');
837 +
838 + $isFrequencyEnabled = !!Arr::get($this->calendarSlot->settings, 'booking_frequency.enabled');
839 +
840 + if (!$isDurationEnabled && !$isFrequencyEnabled) {
841 + return $rangesSlots;
842 + }
843 +
844 + $hostTimeZone = $this->calendarSlot->getScheduleTimezone($this->hostId);
845 +
846 + $convertedRangesSlots = $this->convertSlotsByTimezone($rangesSlots, 'UTC', $hostTimeZone);
847 +
848 + $convertedBookedSlots = $this->convertSlotsByTimezone($bookedSlots, 'UTC', $hostTimeZone);
849 +
850 + $convertedRangesSlots = $this->maybeBookingDurationDayLimit($convertedRangesSlots, $convertedBookedSlots, $duration, $isDurationEnabled);
851 +
852 + $convertedRangesSlots = $this->maybeBookingFrequencyDayLimit($convertedRangesSlots, $convertedBookedSlots, $isFrequencyEnabled);
853 +
854 + $rangesSlots = $this->convertSlotsByTimezone($convertedRangesSlots, $hostTimeZone, 'UTC');
855 +
856 + return $rangesSlots;
857 + }
858 +
859 + protected function maybeBookingDurationDayLimit($rangesSlots, $bookedSlots, $duration, $isEnabled)
860 + {
861 + if (!$isEnabled) {
862 + return $rangesSlots;
863 + }
864 +
865 + $limits = Arr::get($this->calendarSlot->settings, 'booking_duration.limits', []);
866 +
867 + $perDayLimit = null;
868 + foreach ($limits as $limit) {
869 + if (Arr::get($limit, 'unit') == 'per_day' && Arr::get($limit, 'value')) {
870 + $perDayLimit = (int)Arr::get($limit, 'value');
871 + break;
872 + }
873 + }
874 +
875 + if (!$perDayLimit) {
876 + return $rangesSlots;
877 + }
878 +
879 + $isMultiSlot = $this->calendarSlot->isMultiGuestEvent();
880 +
881 + foreach ($rangesSlots as $rangeDate => &$slots) {
882 + if (!isset($bookedSlots[$rangeDate])) {
883 + continue;
884 + }
885 +
886 + $dayDuration = array_reduce($bookedSlots[$rangeDate], function ($carry, $slot) {
887 + if (Arr::get($slot, 'event_id') == $this->calendarSlot->id) {
888 + $carry += (int)((strtotime($slot['end']) - strtotime($slot['start'])) / 60);
879 889 }
890 + return $carry;
891 + }, 0);
880 892
881 - $dayDurarion = array_reduce($bookedSlots[$rangeDate], function ($carry, $slot) {
882 - if (Arr::get($slot, 'event_id') == $this->calendarSlot->id) {
883 - $carry += (int)((strtotime($slot['end']) - strtotime($slot['start'])) / 60);
884 - }
885 - return $carry;
886 - }, 0);
893 + if ($dayDuration + $duration > $perDayLimit) {
894 + if ($isMultiSlot) {
895 + $slots = array_values(array_filter($slots, function ($slot) {
896 + return !empty(Arr::get($slot, 'remaining'));
897 + }));
898 + }
899 + if (!$isMultiSlot || !count($slots)) {
900 + unset($rangesSlots[$rangeDate]);
901 + }
902 + }
903 + }
887 904
888 - if (!$dayDurarion) {
889 - continue;
905 + return $rangesSlots;
906 + }
907 +
908 + protected function maybeBookingFrequencyDayLimit($rangesSlots, $bookedSlots, $isEnabled)
909 + {
910 + if (!$isEnabled) {
911 + return $rangesSlots;
912 + }
913 +
914 + $limits = Arr::get($this->calendarSlot->settings, 'booking_frequency.limits', []);
915 +
916 + $perDayLimit = null;
917 + foreach ($limits as $limit) {
918 + if (Arr::get($limit, 'unit') == 'per_day' && Arr::get($limit, 'value')) {
919 + $perDayLimit = (int)Arr::get($limit, 'value');
920 + break;
921 + }
922 + }
923 +
924 + if (!$perDayLimit) {
925 + return $rangesSlots;
926 + }
927 +
928 + $isMultiSlot = $this->calendarSlot->isMultiGuestEvent();
929 +
930 + foreach ($rangesSlots as $rangeDate => &$slots) {
931 + if (!isset($bookedSlots[$rangeDate])) {
932 + continue;
933 + }
934 +
935 + $dayBooked = array_filter($bookedSlots[$rangeDate], function ($slot) {
936 + return Arr::get($slot, 'event_id') == $this->calendarSlot->id;
937 + });
938 +
939 + if (count($dayBooked) >= $perDayLimit) {
940 + if ($isMultiSlot) {
941 + $slots = array_values(array_filter($slots, function ($slot) {
942 + return !empty(Arr::get($slot, 'remaining'));
943 + }));
890 944 }
945 + if (!$isMultiSlot || !count($slots)) {
946 + unset($rangesSlots[$rangeDate]);
947 + }
948 + }
949 + }
891 950
892 - if ($dayDurarion + $duration > $perDayLimit) {
893 - unset($ranges[$rangeIndex]);
894 - }
951 + return $rangesSlots;
952 + }
953 +
954 + protected function convertSlotsByTimezone($slots, $fromTimeZone, $toTimeZone)
955 + {
956 + if ($fromTimeZone == $toTimeZone) {
957 + return $slots;
958 + }
959 +
960 + $convertedSlots = [];
961 +
962 + foreach ($slots as $spots) {
963 + foreach ($spots as $spot) {
964 + $spot['start'] = DateTimeHelper::convertToTimeZone($spot['start'], $fromTimeZone, $toTimeZone);
965 + $spot['end'] = DateTimeHelper::convertToTimeZone($spot['end'], $fromTimeZone, $toTimeZone);
966 +
967 + $spotDate = gmdate('Y-m-d', strtotime($spot['start']));
968 +
969 + $convertedSlots[$spotDate] = $convertedSlots[$spotDate] ?? [];
970 +
971 + $convertedSlots[$spotDate][] = $spot;
895 972 }
896 973 }
897 974
898 - return $ranges;
975 + return $convertedSlots;
899 976 }
900 977
901 978 public function getFilledWeeks($from, $to, $weekStart = '')
902 979 {
@@ -959,8 +1036,15 @@
959 1036 ->where('event_id', $this->calendarSlot->id)
960 1037 ->whereBetween('start_time', [$start, $end])
961 1038 ->whereIn('status', ['scheduled', 'completed'])
962 1039 ->sum('slot_minutes');
1040 + }
1041 +
1042 + protected function getMaxBookingTimestamp($fromDate, $toDate, $timeZone)
1043 + {
1044 + $maxBookingTime = $this->calendarSlot->getMaxBookableDateTime($toDate, $timeZone, 'Y-m-d H:i:s');
1045 +
1046 + return strtotime($maxBookingTime);
963 1047 }
964 1048
965 1049 protected function getTimezoneInfo($hostId = null)
966 1050 {