PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.10
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.10
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.5.10, at app/Services/BookingService.php

296 lines 11.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\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 if (empty($data['first_name']) && !empty($data['name'])) {
29 $nameArray = explode(' ', trim($data['name']));
30 $data['first_name'] = array_shift($nameArray);
31 $data['last_name'] = implode(' ', $nameArray);
32 }
33
34 if (empty($data['slot_minutes'])) {
35 $data['slot_minutes'] = $calendarSlot->duration;
36 }
37
38 if (empty($data['end_time'])) {
39 $data['end_time'] = gmdate('Y-m-d H:i:s', strtotime($data['start_time']) + ($data['slot_minutes'] * 60));
40 }
41
42 if (!isset($data['person_user_id'])) {
43 $user = get_user_by('email', $data['email']);
44 if ($user) {
45 $data['person_user_id'] = $user->ID;
46 if (empty($data['first_name'])) {
47 $data['first_name'] = $user->first_name;
48 $data['last_name'] = $user->last_name;
49 }
50 }
51 }
52
53 if (empty($data['location_details'])) {
54 $data['location_details'] = LocationService::getLocationDetails($calendarSlot, [], []);
55 }
56
57 $additionalGuests = Arr::get($data, 'additional_guests', []);
58
59 $bookingData = Arr::only(wp_parse_args($data, $defaults), (new Booking())->getFillable());
60
61 if ($calendarSlot->isMultiGuestEvent()){
62 $event = Booking::select('group_id')
63 ->where('event_id', $calendarSlot->id)
64 ->where('calendar_id', $calendarSlot->calendar_id)
65 ->where('start_time', $bookingData['start_time'])
66 ->first();
67
68 $bookingData['group_id'] = $event ? $event->group_id : null;
69 }
70
71 $bookingData['event_type'] = $calendarSlot->event_type;
72
73 $bookingData = apply_filters('fluent_booking/booking_data', $bookingData, $calendarSlot, $customFieldsData);
74
75 if (is_wp_error($bookingData)) {
76 return $bookingData;
77 }
78
79 do_action('fluent_booking/before_booking', $bookingData, $calendarSlot);
80
81 $booking = Booking::create($bookingData);
82
83 if ($customFieldsData) {
84 Helper::updateBookingMeta($booking->id, 'custom_fields_data', $customFieldsData);
85 }
86
87 if ($additionalGuests) {
88 Helper::updateBookingMeta($booking->id, 'additional_guests', $additionalGuests);
89 }
90
91 $hosts = [$booking->host_user_id];
92 if ($calendarSlot->isOneOffEvent()) {
93 $hosts = $calendarSlot->getHostIds();
94 }
95
96 $hostData = [];
97 foreach ($hosts as $hostId) {
98 $hostData[$hostId] = ['status' => 'confirmed'];
99 }
100
101 $booking->hosts()->attach($hostData);
102
103 $booking->load('calendar');
104
105 // this pre hook is for early actions that require for remote calendars and locations
106 do_action('fluent_booking/pre_after_booking_' . $booking->status, $booking, $calendarSlot, $bookingData);
107
108 // We are just renewing this as this may have been changed by the pre hook
109 $booking = Booking::find($booking->id);
110 do_action('fluent_booking/after_booking_' . $booking->status, $booking, $calendarSlot, $bookingData);
111
112 return $booking;
113 }
114
115 public static function getBookingConfirmationHtml(Booking $booking, $actionType = 'confirmation')
116 {
117 $validActions = [
118 'confirmation',
119 'cancel',
120 'reschedule'
121 ];
122
123 if (!in_array($actionType, $validActions)) {
124 $actionType = 'confirmation';
125 }
126
127 $calendarSlot = $booking->calendar_event;
128
129 $author = $booking->getHostDetails(false);
130
131 $bookingTitle = $booking->getBookingTitle();
132
133 $sections = [
134 'what' => [
135 'title' => __('What', 'fluent-booking'),
136 'content' => $bookingTitle
137 ],
138 'when' => [
139 'title' => __('When', 'fluent-booking'),
140 'content' => $booking->getFullBookingDateTimeText($booking->person_time_zone, true) . ' (' . $booking->person_time_zone . ')'
141 ],
142 'who' => [
143 'title' => __('Who', 'fluent-booking'),
144 'content' => $booking->getHostAndGuestDetailsHtml()
145 ],
146 'where' => [
147 'title' => __('Where', 'fluent-booking'),
148 'content' => $booking->getLocationDetailsHtml()
149 ]
150 ];
151
152 if ($guests = $booking->getAdditionalGuests(true)) {
153 $sections['guests'] = [
154 'title' => __('Additional Guests', 'fluent-booking'),
155 'content' => $guests
156 ];
157 }
158
159 if ($booking->status == 'cancelled') {
160 // add cancellation reason at the beginning
161 $sections = array_merge([
162 'cancellation_reason' => [
163 'title' => __('Cancellation Reason', 'fluent-booking'),
164 'content' => $booking->getCancelReason(false, true)
165 ]
166 ], $sections);
167 }
168
169 if ($booking->status == 'rejected') {
170 // add rejection reason at the beginning
171 $sections = array_merge([
172 'cancellation_reason' => [
173 'title' => __('Rejection Reason', 'fluent-booking'),
174 'content' => $booking->getRejectReason(false, true)
175 ]
176 ], $sections);
177 }
178
179 if ($booking->message) {
180 $sections['note'] = [
181 'title' => __('Additional Note', 'fluent-booking'),
182 'content' => wpautop($booking->message)
183 ];
184 }
185
186 $customFieldsData = $booking->getCustomFormData(true);
187
188 foreach ($customFieldsData as $dataKey => $data) {
189 if (!empty($data['value'])) {
190 $sections[$dataKey] = [
191 'title' => $data['label'],
192 'content' => $data['value']
193 ];
194 }
195 }
196
197 $bookingStatus = $booking->getBookingStatus();
198
199 $subHeading = '';
200 if ($booking->status == 'scheduled') {
201 // translators: %s is the name of the person scheduled
202 $subHeading = sprintf(__('You are scheduled with %s', 'fluent-booking'), $author['name']);
203 }
204
205 // translators: %s is the status of the meeting
206 $title = sprintf(__('Your meeting has been %s', 'fluent-booking'), $bookingStatus);
207 if ($booking->status == 'pending' && $booking->payment_status != 'pending') {
208 $title = __('Your booking has been submitted', 'fluent-booking');
209 $subHeading = __('Please wait for the host to confirm your booking', 'fluent-booking');
210 }
211
212 $assetsUrl = App::getInstance('url.assets');
213
214 $confirmationData = [
215 'author' => $author,
216 'title' => $title,
217 'sub_heading' => $subHeading,
218 'sections' => $sections,
219 'slot' => $calendarSlot,
220 'booking' => $booking,
221 'message' => __('A confirmation has been sent to your email address along with meeting location details.', 'fluent-booking'),
222 'action_type' => $actionType,
223 'can_cancel' => $booking->canCancel(),
224 'bookmarks' => [],
225 'confirm_icon' => '',
226 'action_url' => '',
227 'extra_html' => ''
228 ];
229
230 if ($booking->payment_status) {
231 $confirmationData['extra_html'] = EditorShortCodeParser::parse('{{payment.receipt_html}}', $booking);
232 }
233
234 if ($actionType == 'cancel') {
235 $confirmationData['title'] = __('Booking Cancellation', 'fluent-booking');
236 $confirmationData['sub_heading'] = __('Confirm and cancel the scheduled booking', 'fluent-booking');
237 $confirmationData['cancel_field'] = BookingFieldService::getBookingFieldByName($calendarSlot, 'cancellation_reason');
238 $confirmationData['action_url'] = add_query_arg([
239 'action' => 'fcal_cancel_meeting',
240 'meeting_hash' => $booking->hash,
241 'scope' => Arr::get($_REQUEST, 'scope') // phpcs:ignore WordPress.Security.NonceVerification.Recommended
242 ], admin_url('admin-ajax.php'));
243 }
244
245 if ($booking->status == 'scheduled' && $actionType == 'confirmation') {
246 $confirmationData['confirm_icon'] = $assetsUrl . '/images/check-mark.png';
247 $confirmationData['bookmarks'] = $booking->getMeetingBookmarks($assetsUrl);
248 }
249
250 if ($booking->status == 'cancelled' || $booking->status == 'rejected') {
251 $confirmationData['confirm_icon'] = $assetsUrl . '/images/cancel-mark.png';
252 }
253
254 $confirmationData = apply_filters('fluent_booking/schedule_receipt_data', $confirmationData, $booking);
255
256 return (string)App::make('view')->make('public.booking_confirmation', $confirmationData);
257 }
258
259 public static function generateBookingICS(Booking $booking)
260 {
261 $author = $booking->getHostDetails(false);
262
263 // Initialize the ICS content
264 $icsContent = "BEGIN:VCALENDAR\r\n";
265 $icsContent .= "VERSION:2.0\r\n";
266 $icsContent .= "PRODID:-//Google Inc//Fluent Booking//EN\r\n";
267 $icsContent .= "METHOD:REQUEST\r\n";
268 $icsContent .= "STATUS:CONFIRMED\r\n";
269
270 $icsContent .= "BEGIN:VEVENT\r\n";
271 $icsContent .= "UID:" . md5($booking->hash) . "\r\n"; // Unique ID for the event
272
273 // Event details
274 $icsContent .= "SUMMARY:" . $booking->getBookingTitle() . "\r\n";
275 $icsContent .= "DESCRIPTION:" . $booking->getIcsBookingDescription() . "\r\n";
276
277 // Date and time formatting (assuming eventStart and eventEnd are DateTime objects)
278 $icsContent .= "DTSTART:" . gmdate('Ymd\THis\Z', strtotime($booking->start_time)) . "\r\n";
279 $icsContent .= "DTEND:" . gmdate('Ymd\THis\Z', strtotime($booking->end_time)) . "\r\n";
280
281 $icsContent .= "LOCATION:" . $booking->getLocationAsText() . "\r\n";
282
283 $icsContent .= "ORGANIZER;CN=\"" . $author['name'] . "\":mailto:" . $author['email'] . "\r\n";
284
285 $icsContent .= "ATTENDEE;CN=\"" . $booking->email . "\";ROLE=REQ-PARTICIPANT;RSVP=TRUE;PARTSTAT=ACCEPTED:mailto:" . $booking->email . "\r\n";
286
287 $icsContent .= "END:VEVENT\r\n";
288
289 // Close the VCALENDAR component
290 $icsContent .= "END:VCALENDAR\r\n";
291
292 return $icsContent;
293 }
294
295 }
296