PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.10.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.10.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 / Http / Controllers / SchedulesController.php

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

500 lines 17.2 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\BookingActivity;
8 use FluentBooking\App\Services\EmailNotificationService;
9 use FluentBooking\App\Services\Helper;
10 use FluentBooking\Framework\Support\Arr;
11 use FluentBooking\App\Services\CurrenciesHelper;
12 use FluentBooking\Framework\Http\Request\Request;
13 use FluentBooking\App\Services\PermissionManager;
14 use FluentBooking\App\Services\CalendarService;
15
16 class SchedulesController extends Controller
17 {
18 public function index(Request $request)
19 {
20 $filters = $request->get('filters', []);
21
22 $search = $request->getSafe('search', '');
23
24 $eventId = Arr::get($filters, 'event');
25
26 $author = Arr::get($filters, 'author');
27
28 $eventType = sanitize_text_field(Arr::get($filters, 'event_type'));
29
30 $period = sanitize_text_field(Arr::get($filters, 'period', 'upcoming'));
31
32 $range = array_map('sanitize_text_field', Arr::get($filters, 'range', []));
33
34 $query = Booking::with(['calendar_event']);
35
36 if (is_numeric($author)) {
37 $author = (int)$author;
38 } else {
39 $author = sanitize_text_field($author);
40 }
41
42 $currentHostId = get_current_user_id();
43
44 $hasPermission = PermissionManager::userCanSeeAllBookings();
45
46 if (!$hasPermission && (!$author || $author == 'all')) {
47 $authorCalendar = Calendar::where('user_id', $currentHostId)
48 ->where('type', 'simple')
49 ->first();
50
51 if ($authorCalendar) {
52 $author = $authorCalendar->id;
53 }
54 }
55
56 if ($author && $author !== 'all') {
57 if ($author == 'me' || !$hasPermission) {
58 $query->where('host_user_id', $currentHostId);
59 }
60
61 if ($author != 'me') {
62 $query->where('calendar_id', $author);
63 }
64
65 if ($eventId && $eventId !== 'all') {
66 $query->where('event_id', (int) $eventId);
67 }
68
69 if ($eventType && $eventType !== 'all') {
70 $query->where('event_type', $eventType);
71 }
72 }
73
74 do_action_ref_array('fluent_booking/schedules_query', [&$query]);
75
76 $query->applyDateRangeFilter($range);
77
78 $query->applyComputedStatus($period);
79
80 $query->applyBookingOrderByStatus($period);
81
82 $query->groupBy('group_id');
83
84 if (!empty($search)) {
85 $query->searchBy($search);
86 }
87
88 $schedules = $query->paginate();
89
90 foreach ($schedules as $schedule) {
91 $this->formatBooking($schedule);
92 }
93
94 $data = [
95 'schedules' => $schedules,
96 'timezone' => 'UTC'
97 ];
98
99 $data['calendar_event_lists'] = CalendarService::getCalendarOptionsByTitle();
100
101 if ($author == 'me') {
102 $slotOptions = CalendarService::getSlotOptions(null, $currentHostId);
103 $data['slot_options'] = $slotOptions;
104 }
105
106 if ($request->get('page') == 1) {
107 $this->addCountsForFirstPage($author, $data);
108 }
109
110 return $data;
111 }
112
113 private function addCountsForFirstPage($author, &$data)
114 {
115 $bookingQuery = Booking::query()
116 ->when($author == 'me', function($query) {
117 return $query->where('host_user_id', get_current_user_id());
118 })
119 ->when($author && is_numeric($author), function($query) use ($author) {
120 return $query->where('calendar_id', $author);
121 })
122 ->distinct('group_id');
123
124 $data['pending_count'] = (clone $bookingQuery)->whereIn('status', ['pending', 'reserved'])->count('group_id');
125 $data['no_show_count'] = (clone $bookingQuery)->where('status', 'no_show')->count('group_id');
126 $data['cancelled_count'] = (clone $bookingQuery)->where('status', 'cancelled')->count('group_id');
127 }
128
129 public function patchBooking(Request $request, $bookingId)
130 {
131 $booking = Booking::findOrFail($bookingId);
132 $oldBooking = clone $booking;
133
134 $data = $request->all();
135
136 $this->validate($data, [
137 'column' => 'required',
138 ]);
139
140 do_action('fluent_booking/before_patch_booking_schedule', $booking, $data);
141
142 $value = $request->get('value');
143
144 $column = $data['column'];
145
146 if ($booking->{$column} == $value) {
147 return $this->sendError(['message' => __('No changes found', 'fluent-booking')]);
148 }
149
150 $validColumns = [
151 'internal_note',
152 'email',
153 'phone',
154 'first_name',
155 'last_name',
156 'status',
157 'payment_status'
158 ];
159
160 if (!in_array($column, $validColumns)) {
161 return $this->sendError(['message' => __('Invalid column', 'fluent-booking')]);
162 }
163
164 if ($column === 'email') {
165 if (!$value || !is_email($value)) {
166 return $this->sendError(['message' => __('Invalid email address', 'fluent-booking')]);
167 }
168 $value = sanitize_email($value);
169 } else {
170 $value = sanitize_text_field($value);
171 }
172
173 if ($column == 'status') {
174 if (!in_array($value, ['scheduled', 'completed', 'cancelled', 'rejected', 'no_show'])) {
175 return $this->sendError(['message' => __('Invalid status', 'fluent-booking')]);
176 }
177
178 if ($value == 'scheduled' && $booking->payment_method && $booking->payment_order) {
179 $order = $booking->payment_order;
180 $order->total_paid = $order->total_amount;
181 $order->completed_at = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
182 $order->status = 'paid';
183 $order->save();
184
185 $updateData['payment_status'] = 'paid';
186
187 do_action('fluent_booking/log_booking_activity', $this->getPaymentPaidLog($booking->id));
188
189 do_action('fluent_booking/payment/update_payment_status_paid', $booking);
190
191 } else if ($value == 'scheduled') {
192 do_action('fluent_booking/log_booking_activity', $this->getConfirmLog($booking->id));
193 }
194
195 if ($value == 'cancelled') {
196 $cancelReason = sanitize_text_field($data['cancel_reason']);
197 $booking->cancelMeeting($cancelReason, 'host', get_current_user_id());
198 }
199
200 if ($value == 'rejected') {
201 $rejectReason = sanitize_text_field($data['reject_reason']);
202 $booking->rejectMeeting($rejectReason, get_current_user_id());
203 }
204
205 if (in_array($value, ['cancelled', 'rejected'])) {
206 if ($booking->payment_method && Arr::get($data, 'refund_payment') == 'yes') {
207 do_action('fluent_booking/refund_payment_' . $booking->payment_method, $booking, $booking->calendar_event);
208 }
209 return [
210 /* translators: %s: Booking status */
211 'message' => sprintf(__('The booking has been %s', 'fluent-booking'), $value)
212 ];
213 }
214 }
215
216 if ($column == 'payment_status') {
217 if (!in_array($value, ['pending', 'paid'])) {
218 return $this->sendError(['message' => __('Invalid payment status', 'fluent-booking')]);
219 }
220
221 if ($value == 'paid') {
222 do_action('fluent_booking/log_booking_activity', $this->getPaymentPaidLog($booking->id));
223 }
224
225 if ($value == 'pending') {
226 do_action('fluent_booking/log_booking_activity', $this->getPaymentPendingLog($booking->id));
227 }
228
229 if ($booking->payment_order) {
230 do_action('fluent_booking/payment/status_changed', $booking->payment_order, $booking, $value);
231 }
232 }
233
234 $updateData[$column] = $value;
235 $booking->fill($updateData);
236 $booking->save();
237
238 if ($column === 'status') {
239 do_action('fluent_booking/booking_schedule_' . $value, $booking, $booking->calendar_event);
240
241 do_action('fluent_booking/pre_after_booking_' . $value, $booking, $booking->calendar_event);
242
243 $booking = Booking::with(['calendar_event', 'calendar'])->find($booking->id);
244
245 do_action('fluent_booking/after_booking_' . $value, $booking, $booking->calendar_event, $booking);
246 }
247
248 do_action('fluent_booking/after_patch_booking_schedule', $booking, $oldBooking);
249
250 do_action('fluent_booking/after_patch_booking_' . $column, $booking, $booking->calendar_event, $oldBooking->{$column});
251
252 return [
253 /* translators: Updated column name */
254 'message' => sprintf(__('%s has been updated', 'fluent-booking'), ucfirst($column))
255 ];
256 }
257
258 public function getBooking(Request $request, $bookingId)
259 {
260 $booking = Booking::with('calendar_event');
261
262 if (!PermissionManager::userCanSeeAllBookings()) {
263 $booking->whereHas('calendar', function ($q) {
264 $q->where('user_id', get_current_user_id());
265 });
266 }
267
268 $booking = $booking->findOrFail($bookingId);
269 $booking = $this->formatBooking($booking);
270
271 do_action_ref_array('fluent_booking/booking_schedule', [&$booking]);
272
273 $data = [
274 'schedule' => $booking
275 ];
276
277 if (in_array('all_data', $this->request->get('with', []))) {
278 $data = array_merge($data, $this->getBookingMetaInfo($request, $bookingId));
279 }
280
281 return $data;
282 }
283
284 public function deleteBooking(Request $request, $bookingId)
285 {
286 $booking = Booking::findOrFail($bookingId);
287
288 do_action('fluent_booking/before_delete_booking', $booking);
289
290 $booking->delete();
291
292 do_action('fluent_booking/after_delete_booking', $bookingId);
293
294 if ($booking->isMultiGuestBooking()) {
295 Booking::where('event_id', $booking->event_id)->where('group_id', $booking->group_id)->delete();
296 }
297
298 return [
299 'message' => __('Booking Deleted Successfully!', 'fluent-booking')
300 ];
301 }
302
303 public function sendConfirmationEmail(Request $request, $bookingId)
304 {
305 $booking = Booking::with(['calendar', 'calendar_event'])->find($bookingId);
306
307 $emailTo = $request->get('email_to', 'guest');
308
309 $notifications = $booking->calendar_event->getNotifications();
310
311 $email = Arr::get($notifications, 'booking_conf_attendee.email', []);
312 if ($emailTo == 'host') {
313 $email = Arr::get($notifications, 'booking_conf_host.email', []);
314 }
315
316 $result = EmailNotificationService::emailOnBooked($booking, $email, $emailTo, 'scheduled', true);
317
318 if (!$result) {
319 return $this->sendError(['message' => __('Notification sending failed', 'fluent-booking')]);
320 }
321
322 return [
323 'message' => __('Notification sent successfully', 'fluent-booking')
324 ];
325 }
326
327 public function getGroupAttendees(Request $request, $groupId)
328 {
329 $booking = Booking::with('slot');
330
331 if (!PermissionManager::userCanSeeAllBookings()) {
332 $booking->whereHas('calendar', function ($q) {
333 $q->where('user_id', get_current_user_id());
334 });
335 }
336
337 $booking = $booking->where('group_id', $groupId)->first();
338
339 if (!$booking || !$booking->isMultiGuestBooking()) {
340 return $this->sendError(['message' => __('Invalid group id or the event is not a group event', 'fluent-booking')]);
341 }
342
343 $attendees = Booking::where('group_id', $booking->group_id);
344 $search = sanitize_text_field($request->get('search'));
345
346 if (!empty($search)) {
347 $attendees = $attendees->searchBy($search);
348 }
349 $attendees = $attendees->paginate();
350
351 foreach ($attendees as $attendee) {
352 $attendee = $this->formatBooking($attendee);
353 }
354
355 return [
356 'attendees' => $attendees
357 ];
358 }
359
360 public function getBookingActivities(Request $request, $bookingId)
361 {
362 $activities = BookingActivity::where('booking_id', $bookingId)
363 ->orderBy('id', 'DESC')
364 ->get();
365
366 return [
367 'activities' => $activities
368 ];
369 }
370
371 public function getBookingMetaInfo(Request $request, $bookingId)
372 {
373 $booking = Booking::findOrFail($bookingId);
374
375 $activities = BookingActivity::where('booking_id', $booking->id)
376 ->orderBy('id', 'DESC')
377 ->get();
378
379 $activities->each(function ($activity) {
380 $activity->description = wp_unslash($activity->description);
381 });
382
383 $sidebarContents = [];
384 $mainBodyContents = [];
385
386 if (defined('FLUENTCRM')) {
387 $profileHtml = fluentcrm_get_crm_profile_html($booking->email, false);
388 if ($profileHtml) {
389 $sidebarContents[] = [
390 'id' => 'fluent_crm_profule',
391 'title' => __('CRM Profile', 'fluent-booking'),
392 'content' => $profileHtml
393 ];
394 }
395 }
396
397 $order = null;
398 if ($booking->payment_method && $booking->payment_order) {
399 $order = $booking->payment_order;
400 $relations = ['items', 'transaction'];
401 if (method_exists($order, 'discounts')) {
402 $relations[] = 'discounts';
403 }
404 $order->load($relations);
405 $order->currency_sign = CurrenciesHelper::getCurrencySign($order->currency);
406 }
407
408 $mainBodyContents = apply_filters('fluent_booking/booking_meta_info_main_meta', $mainBodyContents, $booking);
409 $mainBodyContents = apply_filters('fluent_booking/booking_meta_info_main_meta_' . $booking->source, $mainBodyContents, $booking);
410
411 return [
412 'activities' => $activities,
413 'sidebar_contents' => $sidebarContents,
414 'payment_order' => $order,
415 'main_body_contents' => $mainBodyContents
416 ];
417 }
418
419 private function formatBooking(&$booking)
420 {
421 $autoCompleteTimeOut = (int) Helper::getGlobalAdminSetting('auto_complete_timing', 60) * 60; // 10 minutes
422
423 if (in_array($booking->status, ['scheduled', 'pending']) && (time() - strtotime($booking->end_time)) > $autoCompleteTimeOut) {
424 $bookingStatus = $booking->status == 'pending' ? 'cancelled' : 'completed';
425 $booking->status = $bookingStatus;
426 $booking->save();
427 do_action('fluent_booking/booking_schedule_' . $bookingStatus, $booking, $booking->calendar_event);
428 }
429
430 if ($booking->isMultiHostBooking()) {
431 $booking->host_profiles = $booking->getHostProfiles();
432 }
433
434 if ($booking->isMultiGuestBooking()) {
435 $booking->booked_count = Booking::where('group_id', $booking->group_id)
436 ->whereIn('status', ['scheduled', 'completed'])->count();
437 } else {
438 $booking->additional_guests = $booking->getAdditionalGuests();
439 }
440
441 $booking->title = $booking->getBookingTitle(true);
442 $booking->author = $booking->getHostDetails(false);
443 $booking->details = $booking->getConfirmationData(true);
444 $booking->location = $booking->getLocationDetailsHtml();
445 $booking->reschedule_url = $booking->getRescheduleUrl();
446 $booking->happening_status = $booking->getOngoingStatus();
447 $booking->booking_status_text = $booking->getBookingStatus();
448 $booking->payment_status_text = $booking->getPaymentStatus();
449 $booking->custom_form_data = $booking->getCustomFormData();
450
451 do_action_ref_array('fluent_booking/format_booking_schedule', [&$booking]);
452
453 return $booking;
454 }
455
456 private function getConfirmedBy()
457 {
458 $confirmedBy = 'host';
459 $userId = get_current_user_id();
460 if ($userId && $user = get_user_by('ID', $userId)) {
461 $confirmedBy = $user->display_name;
462 }
463
464 return $confirmedBy;
465 }
466
467 private function getPaymentPaidLog($bookingId)
468 {
469 return [
470 'booking_id' => $bookingId,
471 'status' => 'closed',
472 'type' => 'success',
473 'title' => __('Payment Successfully Completed', 'fluent-booking'),
474 'description' => __('Payment marked as paid by ', 'fluent-booking') . $this->getConfirmedBy()
475 ];
476 }
477
478 private function getPaymentPendingLog($bookingId)
479 {
480 return [
481 'booking_id' => $bookingId,
482 'status' => 'closed',
483 'type' => 'success',
484 'title' => __('Payment Successfully Marked as Pending', 'fluent-booking'),
485 'description' => __('Payment marked as pending by ', 'fluent-booking') . $this->getConfirmedBy()
486 ];
487 }
488
489 private function getConfirmLog($bookingId)
490 {
491 return [
492 'booking_id' => $bookingId,
493 'status' => 'closed',
494 'type' => 'success',
495 'title' => __('Booking Confirmed', 'fluent-booking'),
496 'description' => __('Booking has been confirmed by ', 'fluent-booking') . $this->getConfirmedBy()
497 ];
498 }
499 }
500