PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.22
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.22
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 1.5.22, at app/Services/AvailabilityService.php

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