PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.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 All 34 releases
fluent-booking / app / Models / CalendarSlot.php

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

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