PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.24
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.24
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 / Booking.php

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

1,160 lines 36.3 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\BookingFieldService;
7 use FluentBooking\App\Services\LocationService;
8 use FluentBooking\App\Services\DateTimeHelper;
9 use FluentBooking\App\Services\Helper;
10 use FluentBooking\Framework\Support\Arr;
11 use FluentBooking\App\Services\PermissionManager;
12 use FluentBooking\App\Services\EditorShortCodeParser;
13
14 class Booking extends Model
15 {
16 protected $table = 'fcal_bookings';
17
18 protected $guarded = ['id'];
19
20 private static $bookingType = 'scheduling';
21
22 protected $fillable = [
23 'calendar_id',
24 'event_id',
25 'parent_id',
26 'group_id',
27 'hash',
28 'person_user_id',
29 'host_user_id',
30 'person_contact_id',
31 'person_time_zone',
32 'start_time',
33 'end_time',
34 'slot_minutes',
35 'first_name',
36 'last_name',
37 'email',
38 'message',
39 'internal_note',
40 'phone',
41 'country',
42 'ip_address',
43 'browser',
44 'device',
45 'other_info',
46 'location_details',
47 'cancelled_by',
48 'status',
49 'payment_method',
50 'payment_status',
51 'event_type',
52 'source',
53 'source_id',
54 'source_url',
55 'utm_source',
56 'utm_medium',
57 'utm_campaign',
58 'utm_term'
59 ];
60
61
62 /**
63 * $searchable Columns in table to search
64 * @var array
65 */
66 protected $searchable = [
67 'email',
68 'first_name',
69 'last_name'
70 ];
71
72 public static function boot()
73 {
74 parent::boot();
75
76 static::creating(function ($model) {
77 if (!isset($model->person_user_id) && $userId = get_current_user_id()) {
78 $model->person_user_id = $userId;
79 }
80
81 if (is_null($model->group_id) || !isset($model->group_id)) {
82 $model->group_id = static::assignNextGroupId();
83 }
84
85 if (defined('FLUENTCRM') && !empty($model->email) && apply_filters('fluent_calender/auto_booking_fluent_crm_sync', true)) {
86 $contact = FluentCrmApi('contacts')->getContact($model->email);
87 if ($contact) {
88 $model->person_contact_id = $contact->id;
89 }
90 }
91
92 if (empty($model->booking_type)) {
93 $model->booking_type = self::$bookingType;
94 }
95
96 $model->hash = md5(wp_generate_uuid4() . time());
97 });
98
99 static::deleting(function ($model) { // before delete() method call this
100 $model->booking_meta()->delete();
101 $model->booking_activities()->delete();
102 });
103
104 static::addGlobalScope('main_bookings', function ($builder) {
105 $builder->where('booking_type', self::$bookingType);
106 });
107 }
108
109 public function calendar()
110 {
111 return $this->belongsTo(Calendar::class, 'calendar_id');
112 }
113
114 public function slot()
115 {
116 return $this->belongsTo(CalendarSlot::class, 'event_id');
117 }
118
119 public function calendar_event()
120 {
121 return $this->belongsTo(CalendarSlot::class, 'event_id');
122 }
123
124 public function booking_meta()
125 {
126 return $this->hasMany(BookingMeta::class, 'booking_id');
127 }
128
129 public function booking_activities()
130 {
131 return $this->hasMany(BookingActivity::class, 'booking_id');
132 }
133
134 public function user()
135 {
136 return $this->belongsTo(User::class, 'host_user_id');
137 }
138
139 public static function assignNextGroupId()
140 {
141 $lastEvent = static::orderBy('group_id', 'desc')->first(['group_id']);
142
143 return $lastEvent ? $lastEvent->group_id + 1 : 1;
144 }
145
146 public function getCustomFormData($isFormatted = true, $isPublic = false)
147 {
148 if ($isFormatted) {
149 return BookingFieldService::getFormattedCustomBookingData($this, true, $isPublic);
150 }
151
152 return $this->getMeta('custom_fields_data', []);
153 }
154
155 public static function getHostTotalBooking($eventId, $hostIds, $ranges)
156 {
157 return self::where('event_id', $eventId)
158 ->whereIn('host_user_id', $hostIds)
159 ->whereBetween('start_time', $ranges)
160 ->whereIn('status', ['scheduled', 'completed'])
161 ->count();
162 }
163
164 public function getAdditionalGuests($isHtml = false)
165 {
166 $additionalGuests = $this->getMeta('additional_guests', []);
167 if (!$additionalGuests) {
168 return [];
169 }
170
171 if ($isHtml) {
172 return wpautop(implode('<br>', $additionalGuests));
173 }
174
175 return $additionalGuests;
176 }
177
178 public function getHostEmails($excludeHostId = null)
179 {
180 $hostIds = $this->getHostIds();
181
182 $emails = [];
183 foreach ($hostIds as $hostId) {
184 if ($hostId != $excludeHostId) {
185 if ($user = get_user_by('ID', $hostId)) {
186 $emails[] = $user->user_email;
187 }
188 }
189 }
190
191 return $emails;
192 }
193
194 public function hosts()
195 {
196 $class = __NAMESPACE__ . '\User';
197
198 return $this->belongsToMany(
199 $class,
200 'fcal_booking_hosts',
201 'booking_id',
202 'user_id'
203 )
204 ->withPivot('status')
205 ->withTimestamps();
206 }
207
208 public function getHostIds()
209 {
210 return $this->hosts()->pluck('user_id')->toArray();
211 }
212
213 public function scopeUpcoming($query)
214 {
215 return $query->where('end_time', '>=', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
216 }
217
218 public function scopePast($query)
219 {
220 return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
221 }
222
223 public function scopeApplyComputedStatus($query, $status)
224 {
225 $validStatuses = [
226 'upcoming',
227 'completed',
228 'cancelled',
229 'pending',
230 'no_show',
231 'latest_bookings'
232 ];
233
234 if (!in_array($status, $validStatuses)) {
235 return $query;
236 }
237
238 if ($status == 'upcoming') {
239 return $query->where('end_time', '>=', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
240 ->where('status', 'scheduled');
241 }
242
243 if ($status == 'completed') {
244 return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
245 ->where('status', '!=', 'cancelled')
246 ->where('status', '!=', 'rejected')
247 ->orWhere('status', 'completed'); // maybe cron did not mark few as completed yet
248 }
249
250 if ($status == 'cancelled') {
251 return $query->where('status', 'cancelled')
252 ->orWhere('status', 'rejected');
253 }
254
255 if ($status == 'pending') {
256 return $query->whereIn('status', ['pending', 'reserved']);
257 }
258
259 if ($status == 'latest_bookings') {
260 return $query->where('status', '!=', 'reserved');
261 }
262
263 return $query->where('status', $status);
264 }
265
266 public function getFullBookingDateTimeText($timeZone = 'UTC', $isHtml = false)
267 {
268 $startDateTime = DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'Y-m-d H:i:s');
269 $endDateTime = DateTimeHelper::convertFromUtc($this->end_time, $timeZone, 'Y-m-d H:i:s');
270
271 $html = DateTimeHelper::formatToLocale($startDateTime, 'time') . ' - ' . DateTimeHelper::formatToLocale($endDateTime, 'time') . ', ';
272 $html .= DateTimeHelper::formatToLocale($startDateTime, 'date');
273
274 if ($isHtml && in_array($this->status, ['cancelled', 'rejected'])) {
275 $html = '<del>' . $html . '</del>';
276 }
277
278 return $html;
279 }
280
281 public function getShortBookingDateTime($timeZone = 'UTC')
282 {
283 // date format for Fri Feb 10, 2023
284 $html = DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'D M d, Y');
285 $html .= ' ' . DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'h:ia');
286
287 return $html;
288 }
289
290 public function getPreviousMeetingTime($timeZone = 'UTC')
291 {
292 $previousMeetingTime = $this->getMeta('previous_meeting_time');
293 $html = DateTimeHelper::convertFromUtc($previousMeetingTime, $timeZone, 'D M d, Y');
294 $html .= ' ' . DateTimeHelper::convertFromUtc($previousMeetingTime, $timeZone, 'h:ia');
295
296 return $html;
297 }
298
299 public function getAttendeeStartTime($format = 'Y-m-d H:i:s')
300 {
301 return DateTimeHelper::convertFromUtc($this->start_time, $this->person_time_zone, $format);
302 }
303
304 public function getAttendeeEndTime($format = 'Y-m-d H:i:s')
305 {
306 return DateTimeHelper::convertFromUtc($this->end_time, $this->person_time_zone, $format);
307 }
308
309 public function getOtherBookingTimes()
310 {
311 $otherBookings = self::where('parent_id', $this->id)->get();
312
313 return $otherBookings->map(function ($otherBooking) {
314 return $otherBooking->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')';
315 })->toArray();
316 }
317
318 public function getHostAndGuestDetailsHtml()
319 {
320 $authors = $this->getHostsDetails();
321
322 $guestNames = (array) trim($this->first_name . ' ' . $this->last_name);
323
324 if ($this->isMultiGuestBooking()) {
325 $otherGuests = self::where('parent_id', $this->id)->get()->map(function ($guest) {
326 return trim($guest->first_name . ' ' . $guest->last_name);
327 })->toArray();
328 $guestNames = array_merge($guestNames, $otherGuests);
329 }
330
331 $hostUserId = $this->host_user_id;
332
333 $authorListHtml = '<ul class="fcal_listed">';
334
335 foreach ($authors as $author) {
336 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
337 $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
338 }
339
340 foreach ($guestNames as $guestName) {
341 $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
342 }
343 $authorListHtml .= '</ul>';
344
345 return $authorListHtml;
346 }
347
348 public function getLocationDetailsHtml()
349 {
350 $details = $this->location_details;
351 $locationType = Arr::get($details, 'type');
352
353 if (!$locationType) {
354 return '--';
355 }
356
357 if ($locationType == 'in_person_guest') {
358 return '<b>' . __('Invitee Address:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description');
359 }
360
361 if ($locationType == 'in_person_organizer') {
362 $html = '<b>' . Arr::get($details, 'title') . ' </b>';
363 if ($description = Arr::get($details, 'description')) {
364 $html .= wpautop($description);
365 }
366 return $html;
367 }
368
369 if ($locationType == 'phone_guest') {
370 return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . $this->phone;
371 }
372
373 if ($locationType == 'phone_organizer') {
374 return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description') . __(' (Host phone number)', 'fluent-booking');
375 }
376
377 if ($locationType == 'custom') {
378 $html = '<b>' . Arr::get($details, 'title') . '</b>';
379 $html .= wpautop(Arr::get($details, 'description'));
380 return $html;
381 }
382
383 if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
384 $platformLabels = [
385 'google_meet' => __('Google Meet', 'fluent-booking'),
386 'online_meeting' => __('Online Meeting', 'fluent-booking'),
387 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
388 'ms_teams' => __('MS Teams', 'fluent-booking'),
389 ];
390
391 $html = '<b>' . $platformLabels[$locationType] . '</b> ';
392
393 if ($meetingLink = Arr::get($details, 'online_platform_link')) {
394 $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . __('Join Meeting', 'fluent-booking') . '</a>';
395 }
396
397 return $html;
398 }
399
400 return '--';
401 }
402
403 public function getLocationAsText()
404 {
405 $details = $this->location_details;
406
407 $locationType = Arr::get($details, 'type');
408 $meetingLink = Arr::get($details, 'online_platform_link');
409
410 $onlinePlatforms = ['google_meet', 'zoom_meeting', 'online_meeting', 'ms_teams'];
411
412 if ($meetingLink && in_array($locationType, $onlinePlatforms)) {
413 return $meetingLink;
414 }
415
416 if ($locationType == 'phone_organizer') {
417 return Arr::get($details, 'description');
418 }
419
420 if ($locationType == 'phone_guest') {
421 return $this->phone;
422 }
423
424 return wp_strip_all_tags($this->getLocationDetailsHtml());
425 }
426
427 public function getMessage()
428 {
429 if (empty($this->message)) {
430 return 'n/a';
431 }
432 return $this->message;
433 }
434
435 public function setLocationDetailsAttribute($locationDetails)
436 {
437 $this->attributes['location_details'] = \maybe_serialize($locationDetails);
438 }
439
440 public function getLocationDetailsAttribute($locationDetails)
441 {
442 return \maybe_unserialize($locationDetails);
443 }
444
445 public function getOngoingStatus()
446 {
447 if ($this->status != 'scheduled') {
448 return [];
449 }
450
451 $currentTime = time();
452 $startTime = strtotime($this->start_time);
453 $endTime = strtotime($this->end_time);
454
455 if ($currentTime > $startTime && $currentTime < $endTime) {
456 return ['happening_now' => __('Happening Now', 'fluent-booking')];
457 }
458
459 if (($startTime - $currentTime) < 1800 && ($startTime - $currentTime) > 0) {
460 return ['starting_soon' => __('Starting Soon', 'fluent-booking')];
461 }
462
463 if (($endTime - $currentTime) > -3600 && ($endTime - $currentTime) < 0) {
464 return ['recently_happened' => __('Recently Happened', 'fluent-booking')];
465 }
466
467 return [];
468 }
469
470 public function getBookingStatus()
471 {
472 $status = $this->status;
473
474 $statusLabels = [
475 'scheduled' => __('Scheduled', 'fluent-booking'),
476 'rescheduled' => __('Rescheduled', 'fluent-booking'),
477 'completed' => __('Completed', 'fluent-booking'),
478 'pending' => __('Pending', 'fluent-booking'),
479 'cancelled' => __('Cancelled', 'fluent-booking'),
480 'rejected' => __('Rejected', 'fluent-booking')
481 ];
482
483 return Arr::get($statusLabels, $status, $status);
484 }
485
486 public function getPaymentStatus()
487 {
488 $status = $this->payment_status;
489
490 $statusLabels = [
491 'pending' => __('Pending', 'fluent-booking'),
492 'paid' => __('Paid', 'fluent-booking'),
493 'failed' => __('Failed', 'fluent-booking'),
494 'refunded' => __('Refunded', 'fluent-booking'),
495 'partially-paid' => __('Partially Paid', 'fluent-booking'),
496 'partially-refunded' => __('Partially Refunded', 'fluent-booking')
497 ];
498
499 return Arr::get($statusLabels, $status, $status);
500 }
501
502 public function payment_order()
503 {
504 if (defined('FLUENT_BOOKING_PRO_DIR_FILE')) {
505 return $this->hasOne(\FluentBookingPro\App\Models\Order::class, 'parent_id');
506 }
507 return $this->belongsTo(static::class, 'parent_id')->whereNull('id');
508 }
509
510 public function getCancelReason($isText = false, $isHtml = false)
511 {
512 $row = BookingActivity::where('booking_id', $this->id)
513 ->where('type', 'cancel_reason')
514 ->first();
515
516 if ($row) {
517 if ($isText) {
518 return $row->description;
519 }
520 if ($isHtml) {
521 return wp_unslash($row->description);
522 }
523 }
524
525 return $row;
526 }
527
528 public function getRejectReason($isText = false, $isHtml = false)
529 {
530 $row = BookingActivity::where('booking_id', $this->id)
531 ->where('type', 'reject_reason')
532 ->first();
533
534 if ($row) {
535 if ($isText) {
536 return $row->description;
537 }
538 if ($isHtml) {
539 return wp_unslash($row->description);
540 }
541 }
542
543 return $row;
544 }
545
546 public function addCancelOrRejectReason($title, $reason, $type = 'cancel_reason')
547 {
548 if (!$reason && !$title) {
549 return null;
550 }
551
552 if ($type == 'cancel_reason') {
553 $exist = $this->getCancelReason();
554 } else {
555 $exist = $this->getRejectReason();
556 }
557
558 if ($exist) {
559 $exist->title = $title;
560 $exist->description = $reason;
561 $exist->save();
562 return $exist;
563 }
564
565 return BookingActivity::create([
566 'booking_id' => $this->id,
567 'type' => $type,
568 'title' => $title,
569 'description' => $reason
570 ]);
571 }
572
573 public function cancelMeeting($reason = '', $cancelledByType = 'guest', $cancelledByUserId = null)
574 {
575 if ($this->status == 'cancelled') {
576 return $this;
577 }
578
579 $cancellableStatuses = [
580 'scheduled',
581 'pending'
582 ];
583
584 if (!in_array($this->status, $cancellableStatuses)) {
585 return new \WP_Error('invalid_status', __('This booking is not cancellable.', 'fluent-booking'));
586 }
587
588 $this->status = 'cancelled';
589 if ($cancelledByUserId) {
590 $this->cancelled_by = $cancelledByUserId;
591 }
592
593 if (!$cancelledByUserId) {
594 $cancelledByUserId = get_current_user_id();
595 }
596
597 $this->save();
598 $this->updateMeta('cancelled_by_type', $cancelledByType);
599
600 $userName = $cancelledByType;
601 if ($cancelledByUserId && $user = get_user_by('ID', $cancelledByUserId)) {
602 $userName = $user->display_name;
603 }
604
605 if ($reason) {
606 /* translators: Name of the user who cancelled the meeting */
607 $this->addCancelOrRejectReason(sprintf(__('Meeting has been cancelled by %s', 'fluent-booking'), $userName), $reason);
608 do_action('fluent_booking/booking_schedule_cancelled', $this, $this->calendar_event);
609 return;
610 }
611
612 BookingActivity::create([
613 'booking_id' => $this->id,
614 'status' => 'closed',
615 'type' => 'error',
616 'title' => __('Meeting Cancelled', 'fluent-booking'),
617 /* translators: Name of the user who cancelled the meeting */
618 'description' => sprintf(__('Meeting has been cancelled by %s', 'fluent-booking'), $userName)
619 ]);
620
621 do_action('fluent_booking/booking_schedule_cancelled', $this, $this->calendar_event);
622 }
623
624 public function rejectMeeting($reason = '', $rejectByUserId = null)
625 {
626 if ($this->status != 'pending') {
627 return;
628 }
629
630 $this->status = 'rejected';
631 $this->save();
632
633 $rejectByUserId = $rejectByUserId ?: get_current_user_id();
634
635 if ($reason) {
636 $userName = 'host';
637 if ($rejectByUserId && $user = get_user_by('ID', $rejectByUserId)) {
638 $userName = $user->display_name;
639 }
640 /* translators: Name of the user who rejected the booking */
641 $this->addCancelOrRejectReason(sprintf(__('Booking request has been rejected by %s', 'fluent-booking'), $userName), $reason, 'reject_reason');
642 }
643
644 do_action('fluent_booking/booking_schedule_rejected', $this, $this->calendar_event);
645 }
646
647 public function getRescheduleReason()
648 {
649 return $this->getMeta('reschedule_reason', '');
650 }
651
652 private function generateBookingTitle($eventTitle, $authorName, $guestName)
653 {
654 /* translators: 1: Calendar slot title, 2: Author name, 3: Full name of the gueset */
655 $bookingTitle = sprintf(__('%1$s meeting between %2$s and %3$s', 'fluent-booking'), $eventTitle, $authorName, $guestName);
656
657 return $bookingTitle;
658 }
659
660 public function getBookingTitle($html = false)
661 {
662 $calendarEvent = $this->calendar_event;
663
664 $eventTitle = $calendarEvent->title;
665
666 $authorName = $this->getHostDetails(false)['name'];
667
668 $guestName = trim($this->first_name . ' ' . $this->last_name);
669
670 $bookingTitle = Arr::get($calendarEvent, 'settings.booking_title');
671
672 $bookingTitle = EditorShortCodeParser::parse($bookingTitle, $this);
673
674 $bookingTitle = $bookingTitle ?: $this->generateBookingTitle($eventTitle, $authorName, $guestName);
675
676 if ($html && strpos($bookingTitle, $eventTitle) !== false) {
677 $bookingTitle = str_replace($eventTitle, "<strong>{$eventTitle}</strong>", $bookingTitle);
678 }
679
680 return apply_filters('fluent_booking/booking_meeting_title', $bookingTitle, $authorName, $guestName, $calendarEvent, $this);
681 }
682
683 public function getActivities()
684 {
685 return BookingActivity::where('booking_id', $this->id)
686 ->orderBy('id', 'DESC')
687 ->get();
688 }
689
690 public function updateMeta($key, $value)
691 {
692 $exist = BookingMeta::where('booking_id', $this->id)
693 ->where('meta_key', $key)
694 ->first();
695
696 if ($exist) {
697 $exist->value = $value;
698 $exist->save();
699 return $exist;
700 }
701
702 return BookingMeta::create([
703 'booking_id' => $this->id,
704 'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
705 'value' => $value
706 ]);
707 }
708
709 public function deleteMeta($key)
710 {
711 return BookingMeta::where('booking_id', $this->id)
712 ->where('meta_key', $key)
713 ->delete();
714 }
715
716 public function getMeta($key, $default = '')
717 {
718 $exist = BookingMeta::where('booking_id', $this->id)
719 ->where('meta_key', $key)
720 ->first();
721
722 if ($exist) {
723 return $exist->value;
724 }
725
726 return $default;
727 }
728
729
730 /**
731 * Local scope to filter hosts by search/query string
732 * @param string $search
733 */
734 public function scopeSearchBy($query, $search)
735 {
736 if ($search) {
737 $fields = $this->searchable;
738 $query->where(function ($query) use ($fields, $search) {
739 $query->where(array_shift($fields), 'LIKE', "%$search%");
740
741 $nameArray = explode(' ', $search);
742 if (count($nameArray) >= 2) {
743 $query->orWhere(function ($q) use ($nameArray) {
744 $fname = array_shift($nameArray);
745 $lastName = implode(' ', $nameArray);
746 $q->where('first_name', 'LIKE', "%$fname%")
747 ->orWhere('last_name', 'LIKE', "%$lastName%");
748 });
749 }
750
751 foreach ($fields as $field) {
752 $query->orWhere($field, 'LIKE', "%$search%");
753 }
754 });
755 }
756
757 return $query;
758 }
759
760 public function getRedirectUrlWithQuery()
761 {
762 $settings = $this->calendar_event->settings;
763
764 $isEnabled = Arr::isTrue($settings, 'custom_redirect.enabled');
765 $redirectUrl = Arr::get($settings, 'custom_redirect.redirect_url', '');
766 $queryString = Arr::get($settings, 'custom_redirect.query_string', '');
767 $isQueryString = Arr::get($settings, 'custom_redirect.is_query_string', 'no') == 'yes';
768
769 if ($isQueryString && $queryString) {
770 if (strpos($redirectUrl, '?')) {
771 $redirectUrl .= '&' . $queryString;
772 } else {
773 $redirectUrl .= '?' . $queryString;
774 }
775 }
776
777 if (!$isEnabled || empty($redirectUrl)) {
778 return '';
779 }
780
781 $redirectUrl = EditorShortCodeParser::parse($redirectUrl, $this);
782
783 $isUrlParser = apply_filters('fluent_booking/will_parse_redirect_url_value', true, $this);
784
785 if ($isUrlParser) {
786 if (strpos($redirectUrl, '=&') || '=' == substr($redirectUrl, -1)) {
787 $urlArray = explode('?', $redirectUrl);
788 $baseUrl = array_shift($urlArray);
789 $query = wp_parse_url($redirectUrl)['query'];
790 $queryParams = explode('&', $query);
791
792 $params = [];
793 foreach ($queryParams as $queryParam) {
794 $paramArray = explode('=', $queryParam);
795 if (!empty($paramArray[1])) {
796 $params[$paramArray[0]] = $paramArray[1];
797 }
798 }
799 $redirectUrl = add_query_arg($params, $baseUrl);
800 }
801 }
802
803 return $redirectUrl;
804 }
805
806 public function getConfirmationUrl()
807 {
808 return add_query_arg([
809 'fluent-booking' => 'booking',
810 'meeting_hash' => $this->hash,
811 'type' => 'confirmation',
812 ], Helper::getBookingReceiptLandingBaseUrl());
813 }
814
815 public function getAdminViewUrl()
816 {
817 return Helper::getAppBaseUrl('scheduled-events?period=upcoming&booking_id=' . $this->id);
818 }
819
820 public function getIcsDownloadUrl()
821 {
822 return add_query_arg([
823 'fluent-booking' => 'booking',
824 'meeting_hash' => $this->hash,
825 'type' => 'confirmation',
826 'ics' => 'download',
827 ], Helper::getBookingReceiptLandingBaseUrl());
828 }
829
830 public function getRescheduleUrl()
831 {
832 return add_query_arg([
833 'fluent-booking' => 'booking',
834 'meeting_hash' => $this->hash,
835 'type' => 'reschedule',
836 ], Helper::getBookingReceiptLandingBaseUrl());
837 }
838
839 public function getCancelUrl()
840 {
841 return add_query_arg([
842 'fluent-booking' => 'booking',
843 'meeting_hash' => $this->hash,
844 'type' => 'cancel',
845 ], Helper::getBookingReceiptLandingBaseUrl());
846 }
847
848 public function hasBookingAccess()
849 {
850 $hostIds = $this->getHostIds();
851 $hasAccess = PermissionManager::userCan('manage_all_bookings');
852 return in_array(get_current_user_id(), $hostIds) || $hasAccess;
853 }
854
855 private function canPerformAction($settings)
856 {
857 if (!in_array($this->status, ['scheduled', 'pending'])) {
858 return false;
859 }
860
861 if (!Arr::isTrue($settings, 'enabled')) {
862 return true;
863 }
864
865 if (Arr::get($settings, 'type') == 'conditional') {
866 $conditionUnit = Arr::get($settings, 'condition.unit');
867 $conditionValue = Arr::get($settings, 'condition.value');
868
869 $bookingStartTime = strtotime($this->start_time);
870 $currentTime = time();
871
872 $conditionTime = $conditionValue * 60;
873 if ($conditionUnit == 'hours') {
874 $conditionTime = $conditionTime * 60;
875 }
876
877 return $bookingStartTime - $currentTime > $conditionTime;
878 }
879
880 return false;
881 }
882
883 public function canCancel()
884 {
885 $settings = $this->calendar_event->getCanNotCancelSettings();
886
887 return $this->canPerformAction($settings);
888 }
889
890 public function canReschedule()
891 {
892 $settings = $this->calendar_event->getCanNotRescheduleSettings();
893
894 return $this->canPerformAction($settings);
895 }
896
897 public function isMultiGuestBooking()
898 {
899 return $this->event_type == 'group' || $this->event_type == 'group_event';
900 }
901
902 public function isMultiHostBooking()
903 {
904 return $this->event_type == 'single_event' || $this->event_type == 'group_event';
905 }
906
907 public function getHostProfiles($public = true)
908 {
909 $hostIds = $this->getHostIds();
910
911 $hosts = [];
912 foreach ($hostIds as $hostId) {
913 $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
914 if ($calendar) {
915 $hosts[] = $calendar->getAuthorProfile($public);
916 }
917 }
918
919 return $hosts;
920 }
921
922 public function getInviteePhoneNumber($calendarEvent)
923 {
924 $customFormData = $this->getCustomFormData(false);
925
926 $customFields = BookingFieldService::getBookingFields($calendarEvent);
927
928 foreach ($customFields as $field) {
929 $fieldValue = Arr::get($customFormData, $field['name']);
930 if ($fieldValue && $field['type'] == 'phone' && Arr::isTrue($field, 'is_sms_number')) {
931 return $fieldValue;
932 }
933 }
934
935 return $this->phone;
936 }
937
938 public function getCancellationMessage()
939 {
940 $message = Arr::get($this->calendar_event->settings, 'can_not_cancel.message');
941
942 $message = EditorShortCodeParser::parse($message, $this);
943
944 if ($message) {
945 return $message;
946 }
947
948 return __('Sorry! you can not cancel this', 'fluent-booking');
949 }
950
951 public function getRescheduleMessage()
952 {
953 $message = Arr::get($this->calendar_event->settings, 'can_not_reschedule.message');
954
955 $message = EditorShortCodeParser::parse($message, $this);
956
957 if ($message) {
958 return $message;
959 }
960
961 return __('Sorry! you can not reschedule this', 'fluent-booking');
962 }
963
964 public function getHostDetails($isPublic = true, $hostId = null)
965 {
966 $hostId = $hostId ?: $this->host_user_id;
967
968 if ($hostId && $user = get_user_by('ID', $hostId)) {
969 $name = trim($user->first_name . ' ' . $user->last_name);
970 if (!$name) {
971 $name = $user->display_name;
972 }
973 $data = [
974 'id' => $user->ID,
975 'name' => $name,
976 'email' => $user->user_email,
977 'first_name' => $user->first_name,
978 'last_name' => $user->last_name,
979 'avatar' => Helper::fluentBookingUserAvatar($user->ID, $user)
980 ];
981 } else {
982 $data = $this->calendar->getAuthorProfile(false);
983 }
984
985 if ($isPublic) {
986 unset($data['email']);
987 }
988
989 return $data;
990 }
991
992 public function getHostsDetails($isPublic = true, $excludeHostId = null)
993 {
994 $hostIds = $this->getHostIds();
995
996 $hosts = [];
997 foreach ($hostIds as $hostId) {
998 if ($hostId != $excludeHostId) {
999 $hosts[] = $this->getHostDetails($isPublic, $hostId);
1000 }
1001 }
1002
1003 return $hosts;
1004 }
1005
1006 public function getHostTimezone()
1007 {
1008 if ($this->host_user_id) {
1009 $calendar = Calendar::where('user_id', $this->host_user_id)
1010 ->where('type', 'simple')
1011 ->first();
1012
1013 if (!$calendar) {
1014 return '';
1015 }
1016 return $calendar->author_timezone;
1017 }
1018 return '';
1019 }
1020
1021 public function getIcsBookingDescription()
1022 {
1023 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
1024
1025 if ($this->message) {
1026 $description .= __('Note: ', 'fluent-booking') . '\\n' . $this->message . '\\n' . '\\n';
1027 }
1028
1029 if ($additionalData = $this->getAdditionalData(false)) {
1030 if (!empty($description )) {
1031 $description .= "\\n";
1032 } else {
1033 $description = '';
1034 }
1035
1036 $additionalData = str_replace(PHP_EOL, '\\n', $additionalData);
1037
1038 $description .= $additionalData;
1039 }
1040
1041 return $description;
1042 }
1043
1044 public function getAdditionalData($isHtml = false)
1045 {
1046 $customData = BookingFieldService::getFormattedCustomBookingData($this, $isHtml, true);
1047
1048 if (!$customData) {
1049 return '';
1050 }
1051
1052 if (!$isHtml) {
1053 $lines = array_filter(array_map(function ($data) {
1054 return !empty($data['value']) ? $data['label'] . ': ' . PHP_EOL . esc_html($data['value']) : null;
1055 }, $customData));
1056
1057 return implode(PHP_EOL . PHP_EOL, $lines);
1058 }
1059
1060 $html = '<table>';
1061 foreach ($customData as $data) {
1062 if (empty($data['value'])) {
1063 continue;
1064 }
1065 $html .= '<tr>';
1066 $html .= '<td><b>' . $data['label'] . '</b></td>';
1067 $html .= '<td>' . $data['value'] . '</td>';
1068 $html .= '</tr>';
1069 }
1070 $html .= '</table>';
1071
1072 return $html;
1073 }
1074
1075 public function getConfirmationData()
1076 {
1077 $author = $this->getHostDetails(false);
1078
1079 $guestName = trim($this->first_name . ' ' . $this->last_name);
1080
1081 $bookingTitle = $this->getBookingTitle();
1082
1083 $sections = [
1084 'what' => [
1085 'title' => __('What', 'fluent-booking'),
1086 'content' => $bookingTitle,
1087 ],
1088 'when' => [
1089 'title' => __('When', 'fluent-booking'),
1090 'content' => $this->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')',
1091 ],
1092 'who' => [
1093 'title' => __('Who', 'fluent-booking'),
1094 'content' => $author['name'] . ' - ' . __('Organizer', 'fluent-booking') . PHP_EOL . $author['email'] . PHP_EOL . PHP_EOL . $guestName . PHP_EOL . $this->email
1095 ],
1096 'where' => [
1097 'title' => __('Where', 'fluent-booking'),
1098 'content' => $this->getLocationAsText()
1099 ],
1100 ];
1101
1102 $lines = array_map(function ($section) {
1103 return $section['title'] . ': ' . PHP_EOL . esc_html($section['content']);
1104 }, $sections);
1105
1106 return implode(PHP_EOL . PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
1107 }
1108
1109 public function getMeetingBookmarks($assetsUrl = '')
1110 {
1111 $bookingTitle = $this->getBookingTitle();
1112
1113 $eventTitle = $this->calendar_event->title;
1114
1115 return apply_filters('fluent_booking/meeting_bookmarks', [
1116 'google' => [
1117 'title' => __('Google Calendar', 'fluent-booking'),
1118 'url' => add_query_arg([
1119 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1120 'text' => $bookingTitle,
1121 'details' => $eventTitle,
1122 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1123 ], 'https://calendar.google.com/calendar/r/eventedit'),
1124 'icon' => $assetsUrl . 'images/g-icon.svg'
1125 ],
1126 'outlook' => [
1127 'title' => __('Outlook', 'fluent-booking'),
1128 'url' => add_query_arg([
1129 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1130 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1131 'subject' => $bookingTitle,
1132 'path' => '/calendar/action/compose',
1133 'body' => $eventTitle,
1134 'rru' => 'addevent',
1135 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1136 ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1137 'icon' => $assetsUrl . 'images/ol-icon.svg'
1138 ],
1139 'msoffice' => [
1140 'title' => __('Microsoft Office', 'fluent-booking'),
1141 'url' => add_query_arg([
1142 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1143 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1144 'subject' => $bookingTitle,
1145 'path' => '/calendar/action/compose',
1146 'body' => $eventTitle,
1147 'rru' => 'addevent',
1148 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1149 ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1150 'icon' => $assetsUrl . 'images/msoffice.svg'
1151 ],
1152 'other' => [
1153 'title' => __('Other Calendar', 'fluent-booking'),
1154 'url' => $this->getIcsDownloadUrl(),
1155 'icon' => $assetsUrl . 'images/ics.svg'
1156 ]
1157 ], $this);
1158 }
1159
1160 }