PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.6.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.6.0
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 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.6.0, at app/Models/Booking.php

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