PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
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
← All changes | app/Http/Controllers/BookingController.php +91 -37 1.5.10trunk View file →
@@ -6,9 +6,8 @@
6 6 use FluentBooking\App\Models\Booking;
7 7 use FluentBooking\App\Models\CalendarSlot;
8 8 use FluentBooking\App\Services\BookingService;
9 9 use FluentBooking\App\Services\DateTimeHelper;
10 -use FluentBooking\App\Services\TimeSlotService;
11 10 use FluentBooking\App\Services\BookingFieldService;
12 11 use FluentBooking\App\Hooks\Handlers\FrontEndHandler;
13 12 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
14 13 use FluentBooking\Framework\Http\Request\Request;
@@ -37,9 +36,9 @@
37 36
38 37 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $slot);
39 38
40 39 if (is_wp_error($timeSlotService)) {
41 - return TimeSlotServiceHandler::sendError($timeSlotService, $slot, $timezone);
40 + return TimeSlotServiceHandler::sendError($timeSlotService, $slot, $timeZone);
42 41 }
43 42
44 43 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone);
45 44
@@ -88,19 +87,31 @@
88 87
89 88 $locationType = Arr::get($postedData, 'location_type');
90 89
91 90 if ($calendarEvent->isPhoneRequired()) {
92 - $rules['location_description'] = 'required';
91 + $rules['location_description'] = ['required', $this->validPhoneNumberRule()];
93 92 $messages['location_description.required'] = __('Please provide attendee\'s phone number', 'fluent-booking');
94 93 } else if ($calendarEvent->isAddressRequired()) {
95 94 $rules['location_description'] = 'required';
96 95 $messages['location_description.required'] = __('Please provide attendee\'s address', 'fluent-booking');
96 + } else if ($locationType === 'phone_guest') {
97 + $rules['location_description'] = [$this->validPhoneNumberRule()];
97 98 }
98 99
99 - if ($additionalGuests = array_filter(Arr::get($postedData, 'guests', []))) {
100 - $postedData['guests'] = array_map('sanitize_email', $additionalGuests);
100 + $additionalGuests = Arr::get($postedData, 'guests', []);
101 + if (!empty($additionalGuests)) {
102 + if ($calendarEvent->isMultiGuestEvent()) {
103 + $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
104 + $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
105 + return Arr::get($guest, 'name') && Arr::get($guest, 'email');
106 + }));
107 + } else {
108 + $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests));
109 + }
101 110 }
102 111
112 + $postedData['guests'] = $additionalGuests;
113 +
103 114 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
104 115 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
105 116 });
106 117
@@ -138,27 +149,27 @@
138 149 return;
139 150 }
140 151
141 152 $duration = $calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
142 - $timezone = Arr::get($postedData, 'timezone', 'UTC');
153 + $timezone = sanitize_text_field($postedData['timezone']);
143 154
144 - $startDateTime = DateTimeHelper::convertToUtc($postedData['event_time'], $timezone);
155 + $startDateTime = DateTimeHelper::convertToUtc(sanitize_text_field($postedData['event_time']), $timezone);
145 156 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60));
146 157
147 - $bookingData = [
148 - 'person_time_zone' => sanitize_text_field($timezone),
158 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
159 + 'person_time_zone' => $timezone,
149 160 'start_time' => $startDateTime,
150 161 'name' => sanitize_text_field($postedData['name']),
151 162 'email' => sanitize_email($postedData['email']),
152 163 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
153 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
164 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
154 165 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
155 166 'ip_address' => Helper::getIp(),
156 - 'status' => sanitize_text_field($postedData['status']),
157 - 'source' => 'admin',
167 + 'status' => sanitize_text_field(Arr::get($postedData, 'status', 'scheduled')),
168 + 'source' => Arr::get($postedData, 'source') == 'admin' ? 'admin' : 'web',
158 169 'event_type' => $calendarEvent->event_type,
159 170 'slot_minutes' => $duration
160 - ];
171 + ], $postedData, $calendarEvent);
161 172
162 173 $eventLocations = [];
163 174 $locationSettings = $calendarEvent->location_settings;
164 175 foreach ($locationSettings as $index => $location) {
@@ -168,15 +179,16 @@
168 179 $locationDetails['type'] = $locationType;
169 180 if ($locationType == 'phone_organizer') {
170 181 $locationDetails['description'] = $eventLocations[$locationType]['host_phone_number'];
171 182 } else if ($locationType == 'phone_guest') {
172 - $bookingData['phone'] = Arr::get($postedData, 'location_description', '');
183 + $bookingData['phone'] = sanitize_text_field(Arr::get($postedData, 'location_description', ''));
173 184 } else if ($locationType == 'in_person_guest') {
174 - $locationDetails['description'] = Arr::get($postedData, 'location_description', '');
185 + $locationDetails['description'] = sanitize_textarea_field(Arr::get($postedData, 'location_description', ''));
175 186 } else if (in_array($locationType, ['custom', 'in_person_organizer'])) {
176 187 $locationDetails['description'] = $eventLocations[$locationType]['description'];
177 188 } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
178 - $locationDetails['description'] = $eventLocations[$locationType]['meeting_link'];
189 + $locationDetails['description'] = Arr::get($eventLocations[$locationType], 'meeting_link', '');
190 + $locationDetails['online_platform_link'] = $locationDetails['description'];
179 191 }
180 192
181 193 $bookingData['location_details'] = $locationDetails;
182 194
@@ -183,25 +195,27 @@
183 195 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
184 196 $bookingData['source_url'] = sanitize_url($sourceUrl);
185 197 }
186 198
187 - if ($additionalGuests) {
188 - $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
189 - $guestLimit = Arr::get($guestField, 'limit', 10);
190 - $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
191 - }
199 + if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
200 + $hostUserId = (int) $hostUserId;
192 201
193 - if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
194 - $bookingData['host_user_id'] = (int)$hostUserId;
202 + if (!in_array($hostUserId, array_map('intval', $calendarEvent->getHostIds()), true)) {
203 + return $this->sendError([
204 + 'message' => __('The selected host is not a host of this event', 'fluent-booking')
205 + ], 422);
206 + }
207 +
208 + $bookingData['host_user_id'] = $hostUserId;
195 209 }
196 210
197 211 $hostIds = null;
198 - if ($calendarEvent->isTeamEvent() && !$hostUserId) {
212 + if ($calendarEvent->isRoundRobin() && !$hostUserId) {
199 213 $hostIds = $calendarEvent->getHostIdsSortedByBookings($startDateTime);
200 214 $bookingData['host_user_id'] = $hostIds[0];
201 215 }
202 216
203 - // Check if the time is available or not for this slot
217 + $availableSpot = false;
204 218 if (!Arr::isTrue($postedData, 'ignore_availability')) {
205 219 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
206 220
207 221 if (is_wp_error($timeSlotService)) {
@@ -207,21 +221,31 @@
207 221 if (is_wp_error($timeSlotService)) {
208 222 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
209 223 }
210 224
211 - $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId);
225 + $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId);
212 226
213 - if (!$isSpotAvailable) {
227 + if (!$availableSpot) {
214 228 wp_send_json([
215 229 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
216 230 ], 422);
217 231 }
218 232
219 - if ($calendarEvent->isTeamEvent() && !$hostUserId) {
233 + if ($calendarEvent->isRoundRobin() && !$hostUserId) {
220 234 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
221 235 }
222 236 }
223 237
238 + if ($additionalGuests) {
239 + $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
240 + $guestLimit = Arr::get($guestField, 'limit', 10);
241 + if ($calendarEvent->isMultiGuestEvent() && $availableSpot) {
242 + $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
243 + $guestLimit = min($remaining, $guestLimit) - 1;
244 + }
245 + $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
246 + }
247 +
224 248 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
225 249
226 250 try {
227 251 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
@@ -238,14 +262,24 @@
238 262 'message' => __('Booking has been created', 'fluent-booking'),
239 263 ];
240 264 }
241 265
242 - public function getEvent(Request $request)
266 + /**
267 + * @return \Closure
268 + */
269 + private function validPhoneNumberRule()
243 270 {
244 - $eventId = $request->get('event_id');
245 -
246 - $calendarEvent = CalendarSlot::query()->find($eventId);
271 + return function ($attribute, $value) {
272 + if (!empty($value) && !Helper::isValidPhoneNumber($value)) {
273 + return __('Please provide a valid phone number', 'fluent-booking');
274 + }
275 + };
276 + }
247 277
278 + public function getEvent(Request $request, $eventId)
279 + {
280 + $calendarEvent = CalendarSlot::find($eventId);
281 +
248 282 if (!$calendarEvent || $calendarEvent->status != 'active') {
249 283 wp_send_json([
250 284 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
251 285 ], 422);
@@ -279,13 +313,23 @@
279 313
280 314 $duration = $calendarEvent->getDuration($request->get('duration'));
281 315
282 316 $hostId = $request->get('host_id', null);
283 -
317 +
318 + if ($hostId) {
319 + $hostId = (int) $hostId;
320 +
321 + if (!in_array($hostId, array_map('intval', $calendarEvent->getHostIds()), true)) {
322 + wp_send_json([
323 + 'message' => __('The selected host is not a host of this event', 'fluent-booking')
324 + ], 422);
325 + }
326 + }
327 +
284 328 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
285 329
286 330 if (is_wp_error($timeSlotService)) {
287 - return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
331 + return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
288 332 }
289 333
290 334 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId);
291 335
@@ -323,10 +367,10 @@
323 367 $bookingPeriod = sanitize_text_field($request->get('period', 'all'));
324 368
325 369 $bookingQuery = Booking::query()->with('calendar_event')
326 370 ->where('email', $userEmail)
327 - ->orderBy('start_time', 'DESC')
328 - ->applyComputedStatus($bookingPeriod);
371 + ->applyComputedStatus($bookingPeriod)
372 + ->applyBookingOrderByStatus($bookingPeriod);
329 373
330 374 $calendarIds = $request->get('calendar_ids', []);
331 375
332 376 if (!in_array('all', $calendarIds)) {
@@ -349,9 +393,9 @@
349 393 'payment_status' => $booking->payment_status,
350 394 'booking_title' => $booking->getBookingTitle(true),
351 395 'author_name' => $booking->getHostDetails(false)['name'],
352 396 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
353 - 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
397 + 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
354 398 ];
355 399 }
356 400
357 401 return [
@@ -357,6 +401,16 @@
357 401 return [
358 402 'bookings' => $formattedBookings,
359 403 'total' => $totalBookings
360 404 ];
405 + }
406 +
407 + private static function sanitize_mapped_data($settings)
408 + {
409 + $sanitizerMap = [
410 + 'name' => 'sanitize_text_field',
411 + 'email' => 'sanitize_email',
412 + ];
413 +
414 + return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
361 415 }
362 416 }