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

1,163 lines 36.7 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();
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 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
546
547 if ($rangeType == 'range_date_between') {
548 $range = Arr::get($this->settings, 'range_date_between', []);
549 if (is_array($range) && count(array_filter($range)) == 2) {
550 if (strtotime($range[0]) >= strtotime($startDate)) {
551 $startDate = gmdate('Y-m-d H:i:s', strtotime($range[0])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
552 }
553 }
554 }
555
556 if ($timeZone) {
557 $startDate = DateTimeHelper::convertToTimeZone($startDate, $timeZone, 'UTC');
558 }
559
560 $totalCutStamp = DateTimeHelper::getTimestamp() + $this->getCutoutSeconds();
561
562 if (strtotime($startDate) < $totalCutStamp) {
563 $startDate = gmdate('Y-m-d H:i:s', $totalCutStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
564 }
565
566 return $startDate;
567 }
568
569 public function getMaxLookUpDate()
570 {
571 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
572
573 if ($rangeType == 'range_indefinite') {
574 return false;
575 }
576
577 if ($rangeType == 'range_date_between') {
578 $range = Arr::get($this->settings, 'range_date_between', []);
579 if (is_array($range) && count(array_filter($range)) == 2) {
580 return gmdate('Y-m-d 23:59:59', strtotime($range[1])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
581 }
582 }
583
584 $rangeDays = Arr::get($this->settings, 'range_days', 60) ?: 60;
585
586 return gmdate('Y-m-d 23:59:59', time() + $rangeDays * DAY_IN_SECONDS); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
587 }
588
589 public function getMinLookUpDate($timeZone = 'UTC')
590 {
591 $rangeType = Arr::get($this->settings, 'range_type', 'range_days');
592
593 $minDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
594
595 if ($rangeType == 'range_date_between') {
596 $range = Arr::get($this->settings, 'range_date_between', []);
597 if (is_array($range) && count(array_filter($range)) == 2) {
598 $minDate = gmdate('Y-m-d H:i:s', strtotime($range[0])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
599 }
600 }
601
602 if ($timeZone != 'UTC') {
603 $minDate = DateTimeHelper::convertToTimeZone($minDate, $timeZone, 'UTC');
604 }
605
606 return $minDate;
607 }
608
609 public function getCutoutSeconds()
610 {
611 $conditions = Arr::get($this->settings, 'schedule_conditions', []);
612
613 if (!$conditions || empty($conditions['unit'])) {
614 return 0;
615 }
616
617 return strtotime('+' . $conditions['value'] . ' ' . $conditions['unit'], 0) - strtotime('+0 seconds', 0);
618 }
619
620 public function getHostIds($hostId = null)
621 {
622 if ($hostId) {
623 return [$hostId];
624 }
625
626 if ($this->isMultiHostEvent()) {
627 return Arr::get($this->settings, 'team_members', []);
628 }
629
630 return [$this->user_id];
631 }
632
633 public function getMaxBookingPerSlot()
634 {
635 return $this->max_book_per_slot;
636 }
637
638 public function getPublicUrl()
639 {
640 $calendar = $this->calendar;
641 if (!$calendar) {
642 return false;
643 }
644
645 $baseUr = $calendar->getLandingPageUrl();
646
647 if (!$baseUr) {
648 return '';
649 }
650
651 if (defined('FLUENT_BOOKING_LANDING_SLUG')) {
652 return $baseUr . '/' . $this->slug;
653 }
654
655 return $baseUr . '&event=' . $this->slug;
656 }
657
658 public function getMeta($key, $default = null)
659 {
660 $meta = Meta::where('object_type', 'calendar_event')
661 ->where('object_id', $this->id)
662 ->where('key', $key)
663 ->first();
664
665 if (!$meta) {
666 return $default;
667 }
668
669 return $meta->value;
670 }
671
672 public function updateMeta($key, $value)
673 {
674 $exist = Meta::where('object_type', 'calendar_event')
675 ->where('object_id', $this->id)
676 ->where('key', $key)
677 ->first();
678
679 if ($exist) {
680 $exist->value = $value;
681 $exist->save();
682 } else {
683 $exist = Meta::create([
684 'object_type' => 'calendar_event',
685 'object_id' => $this->id,
686 'key' => $key,
687 'value' => $value
688 ]);
689 }
690
691 return $exist;
692 }
693
694 public function getLocationFields($calendarEvent = null)
695 {
696 return apply_filters('fluent_booking/get_location_fields', [
697 'conferencing' => [
698 'label' => __('Conferencing', 'fluent-booking'),
699 'options' => [
700 'google_meet' => [
701 'title' => __('Google Meet (Pro)', 'fluent-booking'),
702 'disabled' => true,
703 'location_type' => 'conferencing'
704 ],
705 'ms_teams' => [
706 'title' => __('MS Teams (Pro)', 'fluent-booking'),
707 'disabled' => true,
708 'location_type' => 'conferencing'
709 ],
710 'zoom_meeting' => [
711 'title' => __('Zoom Video (Pro)', 'fluent-booking'),
712 'disabled' => true,
713 'location_type' => 'conferencing'
714 ],
715 ],
716 ],
717 'in_person' => [
718 'label' => __('In Person', 'fluent-booking'),
719 'options' => [
720 'in_person_guest' => [
721 'title' => __('In Person (Attendee Address)', 'fluent-booking'),
722 ],
723 'in_person_organizer' => [
724 'title' => __('In Person (Organizer Address)', 'fluent-booking'),
725 ],
726 ],
727 ],
728 'phone' => [
729 'label' => __('Phone', 'fluent-booking'),
730 'options' => [
731 'phone_guest' => [
732 'title' => __('Attendee Phone Number', 'fluent-booking'),
733 ],
734 'phone_organizer' => [
735 'title' => __('Organizer Phone Number', 'fluent-booking'),
736 ],
737 ],
738 ],
739 'online' => [
740 'label' => __('Online', 'fluent-booking'),
741 'options' => [
742 'online_meeting' => [
743 'title' => __('Online Meeting', 'fluent-booking'),
744 ],
745 ],
746 ],
747 'other' => [
748 'label' => __('Other', 'fluent-booking'),
749 'options' => [
750 'custom' => [
751 'title' => __('Custom', 'fluent-booking'),
752 ],
753 ],
754 ],
755 ], $calendarEvent ?: $this);
756 }
757
758 public function isDisplaySpots()
759 {
760 return $this->is_display_spots == true;
761 }
762
763 public function isAdditionalGuestEnabled()
764 {
765 $guestField = BookingFieldService::getBookingFieldByName($this, 'guests');
766
767 return Arr::isTrue($guestField, 'enabled', false);
768 }
769
770 public function isConfirmationEnabled()
771 {
772 return Arr::isTrue($this->settings, 'requires_confirmation.enabled');
773 }
774
775 public function isConfirmationRequired($bookingStartTime, $bookingCreatedTime = null)
776 {
777 if (!$this->isConfirmationEnabled() || !is_string($bookingStartTime)) {
778 return false;
779 }
780
781 $type = Arr::get($this->settings, 'requires_confirmation.type', 'always');
782 if ($type == 'always') {
783 return true;
784 }
785
786 $bookingStartTime = strtotime($bookingStartTime);
787 $bookingCreatedTime = $bookingCreatedTime ? strtotime($bookingCreatedTime) : time();
788
789 $conditionUnit = Arr::get($this->settings, 'requires_confirmation.condition.unit', 'minutes');
790 $conditionValue = Arr::get($this->settings, 'requires_confirmation.condition.value', 0);
791
792 $conditionTime = $conditionValue * 60;
793 if ($conditionUnit == 'hours') {
794 $conditionTime = $conditionTime * 60;
795 }
796
797 return $bookingStartTime - $bookingCreatedTime < $conditionTime;
798 }
799
800 public function getCanNotCancelSettings()
801 {
802 if (!isset($this->settings['can_not_cancel'])) {
803 $enabled = Arr::get($this->settings, 'can_cancel') == 'no' ? true : false;
804 return [
805 'enabled' => $enabled,
806 'type' => 'always',
807 ];
808 }
809 return Arr::get($this->settings, 'can_not_cancel', []);
810 }
811
812 public function getCanNotRescheduleSettings()
813 {
814 if (!isset($this->settings['can_not_reschedule'])) {
815 $enabled = Arr::get($this->settings, 'can_reschedule') == 'no' ? true : false;
816 return [
817 'enabled' => $enabled,
818 'type' => 'always',
819 ];
820 }
821 return Arr::get($this->settings, 'can_not_reschedule', []);
822 }
823
824 public function defaultLocationHtml()
825 {
826 if (empty($this->location_settings)) {
827 return '';
828 }
829
830 $default = Arr::get($this, 'location_settings');
831 if (!$default) {
832 return '';
833 }
834
835 return LocationService::getLocationIconHeadingHtml($default, $this);
836 }
837
838 public function defaultPaymentIcon($currency, $amount)
839 {
840 $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>';
841 $html = '<span class="fcal_slot_payment_icon">' . $svg . $currency . $amount . '</span>';
842 return $html;
843 }
844
845 public function isPaymentEnabled($duration = null)
846 {
847 if ($this->isMultiDurationEnabled()) {
848 $paymentSettings = $this->getPaymentSettings();
849 if (Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes') {
850 $duration = $duration ?? $this->getDefaultDuration();
851 if (!Arr::get($paymentSettings, 'multi_payment_items.' . $duration . '.value')) {
852 return false;
853 }
854 }
855 }
856
857 return $this->type == 'paid' && Helper::isPaymentEnabled();
858 }
859
860 public function isWooEnabled()
861 {
862 return $this->type == 'woo' && defined('WC_PLUGIN_FILE');
863 }
864
865 public function getPaymentItems($duration = null)
866 {
867 $paymentSettings = $this->getPaymentSettings();
868
869 $isMultiEnabled = Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes';
870
871 if ($this->isMultiDurationEnabled() && $isMultiEnabled) {
872 $duration = $duration ?? $this->getDefaultDuration();
873 return [Arr::get($paymentSettings, 'multi_payment_items.' . $duration)];
874 }
875
876 return Arr::get($paymentSettings, 'items', []);
877 }
878
879 public function getPricingTotal()
880 {
881 if (!$this->isPaymentEnabled()) {
882 return 0;
883 }
884
885 $total = 0;
886 $items = $this->getPaymentItems();
887 foreach ($items as $item) {
888 $total += $item['value'];
889 }
890
891 return $total;
892 }
893
894 public function getWooProductPrice()
895 {
896 $paymentSettings = $this->getPaymentSettings();
897
898 $productId = $paymentSettings['woo_product_id'];
899
900 if (Arr::get($paymentSettings, 'multi_payment_enabled') == 'yes') {
901 $duration = $this->getDefaultDuration();
902 $productId = Arr::get($paymentSettings, 'multi_payment_woo_ids.' . $duration);
903 }
904
905 $price = 0;
906 $product = wc_get_product($productId);
907 if ($product) {
908 $price = $product->get_price();
909 }
910
911 return $price;
912 }
913
914 public function getWooProductPriceByDuration($productIds = [])
915 {
916 $productPrices = [];
917
918 foreach ($productIds as $duration => $productId) {
919 $product = wc_get_product($productId);
920 if ($product) {
921 $productPrices[$duration] = [
922 'value' => $product->get_price()
923 ];
924 }
925 }
926
927 return $productPrices;
928 }
929
930 public function getPaymentHtml()
931 {
932 $paymentHtml = '';
933
934 $driver = Arr::get($this->getPaymentSettings(), 'driver');
935
936 if ($driver == 'native' && $this->isPaymentEnabled()) {
937 $currency = CurrenciesHelper::getGlobalCurrencySign();
938 $totalPayment = $this->getPricingTotal();
939 $paymentHtml = $this->defaultPaymentIcon($currency, $totalPayment);
940 }
941
942 if ($driver == 'woo' && $this->isWooEnabled()) {
943 $currency = get_woocommerce_currency_symbol();
944 $totalPayment = $this->getWooProductPrice();
945 $paymentHtml = $this->defaultPaymentIcon($currency, $totalPayment);
946 }
947
948 return $paymentHtml;
949 }
950
951 public function isTeamDefaultSchedule()
952 {
953 if ($this->isTeamEvent()) {
954 if (!Arr::isTrue($this->settings, 'common_schedule', false)) {
955 return true;
956 }
957 }
958 return false;
959 }
960
961 public function isTeamCommonSchedule()
962 {
963 if ($this->isTeamEvent()) {
964 if (Arr::isTrue($this->settings, 'common_schedule', false)) {
965 return true;
966 }
967 }
968 return false;
969 }
970
971 public function isRoundRobinDefaultSchedule()
972 {
973 return $this->isRoundRobin() && $this->isTeamDefaultSchedule();
974 }
975
976 public function isRoundRobinCommonSchedule()
977 {
978 return $this->isRoundRobin() && $this->isTeamCommonSchedule();
979 }
980
981 public function isCollectiveDefaultSchedule()
982 {
983 return $this->isCollective() && $this->isTeamDefaultSchedule();
984 }
985
986 private function getProcessedWeeklySlots($schedule)
987 {
988 $scheduleData = Arr::get($schedule, 'value.weekly_schedules', []);
989 $scheduleTimezone = Arr::get($schedule, 'value.timezone', 'UTC');
990 $schedule = SanitizeService::weeklySchedules($scheduleData, 'UTC', $scheduleTimezone);
991 return AvailabilityService::getUtcWeeklySchedules($schedule, $scheduleTimezone);
992 }
993
994 private function getProcessedDateOverrides($schedule)
995 {
996 $scheduleData = Arr::get($schedule, 'value.date_overrides', []);
997 $scheduleTimezone = Arr::get($schedule, 'value.timezone', 'UTC');
998 $schedule = SanitizeService::slotDateOverrides($scheduleData, 'UTC', $scheduleTimezone);
999 $overrideSlots = AvailabilityService::getUtcDateOverrides($schedule, $scheduleTimezone);
1000 $overrideDays = AvailabilityService::getDateOverrideDays($schedule, $scheduleTimezone);
1001 return [$overrideSlots, $overrideDays];
1002 }
1003
1004 public function getWeeklySlots($hostId = null)
1005 {
1006 if ($hostId && !$this->isTeamCommonSchedule()) {
1007 $schedule = $this->getHostSchedule($hostId);
1008 return $this->getProcessedWeeklySlots($schedule);
1009 }
1010
1011 if ($this->availability_type === 'existing_schedule') {
1012 $schedule = Availability::findOrFail($this->availability_id);
1013 return $this->getProcessedWeeklySlots($schedule);
1014 }
1015
1016 $scheduleData = Arr::get($this->settings, 'weekly_schedules', []);
1017 $schedule = SanitizeService::weeklySchedules($scheduleData, 'UTC', $this->calendar->author_timezone);
1018 return AvailabilityService::getUtcWeeklySchedules($schedule, $this->calendar->author_timezone);
1019 }
1020
1021 public function getDateOverrides($hostId = null)
1022 {
1023 if ($hostId && !$this->isTeamCommonSchedule()) {
1024 $schedule = $this->getHostSchedule($hostId);
1025 return $this->getProcessedDateOverrides($schedule);
1026 }
1027
1028 if ($this->availability_type === 'existing_schedule') {
1029 $schedule = Availability::findOrFail($this->availability_id);
1030 return $this->getProcessedDateOverrides($schedule);
1031 }
1032
1033 $scheduleData = Arr::get($this->settings, 'date_overrides', []);
1034 $schedule = SanitizeService::slotDateOverrides($scheduleData, 'UTC', $this->calendar->author_timezone);
1035 $overrideSlots = AvailabilityService::getUtcDateOverrides($schedule, $this->calendar->author_timezone);
1036 $overrideDays = AvailabilityService::getDateOverrideDays($schedule, $this->calendar->author_timezone);
1037 return [$overrideSlots, $overrideDays];
1038 }
1039
1040 public function getHostIdsSortedByBookings($startDate, $hostId = null)
1041 {
1042 $hostIds = $this->getHostIds($hostId);
1043
1044 if (count($hostIds) <= 1) {
1045 return $hostIds;
1046 }
1047
1048 $hostBookings = [];
1049 foreach ($hostIds as $hostId) {
1050 $dayStart = gmdate('Y-m-d 00:00:00', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1051 $dayEnd = gmdate('Y-m-d 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1052 $hostBookings[$hostId] = Booking::getHostTotalBooking($this->id, [$hostId], [$dayStart, $dayEnd]);
1053 }
1054 usort($hostIds, function ($a, $b) use ($hostBookings) {
1055 return $hostBookings[$a] - $hostBookings[$b];
1056 });
1057
1058 return $hostIds;
1059 }
1060
1061 public function getPaymentSettings()
1062 {
1063 $settings = $this->getMeta('payment_settings', []);
1064
1065 $duration = $this->getDefaultDuration();
1066
1067 $defaults = [
1068 'enabled' => 'no',
1069 'multi_payment_enabled' => 'no',
1070 'stripe_enabled' => 'no',
1071 'paypal_enabled' => 'no',
1072 'offline_enabled' => 'no',
1073 'driver' => 'native',
1074 'items' => [
1075 [
1076 'title' => __('Booking Fee', 'fluent-booking'),
1077 'value' => 100
1078 ]
1079 ],
1080 'woo_product_id' => '',
1081 'multi_payment_items' => [
1082 $duration => [
1083 'title' => __('Booking Fee', 'fluent-booking'),
1084 'value' => 0
1085 ]
1086 ],
1087 'multi_payment_woo_ids' => [
1088 $duration => ''
1089 ]
1090 ];
1091
1092 $defaults = apply_filters('fluent_booking/event_payment_settings_defaults', $defaults, $this);
1093
1094 if (!$settings) {
1095 $settings = $defaults;
1096 }
1097
1098 $settings = wp_parse_args($settings, $defaults);
1099
1100 return apply_filters('fluent_booking/get_event_payment_settings', $settings, $this);
1101 }
1102
1103 public function getHostSchedule($hostId)
1104 {
1105 $hostSchedules = Arr::get($this->settings, 'hosts_schedules', []);
1106 if (isset($hostSchedules[$hostId])) {
1107 return Availability::find($hostSchedules[$hostId]);
1108 }
1109 return AvailabilityService::getDefaultSchedule($hostId);
1110 }
1111
1112 public function getHostsSchedules() {
1113 $hostIds = $this->getHostIds();
1114 $hostSchedules = Arr::get($this->settings, 'hosts_schedules', []);
1115 foreach ($hostIds as $hostId) {
1116 $hostSchedules[$hostId] = $hostSchedules[$hostId] ?? AvailabilityService::getDefaultSchedule($hostId)['id'];
1117 }
1118 return $hostSchedules;
1119 }
1120
1121 public function getCalendarEventsMeta()
1122 {
1123 $eventsMeta = Meta::where('object_id', $this->id)
1124 ->where('object_type', 'calendar_event')
1125 ->get();
1126
1127 return $eventsMeta;
1128 }
1129
1130 public function getIntegrationsMeta()
1131 {
1132 $integrationsMeta = Meta::where('object_id', $this->id)
1133 ->where('object_type', 'integration')
1134 ->get();
1135
1136 return $integrationsMeta;
1137 }
1138
1139 /**
1140 * Get the attributes that have been changed since last sync.
1141 *
1142 * @return array
1143 */
1144 public function getDirty()
1145 {
1146 $dirty = [];
1147 foreach ($this->attributes as $key => $value) {
1148 if (!in_array($key, $this->fillable)) {
1149 continue;
1150 }
1151
1152 if (!array_key_exists($key, $this->original)) {
1153 $dirty[$key] = $value;
1154 } elseif ($value !== $this->original[$key] &&
1155 !$this->originalIsNumericallyEquivalent($key)) {
1156 $dirty[$key] = $value;
1157 }
1158 }
1159
1160 return $dirty;
1161 }
1162 }
1163