PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.20
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.20
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 / Models / CalendarSlot.php

CalendarSlot.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.20, at app/Models/CalendarSlot.php

1,174 lines 37.5 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\Models;
4
5 use FluentBooking\App\Models\Model;
6 use FluentBooking\App\Services\SanitizeService;
7 use FluentBooking\App\Services\AvailabilityService;
8 use FluentBooking\App\Services\BookingFieldService;
9 use FluentBooking\App\Services\DateTimeHelper;
10 use FluentBooking\App\Services\Helper;
11 use FluentBooking\App\Services\BookingService;
12 use FluentBooking\App\Services\CurrenciesHelper;
13 use FluentBooking\App\Services\EditorShortCodeParser;
14 use FluentBooking\App\Services\LandingPage\LandingPageHandler;
15 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
16 use FluentBooking\App\Services\LocationService;
17 use FluentBooking\Framework\Support\Arr;
18
19 class CalendarSlot extends Model
20 {
21 protected $table = 'fcal_calendar_events';
22
23 protected $guarded = ['id'];
24
25 protected $fillable = [
26 'user_id',
27 'hash',
28 'calendar_id',
29 'duration',
30 'title',
31 'slug',
32 'media_id',
33 'description',
34 'settings',
35 'availability_type',
36 'availability_id',
37 'status',
38 'type',
39 'color_schema',
40 'location_type',
41 'location_heading',
42 'location_settings',
43 'event_type',
44 'is_display_spots',
45 'max_book_per_slot',
46 'created_at',
47 'updated_at',
48 ];
49
50 public static function boot()
51 {
52 parent::boot();
53
54 static::creating(function ($model) {
55 if (empty($model->user_id)) {
56 $model->user_id = get_current_user_id();
57 }
58
59 if (empty($model->hash)) {
60 $model->hash = md5(wp_generate_uuid4() . time());
61 }
62 });
63 }
64
65 public function setSettingsAttribute($settings)
66 {
67 $originalSettings = $this->getOriginal('settings');
68
69 $originalSettings = \maybe_unserialize($originalSettings);
70
71 foreach ($settings as $key => $value) {
72 $originalSettings[$key] = $value;
73 }
74
75 $this->attributes['settings'] = \maybe_serialize($originalSettings);
76 }
77
78 public function getSettingsAttribute($settings)
79 {
80 return \maybe_unserialize($settings);
81 }
82
83 public function setLocationSettingsAttribute($locationSettings)
84 {
85 $this->attributes['location_settings'] = \maybe_serialize($locationSettings);
86 }
87
88 public function getLocationSettingsAttribute($locationSettings)
89 {
90 return \maybe_unserialize($locationSettings);
91 }
92
93 public function calendar()
94 {
95 return $this->belongsTo(Calendar::class, 'calendar_id');
96 }
97
98 public function bookings()
99 {
100 return $this->hasMany(Booking::class, 'event_id');
101 }
102
103 public function user()
104 {
105 return $this->belongsTo(User::class, 'user_id');
106 }
107
108 public function event_metas()
109 {
110 return $this->hasMany(Meta::class, 'object_id', 'id')
111 ->whereIn('object_type', ['calendar_event', 'integration']);
112 }
113
114 public function isGroup()
115 {
116 return $this->event_type == 'group';
117 }
118
119 public function isSingleEvent()
120 {
121 return $this->event_type == 'single_event';
122 }
123
124 public function isGroupEvent()
125 {
126 return $this->event_type == 'group_event';
127 }
128
129 public function isRoundRobin()
130 {
131 return $this->event_type == 'round_robin';
132 }
133
134 public function isCollective()
135 {
136 return $this->event_type == 'collective';
137 }
138
139 public function isTeamEvent()
140 {
141 return $this->isRoundRobin() || $this->isCollective();
142 }
143
144 public function isOneOffEvent()
145 {
146 return $this->isSingleEvent() || $this->isGroupEvent();
147 }
148
149 public function isMultiHostEvent()
150 {
151 return $this->isTeamEvent() || $this->isOneOffEvent();
152 }
153
154 public function isMultiHostsEvent()
155 {
156 return $this->isCollective() || $this->isOneOffEvent();
157 }
158
159 public function isMultiGuestEvent()
160 {
161 return $this->isGroup() || $this->isGroupEvent();
162 }
163
164 public function isProEvent()
165 {
166 return $this->isGroup() || $this->isTeamEvent() || $this->isOneOffEvent();
167 }
168
169 public function allowMultiBooking()
170 {
171 return Arr::isTrue($this->settings, 'multiple_booking.enabled');
172 }
173
174 public function multiBookingLimit()
175 {
176 return Arr::get($this->settings, 'multiple_booking.limit', 5);
177 }
178
179 public function getAuthorProfile($public = true, $userID = null)
180 {
181 $userID = $userID ?: $this->user_id;
182
183 $user = get_user_by('id', $userID);
184 if (!$user) {
185 return false;
186 }
187
188 $name = trim($user->first_name . ' ' . $user->last_name);
189
190 if (!$name) {
191 $name = $user->display_name;
192 }
193
194 $data = [
195 'ID' => $user->ID,
196 'name' => $name,
197 'avatar' => Helper::fluentBookingUserAvatar($user->ID, $user)
198 ];
199
200 if (!$public) {
201 $data['email'] = $user->user_email;
202 }
203
204 return $data;
205 }
206
207 public function getAuthorProfiles($public = true)
208 {
209 $teamMembers = [];
210 $teamMemberIds = $this->getHostIds();
211
212 foreach ($teamMemberIds as $teamMemberId) {
213 $calendar = Calendar::where('user_id', $teamMemberId)->where('type', 'simple')->first();
214 if ($calendar) {
215 $teamMembers[] = $calendar->getAuthorProfile($public);
216 }
217 }
218
219 return $teamMembers;
220 }
221
222 public function isLocationFieldRequired()
223 {
224 $locationSettings = Arr::get($this, 'location_settings');
225
226 if (count($locationSettings) > 1) {
227 return true;
228 }
229
230 return false;
231 }
232
233 public function isPhoneRequired()
234 {
235 if (count($this->location_settings) == 1) {
236 return Arr::get($this->location_settings, '0.type') == 'phone_guest';
237 }
238
239 return false;
240 }
241
242 public function isAddressRequired()
243 {
244 if (count($this->location_settings) == 1) {
245 return Arr::get($this->location_settings, '0.type') == 'in_person_guest';
246 }
247
248 return false;
249 }
250
251 public function isGuestFieldRequired()
252 {
253 return true;
254 }
255
256 public function getEventDefaultData($calendar)
257 {
258 $weeklySchedule = Helper::getWeeklyScheduleSchema();
259
260 $availability = AvailabilityService::maybeCreateAvailability($calendar, $weeklySchedule);
261
262 $defaultData = [
263 'title' => '30 Minute Meeting',
264 'calendar_id' => $calendar->id,
265 'user_id' => $calendar->user_id,
266 'status' => 'active',
267 'event_type' => 'single',
268 'description' => '',
269 'duration' => '30',
270 'color_schema' => '#0099ff',
271 'availability_type' => 'existing_schedule',
272 'availability_id' => (int)$availability->id,
273 'max_book_per_slot' => 1,
274 'is_display_spots' => false,
275 'location_settings' => [
276 [
277 'type' => 'in_person_guest',
278 'title' => 'In Person (Attendee Address)',
279 'description' => '',
280 'host_phone_number' => ''
281 ]
282 ],
283 'settings' => [
284 'schedule_type' => 'weekly_schedules',
285 'weekly_schedules' => $weeklySchedule,
286 'date_overrides' => [],
287 'range_type' => 'range_days',
288 'range_days' => 60,
289 'range_date_between' => ['', ''],
290 'schedule_conditions' => [
291 'value' => 4,
292 'unit' => 'hours'
293 ],
294 'buffer_time_before' => '0',
295 'buffer_time_after' => '0',
296 'slot_interval' => '',
297 'booking_title' => '',
298 'submit_button_text' => '',
299 'multiple_booking' => [
300 'enabled' => false,
301 'limit' => 5
302 ],
303 'booking_frequency' => [
304 'enabled' => false,
305 'limits' => [
306 ['unit' => 'per_day', 'value' => 5]
307 ]
308 ],
309 'booking_duration' => [
310 'enabled' => false,
311 'limits' => [
312 ['unit' => 'per_day', 'value' => 120]
313 ]
314 ],
315 'can_not_cancel' => [
316 'enabled' => false,
317 'message' => 'Sorry! you can not cancel this',
318 'type' => 'always',
319 'condition' => [
320 'unit' => 'minutes',
321 'value' => 30
322 ]
323 ],
324 'can_not_reschedule' => [
325 'enabled' => false,
326 'message' => 'Sorry! you can not reschedule this',
327 'type' => 'always',
328 'condition' => [
329 'unit' => 'minutes',
330 'value' => 30
331 ]
332 ],
333 'custom_redirect' => [
334 'enabled' => false,
335 'redirect_url' => '',
336 'is_query_string' => 'no',
337 'query_string' => ''
338 ],
339 'multi_duration' => [
340 'enabled' => false,
341 'default_duration' => '',
342 'available_durations' => []
343 ],
344 'lock_timezone' => [
345 'enabled' => false,
346 'timezone' => ''
347 ],
348 'requires_confirmation' => [
349 'enabled' => false,
350 'type' => 'always',
351 'condition' => [
352 'unit' => 'minutes',
353 'value' => 30
354 ]
355 ],
356 ]
357 ];
358
359 return $defaultData;
360 }
361
362 public function getEventSchema($calendar)
363 {
364 $userCalendarId = $calendar->type == 'simple' ? $calendar->id : null;
365
366 $settingsSchema = $this->getSlotSettingsSchema($userCalendarId);
367
368 $schema = [
369 'title' => '',
370 'status' => 'active',
371 'description' => '',
372 'duration' => '30',
373 'color_schema' => '#0099ff',
374 'calendar' => $calendar,
375 'settings' => $settingsSchema,
376 'max_book_per_slot' => 1,
377 'location_settings' => [
378 [
379 'type' => '',
380 'title' => '',
381 'description' => '',
382 'host_phone_number' => ''
383 ]
384 ]
385 ];
386
387 return $schema;
388 }
389
390 public function getSlotSettingsSchema($calendarId = null)
391 {
392 $calendarEvent = $calendarId ? CalendarSlot::where('calendar_id', $calendarId)->first() : null;
393
394 return [
395 'schedule_type' => 'weekly_schedules',
396 'weekly_schedules' => Helper::getWeeklyScheduleSchema(),
397 'date_overrides' => [],
398 'range_type' => 'range_days',
399 'range_days' => 60,
400 'range_date_between' => ['', ''],
401 'schedule_conditions' => [
402 'value' => 4,
403 'unit' => 'hours'
404 ],
405 'location_fields' => $this->getLocationFields($calendarEvent)
406 ];
407 }
408
409 public function getNotifications($isEdit = false)
410 {
411 $statuses = $this->getMeta('email_notifications');
412
413 if ($statuses) {
414
415 $defaults = Helper::getDefaultEmailNotificationSettings();
416
417 foreach ($defaults as $key => $default) {
418 if (isset($statuses[$key])) {
419 if ($isEdit) {
420 $statuses[$key]['title'] = $default['title'];
421 }
422 $emailBody = str_replace('fluent-booking-pro/core', 'fluent-booking', $statuses[$key]['email']['body']);
423 $statuses[$key]['email']['body'] = $emailBody;
424 }
425 }
426
427 if (!Arr::get($statuses, 'rescheduled_by_host')) {
428 $statuses['rescheduled_by_host'] = $defaults['rescheduled_by_host'];
429 }
430
431 if (!Arr::get($statuses, 'rescheduled_by_attendee')) {
432 $statuses['rescheduled_by_attendee'] = $defaults['rescheduled_by_attendee'];
433 }
434
435 if (!Arr::get($statuses, 'booking_request_host')) {
436 $statuses['booking_request_host'] = $defaults['booking_request_host'];
437 }
438
439 if (!Arr::get($statuses, 'booking_request_attendee')) {
440 $statuses['booking_request_attendee'] = $defaults['booking_request_attendee'];
441 }
442
443 if (!Arr::get($statuses, 'declined_by_host')) {
444 $statuses['declined_by_host'] = $defaults['declined_by_host'];
445 }
446
447 return $statuses;
448 }
449
450 return Helper::getDefaultEmailNotificationSettings();
451 }
452
453 public function setNotifications($notifications)
454 {
455 $this->updateMeta('email_notifications', $notifications);
456 }
457
458 public function getBookingFields()
459 {
460 return BookingFieldService::getBookingFields($this);
461 }
462
463 public function setBookingFields($bookingFields)
464 {
465 return $this->updateMeta('booking_fields', $bookingFields);
466 }
467
468 public function getScheduleTimezone($hostId = null)
469 {
470 if ($hostId && !$this->isTeamCommonSchedule()) {
471 $schedule = AvailabilityService::getDefaultSchedule($hostId);
472 return Arr::get($schedule, 'value.timezone', 'UTC');
473 }
474
475 if ($this->availability_type == 'existing_schedule') {
476 $schedule = Availability::findOrFail($this->availability_id);
477 return Arr::get($schedule, 'value.timezone', 'UTC');
478 }
479
480 return $this->calendar->author_timezone;
481 }
482
483 public function isMultiDurationEnabled()
484 {
485 return Arr::isTrue($this->settings, 'multi_duration.enabled');
486 }
487
488 public function getDuration($duration = null)
489 {
490 if ($this->isMultiDurationEnabled()) {
491 if (in_array($duration, Arr::get($this->settings, 'multi_duration.available_durations', []))) {
492 return $duration;
493 } else {
494 return Arr::get($this->settings, 'multi_duration.default_duration', '');
495 }
496 }
497
498 return $this->duration;
499 }
500
501 public function getDefaultDuration()
502 {
503 if ($this->isMultiDurationEnabled()) {
504 return Arr::get($this->settings, 'multi_duration.default_duration', '');
505 }
506
507 return $this->duration;
508 }
509
510 public function getAvailableDurations()
511 {
512 if ($this->isMultiDurationEnabled()) {
513 $durationLookup = Helper::getDurationLookup(true);
514 $availableDurations = Arr::get($this->settings, 'multi_duration.available_durations', []);
515
516 return array_map(function ($duration) use ($durationLookup) {
517 return $durationLookup[$duration];
518 }, $availableDurations);
519 }
520
521 $durationLookup = Helper::getDurationLookup();
522
523 $duration = $durationLookup[$this->duration] ?? Helper::formatDuration($this->duration);
524
525 return [$duration];
526 }
527
528 public function getDescription()
529 {
530 if ($this->description) {
531 return $this->description;
532 }
533
534 if ($this->isMultiDurationEnabled()) {
535 return __('Choose your duration and book a meeting with me', 'fluent-booking');
536 }
537
538 // translators: %d is the duration of the meeting in minutes
539 return sprintf(__('Book a meeting with me for %d minutes', 'fluent-booking'), $this->duration);
540 }
541
542 public function getSlotInterval($duration = null)
543 {
544 $duration = $duration ?: $this->duration;
545
546 $interval = Arr::get($this->settings, 'slot_interval', '');
547
548 $slotInterval = empty($interval) ? $duration : intval($interval);
549
550 return $slotInterval;
551 }
552
553 public function isReserveTime()
554 {
555 return Arr::isTrue($this->settings, 'reserve_time', false);
556 }
557
558 public function getAvailableTimes()
559 {
560 return Arr::get($this->settings, 'available_times', []);
561 }
562
563 public function getTotalBufferTime()
564 {
565 $bufferTimeBefore = Arr::get($this->settings, 'buffer_time_before', 0);
566 $bufferTimeAfter = Arr::get($this->settings, 'buffer_time_after', 0);
567
568 return $bufferTimeBefore + $bufferTimeAfter;
569 }
570
571 public function getMaxBookableDateTime($startDate, $timeZone = 'UTC', $format = 'Y-m-d 23:59:59')
572 {
573 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
574
575 $lastDay = gmdate('Y-m-t 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
576
577 if ($timeZone != 'UTC') {
578 $lastDay = DateTimeHelper::convertToTimeZone($lastDay, $timeZone, 'UTC', $format);
579 }
580
581 if ($rangeType == 'range_indefinite') {
582 return $lastDay;
583 }
584
585 $maxLookupDate = $this->getMaxLookUpDate();
586
587 $maxDate = DateTimeHelper::convertToTimeZone($maxLookupDate, $this->calendar->author_timezone, 'UTC');
588
589 if (strtotime($maxDate) > strtotime($lastDay)) {
590 return $lastDay;
591 }
592
593 return $maxDate;
594 }
595
596 public function getMinBookableDateTime($startDate = null, $timeZone = null)
597 {
598 $startDate = $startDate ?: gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
599
600 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
601
602 if ($rangeType == 'range_date_between') {
603 $range = Arr::get($this->settings, 'range_date_between', []);
604 if (is_array($range) && count(array_filter($range)) == 2) {
605 if (strtotime($range[0]) >= strtotime($startDate)) {
606 $startDate = gmdate('Y-m-d H:i:s', strtotime($range[0])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
607 }
608 }
609 }
610
611 $totalCutStamp = DateTimeHelper::getTimestamp() + $this->getCutoutSeconds();
612
613 if (strtotime($startDate) < $totalCutStamp) {
614 $startDate = gmdate('Y-m-d H:i:s', $totalCutStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
615 }
616
617 return $startDate;
618 }
619
620 public function getMaxLookUpDate()
621 {
622 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
623
624 if ($rangeType == 'range_indefinite') {
625 return false;
626 }
627
628 if ($rangeType == 'range_date_between') {
629 $range = Arr::get($this->settings, 'range_date_between', []);
630 if (is_array($range) && count(array_filter($range)) == 2) {
631 return gmdate('Y-m-d 23:59:59', strtotime($range[1])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
632 }
633 }
634
635 $rangeDays = Arr::get($this->settings, 'range_days', 60) ?: 60;
636
637 return gmdate('Y-m-d 23:59:59', time() + $rangeDays * DAY_IN_SECONDS); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
638 }
639
640 public function getMinLookUpDate($timeZone = 'UTC')
641 {
642 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
643
644 $minDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
645
646 if ($rangeType == 'range_date_between') {
647 $range = Arr::get($this->settings, 'range_date_between', []);
648 if (is_array($range) && count(array_filter($range)) == 2) {
649 $minDate = gmdate('Y-m-d H:i:s', strtotime($range[0])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
650 }
651 }
652
653 if ($timeZone != 'UTC') {
654 $minDate = DateTimeHelper::convertToTimeZone($minDate, $timeZone, 'UTC');
655 }
656
657 return $minDate;
658 }
659
660 public function getCutoutSeconds()
661 {
662 $conditions = Arr::get($this->settings, 'schedule_conditions', []);
663
664 if (!$conditions || empty($conditions['unit'])) {
665 return 0;
666 }
667
668 return strtotime('+' . $conditions['value'] . ' ' . $conditions['unit'], 0) - strtotime('+0 seconds', 0);
669 }
670
671 public function getHostIds($hostId = null)
672 {
673 if ($hostId) {
674 return [$hostId];
675 }
676
677 if ($this->isMultiHostEvent()) {
678 return Arr::get($this->settings, 'team_members', []);
679 }
680
681 return [$this->user_id];
682 }
683
684 public function getMaxBookingPerSlot()
685 {
686 return $this->max_book_per_slot;
687 }
688
689 public function getPublicUrl()
690 {
691 $calendar = $this->calendar;
692 if (!$calendar) {
693 return false;
694 }
695
696 $baseUr = $calendar->getLandingPageUrl();
697
698 if (!$baseUr) {
699 return '';
700 }
701
702 if (defined('FLUENT_BOOKING_LANDING_SLUG')) {
703 return $baseUr . '/' . $this->slug;
704 }
705
706 return $baseUr . '&event=' . $this->slug;
707 }
708
709 public function getMeta($key, $default = null)
710 {
711 $meta = Meta::where('object_type', 'calendar_event')
712 ->where('object_id', $this->id)
713 ->where('key', $key)
714 ->first();
715
716 if (!$meta) {
717 return $default;
718 }
719
720 return $meta->value;
721 }
722
723 public function updateMeta($key, $value)
724 {
725 $exist = Meta::where('object_type', 'calendar_event')
726 ->where('object_id', $this->id)
727 ->where('key', $key)
728 ->first();
729
730 if ($exist) {
731 $exist->value = $value;
732 $exist->save();
733 } else {
734 $exist = Meta::create([
735 'object_type' => 'calendar_event',
736 'object_id' => $this->id,
737 'key' => $key,
738 'value' => $value
739 ]);
740 }
741
742 return $exist;
743 }
744
745 public function getLocationFields($calendarEvent = null)
746 {
747 return apply_filters('fluent_booking/get_location_fields', [
748 'conferencing' => [
749 'label' => __('Conferencing', 'fluent-booking'),
750 'options' => [
751 'google_meet' => [
752 'title' => __('Google Meet (Pro)', 'fluent-booking'),
753 'disabled' => true,
754 'location_type' => 'conferencing'
755 ],
756 'ms_teams' => [
757 'title' => __('MS Teams (Pro)', 'fluent-booking'),
758 'disabled' => true,
759 'location_type' => 'conferencing'
760 ],
761 'zoom_meeting' => [
762 'title' => __('Zoom Video (Pro)', 'fluent-booking'),
763 'disabled' => true,
764 'location_type' => 'conferencing'
765 ],
766 ],
767 ],
768 'in_person' => [
769 'label' => __('In Person', 'fluent-booking'),
770 'options' => [
771 'in_person_guest' => [
772 'title' => __('In Person (Attendee Address)', 'fluent-booking'),
773 ],
774 'in_person_organizer' => [
775 'title' => __('In Person (Organizer Address)', 'fluent-booking'),
776 ],
777 ],
778 ],
779 'phone' => [
780 'label' => __('Phone', 'fluent-booking'),
781 'options' => [
782 'phone_guest' => [
783 'title' => __('Attendee Phone Number', 'fluent-booking'),
784 ],
785 'phone_organizer' => [
786 'title' => __('Organizer Phone Number', 'fluent-booking'),
787 ],
788 ],
789 ],
790 'online' => [
791 'label' => __('Online', 'fluent-booking'),
792 'options' => [
793 'online_meeting' => [
794 'title' => __('Online Meeting', 'fluent-booking'),
795 ],
796 ],
797 ],
798 'other' => [
799 'label' => __('Other', 'fluent-booking'),
800 'options' => [
801 'custom' => [
802 'title' => __('Custom', 'fluent-booking'),
803 ],
804 ],
805 ],
806 ], $calendarEvent ?: $this);
807 }
808
809 public function isConfirmationEnabled()
810 {
811 return Arr::isTrue($this->settings, 'requires_confirmation.enabled');
812 }
813
814 public function isConfirmationRequired($bookingStartTime, $bookingCreatedTime = null)
815 {
816 if (!$this->isConfirmationEnabled() || !is_string($bookingStartTime)) {
817 return false;
818 }
819
820 $type = Arr::get($this->settings, 'requires_confirmation.type', 'always');
821 if ($type == 'always') {
822 return true;
823 }
824
825 $bookingStartTime = strtotime($bookingStartTime);
826 $bookingCreatedTime = $bookingCreatedTime ? strtotime($bookingCreatedTime) : time();
827
828 $conditionUnit = Arr::get($this->settings, 'requires_confirmation.condition.unit', 'minutes');
829 $conditionValue = Arr::get($this->settings, 'requires_confirmation.condition.value', 0);
830
831 $conditionTime = $conditionValue * 60;
832 if ($conditionUnit == 'hours') {
833 $conditionTime = $conditionTime * 60;
834 }
835
836 return $bookingStartTime - $bookingCreatedTime < $conditionTime;
837 }
838
839 public function getCanNotCancelSettings()
840 {
841 if (!isset($this->settings['can_not_cancel'])) {
842 $enabled = Arr::get($this->settings, 'can_cancel') == 'no' ? true : false;
843 return [
844 'enabled' => $enabled,
845 'type' => 'always',
846 ];
847 }
848 return Arr::get($this->settings, 'can_not_cancel', []);
849 }
850
851 public function getCanNotRescheduleSettings()
852 {
853 if (!isset($this->settings['can_not_reschedule'])) {
854 $enabled = Arr::get($this->settings, 'can_reschedule') == 'no' ? true : false;
855 return [
856 'enabled' => $enabled,
857 'type' => 'always',
858 ];
859 }
860 return Arr::get($this->settings, 'can_not_reschedule', []);
861 }
862
863 public function defaultLocationHtml()
864 {
865 if (empty($this->location_settings)) {
866 return '';
867 }
868
869 $default = Arr::get($this, 'location_settings');
870 if (!$default) {
871 return '';
872 }
873
874 return LocationService::getLocationIconHeadingHtml($default, $this);
875 }
876
877 public function defaultPaymentIcon($currency, $amount)
878 {
879 $svg = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M256 640v192h640V384H768v-64h150.976c14.272 0 19.456 1.472 24.64 4.288a29.056 29.056 0 0 1 12.16 12.096c2.752 5.184 4.224 10.368 4.224 24.64v493.952c0 14.272-1.472 19.456-4.288 24.64a29.056 29.056 0 0 1-12.096 12.16c-5.184 2.752-10.368 4.224-24.64 4.224H233.024c-14.272 0-19.456-1.472-24.64-4.288a29.056 29.056 0 0 1-12.16-12.096c-2.688-5.184-4.224-10.368-4.224-24.576V640h64z"></path><path fill="currentColor" d="M768 192H128v448h640V192zm64-22.976v493.952c0 14.272-1.472 19.456-4.288 24.64a29.056 29.056 0 0 1-12.096 12.16c-5.184 2.752-10.368 4.224-24.64 4.224H105.024c-14.272 0-19.456-1.472-24.64-4.288a29.056 29.056 0 0 1-12.16-12.096C65.536 682.432 64 677.248 64 663.04V169.024c0-14.272 1.472-19.456 4.288-24.64a29.056 29.056 0 0 1 12.096-12.16C85.568 129.536 90.752 128 104.96 128h685.952c14.272 0 19.456 1.472 24.64 4.288a29.056 29.056 0 0 1 12.16 12.096c2.752 5.184 4.224 10.368 4.224 24.64z"></path><path fill="currentColor" d="M448 576a160 160 0 1 1 0-320 160 160 0 0 1 0 320zm0-64a96 96 0 1 0 0-192 96 96 0 0 0 0 192z"></path></svg>';
880 $html = '<span class="fcal_slot_payment_icon">' . $svg . $currency . $amount . '</span>';
881 return $html;
882 }
883
884 public function isPaymentEnabled($duration = null)
885 {
886 if ($this->isMultiDurationEnabled()) {
887 $paymentSettings = $this->getPaymentSettings();
888 if (Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes') {
889 $duration = $duration ?? $this->getDefaultDuration();
890 if (!Arr::get($paymentSettings, 'multi_payment_items.' . $duration . '.value')) {
891 return false;
892 }
893 }
894 }
895
896 return $this->type == 'paid' && Helper::isPaymentEnabled();
897 }
898
899 public function isWooEnabled()
900 {
901 return $this->type == 'woo' && defined('WC_PLUGIN_FILE');
902 }
903
904 public function getPaymentItems($duration = null)
905 {
906 $paymentSettings = $this->getPaymentSettings();
907
908 $isMultiEnabled = Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes';
909
910 if ($this->isMultiDurationEnabled() && $isMultiEnabled) {
911 $duration = $duration ?? $this->getDefaultDuration();
912 return [Arr::get($paymentSettings, 'multi_payment_items.' . $duration)];
913 }
914
915 return Arr::get($paymentSettings, 'items', []);
916 }
917
918 public function getPricingTotal()
919 {
920 if (!$this->isPaymentEnabled()) {
921 return 0;
922 }
923
924 $total = 0;
925 $items = $this->getPaymentItems();
926 foreach ($items as $item) {
927 $total += (int)$item['value'];
928 }
929
930 return $total;
931 }
932
933 public function getWooProductPrice()
934 {
935 $paymentSettings = $this->getPaymentSettings();
936
937 $productId = $paymentSettings['woo_product_id'];
938
939 if (Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes') {
940 $duration = $this->getDefaultDuration();
941 $productId = Arr::get($paymentSettings, 'multi_payment_woo_ids.' . $duration);
942 }
943
944 $price = 0;
945 $product = wc_get_product($productId);
946 if ($product) {
947 $price = $product->get_price();
948 }
949
950 return $price;
951 }
952
953 public function getWooProductPriceByDuration($productIds = [])
954 {
955 $productPrices = [];
956
957 foreach ($productIds as $duration => $productId) {
958 $product = wc_get_product($productId);
959 if ($product) {
960 $productPrices[$duration] = [
961 'value' => $product->get_price()
962 ];
963 }
964 }
965
966 return $productPrices;
967 }
968
969 public function getPaymentHtml()
970 {
971 $paymentHtml = '';
972
973 $driver = Arr::get($this->getPaymentSettings(), 'driver');
974
975 if ($driver == 'native' && $this->isPaymentEnabled()) {
976 $currency = CurrenciesHelper::getGlobalCurrencySign();
977 $totalPayment = $this->getPricingTotal();
978 $paymentHtml = $this->defaultPaymentIcon($currency, $totalPayment);
979 }
980
981 if ($driver == 'woo' && $this->isWooEnabled()) {
982 $currency = get_woocommerce_currency_symbol();
983 $totalPayment = $this->getWooProductPrice();
984 $paymentHtml = $this->defaultPaymentIcon($currency, $totalPayment);
985 }
986
987 return $paymentHtml;
988 }
989
990 public function isTeamDefaultSchedule()
991 {
992 if ($this->isTeamEvent()) {
993 if (!Arr::isTrue($this->settings, 'common_schedule', false)) {
994 return true;
995 }
996 }
997 return false;
998 }
999
1000 public function isTeamCommonSchedule()
1001 {
1002 if ($this->isTeamEvent()) {
1003 if (Arr::isTrue($this->settings, 'common_schedule', false)) {
1004 return true;
1005 }
1006 }
1007 return false;
1008 }
1009
1010 public function isRoundRobinDefaultSchedule()
1011 {
1012 return $this->isRoundRobin() && $this->isTeamDefaultSchedule();
1013 }
1014
1015 public function isRoundRobinCommonSchedule()
1016 {
1017 return $this->isRoundRobin() && $this->isTeamCommonSchedule();
1018 }
1019
1020 private function getProcessedWeeklySlots($schedule)
1021 {
1022 $scheduleData = Arr::get($schedule, 'value.weekly_schedules', []);
1023 $scheduleTimezone = Arr::get($schedule, 'value.timezone', 'UTC');
1024 $schedule = SanitizeService::weeklySchedules($scheduleData, 'UTC', $scheduleTimezone);
1025 return AvailabilityService::getUtcWeeklySchedules($schedule, $scheduleTimezone);
1026 }
1027
1028 private function getProcessedDateOverrides($schedule)
1029 {
1030 $scheduleData = Arr::get($schedule, 'value.date_overrides', []);
1031 $scheduleTimezone = Arr::get($schedule, 'value.timezone', 'UTC');
1032 $schedule = SanitizeService::slotDateOverrides($scheduleData, 'UTC', $scheduleTimezone);
1033 $overrideSlots = AvailabilityService::getUtcDateOverrides($schedule, $scheduleTimezone);
1034 $overrideDays = AvailabilityService::getDateOverrideDays($schedule, $scheduleTimezone);
1035 return [$overrideSlots, $overrideDays];
1036 }
1037
1038 public function getWeeklySlots($hostId = null)
1039 {
1040 if ($hostId && !$this->isTeamCommonSchedule()) {
1041 $schedule = AvailabilityService::getDefaultSchedule($hostId);
1042 return $this->getProcessedWeeklySlots($schedule);
1043 }
1044
1045 if ($this->availability_type === 'existing_schedule') {
1046 $schedule = Availability::findOrFail($this->availability_id);
1047 return $this->getProcessedWeeklySlots($schedule);
1048 }
1049
1050 $scheduleData = Arr::get($this->settings, 'weekly_schedules', []);
1051 $schedule = SanitizeService::weeklySchedules($scheduleData, 'UTC', $this->calendar->author_timezone);
1052 return AvailabilityService::getUtcWeeklySchedules($schedule, $this->calendar->author_timezone);
1053 }
1054
1055 public function getDateOverrides($hostId = null)
1056 {
1057 if ($hostId && !$this->isTeamCommonSchedule()) {
1058 $schedule = AvailabilityService::getDefaultSchedule($hostId);
1059 return $this->getProcessedDateOverrides($schedule);
1060 }
1061
1062 if ($this->availability_type === 'existing_schedule') {
1063 $schedule = Availability::findOrFail($this->availability_id);
1064 return $this->getProcessedDateOverrides($schedule);
1065 }
1066
1067 $scheduleData = Arr::get($this->settings, 'date_overrides', []);
1068 $schedule = SanitizeService::slotDateOverrides($scheduleData, 'UTC', $this->calendar->author_timezone);
1069 $overrideSlots = AvailabilityService::getUtcDateOverrides($schedule, $this->calendar->author_timezone);
1070 $overrideDays = AvailabilityService::getDateOverrideDays($schedule, $this->calendar->author_timezone);
1071 return [$overrideSlots, $overrideDays];
1072 }
1073
1074 public function getHostIdsSortedByBookings($startDate, $hostId = null)
1075 {
1076 $hostIds = $this->getHostIds($hostId);
1077
1078 if (count($hostIds) <= 1) {
1079 return $hostIds;
1080 }
1081
1082 $hostBookings = [];
1083 foreach ($hostIds as $hostId) {
1084 $dayStart = gmdate('Y-m-d 00:00:00', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1085 $dayEnd = gmdate('Y-m-d 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1086 $hostBookings[$hostId] = Booking::getHostTotalBooking($this->id, [$hostId], [$dayStart, $dayEnd]);
1087 }
1088 usort($hostIds, function ($a, $b) use ($hostBookings) {
1089 return $hostBookings[$a] - $hostBookings[$b];
1090 });
1091
1092 return $hostIds;
1093 }
1094
1095 public function getPaymentSettings()
1096 {
1097 $settings = $this->getMeta('payment_settings', []);
1098
1099 $duration = $this->getDefaultDuration();
1100
1101 $defaults = [
1102 'enabled' => 'no',
1103 'multi_payment_enabled' => 'no',
1104 'stripe_enabled' => 'no',
1105 'paypal_enabled' => 'no',
1106 'driver' => 'native',
1107 'items' => [
1108 [
1109 'title' => __('Booking Fee', 'fluent-booking'),
1110 'value' => 100
1111 ]
1112 ],
1113 'woo_product_id' => '',
1114 'multi_payment_items' => [
1115 $duration => [
1116 'title' => __('Booking Fee', 'fluent-booking'),
1117 'value' => 0
1118 ]
1119 ],
1120 'multi_payment_woo_ids' => [
1121 $duration => ''
1122 ]
1123 ];
1124
1125 if (!$settings) {
1126 $settings = $defaults;
1127 }
1128
1129 return wp_parse_args($settings, $defaults);
1130 }
1131
1132 public function getCalendarEventsMeta()
1133 {
1134 $eventsMeta = Meta::where('object_id', $this->id)
1135 ->where('object_type', 'calendar_event')
1136 ->get();
1137
1138 return $eventsMeta;
1139 }
1140
1141 public function getIntegrationsMeta()
1142 {
1143 $integrationsMeta = Meta::where('object_id', $this->id)
1144 ->where('object_type', 'integration')
1145 ->get();
1146
1147 return $integrationsMeta;
1148 }
1149
1150 /**
1151 * Get the attributes that have been changed since last sync.
1152 *
1153 * @return array
1154 */
1155 public function getDirty()
1156 {
1157 $dirty = [];
1158 foreach ($this->attributes as $key => $value) {
1159 if (!in_array($key, $this->fillable)) {
1160 continue;
1161 }
1162
1163 if (!array_key_exists($key, $this->original)) {
1164 $dirty[$key] = $value;
1165 } elseif ($value !== $this->original[$key] &&
1166 !$this->originalIsNumericallyEquivalent($key)) {
1167 $dirty[$key] = $value;
1168 }
1169 }
1170
1171 return $dirty;
1172 }
1173 }
1174