PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.10.01
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.10.01
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 1.10.01, at app/Http/Controllers/CalendarController.php

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