PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.2.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.2.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 2.2.0, at app/Models/CalendarSlot.php

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