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

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