PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
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 1.7.2 All 33 releases
fluent-booking / app / Services / AvailabilityService.php

AvailabilityService.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution trunk, at app/Services/AvailabilityService.php

489 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services;
4
5 use FluentBooking\App\Models\Availability;
6 use FluentBooking\App\Models\Calendar;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\App\Services\DateTimeHelper;
9 use FluentBooking\Framework\Support\Arr;
10
11 class AvailabilityService
12 {
13 public static function maybeCreateAvailability($calendar, $scheduleData)
14 {
15 $userId = $calendar->user_id;
16
17 $availability = self::getDefaultSchedule($userId);
18
19 if ($availability) {
20 return $availability;
21 }
22
23 $timezone = $calendar->author_timezone;
24
25 $defaultSchedule = self::createScheduleSchema($userId, 'Weekly Hours', true, $timezone, 'UTC', $scheduleData);
26
27 return Availability::create($defaultSchedule);
28 }
29
30 /**
31 * Schedules the current user may read and attach, as a query so callers that only
32 * need ids can avoid formatting every schedule.
33 */
34 public static function usableAvailabilityQuery()
35 {
36 $permissions = ['manage_all_data', 'read_and_use_other_availabilities', 'manage_other_availabilities', 'read_other_calendars', 'manage_other_calendars'];
37
38 return Availability::when(
39 !PermissionManager::userCan($permissions),
40 function ($query) {
41 return $query->where('object_id', get_current_user_id());
42 });
43 }
44
45 public static function availabilitySchedules()
46 {
47 $availabilities = self::usableAvailabilityQuery()->get()->toArray();
48
49 $formattedSchedules = [];
50 foreach ($availabilities as $availability) {
51 $toTimezone = Arr::get($availability, 'value.timezone', 'UTC');
52 $formattedSchedules[] = [
53 'id' => Arr::get($availability, 'id'),
54 'object_id' => Arr::get($availability, 'object_id'),
55 'title' => Arr::get($availability, 'key'),
56 'settings' => [
57 'default' => Arr::isTrue($availability, 'value.default'),
58 'timezone' => $toTimezone,
59 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($availability, 'value.date_overrides', []), 'UTC', $toTimezone),
60 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($availability, 'value.weekly_schedules', []), 'UTC', $toTimezone),
61 ]
62 ];
63 }
64 return $formattedSchedules;
65 }
66
67 public static function getFormattedSchedule($schedule)
68 {
69 $timezone = Arr::get($schedule, 'value.timezone', 'UTC');
70
71 $author = $schedule->getAuthor();
72
73 return [
74 'id' => $schedule->id,
75 'host_name' => $author['name'],
76 'host_avatar' => $author['avatar'],
77 'title' => $schedule->key,
78 'created_at' => $schedule->created_at->format('Y-m-d H:i:s'),
79 'usage_count' => self::getAvailabilityUsageCount($schedule->id),
80 'settings' => [
81 'default' => Arr::isTrue($schedule, 'value.default'),
82 'timezone' => $timezone,
83 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($schedule, 'value.date_overrides', []), 'UTC', $timezone),
84 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($schedule, 'value.weekly_schedules'), 'UTC', $timezone)
85 ],
86 ];
87 }
88
89 public static function isTitleAlreadyExist($title, $userId, $currentTitle = '')
90 {
91 if ($title == $currentTitle) {
92 return false;
93 }
94
95 $scheduleTitles = Availability::where('object_id', $userId)
96 ->pluck('key')
97 ->toArray();
98
99 if (in_array($title, $scheduleTitles)) {
100 return true;
101 }
102
103 return false;
104 }
105
106 public static function updateOtherDefaultStatus($schedule, $id)
107 {
108 $schedules = Availability::where('object_id', $schedule->object_id)
109 ->where('id', '!=', $id)
110 ->get();
111
112 foreach ($schedules as $schedule) {
113 $schedule = Availability::findOrFail($schedule->id);
114 $updatedSettings = [
115 'default' => false,
116 'timezone' => Arr::get($schedule, 'value.timezone', 'UTC'),
117 'date_overrides' => Arr::get($schedule, 'value.data_overrides', []),
118 'weekly_schedules' => Arr::get($schedule, 'value.weekly_schedules'),
119 ];
120
121 $schedule->value = $updatedSettings;
122 $schedule->save();
123 }
124 }
125
126 public static function getScheduleOptions()
127 {
128 $permissions = ['manage_all_data', 'read_and_use_other_availabilities', 'manage_other_availabilities', 'read_other_calendars', 'manage_other_calendars'];
129
130 $availabilities = Availability::when(
131 !PermissionManager::userCan($permissions),
132 function ($query) {
133 return $query->where('object_id', get_current_user_id());
134 })->get();
135
136 $scheduleOptions = [];
137 foreach ($availabilities as $availability) {
138 $calendar = Calendar::with(['user'])
139 ->where('type', 'simple')
140 ->where('user_id', $availability->object_id)
141 ->first();
142
143 if ($calendar && $calendar->user) {
144 $hostName = $calendar->user->full_name;
145 if ($calendar->user_id == get_current_user_id()) {
146 $hostName = __('My Schedules', 'fluent-booking');
147 }
148 } else {
149 $hostName = __('Deleted User', 'fluent-booking');
150 }
151
152 $scheduleOptions[$hostName] = $scheduleOptions[$hostName] ?? [];
153
154 $scheduleOptions[$hostName][] = [
155 'label' => Arr::get($availability, 'key'),
156 'value' => Arr::get($availability, 'id'),
157 'default' => Arr::isTrue($availability, 'value.default')
158 ];
159 }
160
161 return apply_filters('fluent_booking/availability_schedule_options', $scheduleOptions);
162 }
163
164 public static function getDefaultSchedule($userId)
165 {
166 return Availability::where('object_id', $userId)
167 ->get()
168 ->first(function ($schedule) {
169 return Arr::isTrue($schedule, 'value.default');
170 });
171 }
172
173 public static function createScheduleSchema($userId, $title, $default, $fromTimezone, $toTimezone = 'UTC', $weeklySchedule = [], $dateOverrides = [])
174 {
175 $weeklySchedule = $weeklySchedule ? $weeklySchedule : Helper::getWeeklyScheduleSchema();
176
177 $dateOverrides = $dateOverrides ? SanitizeService::slotDateOverrides($dateOverrides, $fromTimezone, $toTimezone, null, true) : [];
178
179 $defaultSchedule = [
180 'object_id' => $userId,
181 'key' => sanitize_text_field($title),
182 'value' => [
183 'default' => (bool)$default,
184 'timezone' => sanitize_text_field($fromTimezone),
185 'date_overrides' => $dateOverrides,
186 'weekly_schedules' => SanitizeService::weeklySchedules($weeklySchedule, $fromTimezone, $toTimezone, true),
187 ]
188 ];
189 return $defaultSchedule;
190 }
191
192 public static function getAvailabilityUsageCount($scheduleId)
193 {
194 return CalendarSlot::where('availability_type', 'existing_schedule')
195 ->where('availability_id', $scheduleId)
196 ->count();
197 }
198
199 public static function getUtcWeeklySchedules($schedules, $fromTimeZone = false, $toTimeZone = 'UTC')
200 {
201 if (!$schedules) {
202 return [];
203 }
204
205 if (!$fromTimeZone || !$toTimeZone || $fromTimeZone == $toTimeZone) {
206 return $schedules;
207 }
208
209 $weekDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
210
211 $dstTimeZone = $fromTimeZone == 'UTC' ? $toTimeZone : $fromTimeZone;
212
213 $dateWithoutDST = DateTimeHelper::getDateWithoutDST($dstTimeZone);
214
215 foreach ($schedules as $day => &$schedule) {
216 $schedule['enabled'] = Arr::isTrue($schedule, 'enabled');
217 if (!$schedule['enabled'] || empty($schedule['slots'])) {
218 $schedule['slots'] = [];
219 $schedule['enabled'] = false;
220 continue;
221 }
222
223 $schedule['enabled'] = true;
224
225 $nextDay = null;
226 $nextDayIndex = 0;
227 $dayIndex = array_search($day, $weekDays);
228 foreach ($schedule['slots'] as $index => $slot) {
229 if (!$slot['start'] || !$slot['end']) {
230 unset($schedule['slots'][$index]);
231 continue;
232 }
233
234 if (!empty(Arr::get($slot, 'type', ''))) {
235 continue;
236 }
237
238 $dayDiff = DateTimeHelper::getDayDifference($slot['start'], $fromTimeZone, $toTimeZone, $dateWithoutDST);
239 $slot['start'] = DateTimeHelper::convertToTimeZone($slot['start'], $fromTimeZone, $toTimeZone, 'H:i', $dateWithoutDST);
240 $slot['end'] = DateTimeHelper::convertToTimeZone($slot['end'], $fromTimeZone, $toTimeZone, 'H:i', $dateWithoutDST);
241
242 if ($nextDayIndex) {
243 array_splice($schedules[$nextDay]['slots'], $nextDayIndex, 0, [[
244 'start' => $slot['start'],
245 'end' => $slot['end'],
246 'type' => 'next_day'
247 ]]);
248 unset($schedule['slots'][$index]);
249 $nextDayIndex++;
250 continue;
251 }
252
253 if ($dayDiff > 0) {
254 $nextDayIndex = 1;
255 $nextDay = $weekDays[($dayIndex + $dayDiff) % 7];
256 $schedules[$nextDay]['enabled'] = true;
257 if (strtotime($slot['start']) < strtotime($slot['end'])) {
258 array_unshift($schedules[$nextDay]['slots'], [
259 'start' => $slot['start'],
260 'end' => $slot['end'],
261 'type' => 'next_day'
262 ]);
263 unset($schedule['slots'][$index]);
264 continue;
265 }
266 if ($slot['end'] != '00:00') {
267 array_unshift($schedules[$nextDay]['slots'], [
268 'start' => '00:00',
269 'end' => $slot['end'],
270 'type' => 'next_day'
271 ]);
272 $slot['end'] = '00:00';
273 }
274 } else if ($dayDiff < 0) {
275 $prevDay = $weekDays[($dayIndex + $dayDiff + 7) % 7];
276 $schedules[$prevDay]['enabled'] = true;
277 if (strtotime($slot['start']) < strtotime($slot['end'])) {
278 array_push($schedules[$prevDay]['slots'], [
279 'start' => $slot['start'],
280 'end' => $slot['end'],
281 'type' => 'prev_day'
282 ]);
283 unset($schedule['slots'][$index]);
284 continue;
285 }
286 if ($slot['start'] != '00:00') {
287 array_push($schedules[$prevDay]['slots'], [
288 'start' => $slot['start'],
289 'end' => '00:00',
290 'type' => 'prev_day'
291 ]);
292 $slot['start'] = '00:00';
293 }
294 } else {
295 if (strtotime($slot['start']) > strtotime($slot['end'])) {
296 if ($slot['end'] != '00:00') {
297 $nextDayIndex = 1;
298 $nextDay = $weekDays[($dayIndex + 1) % 7];
299 $schedules[$nextDay]['enabled'] = true;
300 array_unshift($schedules[$nextDay]['slots'], [
301 'start' => '00:00',
302 'end' => $slot['end'],
303 'type' => 'next_day'
304 ]);
305 $slot['end'] = '00:00';
306 }
307 }
308 }
309
310 if ($slot['start'] == '00:00' && $slot['end'] == '00:00') {
311 unset($schedule['slots'][$index]);
312 continue;
313 }
314
315 $schedule['slots'][$index] = $slot;
316 }
317
318 $schedule['slots'] = array_values($schedule['slots']);
319 }
320
321 return $schedules;
322 }
323
324 public static function getUtcDateOverrides($overrides, $fromTimeZone = false, $toTimeZone = 'UTC')
325 {
326 if (!$overrides) {
327 return [];
328 }
329
330 if (!$fromTimeZone || !$toTimeZone) {
331 return $overrides;
332 }
333
334 $dstTimeZone = $fromTimeZone == 'UTC' ? $toTimeZone : $fromTimeZone;
335
336 $dateWithoutDST = DateTimeHelper::getDateWithoutDST($dstTimeZone);
337
338 $todayTimeStamp = strtotime(gmdate('Y-m-d')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
339
340 $validOverrides = [];
341 foreach ($overrides as $date => $slots) {
342 $dateTimestamp = strtotime($date);
343 if ($dateTimestamp < $todayTimeStamp) {
344 continue;
345 }
346
347 $nextDayIndex = 0;
348 foreach ($slots as $index => $slot) {
349 if (empty($slot['start']) || empty($slot['end']) || $slot['start'] == $slot['end']) {
350 unset($slots[$index]);
351 continue;
352 }
353
354 $dayDiff = DateTimeHelper::getDayDifference($slot['start'], $fromTimeZone, $toTimeZone, $dateWithoutDST);
355 $slot['start'] = DateTimeHelper::convertToTimeZone($slot['start'], $fromTimeZone, $toTimeZone, 'H:i', $dateWithoutDST);
356 $slot['end'] = DateTimeHelper::convertToTimeZone($slot['end'], $fromTimeZone, $toTimeZone, 'H:i', $dateWithoutDST);
357
358 if ($nextDayIndex) {
359 $nextDay = gmdate('Y-m-d', ($dateTimestamp + 86400 * $dayDiff)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
360 array_splice($validOverrides[$nextDay], $nextDayIndex, 0, [[
361 'start' => $slot['start'],
362 'end' => $slot['end']
363 ]]);
364 unset($slots[$index]);
365 $nextDayIndex++;
366 continue;
367 }
368
369 if ($dayDiff > 0) {
370 $nextDayIndex = 1;
371 $nextDay = gmdate('Y-m-d', ($dateTimestamp + 86400 * $dayDiff)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
372 $validOverrides[$nextDay] = $validOverrides[$nextDay] ?? [];
373 if (strtotime($slot['start']) < strtotime($slot['end'])) {
374 array_unshift($validOverrides[$nextDay], [
375 'start' => $slot['start'],
376 'end' => $slot['end']
377 ]);
378 unset($slots[$index]);
379 continue;
380 }
381 if ($slot['end'] != '00:00') {
382 array_unshift($validOverrides[$nextDay], [
383 'start' => '00:00',
384 'end' => $slot['end']
385 ]);
386 $slot['end'] = '00:00';
387 }
388 } else if ($dayDiff < 0) {
389 $prevDay = gmdate('Y-m-d', ($dateTimestamp - 86400 * abs($dayDiff))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
390 $validOverrides[$prevDay] = $validOverrides[$prevDay] ?? [];
391 if (strtotime($slot['start']) < strtotime($slot['end'])) {
392 array_push($validOverrides[$prevDay], [
393 'start' => $slot['start'],
394 'end' => $slot['end']
395 ]);
396 unset($slots[$index]);
397 continue;
398 }
399 if ($slot['start'] != '00:00') {
400 array_push($validOverrides[$prevDay], [
401 'start' => $slot['start'],
402 'end' => '00:00'
403 ]);
404 $slot['start'] = '00:00';
405 }
406 } else {
407 if (strtotime($slot['start']) > strtotime($slot['end'])) {
408 if ($slot['end'] != '00:00') {
409 $nextDayIndex = 1;
410 $nextDay = gmdate('Y-m-d', ($dateTimestamp + 86400)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
411 $validOverrides[$nextDay] = $validOverrides[$nextDay] ?? [];
412 array_unshift($validOverrides[$nextDay], [
413 'start' => '00:00',
414 'end' => $slot['end']
415 ]);
416 $slot['end'] = '00:00';
417 }
418 }
419 }
420
421 if ($slot['start'] == '00:00' && $slot['end'] == '00:00') {
422 unset($slots[$index]);
423 continue;
424 }
425 $slots[$index] = $slot;
426 }
427
428 if ($slots) {
429 $validOverrides[$date] = $validOverrides[$date] ?? [];
430 $validOverrides[$date] = array_merge($validOverrides[$date], $slots);
431 }
432 }
433
434 return $validOverrides;
435 }
436
437 public static function getDateOverrideDays($overrides, $fromTimeZone, $toTimeZone = 'UTC')
438 {
439 if (!$overrides || !$fromTimeZone || !$toTimeZone) {
440 return [];
441 }
442
443 $overrideDays = [];
444 foreach ($overrides as $date => $slots) {
445 $dayStart = gmdate('Y-m-d 00:00:00', strtotime($date));
446 $dayEnd = gmdate('Y-m-d 24:00:00', strtotime($date));
447
448 $convertedStart = DateTimeHelper::convertToTimeZone($dayStart, $fromTimeZone, $toTimeZone);
449 $convertedEnd = DateTimeHelper::convertToTimeZone($dayEnd, $fromTimeZone, $toTimeZone);
450
451 $startDate = gmdate('Y-m-d', strtotime($convertedStart));
452 $endDate = gmdate('Y-m-d', strtotime($convertedEnd));
453
454 if (strtotime($dayStart) == strtotime($convertedStart)) {
455 $overrideDays[$startDate] = [
456 'start' => '00:00',
457 'end' => '24:00'
458 ];
459 continue;
460 }
461
462 if (isset($overrideDays[$startDate])) {
463 $overrideDays[$startDate] = [
464 'start' =>'00:00',
465 'end' =>'24:00'
466 ];
467 } else {
468 $overrideDays[$startDate] = [
469 'start' => gmdate('H:i', strtotime($convertedStart)),
470 'end' => '24:00'
471 ];
472 }
473
474 if (isset($overrideDays[$endDate])) {
475 $overrideDays[$endDate] = [
476 'start' => '00:00',
477 'end' => '24:00'
478 ];
479 } else {
480 $overrideDays[$endDate] = [
481 'start' => '00:00',
482 'end' => gmdate('H:i', strtotime($convertedEnd))
483 ];
484 }
485 }
486 return $overrideDays;
487 }
488 }
489