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

1,153 lines 36.0 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)
147 {
148 if ($isFormatted) {
149 return BookingFieldService::getFormattedCustomBookingData($this);
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 getMeta($key, $default = '')
710 {
711 $exist = BookingMeta::where('booking_id', $this->id)
712 ->where('meta_key', $key)
713 ->first();
714
715 if ($exist) {
716 return $exist->value;
717 }
718
719 return $default;
720 }
721
722
723 /**
724 * Local scope to filter hosts by search/query string
725 * @param string $search
726 */
727 public function scopeSearchBy($query, $search)
728 {
729 if ($search) {
730 $fields = $this->searchable;
731 $query->where(function ($query) use ($fields, $search) {
732 $query->where(array_shift($fields), 'LIKE', "%$search%");
733
734 $nameArray = explode(' ', $search);
735 if (count($nameArray) >= 2) {
736 $query->orWhere(function ($q) use ($nameArray) {
737 $fname = array_shift($nameArray);
738 $lastName = implode(' ', $nameArray);
739 $q->where('first_name', 'LIKE', "%$fname%")
740 ->orWhere('last_name', 'LIKE', "%$lastName%");
741 });
742 }
743
744 foreach ($fields as $field) {
745 $query->orWhere($field, 'LIKE', "%$search%");
746 }
747 });
748 }
749
750 return $query;
751 }
752
753 public function getRedirectUrlWithQuery()
754 {
755 $settings = $this->calendar_event->settings;
756
757 $isEnabled = Arr::isTrue($settings, 'custom_redirect.enabled');
758 $redirectUrl = Arr::get($settings, 'custom_redirect.redirect_url', '');
759 $queryString = Arr::get($settings, 'custom_redirect.query_string', '');
760 $isQueryString = Arr::get($settings, 'custom_redirect.is_query_string', 'no') == 'yes';
761
762 if ($isQueryString && $queryString) {
763 if (strpos($redirectUrl, '?')) {
764 $redirectUrl .= '&' . $queryString;
765 } else {
766 $redirectUrl .= '?' . $queryString;
767 }
768 }
769
770 if (!$isEnabled || empty($redirectUrl)) {
771 return '';
772 }
773
774 $redirectUrl = EditorShortCodeParser::parse($redirectUrl, $this);
775
776 $isUrlParser = apply_filters('fluent_booking/will_parse_redirect_url_value', true, $this);
777
778 if ($isUrlParser) {
779 if (strpos($redirectUrl, '=&') || '=' == substr($redirectUrl, -1)) {
780 $urlArray = explode('?', $redirectUrl);
781 $baseUrl = array_shift($urlArray);
782 $query = wp_parse_url($redirectUrl)['query'];
783 $queryParams = explode('&', $query);
784
785 $params = [];
786 foreach ($queryParams as $queryParam) {
787 $paramArray = explode('=', $queryParam);
788 if (!empty($paramArray[1])) {
789 $params[$paramArray[0]] = $paramArray[1];
790 }
791 }
792 $redirectUrl = add_query_arg($params, $baseUrl);
793 }
794 }
795
796 return $redirectUrl;
797 }
798
799 public function getConfirmationUrl()
800 {
801 return add_query_arg([
802 'fluent-booking' => 'booking',
803 'meeting_hash' => $this->hash,
804 'type' => 'confirmation',
805 ], Helper::getBookingReceiptLandingBaseUrl());
806 }
807
808 public function getAdminViewUrl()
809 {
810 return Helper::getAppBaseUrl('scheduled-events?period=upcoming&booking_id=' . $this->id);
811 }
812
813 public function getIcsDownloadUrl()
814 {
815 return add_query_arg([
816 'fluent-booking' => 'booking',
817 'meeting_hash' => $this->hash,
818 'type' => 'confirmation',
819 'ics' => 'download',
820 ], Helper::getBookingReceiptLandingBaseUrl());
821 }
822
823 public function getRescheduleUrl()
824 {
825 return add_query_arg([
826 'fluent-booking' => 'booking',
827 'meeting_hash' => $this->hash,
828 'type' => 'reschedule',
829 ], Helper::getBookingReceiptLandingBaseUrl());
830 }
831
832 public function getCancelUrl()
833 {
834 return add_query_arg([
835 'fluent-booking' => 'booking',
836 'meeting_hash' => $this->hash,
837 'type' => 'cancel',
838 ], Helper::getBookingReceiptLandingBaseUrl());
839 }
840
841 public function hasBookingAccess()
842 {
843 $hostIds = $this->getHostIds();
844 $hasAccess = PermissionManager::userCan('manage_all_bookings');
845 return in_array(get_current_user_id(), $hostIds) || $hasAccess;
846 }
847
848 private function canPerformAction($settings)
849 {
850 if (!in_array($this->status, ['scheduled', 'pending'])) {
851 return false;
852 }
853
854 if (!Arr::isTrue($settings, 'enabled')) {
855 return true;
856 }
857
858 if (Arr::get($settings, 'type') == 'conditional') {
859 $conditionUnit = Arr::get($settings, 'condition.unit');
860 $conditionValue = Arr::get($settings, 'condition.value');
861
862 $bookingStartTime = strtotime($this->start_time);
863 $currentTime = time();
864
865 $conditionTime = $conditionValue * 60;
866 if ($conditionUnit == 'hours') {
867 $conditionTime = $conditionTime * 60;
868 }
869
870 return $bookingStartTime - $currentTime > $conditionTime;
871 }
872
873 return false;
874 }
875
876 public function canCancel()
877 {
878 $settings = $this->calendar_event->getCanNotCancelSettings();
879
880 return $this->canPerformAction($settings);
881 }
882
883 public function canReschedule()
884 {
885 $settings = $this->calendar_event->getCanNotRescheduleSettings();
886
887 return $this->canPerformAction($settings);
888 }
889
890 public function isMultiGuestBooking()
891 {
892 return $this->event_type == 'group' || $this->event_type == 'group_event';
893 }
894
895 public function isMultiHostBooking()
896 {
897 return $this->event_type == 'single_event' || $this->event_type == 'group_event';
898 }
899
900 public function getHostProfiles($public = true)
901 {
902 $hostIds = $this->getHostIds();
903
904 $hosts = [];
905 foreach ($hostIds as $hostId) {
906 $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
907 if ($calendar) {
908 $hosts[] = $calendar->getAuthorProfile($public);
909 }
910 }
911
912 return $hosts;
913 }
914
915 public function getInviteePhoneNumber($calendarEvent)
916 {
917 $customFormData = $this->getCustomFormData(false);
918
919 $customFields = BookingFieldService::getBookingFields($calendarEvent);
920
921 foreach ($customFields as $field) {
922 $fieldValue = Arr::get($customFormData, $field['name']);
923 if ($fieldValue && $field['type'] == 'phone' && Arr::isTrue($field, 'is_sms_number')) {
924 return $fieldValue;
925 }
926 }
927
928 return $this->phone;
929 }
930
931 public function getCancellationMessage()
932 {
933 $message = Arr::get($this->calendar_event->settings, 'can_not_cancel.message');
934
935 $message = EditorShortCodeParser::parse($message, $this);
936
937 if ($message) {
938 return $message;
939 }
940
941 return __('Sorry! you can not cancel this', 'fluent-booking');
942 }
943
944 public function getRescheduleMessage()
945 {
946 $message = Arr::get($this->calendar_event->settings, 'can_not_reschedule.message');
947
948 $message = EditorShortCodeParser::parse($message, $this);
949
950 if ($message) {
951 return $message;
952 }
953
954 return __('Sorry! you can not reschedule this', 'fluent-booking');
955 }
956
957 public function getHostDetails($isPublic = true, $hostId = null)
958 {
959 $hostId = $hostId ?: $this->host_user_id;
960
961 if ($hostId && $user = get_user_by('ID', $hostId)) {
962 $name = trim($user->first_name . ' ' . $user->last_name);
963 if (!$name) {
964 $name = $user->display_name;
965 }
966 $data = [
967 'id' => $user->ID,
968 'name' => $name,
969 'email' => $user->user_email,
970 'first_name' => $user->first_name,
971 'last_name' => $user->last_name,
972 'avatar' => Helper::fluentBookingUserAvatar($user->ID, $user)
973 ];
974 } else {
975 $data = $this->calendar->getAuthorProfile(false);
976 }
977
978 if ($isPublic) {
979 unset($data['email']);
980 }
981
982 return $data;
983 }
984
985 public function getHostsDetails($isPublic = true, $excludeHostId = null)
986 {
987 $hostIds = $this->getHostIds();
988
989 $hosts = [];
990 foreach ($hostIds as $hostId) {
991 if ($hostId != $excludeHostId) {
992 $hosts[] = $this->getHostDetails($isPublic, $hostId);
993 }
994 }
995
996 return $hosts;
997 }
998
999 public function getHostTimezone()
1000 {
1001 if ($this->host_user_id) {
1002 $calendar = Calendar::where('user_id', $this->host_user_id)
1003 ->where('type', 'simple')
1004 ->first();
1005
1006 if (!$calendar) {
1007 return '';
1008 }
1009 return $calendar->author_timezone;
1010 }
1011 return '';
1012 }
1013
1014 public function getIcsBookingDescription()
1015 {
1016 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
1017
1018 if ($this->message) {
1019 $description .= __('Note: ', 'fluent-booking') . '\\n' . $this->message . '\\n' . '\\n';
1020 }
1021
1022 if ($additionalData = $this->getAdditionalData(false)) {
1023 if (!empty($description )) {
1024 $description .= "\\n";
1025 } else {
1026 $description = '';
1027 }
1028
1029 $additionalData = str_replace(PHP_EOL, '\\n', $additionalData);
1030
1031 $description .= $additionalData;
1032 }
1033
1034 return $description;
1035 }
1036
1037 public function getAdditionalData($isHtml = false)
1038 {
1039 $customData = BookingFieldService::getFormattedCustomBookingData($this);
1040
1041 if (!$customData) {
1042 return '';
1043 }
1044
1045 if (!$isHtml) {
1046 $lines = array_filter(array_map(function ($data) {
1047 return !empty($data['value']) ? $data['label'] . ': ' . PHP_EOL . esc_html($data['value']) : null;
1048 }, $customData));
1049
1050 return implode(PHP_EOL . PHP_EOL, $lines);
1051 }
1052
1053 $html = '<table>';
1054 foreach ($customData as $data) {
1055 if (empty($data['value'])) {
1056 continue;
1057 }
1058 $html .= '<tr>';
1059 $html .= '<td><b>' . $data['label'] . '</b></td>';
1060 $html .= '<td>' . $data['value'] . '</td>';
1061 $html .= '</tr>';
1062 }
1063 $html .= '</table>';
1064
1065 return $html;
1066 }
1067
1068 public function getConfirmationData()
1069 {
1070 $author = $this->getHostDetails(false);
1071
1072 $guestName = trim($this->first_name . ' ' . $this->last_name);
1073
1074 $bookingTitle = $this->getBookingTitle();
1075
1076 $sections = [
1077 'what' => [
1078 'title' => __('What', 'fluent-booking'),
1079 'content' => $bookingTitle,
1080 ],
1081 'when' => [
1082 'title' => __('When', 'fluent-booking'),
1083 'content' => $this->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')',
1084 ],
1085 'who' => [
1086 'title' => __('Who', 'fluent-booking'),
1087 'content' => $author['name'] . ' - ' . __('Organizer', 'fluent-booking') . PHP_EOL . $author['email'] . PHP_EOL . PHP_EOL . $guestName . PHP_EOL . $this->email
1088 ],
1089 'where' => [
1090 'title' => __('Where', 'fluent-booking'),
1091 'content' => $this->getLocationAsText()
1092 ],
1093 ];
1094
1095 $lines = array_map(function ($section) {
1096 return $section['title'] . ': ' . PHP_EOL . esc_html($section['content']);
1097 }, $sections);
1098
1099 return implode(PHP_EOL . PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
1100 }
1101
1102 public function getMeetingBookmarks($assetsUrl = '')
1103 {
1104 $bookingTitle = $this->getBookingTitle();
1105
1106 $eventTitle = $this->calendar_event->title;
1107
1108 return apply_filters('fluent_booking/meeting_bookmarks', [
1109 'google' => [
1110 'title' => __('Google Calendar', 'fluent-booking'),
1111 'url' => add_query_arg([
1112 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1113 'text' => $bookingTitle,
1114 'details' => $eventTitle,
1115 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1116 ], 'https://calendar.google.com/calendar/r/eventedit'),
1117 'icon' => $assetsUrl . 'images/g-icon.svg'
1118 ],
1119 'outlook' => [
1120 'title' => __('Outlook', 'fluent-booking'),
1121 'url' => add_query_arg([
1122 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1123 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1124 'subject' => $bookingTitle,
1125 'path' => '/calendar/action/compose',
1126 'body' => $eventTitle,
1127 'rru' => 'addevent',
1128 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1129 ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1130 'icon' => $assetsUrl . 'images/ol-icon.svg'
1131 ],
1132 'msoffice' => [
1133 'title' => __('Microsoft Office', 'fluent-booking'),
1134 'url' => add_query_arg([
1135 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1136 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1137 'subject' => $bookingTitle,
1138 'path' => '/calendar/action/compose',
1139 'body' => $eventTitle,
1140 'rru' => 'addevent',
1141 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1142 ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1143 'icon' => $assetsUrl . 'images/msoffice.svg'
1144 ],
1145 'other' => [
1146 'title' => __('Other Calendar', 'fluent-booking'),
1147 'url' => $this->getIcsDownloadUrl(),
1148 'icon' => $assetsUrl . 'images/ics.svg'
1149 ]
1150 ], $this);
1151 }
1152
1153 }