| @@ -180,12 +180,12 @@ | ||
| 180 | 180 | $duration = $this->calendarSlot->getDuration($duration); |
| 181 | 181 | |
| 182 | 182 | list($scheduleTimezone, $dstTime) = $this->getTimezoneInfo(); |
| 183 | 183 | |
| 184 | - $fromStartTime = $this->maybeDayLightSavingTime($fromTime, $dstTime, $scheduleTimezone); | |
| 185 | 184 | $toEndTime = $this->maybeDayLightSavingTime($toTime, $dstTime, $scheduleTimezone); |
| 186 | 185 | |
| 187 | - $fromTime = gmdate('Y-m-d 00:00:00', strtotime($fromStartTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date | |
| 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. | |
| 187 | + $fromTime = gmdate('Y-m-d 00:00:00', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date | |
| 188 | 188 | $toTime = gmdate('Y-m-d 23:59:59', strtotime($toEndTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 189 | 189 | |
| 190 | 190 | $slots = $this->getDates($fromTime, $toTime, $duration, true); |
| 191 | 191 | |
| @@ -202,8 +202,15 @@ | ||
| 202 | 202 | } |
| 203 | 203 | |
| 204 | 204 | protected function isSlotExists($availableSlots, $fromTimeStamp, $toTimeStamp) |
| 205 | 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 | + | |
| 206 | 213 | $left = 0; |
| 207 | 214 | $right = count($availableSlots) - 1; |
| 208 | 215 | |
| 209 | 216 | while ($left <= $right) { |
| @@ -269,16 +276,20 @@ | ||
| 269 | 276 | |
| 270 | 277 | $hostIds = $this->calendarSlot->getHostIds($this->hostId); |
| 271 | 278 | $status = ['pending', 'reserved', 'approved', 'scheduled', 'completed']; |
| 272 | 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 | + | |
| 273 | 285 | $bookings = Booking::with(['calendar_event']) |
| 274 | 286 | ->whereHas('hosts', function ($query) use ($hostIds) { |
| 275 | 287 | $query->whereIn('user_id', $hostIds); |
| 276 | 288 | }) |
| 277 | - ->where(function ($query) use ($dateRange) { | |
| 278 | - $query->whereBetween('start_time', $dateRange) | |
| 279 | - ->orWhereBetween('end_time', $dateRange); | |
| 280 | - }) | |
| 289 | + ->where('start_time', '>=', $rangeLowerBound) | |
| 290 | + ->where('start_time', '<=', $dateRange[1]) | |
| 291 | + ->where('end_time', '>=', $dateRange[0]) | |
| 281 | 292 | ->orderBy('start_time', 'ASC') |
| 282 | 293 | ->whereIn('status', $status) |
| 283 | 294 | ->get() |
| 284 | 295 | ->groupBy('group_id'); |