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

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