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

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