PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.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 All 34 releases
fluent-booking / app / Http / Controllers / SchedulesController.php

SchedulesController.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.5.0, at app/Http/Controllers/SchedulesController.php

798 lines 27.8 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\Http\Controllers;
4
5 use FluentBooking\App\Models\Booking;
6 use FluentBooking\App\Models\Calendar;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\App\Models\BookingActivity;
9 use FluentBooking\Framework\Database\Orm\ModelNotFoundException;
10 use FluentBooking\App\Services\EmailNotificationService;
11 use FluentBooking\App\Services\Helper;
12 use FluentBooking\Framework\Support\Arr;
13 use FluentBooking\App\Services\CurrenciesHelper;
14 use FluentBooking\Framework\Http\Request\Request;
15 use FluentBooking\App\Services\PermissionManager;
16 use FluentBooking\App\Services\CalendarService;
17 use FluentBooking\App\Services\ExportHelper;
18 use FluentBooking\App\Services\Integrations\FluentCRM\CrmContactService;
19 use FluentBooking\App\Services\Integrations\FluentCart\CustomerProfileService;
20 use FluentCrm\App\Services\PermissionManager as CrmPermissionManager;
21
22 class SchedulesController extends Controller
23 {
24 public function index(Request $request)
25 {
26 $author = $this->resolveAuthor($request);
27
28 $query = $this->buildSchedulesQuery($request, $author);
29
30 $query->groupBy('group_id');
31
32 $schedules = $query->paginate();
33
34 foreach ($schedules as $schedule) {
35 $this->formatBooking($schedule);
36 }
37
38 $data = [
39 'schedules' => $schedules,
40 'timezone' => 'UTC'
41 ];
42
43 $data['calendar_event_lists'] = CalendarService::getCalendarOptionsByTitle();
44
45 if ($author == 'me') {
46 $slotOptions = CalendarService::getSlotOptions(null, get_current_user_id());
47 $data['slot_options'] = $slotOptions;
48 }
49
50 if ($request->get('page') == 1) {
51 $this->addCountsForFirstPage($author, $data);
52 }
53
54 return $data;
55 }
56
57 public function export(Request $request)
58 {
59 $limit = (int) apply_filters('fluent_booking/data_export_limit', 2000);
60
61 $author = $this->resolveAuthor($request);
62
63 $query = $this->buildSchedulesQuery($request, $author);
64
65 $relations = [
66 'calendar_event',
67 'user',
68 'booking_activities' => function ($q) {
69 $q->where('type', 'cancel_reason');
70 },
71 'booking_meta',
72 ];
73
74 if (defined('FLUENT_BOOKING_PRO_DIR_FILE')) {
75 $relations[] = 'payment_order.transaction';
76 }
77
78 $query->with($relations);
79
80 $total = (clone $query)->withoutEagerLoads()->count();
81 $limited = $total > $limit;
82
83 $bookings = $query->take($limit)->get();
84
85 $rows = $bookings->map(function ($booking) {
86 $row = ExportHelper::mapBooking($booking);
87 return apply_filters('fluent_booking/booking_export_row', $row, $booking);
88 })->all();
89
90 $rows = apply_filters('fluent_booking/booking_export_columns', $rows, $bookings);
91
92 return [
93 'bookings' => $rows,
94 'limited' => $limited,
95 'total' => $total,
96 ];
97 }
98
99 protected function resolveAuthor(Request $request)
100 {
101 $author = Arr::get($request->get('filters', []), 'author');
102
103 if (is_numeric($author)) {
104 $author = (int) $author;
105 } else {
106 $author = sanitize_text_field($author);
107 }
108
109 $hasPermission = PermissionManager::userCanSeeAllBookings();
110
111 if ($hasPermission) {
112 return $author;
113 }
114
115 if (!$author || $author == 'all') {
116 $authorCalendar = Calendar::where('user_id', get_current_user_id())
117 ->where('type', 'simple')
118 ->first();
119
120 $author = $authorCalendar ? $authorCalendar->id : '';
121 }
122
123 return $author;
124 }
125
126 protected function buildSchedulesQuery(Request $request, $author = null)
127 {
128 $filters = $request->get('filters', []);
129 $search = $request->getSafe('search');
130 $eventVal = Arr::get($filters, 'event');
131 $eventId = is_numeric($eventVal) ? (int) $eventVal : 0;
132 $eventType = sanitize_text_field(Arr::get($filters, 'event_type'));
133 $period = sanitize_text_field(Arr::get($filters, 'period', 'upcoming'));
134 $range = array_map('sanitize_text_field', Arr::get($filters, 'range', []));
135 $email = sanitize_email(Arr::get($filters, 'email', ''));
136
137 $allowedPeriods = ['upcoming', 'completed', 'cancelled', 'pending', 'no_show', 'latest_bookings', 'all'];
138 if (!in_array($period, $allowedPeriods, true)) {
139 $period = 'upcoming';
140 }
141
142 $query = Booking::with(['calendar_event']);
143
144 $hasPermission = PermissionManager::userCanSeeAllBookings();
145
146 if (!$hasPermission || $author == 'me') {
147 $query->where('host_user_id', get_current_user_id());
148 }
149
150 if ($author && $author !== 'all') {
151 if ($author != 'me') {
152 $query->where('calendar_id', $author);
153 }
154 if ($eventId > 0) {
155 $query->where('event_id', $eventId);
156 }
157 if ($eventType && $eventType !== 'all') {
158 $query->where('event_type', $eventType);
159 }
160 }
161
162 if (!empty($email) && is_email($email)) {
163 $query->where('email', $email);
164 }
165
166 do_action_ref_array('fluent_booking/schedules_query', [&$query]);
167
168 $query->applyDateRangeFilter($range);
169 $query->applyComputedStatus($period);
170 $query->applyBookingOrderByStatus($period);
171
172 if (!empty($search)) {
173 $query->searchBy($search);
174 }
175
176 return $query;
177 }
178
179 private function addCountsForFirstPage($author, &$data)
180 {
181 $bookingQuery = Booking::query()
182 ->when(!PermissionManager::userCanSeeAllBookings() || $author == 'me', function($query) {
183 return $query->where('host_user_id', get_current_user_id());
184 })
185 ->when($author && is_numeric($author), function($query) use ($author) {
186 return $query->where('calendar_id', $author);
187 })
188 ->distinct('group_id');
189
190 $data['pending_count'] = (clone $bookingQuery)->whereIn('status', ['pending', 'reserved'])->count('group_id');
191 $data['no_show_count'] = (clone $bookingQuery)->where('status', 'no_show')->count('group_id');
192 $data['cancelled_count'] = (clone $bookingQuery)->where('status', 'cancelled')->count('group_id');
193 }
194
195 public function patchBooking(Request $request, $bookingId)
196 {
197 $booking = Booking::findOrFail($bookingId);
198 $oldBooking = clone $booking;
199
200 $data = $request->all();
201
202 $this->validate($data, [
203 'column' => 'required',
204 ]);
205
206 do_action('fluent_booking/before_patch_booking_schedule', $booking, $data);
207
208 $value = $request->get('value');
209
210 $column = $data['column'];
211
212 if ($booking->{$column} == $value) {
213 return $this->sendError(['message' => __('No changes found', 'fluent-booking')]);
214 }
215
216 $validColumns = [
217 'internal_note',
218 'email',
219 'phone',
220 'first_name',
221 'last_name',
222 'status',
223 'payment_status'
224 ];
225
226 if (!in_array($column, $validColumns)) {
227 return $this->sendError(['message' => __('Invalid column', 'fluent-booking')]);
228 }
229
230 if ($column === 'email') {
231 if (!$value || !is_email($value)) {
232 return $this->sendError(['message' => __('Invalid email address', 'fluent-booking')]);
233 }
234 $value = sanitize_email($value);
235 } else {
236 $value = sanitize_text_field($value);
237 }
238
239 if ($column == 'status') {
240 if (!in_array($value, ['scheduled', 'completed', 'cancelled', 'rejected', 'no_show'])) {
241 return $this->sendError(['message' => __('Invalid status', 'fluent-booking')]);
242 }
243
244 if (in_array($booking->status, ['cancelled', 'rejected'])) {
245 return $this->sendError(['message' => __('A cancelled or rejected booking can not be changed', 'fluent-booking')]);
246 }
247
248 if ($value == 'scheduled' && $booking->payment_method && $booking->payment_order) {
249 $order = $booking->payment_order;
250
251 if (in_array($order->status, ['refunded', 'partially-refunded'])) {
252 return $this->sendError(['message' => __('A refunded payment can not be marked as paid', 'fluent-booking')]);
253 }
254
255 $order->total_paid = $order->total_amount;
256 $order->completed_at = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
257 $order->status = 'paid';
258 $order->save();
259
260 $updateData['payment_status'] = 'paid';
261
262 do_action('fluent_booking/log_booking_activity', $this->getPaymentPaidLog($booking->id));
263
264 do_action('fluent_booking/payment/update_payment_status_paid', $booking);
265
266 } else if ($value == 'scheduled') {
267 do_action('fluent_booking/log_booking_activity', $this->getConfirmLog($booking->id));
268 }
269
270 if ($value == 'cancelled') {
271 $cancelReason = sanitize_text_field(Arr::get($data, 'cancel_reason', ''));
272 $booking->cancelMeeting($cancelReason, 'host', get_current_user_id());
273 }
274
275 if ($value == 'rejected') {
276 $rejectReason = sanitize_text_field(Arr::get($data, 'reject_reason', ''));
277 $booking->rejectMeeting($rejectReason, get_current_user_id());
278 }
279
280 if (in_array($value, ['cancelled', 'rejected'])) {
281 // cancelMeeting() and rejectMeeting() refuse some statuses without changing the booking.
282 if ($booking->status != $value) {
283 /* translators: %s: Booking status */
284 return $this->sendError(['message' => sprintf(__('This booking can not be %s', 'fluent-booking'), $value)]);
285 }
286
287 if ($booking->payment_method && Arr::get($data, 'refund_payment') == 'yes') {
288 do_action('fluent_booking/refund_payment_' . $booking->payment_method, $booking, $booking->calendar_event);
289 }
290 return [
291 /* translators: %s: Booking status */
292 'message' => sprintf(__('The booking has been %s', 'fluent-booking'), $value)
293 ];
294 }
295
296 $updateAll = Arr::isTrue($data, 'update_all') && $booking->isMultiGuestBooking();
297
298 if ($updateAll && in_array($value, ['no_show', 'completed'])) {
299 Booking::where('event_id', $booking->event_id)
300 ->where('group_id', $booking->group_id)
301 ->update([
302 'status' => $value
303 ]);
304 return [
305 /* translators: %s: Booking status */
306 'message' => sprintf(__('The booking has been %s', 'fluent-booking'), $value)
307 ];
308 }
309 }
310
311 if ($column == 'payment_status') {
312 if (!in_array($value, ['pending', 'paid'])) {
313 return $this->sendError(['message' => __('Invalid payment status', 'fluent-booking')]);
314 }
315
316 if ($value == 'paid') {
317 do_action('fluent_booking/log_booking_activity', $this->getPaymentPaidLog($booking->id));
318 }
319
320 if ($value == 'pending') {
321 do_action('fluent_booking/log_booking_activity', $this->getPaymentPendingLog($booking->id));
322 }
323
324 if ($booking->payment_order) {
325 do_action('fluent_booking/payment/status_changed', $booking->payment_order, $booking, $value);
326 }
327 }
328
329 $updateData[$column] = $value;
330 $booking->fill($updateData);
331 $booking->save();
332
333 if ($column === 'status') {
334 do_action('fluent_booking/booking_schedule_' . $value, $booking, $booking->calendar_event);
335
336 do_action('fluent_booking/pre_after_booking_' . $value, $booking, $booking->calendar_event);
337
338 $booking = Booking::with(['calendar_event', 'calendar'])->find($booking->id);
339
340 do_action('fluent_booking/after_booking_' . $value, $booking, $booking->calendar_event, $booking);
341 }
342
343 do_action('fluent_booking/after_patch_booking_schedule', $booking, $oldBooking);
344
345 do_action('fluent_booking/after_patch_booking_' . $column, $booking, $booking->calendar_event, $oldBooking->{$column});
346
347 return [
348 /* translators: Updated column name */
349 'message' => sprintf(__('%s has been updated', 'fluent-booking'), ucfirst($column))
350 ];
351 }
352
353 public function getBooking(Request $request, $bookingId)
354 {
355 $booking = Booking::with('calendar_event');
356
357 if (!PermissionManager::userCanSeeAllBookings()) {
358 $booking->whereHostAccess(get_current_user_id());
359 }
360
361 $booking = $booking->findOrFail($bookingId);
362 $booking = $this->formatBooking($booking);
363
364 do_action_ref_array('fluent_booking/booking_schedule', [&$booking]);
365
366 $data = [
367 'schedule' => $booking
368 ];
369
370 if (in_array('all_data', $this->request->get('with', []))) {
371 $data = array_merge($data, $this->getBookingMetaInfo($request, $bookingId));
372 }
373
374 return $data;
375 }
376
377 public function deleteBooking(Request $request, $bookingId)
378 {
379 $booking = Booking::findOrFail($bookingId);
380
381 do_action('fluent_booking/before_delete_booking', $booking);
382
383 $booking->delete();
384
385 do_action('fluent_booking/after_delete_booking', $bookingId);
386
387 if ($booking->isMultiGuestBooking()) {
388 Booking::where('event_id', $booking->event_id)->where('group_id', $booking->group_id)->delete();
389 }
390
391 return [
392 'message' => __('Booking Deleted Successfully!', 'fluent-booking')
393 ];
394 }
395
396 public function sendConfirmationEmail(Request $request, $bookingId)
397 {
398 $booking = Booking::with(['calendar', 'calendar_event'])->findOrFail($bookingId);
399
400 $emailTo = $request->get('email_to', 'guest');
401
402 $notifications = $booking->calendar_event->getNotifications();
403
404 $email = Arr::get($notifications, 'booking_conf_attendee.email', []);
405 if ($emailTo == 'host') {
406 $email = Arr::get($notifications, 'booking_conf_host.email', []);
407 }
408
409 $result = EmailNotificationService::emailOnBooked($booking, $email, $emailTo, 'scheduled', true);
410
411 if (!$result) {
412 return $this->sendError(['message' => __('Notification sending failed', 'fluent-booking')]);
413 }
414
415 return [
416 'message' => __('Notification sent successfully', 'fluent-booking')
417 ];
418 }
419
420 public function getGroupAttendees(Request $request, $groupId)
421 {
422 $booking = Booking::with('slot');
423
424 if (!PermissionManager::userCanSeeAllBookings()) {
425 $booking->whereHostAccess(get_current_user_id());
426 }
427
428 $booking = $booking->where('group_id', $groupId)->first();
429
430 if (!$booking || !$booking->isMultiGuestBooking()) {
431 return $this->sendError(['message' => __('Invalid group id or the event is not a group event', 'fluent-booking')]);
432 }
433
434 $attendees = Booking::where('group_id', $booking->group_id);
435 $search = $request->getSafe('search');
436
437 if (!empty($search)) {
438 $attendees = $attendees->searchBy($search);
439 }
440 $attendees = $attendees->paginate();
441
442 foreach ($attendees as $attendee) {
443 $attendee = $this->formatBooking($attendee);
444 }
445
446 return [
447 'attendees' => $attendees
448 ];
449 }
450
451 public function getBookingActivities(Request $request, $bookingId)
452 {
453 $this->resolveOwnedBookingOrFail($bookingId);
454
455 $activities = BookingActivity::where('booking_id', $bookingId)
456 ->where('type', '!=', BookingActivity::TYPE_NOTE)
457 ->orderBy('id', 'DESC')
458 ->get();
459
460 return [
461 'activities' => $activities
462 ];
463 }
464
465 public function getBookingMetaInfo(Request $request, $bookingId)
466 {
467 $booking = $this->resolveOwnedBookingOrFail($bookingId);
468
469 $activities = BookingActivity::where('booking_id', $booking->id)
470 ->where('type', '!=', BookingActivity::TYPE_NOTE)
471 ->orderBy('id', 'DESC')
472 ->get();
473
474 $activities->each(function ($activity) {
475 $activity->description = wp_unslash($activity->description);
476 });
477
478 $sidebarContents = [];
479 $mainBodyContents = [];
480
481 $canReadCrm = CrmContactService::isActive() && CrmPermissionManager::currentUserCan('fcrm_read_contacts');
482 $crmProfile = $canReadCrm ? CrmContactService::getProfileData($booking->email) : null;
483 if ($crmProfile) {
484 $sidebarContents[] = [
485 'id' => 'fluent_crm_profule',
486 'title' => __('CRM Profile', 'fluent-booking'),
487 'type' => 'crm_profile',
488 'profile' => $crmProfile,
489 ];
490 }
491
492 $cartProfile = CustomerProfileService::canView()
493 ? CustomerProfileService::getProfileData($booking->email)
494 : null;
495 if ($cartProfile) {
496 $sidebarContents[] = [
497 'id' => 'fluent_cart_profile',
498 'title' => __('Cart Profile', 'fluent-booking'),
499 'type' => 'cart_profile',
500 'profile' => $cartProfile,
501 ];
502 }
503
504 $order = null;
505 if ($booking->payment_status && $booking->payment_order) {
506 $order = $booking->payment_order;
507 $relations = ['items', 'transaction'];
508 if (method_exists($order, 'discounts')) {
509 $relations[] = 'discounts';
510 }
511 $order->load($relations);
512 $order->currency_sign = CurrenciesHelper::getCurrencySign($order->currency);
513 }
514
515 $mainBodyContents = apply_filters('fluent_booking/booking_meta_info_main_meta', $mainBodyContents, $booking);
516 $mainBodyContents = apply_filters('fluent_booking/booking_meta_info_main_meta_' . $booking->source, $mainBodyContents, $booking);
517
518 return [
519 'activities' => $activities,
520 'sidebar_contents' => $sidebarContents,
521 'payment_order' => $order,
522 'main_body_contents' => $mainBodyContents
523 ];
524 }
525
526 /**
527 * Return the CRM contact state for a booking plus the full tag/list option
528 * sets, so the admin can manage tags/lists inline from the CRM Profile card.
529 */
530 public function getCrmContact(Request $request, $bookingId)
531 {
532 $booking = $this->resolveOwnedBookingOrFail($bookingId);
533
534 if (!CrmContactService::isActive()) {
535 return $this->sendError([
536 'message' => __('FluentCRM is not active.', 'fluent-booking')
537 ]);
538 }
539
540 if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
541 return $this->sendError([
542 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
543 ], 403);
544 }
545
546 $state = CrmContactService::getContactState($booking->email);
547
548 if (!$state) {
549 return $this->sendError([
550 'message' => __('No CRM contact found for this booking.', 'fluent-booking')
551 ], 404);
552 }
553
554 return $state;
555 }
556
557 /**
558 * Guest-field prefill for the CRM "Book Appointment" action.
559 * Keyed by contact id, so it carries its own CRM-read gate.
560 */
561 public function getCrmContactPrefill(Request $request)
562 {
563 if (!CrmContactService::isActive()) {
564 return $this->sendError([
565 'message' => __('FluentCRM is not active.', 'fluent-booking')
566 ]);
567 }
568
569 if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
570 return $this->sendError([
571 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
572 ], 403);
573 }
574
575 $contactId = absint($request->get('contact_id'));
576
577 if (!$contactId) {
578 return $this->sendError([
579 'message' => __('Invalid contact.', 'fluent-booking')
580 ], 422);
581 }
582
583 $prefill = CrmContactService::getBookingPrefillData($contactId);
584
585 if (!$prefill) {
586 return $this->sendError([
587 'message' => __('No CRM contact found.', 'fluent-booking')
588 ], 404);
589 }
590
591 return [
592 'prefill' => $prefill,
593 ];
594 }
595
596 /**
597 * Typeahead search for the admin booking modal's CRM contact picker.
598 * Same intersection gate as getCrmContactPrefill: booking access (policy) AND fcrm_read_contacts.
599 */
600 public function searchCrmContacts(Request $request)
601 {
602 if (!CrmContactService::isActive()) {
603 return $this->sendError([
604 'message' => __('FluentCRM is not active.', 'fluent-booking')
605 ]);
606 }
607
608 if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
609 return $this->sendError([
610 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
611 ], 403);
612 }
613
614 $search = sanitize_text_field($request->get('search', ''));
615
616 return [
617 'contacts' => CrmContactService::searchContacts($search),
618 ];
619 }
620
621 /**
622 * Bounded, searchable tag/list options for the CRM Profile picker.
623 */
624 public function getCrmOptions(Request $request, $bookingId)
625 {
626 $this->resolveOwnedBookingOrFail($bookingId);
627
628 if (!CrmContactService::isActive()) {
629 return $this->sendError([
630 'message' => __('FluentCRM is not active.', 'fluent-booking')
631 ]);
632 }
633
634 if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
635 return $this->sendError([
636 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
637 ], 403);
638 }
639
640 $type = $request->get('type') === 'lists' ? 'lists' : 'tags';
641 $search = sanitize_text_field($request->get('search', ''));
642
643 return [
644 'options' => CrmContactService::getOptions($type, $search),
645 ];
646 }
647
648 public function updateCrmTags(Request $request, $bookingId)
649 {
650 return $this->updateCrmTaxonomy($request, $bookingId, 'tags');
651 }
652
653 public function updateCrmLists(Request $request, $bookingId)
654 {
655 return $this->updateCrmTaxonomy($request, $bookingId, 'lists');
656 }
657
658 private function updateCrmTaxonomy(Request $request, $bookingId, $type)
659 {
660 $booking = $this->resolveOwnedBookingOrFail($bookingId);
661
662 if (!CrmContactService::isActive()) {
663 return $this->sendError([
664 'message' => __('FluentCRM is not active.', 'fluent-booking')
665 ]);
666 }
667
668 if (!CrmPermissionManager::currentUserCan('fcrm_manage_contacts')) {
669 return $this->sendError([
670 'message' => __('You do not have permission to manage CRM contacts.', 'fluent-booking')
671 ], 403);
672 }
673
674 $requestKey = $type === 'tags' ? 'tag_ids' : 'list_ids';
675 $desired = (array) $request->get($requestKey, []);
676
677 $result = CrmContactService::syncTaxonomy($booking->email, $type, $desired);
678
679 if ($result === null) {
680 return $this->sendError([
681 'message' => __('No CRM contact found for this booking.', 'fluent-booking')
682 ], 404);
683 }
684
685 return $this->sendSuccess(array_merge([
686 'message' => __('CRM contact updated successfully.', 'fluent-booking'),
687 ], $result));
688 }
689
690 private function resolveOwnedBookingOrFail($bookingId)
691 {
692 $booking = Booking::findOrFail($bookingId);
693
694 if (PermissionManager::userCanSeeAllBookings()) {
695 return $booking;
696 }
697
698 $allowedIds = $booking->getHostIds();
699
700 if (PermissionManager::userCan('manage_own_calendar')) {
701 if ($event = CalendarSlot::find($booking->event_id)) {
702 $allowedIds = array_merge($allowedIds, $event->getHostIds());
703 }
704 }
705
706 if (in_array(get_current_user_id(), $allowedIds)) {
707 return $booking;
708 }
709
710 $exception = new ModelNotFoundException();
711 $exception->setModel(Booking::class, [$bookingId]);
712 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
713 throw $exception;
714 }
715
716 private function formatBooking(&$booking)
717 {
718 $autoCompleteTimeOut = (int) Helper::getGlobalAdminSetting('auto_complete_timing', 60) * 60; // 10 minutes
719
720 if (in_array($booking->status, ['scheduled', 'pending']) && (time() - strtotime($booking->end_time)) > $autoCompleteTimeOut) {
721 $bookingStatus = $booking->status == 'pending' ? 'cancelled' : 'completed';
722 $booking->status = $bookingStatus;
723 $booking->save();
724 $hookName = $bookingStatus === 'cancelled' ? 'auto_cancelled' : $bookingStatus;
725 do_action('fluent_booking/booking_schedule_' . $hookName, $booking, $booking->calendar_event);
726 }
727
728 if ($booking->isMultiHostBooking()) {
729 $booking->host_profiles = $booking->getHostProfiles();
730 }
731
732 if ($booking->isMultiGuestBooking()) {
733 $booking->booked_count = Booking::where('group_id', $booking->group_id)
734 ->whereIn('status', ['scheduled', 'completed'])->count();
735 } else {
736 $booking->additional_guests = $booking->getAdditionalGuests();
737 }
738
739 $booking->title = $booking->getBookingTitle(true);
740 $booking->author = $booking->getHostDetails(false);
741 $booking->details = $booking->getConfirmationData(true);
742 $booking->location = $booking->getLocationDetailsHtml();
743 $booking->reschedule_url = $booking->getRescheduleUrl();
744 $booking->happening_status = $booking->getOngoingStatus();
745 $booking->booking_status_text = $booking->getBookingStatus();
746 $booking->payment_status_text = $booking->getPaymentStatus();
747 $booking->custom_form_data = $booking->getCustomFormData();
748
749 do_action_ref_array('fluent_booking/format_booking_schedule', [&$booking]);
750
751 return $booking;
752 }
753
754 private function getConfirmedBy()
755 {
756 $confirmedBy = 'host';
757 $userId = get_current_user_id();
758 if ($userId && $user = get_user_by('ID', $userId)) {
759 $confirmedBy = $user->display_name;
760 }
761
762 return $confirmedBy;
763 }
764
765 private function getPaymentPaidLog($bookingId)
766 {
767 return [
768 'booking_id' => $bookingId,
769 'status' => 'closed',
770 'type' => 'success',
771 'title' => __('Payment Successfully Completed', 'fluent-booking'),
772 'description' => __('Payment marked as paid by ', 'fluent-booking') . $this->getConfirmedBy()
773 ];
774 }
775
776 private function getPaymentPendingLog($bookingId)
777 {
778 return [
779 'booking_id' => $bookingId,
780 'status' => 'closed',
781 'type' => 'success',
782 'title' => __('Payment Successfully Marked as Pending', 'fluent-booking'),
783 'description' => __('Payment marked as pending by ', 'fluent-booking') . $this->getConfirmedBy()
784 ];
785 }
786
787 private function getConfirmLog($bookingId)
788 {
789 return [
790 'booking_id' => $bookingId,
791 'status' => 'closed',
792 'type' => 'success',
793 'title' => __('Booking Confirmed', 'fluent-booking'),
794 'description' => __('Booking has been confirmed by ', 'fluent-booking') . $this->getConfirmedBy()
795 ];
796 }
797 }
798