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

1,135 lines 35.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)
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 getHostAndGuestDetailsHtml()
310 {
311 $authors = $this->getHostsDetails();
312
313 $guestName = trim($this->first_name . ' ' . $this->last_name);
314
315 $hostUserId = $this->host_user_id;
316
317 $authorListHtml = '<ul class="fcal_listed">';
318
319 foreach ($authors as $author) {
320 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
321 $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
322 }
323
324 $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
325 $authorListHtml .= '</ul>';
326
327 return $authorListHtml;
328 }
329
330 public function getLocationDetailsHtml()
331 {
332 $details = $this->location_details;
333 $locationType = Arr::get($details, 'type');
334
335 if (!$locationType) {
336 return '--';
337 }
338
339 if ($locationType == 'in_person_guest') {
340 return '<b>' . __('Invitee Address:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description');
341 }
342
343 if ($locationType == 'in_person_organizer') {
344 $html = '<b>' . Arr::get($details, 'title') . ' </b>';
345 if ($description = Arr::get($details, 'description')) {
346 $html .= wpautop($description);
347 }
348 return $html;
349 }
350
351 if ($locationType == 'phone_guest') {
352 return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . $this->phone;
353 }
354
355 if ($locationType == 'phone_organizer') {
356 return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description') . __(' (Host phone number)', 'fluent-booking');
357 }
358
359 if ($locationType == 'custom') {
360 $html = '<b>' . Arr::get($details, 'title') . '</b>';
361 $html .= wpautop(Arr::get($details, 'description'));
362 return $html;
363 }
364
365 if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
366 $platformLabels = [
367 'google_meet' => __('Google Meet', 'fluent-booking'),
368 'online_meeting' => __('Online Meeting', 'fluent-booking'),
369 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
370 'ms_teams' => __('MS Teams', 'fluent-booking'),
371 ];
372
373 $html = '<b>' . $platformLabels[$locationType] . '</b> ';
374
375 if ($meetingLink = Arr::get($details, 'online_platform_link')) {
376 $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . __('Join Meeting', 'fluent-booking') . '</a>';
377 }
378
379 return $html;
380 }
381
382 return '--';
383 }
384
385 public function getLocationAsText()
386 {
387 $details = $this->location_details;
388
389 $locationType = Arr::get($details, 'type');
390 $meetingLink = Arr::get($details, 'online_platform_link');
391
392 $onlinePlatforms = ['google_meet', 'zoom_meeting', 'online_meeting', 'ms_teams'];
393
394 if ($meetingLink && in_array($locationType, $onlinePlatforms)) {
395 return $meetingLink;
396 }
397
398 if ($locationType == 'phone_organizer') {
399 return Arr::get($details, 'description');
400 }
401
402 if ($locationType == 'phone_guest') {
403 return $this->phone;
404 }
405
406 return wp_strip_all_tags($this->getLocationDetailsHtml());
407 }
408
409 public function getMessage()
410 {
411 if (empty($this->message)) {
412 return 'n/a';
413 }
414 return $this->message;
415 }
416
417 public function setLocationDetailsAttribute($locationDetails)
418 {
419 $this->attributes['location_details'] = \maybe_serialize($locationDetails);
420 }
421
422 public function getLocationDetailsAttribute($locationDetails)
423 {
424 return \maybe_unserialize($locationDetails);
425 }
426
427 public function getOngoingStatus()
428 {
429 if ($this->status != 'scheduled') {
430 return [];
431 }
432
433 $currentTime = time();
434 $startTime = strtotime($this->start_time);
435 $endTime = strtotime($this->end_time);
436
437 if ($currentTime > $startTime && $currentTime < $endTime) {
438 return ['happening_now' => __('Happening Now', 'fluent-booking')];
439 }
440
441 if (($startTime - $currentTime) < 1800 && ($startTime - $currentTime) > 0) {
442 return ['starting_soon' => __('Starting Soon', 'fluent-booking')];
443 }
444
445 if (($endTime - $currentTime) > -3600 && ($endTime - $currentTime) < 0) {
446 return ['recently_happened' => __('Recently Happened', 'fluent-booking')];
447 }
448
449 return [];
450 }
451
452 public function getBookingStatus()
453 {
454 $status = $this->status;
455
456 $statusLabels = [
457 'scheduled' => __('Scheduled', 'fluent-booking'),
458 'rescheduled' => __('Rescheduled', 'fluent-booking'),
459 'completed' => __('Completed', 'fluent-booking'),
460 'pending' => __('Pending', 'fluent-booking'),
461 'cancelled' => __('Cancelled', 'fluent-booking'),
462 'rejected' => __('Rejected', 'fluent-booking')
463 ];
464
465 return Arr::get($statusLabels, $status, $status);
466 }
467
468 public function getPaymentStatus()
469 {
470 $status = $this->payment_status;
471
472 $statusLabels = [
473 'pending' => __('Pending', 'fluent-booking'),
474 'paid' => __('Paid', 'fluent-booking'),
475 'failed' => __('Failed', 'fluent-booking'),
476 'refunded' => __('Refunded', 'fluent-booking'),
477 'partially-paid' => __('Partially Paid', 'fluent-booking'),
478 'partially-refunded' => __('Partially Refunded', 'fluent-booking')
479 ];
480
481 return Arr::get($statusLabels, $status, $status);
482 }
483
484 public function payment_order()
485 {
486 if (defined('FLUENT_BOOKING_PRO_DIR_FILE')) {
487 return $this->hasOne(\FluentBookingPro\App\Models\Order::class, 'parent_id');
488 }
489 return $this->belongsTo(static::class, 'parent_id')->whereNull('id');
490 }
491
492 public function getCancelReason($isText = false, $isHtml = false)
493 {
494 $row = BookingActivity::where('booking_id', $this->id)
495 ->where('type', 'cancel_reason')
496 ->first();
497
498 if ($row) {
499 if ($isText) {
500 return $row->description;
501 }
502 if ($isHtml) {
503 return wp_unslash($row->description);
504 }
505 }
506
507 return $row;
508 }
509
510 public function getRejectReason($isText = false, $isHtml = false)
511 {
512 $row = BookingActivity::where('booking_id', $this->id)
513 ->where('type', 'reject_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 addCancelOrRejectReason($title, $reason, $type = 'cancel_reason')
529 {
530 if (!$reason && !$title) {
531 return null;
532 }
533
534 if ($type == 'cancel_reason') {
535 $exist = $this->getCancelReason();
536 } else {
537 $exist = $this->getRejectReason();
538 }
539
540 if ($exist) {
541 $exist->title = $title;
542 $exist->description = $reason;
543 $exist->save();
544 return $exist;
545 }
546
547 return BookingActivity::create([
548 'booking_id' => $this->id,
549 'type' => $type,
550 'title' => $title,
551 'description' => $reason
552 ]);
553 }
554
555 public function cancelMeeting($reason = '', $cancelledByType = 'guest', $cancelledByUserId = null)
556 {
557 if ($this->status == 'cancelled') {
558 return $this;
559 }
560
561 $cancellableStatuses = [
562 'scheduled',
563 'pending'
564 ];
565
566 if (!in_array($this->status, $cancellableStatuses)) {
567 return new \WP_Error('invalid_status', __('This booking is not cancellable.', 'fluent-booking'));
568 }
569
570 $this->status = 'cancelled';
571 if ($cancelledByUserId) {
572 $this->cancelled_by = $cancelledByUserId;
573 }
574
575 if (!$cancelledByUserId) {
576 $cancelledByUserId = get_current_user_id();
577 }
578
579 $this->save();
580 $this->updateMeta('cancelled_by_type', $cancelledByType);
581
582 $userName = $cancelledByType;
583 if ($cancelledByUserId && $user = get_user_by('ID', $cancelledByUserId)) {
584 $userName = $user->display_name;
585 }
586
587 if ($reason) {
588 /* translators: Name of the user who cancelled the meeting */
589 $this->addCancelOrRejectReason(sprintf(__('Meeting has been cancelled by %s', 'fluent-booking'), $userName), $reason);
590 do_action('fluent_booking/booking_schedule_cancelled', $this, $this->calendar_event);
591 return;
592 }
593
594 BookingActivity::create([
595 'booking_id' => $this->id,
596 'status' => 'closed',
597 'type' => 'error',
598 'title' => __('Meeting Cancelled', 'fluent-booking'),
599 /* translators: Name of the user who cancelled the meeting */
600 'description' => sprintf(__('Meeting has been cancelled by %s', 'fluent-booking'), $userName)
601 ]);
602
603 do_action('fluent_booking/booking_schedule_cancelled', $this, $this->calendar_event);
604 }
605
606 public function rejectMeeting($reason = '', $rejectByUserId = null)
607 {
608 if ($this->status != 'pending') {
609 return;
610 }
611
612 $this->status = 'rejected';
613 $this->save();
614
615 $rejectByUserId = $rejectByUserId ?: get_current_user_id();
616
617 if ($reason) {
618 $userName = 'host';
619 if ($rejectByUserId && $user = get_user_by('ID', $rejectByUserId)) {
620 $userName = $user->display_name;
621 }
622 /* translators: Name of the user who rejected the booking */
623 $this->addCancelOrRejectReason(sprintf(__('Booking request has been rejected by %s', 'fluent-booking'), $userName), $reason, 'reject_reason');
624 }
625
626 do_action('fluent_booking/booking_schedule_rejected', $this, $this->calendar_event);
627 }
628
629 public function getRescheduleReason()
630 {
631 return $this->getMeta('reschedule_reason', '');
632 }
633
634 private function generateBookingTitle($eventTitle, $authorName, $guestName)
635 {
636 /* translators: 1: Calendar slot title, 2: Author name, 3: Full name of the gueset */
637 $bookingTitle = sprintf(__('%1$s meeting between %2$s and %3$s', 'fluent-booking'), $eventTitle, $authorName, $guestName);
638
639 return $bookingTitle;
640 }
641
642 public function getBookingTitle($html = false)
643 {
644 $calendarEvent = $this->calendar_event;
645
646 $eventTitle = $calendarEvent->title;
647
648 $authorName = $this->getHostDetails(false)['name'];
649
650 $guestName = trim($this->first_name . ' ' . $this->last_name);
651
652 $bookingTitle = Arr::get($calendarEvent, 'settings.booking_title');
653
654 $bookingTitle = EditorShortCodeParser::parse($bookingTitle, $this);
655
656 $bookingTitle = $bookingTitle ?: $this->generateBookingTitle($eventTitle, $authorName, $guestName);
657
658 if ($html && strpos($bookingTitle, $eventTitle) !== false) {
659 $bookingTitle = str_replace($eventTitle, "<strong>{$eventTitle}</strong>", $bookingTitle);
660 }
661
662 return apply_filters('fluent_booking/booking_meeting_title', $bookingTitle, $authorName, $guestName, $calendarEvent, $this);
663 }
664
665 public function getActivities()
666 {
667 return BookingActivity::where('booking_id', $this->id)
668 ->orderBy('id', 'DESC')
669 ->get();
670 }
671
672 public function updateMeta($key, $value)
673 {
674 $exist = BookingMeta::where('booking_id', $this->id)
675 ->where('meta_key', $key)
676 ->first();
677
678 if ($exist) {
679 $exist->value = $value;
680 $exist->save();
681 return $exist;
682 }
683
684 return BookingMeta::create([
685 'booking_id' => $this->id,
686 'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
687 'value' => $value
688 ]);
689 }
690
691 public function getMeta($key, $default = '')
692 {
693 $exist = BookingMeta::where('booking_id', $this->id)
694 ->where('meta_key', $key)
695 ->first();
696
697 if ($exist) {
698 return $exist->value;
699 }
700
701 return $default;
702 }
703
704
705 /**
706 * Local scope to filter hosts by search/query string
707 * @param string $search
708 */
709 public function scopeSearchBy($query, $search)
710 {
711 if ($search) {
712 $fields = $this->searchable;
713 $query->where(function ($query) use ($fields, $search) {
714 $query->where(array_shift($fields), 'LIKE', "%$search%");
715
716 $nameArray = explode(' ', $search);
717 if (count($nameArray) >= 2) {
718 $query->orWhere(function ($q) use ($nameArray) {
719 $fname = array_shift($nameArray);
720 $lastName = implode(' ', $nameArray);
721 $q->where('first_name', 'LIKE', "%$fname%")
722 ->orWhere('last_name', 'LIKE', "%$lastName%");
723 });
724 }
725
726 foreach ($fields as $field) {
727 $query->orWhere($field, 'LIKE', "%$search%");
728 }
729 });
730 }
731
732 return $query;
733 }
734
735 public function getRedirectUrlWithQuery()
736 {
737 $settings = $this->calendar_event->settings;
738
739 $isEnabled = Arr::isTrue($settings, 'custom_redirect.enabled');
740 $redirectUrl = Arr::get($settings, 'custom_redirect.redirect_url', '');
741 $queryString = Arr::get($settings, 'custom_redirect.query_string', '');
742 $isQueryString = Arr::get($settings, 'custom_redirect.is_query_string', 'no') == 'yes';
743
744 if ($isQueryString && $queryString) {
745 if (strpos($redirectUrl, '?')) {
746 $redirectUrl .= '&' . $queryString;
747 } else {
748 $redirectUrl .= '?' . $queryString;
749 }
750 }
751
752 if (!$isEnabled || empty($redirectUrl)) {
753 return '';
754 }
755
756 $redirectUrl = EditorShortCodeParser::parse($redirectUrl, $this);
757
758 $isUrlParser = apply_filters('fluent_booking/will_parse_redirect_url_value', true, $this);
759
760 if ($isUrlParser) {
761 if (strpos($redirectUrl, '=&') || '=' == substr($redirectUrl, -1)) {
762 $urlArray = explode('?', $redirectUrl);
763 $baseUrl = array_shift($urlArray);
764 $query = wp_parse_url($redirectUrl)['query'];
765 $queryParams = explode('&', $query);
766
767 $params = [];
768 foreach ($queryParams as $queryParam) {
769 $paramArray = explode('=', $queryParam);
770 if (!empty($paramArray[1])) {
771 $params[$paramArray[0]] = $paramArray[1];
772 }
773 }
774 $redirectUrl = add_query_arg($params, $baseUrl);
775 }
776 }
777
778 return $redirectUrl;
779 }
780
781 public function getConfirmationUrl()
782 {
783 return add_query_arg([
784 'fluent-booking' => 'booking',
785 'meeting_hash' => $this->hash,
786 'type' => 'confirmation',
787 ], Helper::getBookingReceiptLandingBaseUrl());
788 }
789
790 public function getAdminViewUrl()
791 {
792 return Helper::getAppBaseUrl('scheduled-events?period=upcoming&booking_id=' . $this->id);
793 }
794
795 public function getIcsDownloadUrl()
796 {
797 return add_query_arg([
798 'fluent-booking' => 'booking',
799 'meeting_hash' => $this->hash,
800 'type' => 'confirmation',
801 'ics' => 'download',
802 ], Helper::getBookingReceiptLandingBaseUrl());
803 }
804
805 public function getRescheduleUrl()
806 {
807 return add_query_arg([
808 'fluent-booking' => 'booking',
809 'meeting_hash' => $this->hash,
810 'type' => 'reschedule',
811 ], Helper::getBookingReceiptLandingBaseUrl());
812 }
813
814 public function getCancelUrl()
815 {
816 return add_query_arg([
817 'fluent-booking' => 'booking',
818 'meeting_hash' => $this->hash,
819 'type' => 'cancel',
820 ], Helper::getBookingReceiptLandingBaseUrl());
821 }
822
823 public function hasBookingAccess()
824 {
825 $hostIds = $this->getHostIds();
826 $hasAccess = PermissionManager::userCan('manage_all_bookings');
827 return in_array(get_current_user_id(), $hostIds) || $hasAccess;
828 }
829
830 private function canPerformAction($settings)
831 {
832 if (!in_array($this->status, ['scheduled', 'pending'])) {
833 return false;
834 }
835
836 if (!Arr::isTrue($settings, 'enabled')) {
837 return true;
838 }
839
840 if (Arr::get($settings, 'type') == 'conditional') {
841 $conditionUnit = Arr::get($settings, 'condition.unit');
842 $conditionValue = Arr::get($settings, 'condition.value');
843
844 $bookingStartTime = strtotime($this->start_time);
845 $currentTime = time();
846
847 $conditionTime = $conditionValue * 60;
848 if ($conditionUnit == 'hours') {
849 $conditionTime = $conditionTime * 60;
850 }
851
852 return $bookingStartTime - $currentTime > $conditionTime;
853 }
854
855 return false;
856 }
857
858 public function canCancel()
859 {
860 $settings = $this->calendar_event->getCanNotCancelSettings();
861
862 return $this->canPerformAction($settings);
863 }
864
865 public function canReschedule()
866 {
867 $settings = $this->calendar_event->getCanNotRescheduleSettings();
868
869 return $this->canPerformAction($settings);
870 }
871
872 public function isMultiGuestBooking()
873 {
874 return $this->event_type == 'group' || $this->event_type == 'group_event';
875 }
876
877 public function isMultiHostBooking()
878 {
879 return $this->event_type == 'single_event' || $this->event_type == 'group_event';
880 }
881
882 public function getHostProfiles($public = true)
883 {
884 $hostIds = $this->getHostIds();
885
886 $hosts = [];
887 foreach ($hostIds as $hostId) {
888 $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
889 if ($calendar) {
890 $hosts[] = $calendar->getAuthorProfile($public);
891 }
892 }
893
894 return $hosts;
895 }
896
897 public function getInviteePhoneNumber($calendarEvent)
898 {
899 $customFormData = $this->getCustomFormData(false);
900
901 $customFields = BookingFieldService::getBookingFields($calendarEvent);
902
903 foreach ($customFields as $field) {
904 $fieldValue = Arr::get($customFormData, $field['name']);
905 if ($fieldValue && $field['type'] == 'phone' && Arr::isTrue($field, 'is_sms_number')) {
906 return $fieldValue;
907 }
908 }
909
910 return $this->phone;
911 }
912
913 public function getCancellationMessage()
914 {
915 $message = Arr::get($this->calendar_event->settings, 'can_not_cancel.message');
916
917 $message = EditorShortCodeParser::parse($message, $this);
918
919 if ($message) {
920 return $message;
921 }
922
923 return __('Sorry! you can not cancel this', 'fluent-booking');
924 }
925
926 public function getRescheduleMessage()
927 {
928 $message = Arr::get($this->calendar_event->settings, 'can_not_reschedule.message');
929
930 $message = EditorShortCodeParser::parse($message, $this);
931
932 if ($message) {
933 return $message;
934 }
935
936 return __('Sorry! you can not reschedule this', 'fluent-booking');
937 }
938
939 public function getHostDetails($isPublic = true, $hostId = null)
940 {
941 $hostId = $hostId ?: $this->host_user_id;
942
943 if ($hostId && $user = get_user_by('ID', $hostId)) {
944 $name = trim($user->first_name . ' ' . $user->last_name);
945 if (!$name) {
946 $name = $user->display_name;
947 }
948 $data = [
949 'id' => $user->ID,
950 'name' => $name,
951 'email' => $user->user_email,
952 'first_name' => $user->first_name,
953 'last_name' => $user->last_name,
954 'avatar' => Helper::fluentBookingUserAvatar($user->ID, $user)
955 ];
956 } else {
957 $data = $this->calendar->getAuthorProfile(false);
958 }
959
960 if ($isPublic) {
961 unset($data['email']);
962 }
963
964 return $data;
965 }
966
967 public function getHostsDetails($isPublic = true, $excludeHostId = null)
968 {
969 $hostIds = $this->getHostIds();
970
971 $hosts = [];
972 foreach ($hostIds as $hostId) {
973 if ($hostId != $excludeHostId) {
974 $hosts[] = $this->getHostDetails($isPublic, $hostId);
975 }
976 }
977
978 return $hosts;
979 }
980
981 public function getHostTimezone()
982 {
983 if ($this->host_user_id) {
984 $calendar = Calendar::where('user_id', $this->host_user_id)
985 ->where('type', 'simple')
986 ->first();
987
988 if (!$calendar) {
989 return '';
990 }
991 return $calendar->author_timezone;
992 }
993 return '';
994 }
995
996 public function getIcsBookingDescription()
997 {
998 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
999
1000 if ($this->message) {
1001 $description .= __('Note: ', 'fluent-booking') . '\\n' . $this->message . '\\n' . '\\n';
1002 }
1003
1004 if ($additionalData = $this->getAdditionalData(false)) {
1005 if (!empty($description )) {
1006 $description .= "\\n";
1007 } else {
1008 $description = '';
1009 }
1010
1011 $additionalData = str_replace(PHP_EOL, '\\n', $additionalData);
1012
1013 $description .= $additionalData;
1014 }
1015
1016 return $description;
1017 }
1018
1019 public function getAdditionalData($isHtml = false)
1020 {
1021 $customData = BookingFieldService::getFormattedCustomBookingData($this);
1022
1023 if (!$customData) {
1024 return '';
1025 }
1026
1027 if (!$isHtml) {
1028 $lines = array_filter(array_map(function ($data) {
1029 return !empty($data['value']) ? $data['label'] . ': ' . PHP_EOL . esc_html($data['value']) : null;
1030 }, $customData));
1031
1032 return implode(PHP_EOL . PHP_EOL, $lines);
1033 }
1034
1035 $html = '<table>';
1036 foreach ($customData as $data) {
1037 if (empty($data['value'])) {
1038 continue;
1039 }
1040 $html .= '<tr>';
1041 $html .= '<td><b>' . $data['label'] . '</b></td>';
1042 $html .= '<td>' . $data['value'] . '</td>';
1043 $html .= '</tr>';
1044 }
1045 $html .= '</table>';
1046
1047 return $html;
1048 }
1049
1050 public function getConfirmationData()
1051 {
1052 $author = $this->getHostDetails(false);
1053
1054 $guestName = trim($this->first_name . ' ' . $this->last_name);
1055
1056 $bookingTitle = $this->getBookingTitle();
1057
1058 $sections = [
1059 'what' => [
1060 'title' => __('What', 'fluent-booking'),
1061 'content' => $bookingTitle,
1062 ],
1063 'when' => [
1064 'title' => __('When', 'fluent-booking'),
1065 'content' => $this->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')',
1066 ],
1067 'who' => [
1068 'title' => __('Who', 'fluent-booking'),
1069 'content' => $author['name'] . ' - ' . __('Organizer', 'fluent-booking') . PHP_EOL . $author['email'] . PHP_EOL . PHP_EOL . $guestName . PHP_EOL . $this->email
1070 ],
1071 'where' => [
1072 'title' => __('Where', 'fluent-booking'),
1073 'content' => $this->getLocationAsText()
1074 ],
1075 ];
1076
1077 $lines = array_map(function ($section) {
1078 return $section['title'] . ': ' . PHP_EOL . esc_html($section['content']);
1079 }, $sections);
1080
1081 return implode(PHP_EOL . PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
1082 }
1083
1084 public function getMeetingBookmarks($assetsUrl = '')
1085 {
1086 $bookingTitle = $this->getBookingTitle();
1087
1088 $eventTitle = $this->calendar_event->title;
1089
1090 return apply_filters('fluent_booking/meeting_bookmarks', [
1091 'google' => [
1092 'title' => __('Google Calendar', 'fluent-booking'),
1093 'url' => add_query_arg([
1094 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1095 'text' => $bookingTitle,
1096 'details' => $eventTitle,
1097 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1098 ], 'https://calendar.google.com/calendar/r/eventedit'),
1099 'icon' => $assetsUrl . 'images/g-icon.svg'
1100 ],
1101 'outlook' => [
1102 'title' => __('Outlook', 'fluent-booking'),
1103 'url' => add_query_arg([
1104 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1105 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1106 'subject' => $bookingTitle,
1107 'path' => '/calendar/action/compose',
1108 'body' => $eventTitle,
1109 'rru' => 'addevent',
1110 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1111 ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1112 'icon' => $assetsUrl . 'images/ol-icon.svg'
1113 ],
1114 'msoffice' => [
1115 'title' => __('Microsoft Office', 'fluent-booking'),
1116 'url' => add_query_arg([
1117 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1118 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1119 'subject' => $bookingTitle,
1120 'path' => '/calendar/action/compose',
1121 'body' => $eventTitle,
1122 'rru' => 'addevent',
1123 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1124 ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1125 'icon' => $assetsUrl . 'images/msoffice.svg'
1126 ],
1127 'other' => [
1128 'title' => __('Other Calendar', 'fluent-booking'),
1129 'url' => $this->getIcsDownloadUrl(),
1130 'icon' => $assetsUrl . 'images/ics.svg'
1131 ]
1132 ], $this);
1133 }
1134
1135 }