PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.6.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.6.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 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.6.0, at app/Models/CalendarSlot.php

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