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

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