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 / Services / BookingService.php

BookingService.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.10.0, at app/Services/BookingService.php

455 lines 16.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\Services;
4
5 use FluentBooking\App\App;
6 use FluentBooking\App\Models\Booking;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\Framework\Support\Arr;
9
10 class BookingService
11 {
12 public static function createBooking($data = [], $calendarSlot = null, $customFieldsData = [])
13 {
14 if (empty($data['email']) || empty($data['start_time']) || empty($data['person_time_zone'])) {
15 throw new \Exception(esc_html__('Email, Start Time and timezone are required to create a booking', 'fluent-booking'), 422);
16 }
17
18 if (!$calendarSlot) {
19 $calendarSlot = CalendarSlot::findOrFail($data['event_id']);
20 }
21
22 $defaults = [
23 'event_id' => $calendarSlot->id,
24 'calendar_id' => $calendarSlot->calendar_id,
25 'host_user_id' => $calendarSlot->user_id
26 ];
27
28 $data = self::prepareBookingData($data, $calendarSlot);
29
30 $guests = Arr::get($data, 'additional_guests', []);
31
32 $bookingData = Arr::only(wp_parse_args($data, $defaults), (new Booking())->getFillable());
33
34 $bookingData['group_id'] = self::getGroupId($calendarSlot, $bookingData);
35
36 $bookingData['event_type'] = $calendarSlot->event_type;
37
38 $bookingData = apply_filters('fluent_booking/booking_data', $bookingData, $calendarSlot, $customFieldsData, $data);
39
40 if (is_wp_error($bookingData)) {
41 return $bookingData;
42 }
43
44 return self::createSingleOrMultiBooking($bookingData, $calendarSlot, $customFieldsData, $guests);
45 }
46
47 public static function createSingleOrMultiBooking($bookingData, $calendarSlot, $customFieldsData, $guests = [], $bookingIds = [])
48 {
49 if (is_array($bookingData['start_time'])) {
50 return self::createMultiTimeBooking($bookingData, $calendarSlot, $customFieldsData, $guests);
51 }
52
53 if (is_array($bookingData['email'])) {
54 return self::createMultiGuestBooking($bookingData, $calendarSlot, $customFieldsData);
55 }
56
57 do_action('fluent_booking/before_booking', $bookingData, $calendarSlot);
58
59 $booking = Booking::create($bookingData);
60
61 self::attachHosts($booking, $calendarSlot);
62 self::updateParentInfo($booking, $bookingIds);
63 self::updateMetas($booking, $bookingData, $guests, $customFieldsData, $calendarSlot);
64
65 $booking->load('calendar');
66
67 $bookingData = apply_filters('fluent_booking/after_booking_data', $bookingData, $booking, $calendarSlot, $customFieldsData);
68
69 // this pre hook is for early actions that require for remote calendars and locations
70 do_action('fluent_booking/pre_after_booking_' . $booking->status, $booking, $calendarSlot, $bookingData);
71
72 // We are just renewing this as this may have been changed by the pre hook
73 $booking = Booking::find($booking->id);
74 do_action('fluent_booking/after_booking_' . $booking->status, $booking, $calendarSlot, $bookingData);
75
76 return $booking;
77 }
78
79 public static function createMultiTimeBooking($data, $calendarSlot, $customFieldsData, $guests)
80 {
81 $booking = [];
82 $bookingIds = [];
83 $createdBookingIds = [];
84 $lastBooking = end($data['start_time']);
85 $totalBooking = count($data['start_time']);
86 $bookingTimes = array_combine($data['start_time'], $data['end_time']);
87
88 foreach ($bookingTimes as $startTime => $endTime) {
89 $bookingData = $data;
90
91 $bookingData['start_time'] = $startTime;
92 $bookingData['end_time'] = $endTime;
93
94 $isConfRequired = $calendarSlot->isConfirmationRequired($startTime);
95 $bookingData['status'] = $isConfRequired ? 'pending' : $data['status'];
96
97 if ($startTime == $lastBooking) {
98 $createdBookingIds = $bookingIds;
99 }
100
101 if (Arr::get($data, 'payment_method')) {
102 if ($startTime == $lastBooking) {
103 $bookingData['quantity'] = $totalBooking;
104 } else {
105 $bookingData['payment_status'] = '';
106 $bookingData['payment_method'] = '';
107 }
108 }
109
110 $booking = self::createSingleOrMultiBooking($bookingData, $calendarSlot, $customFieldsData, $guests, $createdBookingIds);
111
112 $bookingIds[] = $booking->id;
113 }
114
115 return $booking;
116 }
117
118 public static function createMultiGuestBooking($data, $calendarSlot, $customFieldsData)
119 {
120 $booking = [];
121 $bookingIds = [];
122 $createdBookingIds = [];
123 $lastBooking = end($data['email']);
124 $totalBooking = count($data['email']);
125 $guests = array_combine($data['email'], $data['first_name']);
126
127 foreach ($guests as $email => $name) {
128 $bookingData = $data;
129
130 $bookingData['email'] = $email;
131
132 $nameArray = explode(' ', trim($name));
133 $bookingData['first_name'] = array_shift($nameArray);
134 $bookingData['last_name'] = implode(' ', $nameArray);
135
136 if ($user = get_user_by('email', $email)) {
137 $bookingData['person_user_id'] = $user->ID;
138 }
139
140 $bookingData['group_id'] = self::getGroupId($calendarSlot, $bookingData);
141
142 if ($email == $lastBooking) {
143 $createdBookingIds = $bookingIds;
144 }
145
146 if (Arr::get($data, 'payment_method')) {
147 if ($email == $lastBooking) {
148 $bookingData['quantity'] = $totalBooking;
149 } else {
150 $bookingData['payment_status'] = '';
151 $bookingData['payment_method'] = '';
152 }
153 }
154
155 $booking = self::createSingleOrMultiBooking($bookingData, $calendarSlot, $customFieldsData, [], $createdBookingIds);
156
157 $bookingIds[] = $booking->id;
158 }
159
160 return $booking;
161 }
162
163 private static function prepareBookingData($data, $calendarSlot)
164 {
165 if (empty($data['first_name']) && !empty($data['name'])) {
166 $nameArray = explode(' ', trim($data['name']));
167 $data['first_name'] = array_shift($nameArray);
168 $data['last_name'] = implode(' ', $nameArray);
169 }
170
171 if (empty($data['slot_minutes'])) {
172 $data['slot_minutes'] = $calendarSlot->duration;
173 }
174
175 if (empty($data['end_time'])) {
176 $data['end_time'] = gmdate('Y-m-d H:i:s', strtotime($data['start_time']) + ($data['slot_minutes'] * 60));
177 }
178
179 if (!isset($data['person_user_id'])) {
180 $user = get_user_by('email', $data['email']);
181 if ($user) {
182 $data['person_user_id'] = $user->ID;
183 if (empty($data['first_name'])) {
184 $data['first_name'] = $user->first_name;
185 $data['last_name'] = $user->last_name;
186 }
187 }
188 }
189
190 if (empty($data['location_details'])) {
191 $data['location_details'] = LocationService::getLocationDetails($calendarSlot, [], []);
192 }
193
194 if ($additionalGuests = Arr::get($data, 'additional_guests', [])) {
195 if ($calendarSlot->isMultiGuestEvent()) {
196 $guestEmails = array_map(function ($guest) {
197 return $guest['email'];
198 }, $additionalGuests);
199 $data['email'] = array_merge($guestEmails, (array) $data['email']);
200
201 $guestNames = array_map(function ($guest) {
202 return $guest['name'];
203 }, $additionalGuests);
204 $data['first_name'] = array_merge($guestNames, (array) ($data['first_name'] . ' ' . $data['last_name']));
205
206 $data['additional_guests'] = [];
207 }
208 }
209
210 return $data;
211 }
212
213 private static function attachHosts($booking, $calendarSlot)
214 {
215 $hosts = [$booking->host_user_id];
216 if ($calendarSlot->isMultiHostsEvent()) {
217 $hosts = $calendarSlot->getHostIds();
218 }
219
220 $hostData = [];
221 foreach ($hosts as $hostId) {
222 $hostData[$hostId] = ['status' => 'confirmed'];
223 }
224
225 $booking->hosts()->attach($hostData);
226 }
227
228 protected static function getGroupId($calendarSlot, $bookingData)
229 {
230 if (!$calendarSlot->isMultiGuestEvent()) {
231 return null;
232 }
233
234 $event = Booking::select('group_id')
235 ->where('event_id', $calendarSlot->id)
236 ->where('calendar_id', $calendarSlot->calendar_id)
237 ->where('start_time', $bookingData['start_time'])
238 ->first();
239
240 return $event ? $event->group_id : null;
241 }
242
243 private static function updateMetas($booking, $bookingData, $guests, $customFieldsData, $calendarSlot)
244 {
245 if ($customFieldsData) {
246 Helper::updateBookingMeta($booking->id, 'custom_fields_data', $customFieldsData);
247 }
248
249 if ($guests) {
250 Helper::updateBookingMeta($booking->id, 'additional_guests', $guests);
251 }
252
253 if ($quantity = Arr::get($bookingData, 'quantity')) {
254 Helper::updateBookingMeta($booking->id, 'quantity', $quantity);
255 }
256
257 do_action('fluent_booking/after_booking_meta_update', $booking, $bookingData, $customFieldsData, $calendarSlot);
258 }
259
260 private static function updateParentInfo($booking, $bookingIds)
261 {
262 if (!$bookingIds) {
263 return;
264 }
265
266 Booking::whereIn('id', $bookingIds)->update(['parent_id' => $booking->id]);
267 }
268
269 public static function getBookingConfirmationHtml(Booking $booking, $actionType = 'confirmation')
270 {
271 $validActions = [
272 'confirmation',
273 'cancel',
274 'reschedule'
275 ];
276
277 if (!in_array($actionType, $validActions)) {
278 $actionType = 'confirmation';
279 }
280
281 $calendarSlot = $booking->calendar_event;
282
283 $author = $booking->getHostDetails(false);
284
285 $bookingTitle = $booking->getBookingTitle();
286
287 $sections = [
288 'what' => [
289 'title' => __('What', 'fluent-booking'),
290 'content' => $bookingTitle
291 ],
292 'when' => [
293 'title' => __('When', 'fluent-booking'),
294 'content' => $booking->getFullBookingDateTimeText($booking->person_time_zone, true) . ' (' . $booking->person_time_zone . ')'
295 ],
296 'who' => [
297 'title' => __('Who', 'fluent-booking'),
298 'content' => $booking->getHostAndGuestDetailsHtml()
299 ],
300 'where' => [
301 'title' => __('Where', 'fluent-booking'),
302 'content' => $booking->getLocationDetailsHtml()
303 ]
304 ];
305
306 if ($guests = $booking->getAdditionalGuests(true)) {
307 $sections['guests'] = [
308 'title' => __('Additional Guests', 'fluent-booking'),
309 'content' => $guests
310 ];
311 }
312
313 if ($booking->status == 'cancelled') {
314 // add cancellation reason at the beginning
315 $sections = array_merge([
316 'cancellation_reason' => [
317 'title' => __('Cancellation Reason', 'fluent-booking'),
318 'content' => $booking->getCancelReason(false, true)
319 ]
320 ], $sections);
321 }
322
323 if ($booking->status == 'rejected') {
324 // add rejection reason at the beginning
325 $sections = array_merge([
326 'cancellation_reason' => [
327 'title' => __('Rejection Reason', 'fluent-booking'),
328 'content' => $booking->getRejectReason(false, true)
329 ]
330 ], $sections);
331 }
332
333 if ($booking->message) {
334 $sections['note'] = [
335 'title' => __('Additional Note', 'fluent-booking'),
336 'content' => wpautop($booking->message)
337 ];
338 }
339
340 $customFieldsData = $booking->getCustomFormData(true, true);
341
342 foreach ($customFieldsData as $dataKey => $data) {
343 if (!empty($data['value'])) {
344 $sections[$dataKey] = [
345 'title' => $data['label'],
346 'content' => $data['value']
347 ];
348 }
349 }
350
351 $bookingStatus = $booking->getBookingStatus();
352
353 $subHeading = '';
354 if ($booking->status == 'scheduled') {
355 // translators: %s is the name of the person scheduled
356 $subHeading = sprintf(__('You are scheduled with %s', 'fluent-booking'), $author['name']);
357
358 if ($actionType == 'confirmation' && $calendarSlot->allowMultiBooking()) {
359 $bookingTime = (array) $sections['when']['content'];
360 $sections['when']['content'] = array_merge($bookingTime, $booking->getOtherBookingTimes());
361 }
362 }
363
364 // translators: %s is the status of the meeting
365 $title = sprintf(__('Your meeting has been %s', 'fluent-booking'), $bookingStatus);
366 if ($booking->status == 'pending' && $booking->payment_status != 'pending') {
367 $title = __('Your booking has been submitted', 'fluent-booking');
368 $subHeading = __('Please wait for the host to confirm your booking', 'fluent-booking');
369 }
370
371 $assetsUrl = App::getInstance('url.assets');
372
373 $confirmationData = [
374 'author' => $author,
375 'title' => $title,
376 'sub_heading' => $subHeading,
377 'sections' => $sections,
378 'slot' => $calendarSlot,
379 'booking' => $booking,
380 'message' => __('A confirmation has been sent to your email address along with meeting location details.', 'fluent-booking'),
381 'action_type' => $actionType,
382 'can_cancel' => $booking->canCancel(),
383 'bookmarks' => [],
384 'confirm_icon' => '',
385 'action_url' => '',
386 'extra_html' => ''
387 ];
388
389 if ($booking->payment_status) {
390 $confirmationData['extra_html'] = EditorShortCodeParser::parse('{{payment.receipt_html}}', $booking);
391 }
392
393 if ($actionType == 'cancel') {
394 $confirmationData['title'] = __('Booking Cancellation', 'fluent-booking');
395 $confirmationData['sub_heading'] = __('Confirm and cancel the scheduled booking', 'fluent-booking');
396 $confirmationData['cancel_field'] = BookingFieldService::getBookingFieldByName($calendarSlot, 'cancellation_reason');
397 $confirmationData['action_url'] = add_query_arg([
398 'action' => 'fcal_cancel_meeting',
399 'meeting_hash' => $booking->hash,
400 'scope' => Arr::get($_REQUEST, 'scope') // phpcs:ignore WordPress.Security.NonceVerification.Recommended
401 ], admin_url('admin-ajax.php'));
402 }
403
404 if ($booking->status == 'scheduled' && $actionType == 'confirmation') {
405 $confirmationData['confirm_icon'] = $assetsUrl . '/images/check-mark.png';
406 $confirmationData['bookmarks'] = $booking->getMeetingBookmarks($assetsUrl);
407 }
408
409 if ($booking->status == 'cancelled' || $booking->status == 'rejected') {
410 $confirmationData['confirm_icon'] = $assetsUrl . '/images/cancel-mark.png';
411 }
412
413 $confirmationData = apply_filters('fluent_booking/schedule_receipt_data', $confirmationData, $booking);
414
415 return (string)App::make('view')->make('public.booking_confirmation', $confirmationData);
416 }
417
418 public static function generateBookingICS(Booking $booking)
419 {
420 $author = $booking->getHostDetails(false);
421
422 // Initialize the ICS content
423 $icsContent = "BEGIN:VCALENDAR\r\n";
424 $icsContent .= "VERSION:2.0\r\n";
425 $icsContent .= "PRODID:-//Google Inc//Fluent Booking//EN\r\n";
426 $icsContent .= "METHOD:REQUEST\r\n";
427 $icsContent .= "STATUS:CONFIRMED\r\n";
428
429 $icsContent .= "BEGIN:VEVENT\r\n";
430 $icsContent .= "UID:" . md5($booking->hash) . "\r\n"; // Unique ID for the event
431
432 // Event details
433 $icsContent .= "SUMMARY:" . $booking->getBookingTitle() . "\r\n";
434 $icsContent .= "DESCRIPTION:" . $booking->getIcsBookingDescription() . "\r\n";
435
436 // Date and time formatting (assuming eventStart and eventEnd are DateTime objects)
437 $icsContent .= "DTSTART:" . gmdate('Ymd\THis\Z', strtotime($booking->start_time)) . "\r\n";
438 $icsContent .= "DTEND:" . gmdate('Ymd\THis\Z', strtotime($booking->end_time)) . "\r\n";
439
440 $icsContent .= "LOCATION:" . $booking->getLocationAsText() . "\r\n";
441
442 $icsContent .= "ORGANIZER;CN=\"" . $author['name'] . "\":mailto:" . $author['email'] . "\r\n";
443
444 $icsContent .= "ATTENDEE;CN=\"" . $booking->email . "\";ROLE=REQ-PARTICIPANT;RSVP=TRUE;PARTSTAT=ACCEPTED:mailto:" . $booking->email . "\r\n";
445
446 $icsContent .= "END:VEVENT\r\n";
447
448 // Close the VCALENDAR component
449 $icsContent .= "END:VCALENDAR\r\n";
450
451 return $icsContent;
452 }
453
454 }
455