PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.2.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.2.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 / CalendarController.php

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

985 lines 42.7 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\Calendar;
6 use FluentBooking\App\Models\CalendarSlot;
7 use FluentBooking\App\Services\Helper;
8 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
9 use FluentBooking\App\Services\PermissionManager;
10 use FluentBooking\App\Services\AvailabilityService;
11 use FluentBooking\App\Services\SanitizeService;
12 use FluentBooking\App\Services\CalendarService;
13 use FluentBooking\App\Services\OnboardingService;
14 use FluentBooking\App\Services\CalendarEventService;
15 use FluentBooking\App\Services\BookingFieldService;
16 use FluentBooking\App\Hooks\Handlers\AdminMenuHandler;
17 use FluentBooking\Framework\Http\Request\Request;
18 use FluentBooking\Framework\Support\Arr;
19
20 class CalendarController extends Controller
21 {
22 public function getAllCalendars(Request $request)
23 {
24 do_action('fluent_booking/before_get_all_calendars', $request);
25
26 $search = sanitize_text_field(Arr::get($request->get('query'), 'search'));
27 $calendarType = sanitize_text_field(Arr::get($request->get('query'), 'calendarType'));
28
29 $applySearchFilter = function($query) use ($search) {
30 $query->where('status', '!=', 'expired');
31 if (!empty($search)) {
32 $query->where('title', 'LIKE', '%' . $search . '%');
33 }
34 };
35
36 $calendarsQuery = Calendar::with(['slots' => function($query) use ($applySearchFilter) {
37 $query->where($applySearchFilter);
38 }])
39 ->where('status', '!=', 'expired');
40
41 if (!empty($search)) {
42 $calendarsQuery->whereHas('slots', $applySearchFilter);
43 }
44
45 if (!empty($calendarType) && $calendarType != 'all') {
46 $calendarsQuery->where('type', $calendarType);
47 }
48
49 $calendarsQuery = $calendarsQuery->latest();
50
51 $hasPermission = PermissionManager::hasAllCalendarAccess(true);
52
53 if (!$hasPermission) {
54 $attachedCalendarIds = CalendarService::getAttachedCalendarIds($calendarsQuery);
55 $calendarsQuery->whereIn('id', $attachedCalendarIds);
56 }
57
58 $calendars = $calendarsQuery->paginate();
59
60 foreach ($calendars as $calendar) {
61 $calendar->author_profile = $calendar->getAuthorProfile();
62 $calendar->public_url = $calendar->getLandingPageUrl();
63 $calendar->event_order = $calendar->getMeta('event_order');
64
65 if (!$hasPermission) {
66 $calendar->setRelation('slots', $calendar->slots->filter(function ($slot) {
67 return CalendarEventService::isSharedCalendarEvent($slot);
68 })->values());
69 }
70
71 foreach ($calendar->slots as $slot) {
72 $slot->setRelation('calendar', $calendar);
73 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
74 $slot->public_url = $slot->getPublicUrl();
75 $slot->duration = $slot->getDefaultDuration();
76 $slot->price_total = $slot->getEventPrice();
77 $slot->location_fields = $slot->getLocationFields();
78 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
79 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
80 $slot->unsetRelation('calendar');
81 }
82
83 if(empty($calendar->author_profile['ID'])) {
84 $calendar->generic_error = '<p style="color: red; margin:0;">Connected Host user is missing</p>';
85 }
86
87 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
88 }
89
90 $data = [
91 'calendars' => $calendars
92 ];
93
94 if (in_array('calendar_event_lists', $request->get('with', []))) {
95 $data['calendar_event_lists'] = [
96 'hosts' => CalendarService::getCalendarOptionsByTitle('only_hosts'),
97 'teams' => CalendarService::getCalendarOptionsByTitle('only_teams'),
98 'events' => CalendarService::getCalendarOptionsByTitle('only_events')
99 ];
100 }
101
102 return $data;
103 }
104
105 public function checkSlug(Request $request)
106 {
107 $slug = sanitize_text_field(trim($request->get('slug')));
108
109 if (!Helper::isCalendarSlugAvailable($slug, true)) {
110 return $this->sendError([
111 'message' => __('The provided slug is not available. Please choose a different one', 'fluent-booking')
112 ], 422);
113 }
114
115 return [
116 'status' => true,
117 'message' => __('The provided slug is available', 'fluent-booking')
118 ];
119 }
120
121 public function createCalendar(Request $request)
122 {
123 $data = $request->get('calendar');
124
125 $rules = [
126 'author_timezone' => 'required',
127 'slot.duration' => 'required|numeric|min:5',
128 'slot.event_type' => 'required',
129 'slot.availability_type' => 'required',
130 'slot.schedule_type' => 'required',
131 'slot.title' => 'required',
132 'slot.weekly_schedules' => 'required_if:slot.schedule_type,weekly_schedules',
133 'slot.location_settings.*.type' => 'required',
134 'slot.location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
135 ];
136
137 $messages = [
138 'author_timezone.required' => __('Author timezone field is required', 'fluent-booking'),
139 'slot.duration.required' => __('Event duration field is required', 'fluent-booking'),
140 'slot.event_type.required' => __('Event type field is required', 'fluent-booking'),
141 'slot.availability_type.required' => __('Event availability type field is required', 'fluent-booking'),
142 'slot.schedule_type.required' => __('Event schedule type field is required', 'fluent-booking'),
143 'slot.title.required' => __('Event title field is required', 'fluent-booking'),
144 'slot.weekly_schedules.required_if' => __('Event weekly schedules field is required', 'fluent-booking'),
145 'slot.location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
146 'slot.location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
147 ];
148
149 $validationConfig = apply_filters('fluent_booking/create_calendar_validation_rule', [
150 'rules' => $rules,
151 'messages' => $messages
152 ], $data);
153
154 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
155
156 do_action('fluent_booking/before_create_calendar', $data, $this);
157
158 if (!empty($data['user_id']) && PermissionManager::userCan(['manage_all_data', 'invite_team_members'])) {
159 $user = get_user_by('ID', $data['user_id']);
160 } else {
161 $user = get_user_by('ID', get_current_user_id());
162 }
163
164 if (!$user) {
165 return $this->sendError([
166 'message' => __('User not found', 'fluent-booking')
167 ], 422);
168 }
169
170 $onboardinFeatures = $request->get('features');
171 if (!empty($onboardinFeatures)) {
172 $installableAddons = SanitizeService::sanitizeAddons($onboardinFeatures);
173 OnboardingService::installAddons($installableAddons);
174 }
175
176 $type = sanitize_text_field(Arr::get($data, 'type', 'simple'));
177
178 $isHostCalendar = $type == 'simple' ? true : false;
179
180 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
181 return $this->sendError([
182 'message' => __('The user already has a calendar. Please delete it first to create a new one', 'fluent-booking')
183 ], 422);
184 }
185
186 if ($isHostCalendar) {
187 $userName = $user->user_login;
188 if (is_email($userName)) {
189 $userName = explode('@', $userName);
190 $userName = $userName[0] . '-' . time();
191 }
192 $data['slug'] = sanitize_title($userName, '', 'display');
193 }
194
195 $slot = $data['slot'];
196
197 if (!$isHostCalendar) {
198 $title = sanitize_text_field(Arr::get($data, 'title', ''));
199 $data['slug'] = sanitize_title($title, '', 'display');
200 $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
201 if (!in_array($user->ID, $teamMembers)) {
202 $user = get_user_by('ID', reset($teamMembers));
203 if (!$user) {
204 return $this->sendError([
205 'message' => __('Invalid Team Member', 'fluent-booking')
206 ], 422);
207 }
208 }
209 }
210
211 if (!Helper::isCalendarSlugAvailable($data['slug'], true)) {
212 $data['slug'] .= '-' . time();
213 }
214
215 if (!empty($data['slug'])) {
216 $slug = trim(sanitize_text_field($data['slug']));
217 if (!Helper::isCalendarSlugAvailable($slug, true)) {
218 return $this->sendError([
219 'message' => __('The provided slug is not available. Please choose a different one', 'fluent-booking')
220 ], 422);
221 }
222
223 $personName = trim($user->first_name . ' ' . $user->last_name);
224 if (!$personName) {
225 $personName = $user->display_name;
226 }
227
228 $calendarData = [
229 'slug' => $slug,
230 'user_id' => $user->ID,
231 'title' => $isHostCalendar ? $personName : $title,
232 'type' => $type,
233 'author_timezone' => sanitize_text_field($data['author_timezone']) ?: 'UTC',
234 ];
235 $calendar = Calendar::create($calendarData);
236 } else {
237 $calendar = Calendar::where('user_id', $user->ID)->where('type', 'simple')->first();
238 }
239
240 if (!$calendar) {
241 return $this->sendError([
242 'message' => __('Calendar could not be found. Please try again', 'fluent-booking')
243 ], 422);
244 }
245
246 $weeklySchedule = Arr::get($data, 'slot.weekly_schedules', []);
247
248 $availability = AvailabilityService::maybeCreateAvailability($calendar, $weeklySchedule);
249
250 $title = (!empty($slot['title'])) ? sanitize_text_field($slot['title']) : $slot['duration'] . ' Minute Meeting';
251
252 $slotData = [
253 'title' => $title,
254 'slug' => Helper::generateSlotSlug((int)$slot['duration'] . 'min', $calendar),
255 'calendar_id' => $calendar->id,
256 'user_id' => $calendar->user_id,
257 'duration' => (int)$slot['duration'],
258 'description' => wp_kses_post(Arr::get($slot, 'description')),
259 'settings' => [
260 'team_members' => !$isHostCalendar ? $teamMembers : [],
261 'schedule_type' => sanitize_text_field($slot['schedule_type']),
262 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC', true)
263 ],
264 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft']),
265 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
266 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
267 'availability_type' => SanitizeService::checkCollection($slot['availability_type'], ['existing_schedule', 'custom'], 'existing_schedule'),
268 'availability_id' => (int)$availability->id,
269 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
270 'location_heading' => wp_kses_post(Arr::get($slot, 'location_heading')),
271 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
272 ];
273
274 $slotData['settings'] = wp_parse_args($slotData['settings'], (new CalendarSlot())->getSlotSettingsSchema());
275
276 $slotData = apply_filters('fluent_booking/create_calendar_event_data', $slotData, $calendar);
277
278 $slot = CalendarSlot::create($slotData);
279
280 do_action('fluent_booking/after_create_calendar', $calendar);
281
282 do_action('fluent_booking/after_create_calendar_slot', $slot, $calendar);
283
284 return [
285 'calendar' => $calendar,
286 'slot' => $slot,
287 'redirect_url' => Helper::getAppBaseUrl('calendars/' . $calendar->id . '/slot-settings/' . $slot->id)
288 ];
289 }
290
291 public function getCalendar(Request $request, $calendarId)
292 {
293 $calendar = Calendar::with(['slots' => function ($query) {
294 $query->where('status', '!=', 'expired');
295 }])->findOrFail($calendarId);
296
297 $calendar->author_profile = $calendar->getAuthorProfile();
298
299 $data = [
300 'calendar' => $calendar
301 ];
302
303 if (in_array('settings_menu', $request->get('with', []))) {
304 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
305 }
306
307 if (in_array('public_url', $request->get('with', []))) {
308 $data['public_url'] = $calendar->getLandingPageUrl();
309 }
310
311 return $data;
312 }
313
314 public function getSharingSettings(Request $request, $calendarId)
315 {
316 $calendar = Calendar::findOrFail($calendarId);
317
318 return [
319 'settings' => LandingPageHelper::getSettings($calendar),
320 'share_url' => $calendar->getLandingPageUrl(true)
321 ];
322 }
323
324 public function saveSharingSettings(Request $request, $calendarId)
325 {
326 $calendar = Calendar::findOrFail($calendarId);
327
328 $calendarDataItems = Arr::only($request->get('calendar_data', []), ['title', 'timezone', 'description', 'calendar_avatar', 'featured_image', 'phone']);
329
330 if ($calendarDataItems) {
331 $this->validate($calendarDataItems, [
332 'title' => 'required',
333 'calendar_avatar' => 'nullable|url',
334 'featured_image' => 'nullable|url'
335 ]);
336
337 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
338 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
339 CalendarService::updateCalendarEventsSchedule($calendarId, $calendar->author_timezone, $updatedTimezone);
340 $calendar->author_timezone = $updatedTimezone;
341 }
342
343 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
344 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
345 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
346 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
347 $calendar->save();
348
349 if ($calendar->user) {
350 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
351 }
352 }
353
354 $sharingSettings = $request->get('landing_page_settings', []);
355 LandingPageHelper::updateSettings($calendar, $sharingSettings);
356
357 return [
358 'message' => __('Landing Page settings has been updated', 'fluent-booking')
359 ];
360 }
361
362 public function updateCalendar(Request $request, $calendarId)
363 {
364 $data = $request->all();
365
366 $calendar = Calendar::findOrFail($calendarId);
367
368 do_action_ref_array('fluent_booking/before_update_calendar', [&$calendar, $data]);
369
370 $calendar->description = wp_kses_post($request->get('description'));
371 $calendar->save();
372 do_action('fluent_booking/after_update_calendar', $calendar, $data);
373
374 $calendar->author_profile = $calendar->getAuthorProfile();
375
376 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'update']);
377
378 return [
379 'calendar' => $calendar,
380 'message' => __('Calendar has been updated successfully', 'fluent-booking')
381 ];
382 }
383
384 public function getEvent(Request $request, $calendarId, $eventId)
385 {
386 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($eventId);
387
388 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
389
390 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
391
392 $calendarEvent->public_url = $calendarEvent->getPublicUrl();
393
394 $eventSettings = $calendarEvent->settings;
395
396 $eventSettings['weekly_schedules'] = SanitizeService::weeklySchedules($eventSettings['weekly_schedules'], 'UTC', $calendarEvent->calendar->author_timezone);
397
398 $eventSettings['date_overrides'] = (object)SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), 'UTC', $calendarEvent->calendar->author_timezone, $calendarEvent);
399
400 $eventSettings['location_fields'] = $calendarEvent->getLocationFields();
401
402 $eventSettings['hosts_schedules'] = $calendarEvent->getHostsSchedules();
403
404 $calendarEvent->settings = apply_filters('fluent_booking/get_calendar_event_settings', $eventSettings, $calendarEvent, $calendarEvent->calendar);
405
406 $data = [
407 'calendar_event' => $calendarEvent
408 ];
409
410 if (in_array('calendar', $this->request->get('with', []))) {
411 $calendar = $calendarEvent->calendar;
412 $calendar->author_profile = $calendar->getAuthorProfile();
413 $data['calendar'] = $calendar;
414 }
415
416 if (in_array('smart_codes', $this->request->get('with', []))) {
417 $data['smart_codes'] = [
418 'texts' => Helper::getEditorShortCodes($calendarEvent),
419 'html' => Helper::getEditorShortCodes($calendarEvent, true)
420 ];
421 }
422
423 if (in_array('settings_menu', $this->request->get('with', []))) {
424 $data['settings_menu'] = AdminMenuHandler::getEventSettingsMenuItems($calendarEvent);
425 }
426
427 if (in_array('calendar_event_lists', $this->request->get('with', []))) {
428 $data['calendar_event_lists'] = CalendarService::getCalendarOptionsByTitle();
429 }
430
431 return $data;
432 }
433
434 public function getEventSchema(Request $request, $calendarId)
435 {
436 $calendar = Calendar::findOrFail($calendarId);
437
438 $schema = (new CalendarSlot())->getEventSchema($calendar);
439
440 return [
441 'slot' => $schema
442 ];
443 }
444
445 public function getAvailabilitySettings(Request $request, $calendarId, $eventId)
446 {
447 $availableSchedules = AvailabilityService::availabilitySchedules();
448
449 $scheduleOptions = AvailabilityService::getScheduleOptions();
450
451 return [
452 'schedule_options' => $scheduleOptions,
453 'available_schedules' => $availableSchedules
454 ];
455 }
456
457 public function createCalendarEvent(Request $request, $calendarId)
458 {
459 $calendar = Calendar::findOrFail($calendarId);
460
461 $slot = $request->all();
462
463 $rules = [
464 'title' => 'required',
465 'duration' => 'required|numeric|min:5',
466 'status' => 'required',
467 'event_type' => 'required',
468 'location_settings.*.type' => 'required',
469 'location_settings.*.title' => 'required_if:location_settings.*.type,custom',
470 'location_settings.*.description' => 'required_if:location_settings.*.type,address_organizer',
471 'location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
472 ];
473
474 $messages = [
475 'title.required' => __('Event title field is required', 'fluent-booking'),
476 'duration.required' => __('Event duration field is required', 'fluent-booking'),
477 'status.required' => __('Event status field is required', 'fluent-booking'),
478 'event_type.required' => __('Event type field is required', 'fluent-booking'),
479 'location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
480 'location_settings.*.title.required_if' => __('Event location title field is required', 'fluent-booking'),
481 'location_settings.*.description.required_if' => __('Event location description field is required', 'fluent-booking'),
482 'location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
483 ];
484
485 $validationConfig = apply_filters('fluent_booking/create_calendar_event_validation_rule', [
486 'rules' => $rules,
487 'messages' => $messages
488 ], $slot);
489
490 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
491
492 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
493
494 $slotData = [
495 'title' => $slot['title'],
496 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
497 'calendar_id' => $calendar->id,
498 'user_id' => $calendar->user_id,
499 'duration' => (int)$slot['duration'],
500 'description' => wp_kses_post(Arr::get($slot, 'description')),
501 'settings' => [
502 'schedule_type' => sanitize_text_field($slot['settings']['schedule_type']),
503 'weekly_schedules' => SanitizeService::weeklySchedules($slot['settings']['weekly_schedules'], $calendar->author_timezone, 'UTC', true),
504 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($slot['settings'], 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
505 'range_type' => sanitize_text_field(Arr::get($slot['settings'], 'range_type')),
506 'range_days' => (int)(Arr::get($slot['settings'], 'range_days', 60)) ?: 60,
507 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($slot['settings'], 'range_date_between', ['', ''])),
508 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
509 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
510 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
511 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
512 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
513 ],
514 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
515 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
516 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
517 'availability_type' => 'existing_schedule',
518 'availability_id' => $availability ? $availability->id : null,
519 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
520 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
521 'max_book_per_slot' => (int)Arr::get($slot, 'max_book_per_slot', 1),
522 'is_display_spots' => (bool)Arr::get($slot, 'is_display_spots', false),
523 ];
524
525 $slotData = apply_filters('fluent_booking/create_calendar_event_data', $slotData, $calendar);
526
527 do_action('fluent_booking/before_create_event', $calendar, $slotData);
528
529 $createdSlot = CalendarSlot::create($slotData);
530
531 do_action('fluent_booking/after_create_event', $calendar, $createdSlot);
532
533 $calendar->updateEventOrder($createdSlot->id);
534
535 return [
536 'message' => __('New Event Type has been created successfully', 'fluent-booking'),
537 'slot' => $createdSlot
538 ];
539 }
540
541 public function updateEventDetails(Request $request, $calendarId, $eventId)
542 {
543 $data = $request->all();
544
545 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
546
547 $rules = [
548 'title' => 'required',
549 'duration' => 'required|numeric',
550 'location_settings.*.type' => 'required',
551 'location_settings.*.title' => 'required_if:location_settings.*.type,in_person_organizer',
552 'location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
553 ];
554
555 $messages = [
556 'title.required' => __('Event title field is required', 'fluent-booking'),
557 'duration.required' => __('Event duration field is required', 'fluent-booking'),
558 'location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
559 'location_settings.*.title.required_if' => __('Event location title field is required', 'fluent-booking'),
560 'location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
561 ];
562
563 if ('group' === $event->event_type) {
564 $rules = array_merge($rules, [
565 'max_book_per_slot' => 'required|numeric|min:1',
566 'is_display_spots' => 'required|min:0|max:1',
567 ]);
568 $messages = array_merge($messages, [
569 'max_book_per_slot.required' => __('Event max book per slot field is required', 'fluent-booking'),
570 'is_display_spots.required' => __('Event is display spots field is required', 'fluent-booking')
571 ]);
572 } else {
573 $rules = array_merge($rules, [
574 'multi_duration.default_duration' => 'required_if:multi_duration.enabled,true',
575 'multi_duration.available_durations' => 'required_if:multi_duration.enabled,true'
576 ]);
577 $messages = array_merge($messages, [
578 'multi_duration.default_duration.required_if' => __('Event default duration is required', 'fluent-booking'),
579 'multi_duration.available_durations.required_if' => __('Event available durations is required', 'fluent-booking')
580 ]);
581 }
582
583 $validationConfig = apply_filters('fluent_booking/update_event_details_validation_rule', [
584 'rules' => $rules,
585 'messages' => $messages
586 ], $event);
587
588 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
589
590 $event->title = sanitize_text_field($data['title']);
591 $event->duration = (int)$data['duration'];
592 $event->status = SanitizeService::checkCollection($data['status'], ['active', 'draft']);
593 $event->color_schema = sanitize_text_field(Arr::get($data, 'color_schema', '#0099ff'));
594 $event->description = wp_kses_post(Arr::get($data, 'description'));
595 $event->max_book_per_slot = (int)Arr::get($data, 'max_book_per_slot');
596 $event->is_display_spots = (bool)Arr::get($data, 'is_display_spots');
597 $event->location_settings = SanitizeService::locationSettings(Arr::get($data, 'location_settings', []));
598
599 $event->settings = [
600 'multi_duration' => [
601 'enabled' => Arr::isTrue($data, 'multi_duration.enabled'),
602 'default_duration' => Arr::get($data, 'multi_duration.default_duration', ''),
603 'available_durations' => array_map('sanitize_text_field', Arr::get($data, 'multi_duration.available_durations', []))
604 ]
605 ];
606
607 $event->save();
608
609 do_action('fluent_booking/after_update_event_details', $event);
610
611 return [
612 'message' => __('Data has been updated', 'fluent-booking'),
613 'event' => $event
614 ];
615 }
616
617 public function updateEventAvailability(Request $request, $calendarId, $eventId)
618 {
619 $data = $request->all();
620
621 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
622
623 $eventSettings = [
624 'schedule_type' => sanitize_text_field(Arr::get($data, 'schedule_type')),
625 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($data, 'weekly_schedules'), $event->calendar->author_timezone, 'UTC', true),
626 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($data, 'date_overrides', []), $event->calendar->author_timezone, 'UTC', null, true),
627 'range_type' => sanitize_text_field(Arr::get($data, 'range_type')),
628 'range_days' => (int)(Arr::get($data, 'range_days', 60)) ?: 60,
629 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
630 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
631 ];
632
633 if ($event->isTeamEvent()) {
634 $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
635 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
636 array_map('intval', Arr::get($data, 'hosts_schedules', []))
637 ));
638 }
639
640 $event->settings = $eventSettings;
641
642 $event->availability_id = (int)Arr::get($data, 'availability_id');
643 $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
644
645 $event->save();
646
647 return [
648 'message' => __('Data has been updated', 'fluent-booking'),
649 'event' => $event
650 ];
651 }
652
653 public function updateEventLimits(Request $request, $calendarId, $eventId)
654 {
655 $data = $request->all();
656
657 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
658
659 $event->settings = [
660 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($data['settings'], 'schedule_conditions', [])),
661 'buffer_time_before' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_before', '0')),
662 'buffer_time_after' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_after', '0')),
663 'slot_interval' => sanitize_text_field(Arr::get($data, 'settings.slot_interval', '')),
664 'booking_frequency' => [
665 'enabled' => Arr::isTrue($data, 'settings.booking_frequency.enabled'),
666 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_frequency.limits'))
667 ],
668 'booking_duration' => [
669 'enabled' => Arr::isTrue($data, 'settings.booking_duration.enabled'),
670 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_duration.limits'))
671 ],
672 'lock_timezone' => [
673 'enabled' => Arr::isTrue($data, 'settings.lock_timezone.enabled'),
674 'timezone' => sanitize_text_field(Arr::get($data, 'settings.lock_timezone.timezone'))
675 ],
676 ];
677
678 $event->save();
679
680 return [
681 'message' => __('Data has been updated', 'fluent-booking'),
682 'event' => $event
683 ];
684 }
685
686 public function patchCalendarEvent(Request $request, $calendarId, $eventId)
687 {
688 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
689
690 $status = $request->get('status');
691
692 if ($status) {
693 $slot->status = $status;
694 $slot->save();
695 }
696
697 return [
698 'message' => __('Data has been updated', 'fluent-booking')
699 ];
700
701 }
702
703 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
704 {
705 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
706
707 if (!PermissionManager::canWriteCalendar($newCalendarId)) {
708 return $this->sendError([
709 'message' => __('You do not have permission to write to the destination calendar.', 'fluent-booking')
710 ], 403);
711 }
712
713 $calendar = Calendar::findOrFail($newCalendarId);
714
715 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
716
717 $clonedEvent = $originalEvent->replicate();
718
719 $clonedEvent->hash = null;
720
721 $clonedEvent->calendar_id = $calendar->id;
722
723 $clonedEvent->user_id = $calendar->user_id;
724
725 $clonedEvent->title = $originalEvent->title . ' (clone)';
726
727 $clonedEvent->slug = Helper::generateSlotSlug($clonedEvent->duration . 'min', $calendar);
728
729 $clonedEvent->save();
730
731 $calendar->updateEventOrder($clonedEvent->id);
732
733 $eventsMeta = $originalEvent->event_metas;
734
735 foreach ($eventsMeta as $meta) {
736 $clonedMeta = $meta->replicate();
737 $clonedMeta->object_id = $clonedEvent->id;
738 $clonedMeta->save();
739 }
740
741 return [
742 'slot' => $clonedEvent,
743 'message' => __('The Event Type has been cloned successfully', 'fluent-booking')
744 ];
745 }
746
747 public function saveCalendarEventOrder(Request $request, $calendarId)
748 {
749 $calendar = Calendar::findOrFail($calendarId);
750
751 $eventOrder = array_map('intval', $request->get('event_order', []));
752
753 $calendar->updateMeta('event_order', array_filter($eventOrder));
754
755 return [
756 'calendar' => $calendar,
757 'message' => __('Event order has been updated', 'fluent-booking')
758 ];
759 }
760
761 public function cloneEventEmailNotification(Request $request, $calendarId, $eventId)
762 {
763 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
764
765 $fromEventId = intval($request->get('from_event_id'));
766
767 if (!$fromEventId || !PermissionManager::canUpdateCalendarEvent($fromEventId)) {
768 return $this->sendError([
769 'message' => __('You do not have permission to clone from the selected event.', 'fluent-booking')
770 ], 403);
771 }
772
773 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
774
775 $notification = $fromCalendarEvent->getNotifications(true);
776
777 $calendarEvent->setNotifications($notification);
778
779 $calendarEvent->save();
780
781 return [
782 'message' => __('The Notification has been cloned successfully', 'fluent-booking'),
783 'notifications' => $notification
784 ];
785 }
786
787 public function getEventEmailNotifications(Request $request, $calendarId, $eventId)
788 {
789 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
790
791 /*
792 * Confirmation Email to Attendee
793 * Confirmation Email to Organizer
794 * Reminder Email to Attendee [before 1 day, 1 hour, 30 minutes, 5 minutes]
795 * Cancelled By Organizer to Attendee
796 * Cancelled By Attendee to Organizer
797 */
798 $data = [
799 'notifications' => $calendarEvent->getNotifications(true)
800 ];
801
802 if (in_array('smart_codes', $request->get('with', []))) {
803 $data['smart_codes'] = [
804 'texts' => Helper::getEditorShortCodes($calendarEvent),
805 'html' => Helper::getEditorShortCodes($calendarEvent, true)
806 ];
807 }
808
809 return $data;
810 }
811
812 public function saveEventEmailNotifications(Request $request, $calendarId, $eventId)
813 {
814 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
815
816 $notifications = $request->get('notifications', []);
817
818 $formattedNotifications = [];
819
820 foreach ($notifications as $key => $value) {
821 $formattedNotifications[$key] = [
822 'title' => sanitize_text_field(Arr::get($value, 'title')),
823 'enabled' => Arr::isTrue($value, 'enabled'),
824 'email' => $this->sanitize_mapped_data(Arr::get($value, 'email')),
825 'is_host' => Arr::isTrue($value, 'is_host')
826 ];
827 }
828
829 $slot->setNotifications($formattedNotifications);
830
831 return [
832 'message' => __('Notifications has been saved', 'fluent-booking')
833 ];
834 }
835
836 public function getEventBookingFields(Request $request, $calendarId, $eventId)
837 {
838 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
839
840 $data = [
841 'fields' => $calendarEvent->getBookingFields()
842 ];
843
844 if (in_array('smart_codes', $request->get('with', []))) {
845 $data['smart_codes'] = [
846 'texts' => Helper::getEditorShortCodes($calendarEvent),
847 'html' => Helper::getEditorShortCodes($calendarEvent, true)
848 ];
849 }
850
851 return $data;
852 }
853
854 public function saveEventBookingFields(Request $request, $calendarId, $eventId)
855 {
856 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
857
858 $bookingFields = $request->get('booking_fields');
859
860 $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
861
862 $formattedFields = [];
863
864 $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
865 $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
866
867 foreach ($bookingFields as $value) {
868 if (empty($value['name'])) {
869 $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
870 } else {
871 $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
872 }
873
874 $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
875
876 $booleanValues = array_map(function ($valueItem) {
877 return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
878 }, Arr::only($value, $booleanFields));
879
880 $formattedField = array_merge($textValues, $booleanValues);
881
882 $fieldType = Arr::get($value, 'type');
883
884 $formattedField['index'] = (int)Arr::get($value, 'index');
885 if (in_array($fieldType, $optionRequiredFields)) {
886 $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
887 $formattedField['options'] = $sanitizedOptions;
888 }
889 if ($fieldType == 'file') {
890 $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
891 $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
892 $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
893 $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
894 }
895 if ($fieldType == 'hidden') {
896 $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
897 }
898 if ($fieldType == 'terms-and-conditions') {
899 $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
900 }
901
902 $formattedField = apply_filters('fluent_booking/save_event_booking_field_' . $fieldType, $formattedField, $value, $calendarEvent);
903
904 $formattedFields[] = $formattedField;
905 }
906
907 $calendarEvent->setBookingFields($formattedFields);
908
909 return [
910 'message' => __('Fields has been updated', 'fluent-booking')
911 ];
912 }
913
914 public function getEventPaymentSettings($calendarId, $eventId)
915 {
916 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
917
918 $config = [
919 'native_enabled' => Helper::isPaymentEnabled(),
920 'stripe_configured' => Helper::isPaymentConfigured('stripe'),
921 'paypal_configured' => Helper::isPaymentConfigured('paypal'),
922 'offline_configured' => Helper::isPaymentConfigured('offline'),
923 'native_config_link' => Helper::getAppBaseUrl('settings/payment-methods/stripe'),
924 'woo_config_link' => Helper::getAppBaseUrl('settings/configure-integrations/global-modules'),
925 'has_cart' => defined('FLUENTCART_VERSION'),
926 'has_woo' => defined('WC_PLUGIN_FILE'),
927 'woo_enabled' => defined('WC_PLUGIN_FILE') && Helper::isModuleEnabled('woo')
928 ];
929
930 $data = apply_filters('fluent_booking/payment/get_payment_settings', [
931 'settings' => $calendarEvent->getPaymentSettings(),
932 'config' => $config
933 ], $calendarEvent);
934
935 return $data;
936 }
937
938 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
939 {
940 $calendar = Calendar::query()->findOrFail($calendarId);
941
942 $calendarEvent = CalendarSlot::query()->where('calendar_id', $calendar->id)->findOrFail($calendarEventId);
943
944 $calendar->updateEventOrder($calendarEvent->id);
945
946 do_action('fluent_booking/before_delete_calendar_event', $calendarEvent, $calendar);
947
948 $calendarEvent->delete();
949
950 do_action('fluent_booking/after_delete_calendar_event', $calendarEventId, $calendar);
951
952 return [
953 'message' => __('Calendar Event has been deleted', 'fluent-booking')
954 ];
955 }
956
957 private function sanitize_mapped_data($settings)
958 {
959 $sanitizerMap = [
960 'value' => 'intval',
961 'unit' => 'sanitize_text_field',
962 'subject' => 'sanitize_text_field',
963 'body' => 'fcal_sanitize_html',
964 'additional_recipients' => 'sanitize_text_field'
965 ];
966
967 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
968 }
969
970 public function deleteCalendar(Request $request, $calendarId)
971 {
972 $calendar = Calendar::findOrFail($calendarId);
973
974 do_action('fluent_booking/before_delete_calendar', $calendar);
975
976 $calendar->delete();
977
978 do_action('fluent_booking/after_delete_calendar', $calendarId);
979
980 return [
981 'message' => __('Calendar Deleted Successfully!', 'fluent-booking')
982 ];
983 }
984 }
985