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

1,004 lines 43.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(['metas', '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 $calendar->event_order = $calendar->getMeta('event_order');
299
300 if (!PermissionManager::hasAllCalendarAccess(true)) {
301 $calendar->setRelation('slots', $calendar->slots->filter(function ($slot) {
302 return CalendarEventService::isSharedCalendarEvent($slot);
303 })->values());
304 }
305
306 foreach ($calendar->slots as $slot) {
307 $slot->setRelation('calendar', $calendar);
308 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
309 $slot->public_url = $slot->getPublicUrl();
310 $slot->duration = $slot->getDefaultDuration();
311 $slot->price_total = $slot->getEventPrice();
312 $slot->location_fields = $slot->getLocationFields();
313 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
314 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
315 $slot->unsetRelation('calendar');
316 }
317
318 $data = [
319 'calendar' => $calendar
320 ];
321
322 if (in_array('settings_menu', $request->get('with', []))) {
323 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
324 }
325
326 if (in_array('public_url', $request->get('with', []))) {
327 $data['public_url'] = $calendar->getLandingPageUrl();
328 }
329
330 return $data;
331 }
332
333 public function getSharingSettings(Request $request, $calendarId)
334 {
335 $calendar = Calendar::findOrFail($calendarId);
336
337 return [
338 'settings' => LandingPageHelper::getSettings($calendar),
339 'share_url' => $calendar->getLandingPageUrl(true)
340 ];
341 }
342
343 public function saveSharingSettings(Request $request, $calendarId)
344 {
345 $calendar = Calendar::findOrFail($calendarId);
346
347 $calendarDataItems = Arr::only($request->get('calendar_data', []), ['title', 'timezone', 'description', 'calendar_avatar', 'featured_image', 'phone']);
348
349 if ($calendarDataItems) {
350 $this->validate($calendarDataItems, [
351 'title' => 'required',
352 'calendar_avatar' => 'nullable|url',
353 'featured_image' => 'nullable|url'
354 ]);
355
356 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
357 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
358 CalendarService::updateCalendarEventsSchedule($calendarId, $calendar->author_timezone, $updatedTimezone);
359 $calendar->author_timezone = $updatedTimezone;
360 }
361
362 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
363 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
364 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
365 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
366 $calendar->save();
367
368 if ($calendar->user) {
369 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
370 }
371 }
372
373 $sharingSettings = $request->get('landing_page_settings', []);
374 LandingPageHelper::updateSettings($calendar, $sharingSettings);
375
376 return [
377 'message' => __('Landing Page settings has been updated', 'fluent-booking')
378 ];
379 }
380
381 public function updateCalendar(Request $request, $calendarId)
382 {
383 $data = $request->all();
384
385 $calendar = Calendar::findOrFail($calendarId);
386
387 do_action_ref_array('fluent_booking/before_update_calendar', [&$calendar, $data]);
388
389 $calendar->description = wp_kses_post($request->get('description'));
390 $calendar->save();
391 do_action('fluent_booking/after_update_calendar', $calendar, $data);
392
393 $calendar->author_profile = $calendar->getAuthorProfile();
394
395 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'update']);
396
397 return [
398 'calendar' => $calendar,
399 'message' => __('Calendar has been updated successfully', 'fluent-booking')
400 ];
401 }
402
403 public function getEvent(Request $request, $calendarId, $eventId)
404 {
405 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($eventId);
406
407 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
408
409 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
410
411 $calendarEvent->public_url = $calendarEvent->getPublicUrl();
412
413 $eventSettings = $calendarEvent->settings;
414
415 $eventSettings['weekly_schedules'] = SanitizeService::weeklySchedules($eventSettings['weekly_schedules'], 'UTC', $calendarEvent->calendar->author_timezone);
416
417 $eventSettings['date_overrides'] = (object)SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), 'UTC', $calendarEvent->calendar->author_timezone, $calendarEvent);
418
419 $eventSettings['location_fields'] = $calendarEvent->getLocationFields();
420
421 $eventSettings['hosts_schedules'] = $calendarEvent->getHostsSchedules();
422
423 $calendarEvent->settings = apply_filters('fluent_booking/get_calendar_event_settings', $eventSettings, $calendarEvent, $calendarEvent->calendar);
424
425 $data = [
426 'calendar_event' => $calendarEvent
427 ];
428
429 if (in_array('calendar', $this->request->get('with', []))) {
430 $calendar = $calendarEvent->calendar;
431 $calendar->author_profile = $calendar->getAuthorProfile();
432 $data['calendar'] = $calendar;
433 }
434
435 if (in_array('smart_codes', $this->request->get('with', []))) {
436 $data['smart_codes'] = [
437 'texts' => Helper::getEditorShortCodes($calendarEvent),
438 'html' => Helper::getEditorShortCodes($calendarEvent, true)
439 ];
440 }
441
442 if (in_array('settings_menu', $this->request->get('with', []))) {
443 $data['settings_menu'] = AdminMenuHandler::getEventSettingsMenuItems($calendarEvent);
444 }
445
446 if (in_array('calendar_event_lists', $this->request->get('with', []))) {
447 $data['calendar_event_lists'] = CalendarService::getCalendarOptionsByTitle();
448 }
449
450 return $data;
451 }
452
453 public function getEventSchema(Request $request, $calendarId)
454 {
455 $calendar = Calendar::findOrFail($calendarId);
456
457 $schema = (new CalendarSlot())->getEventSchema($calendar);
458
459 return [
460 'slot' => $schema
461 ];
462 }
463
464 public function getAvailabilitySettings(Request $request, $calendarId, $eventId)
465 {
466 $availableSchedules = AvailabilityService::availabilitySchedules();
467
468 $scheduleOptions = AvailabilityService::getScheduleOptions();
469
470 return [
471 'schedule_options' => $scheduleOptions,
472 'available_schedules' => $availableSchedules
473 ];
474 }
475
476 public function createCalendarEvent(Request $request, $calendarId)
477 {
478 $calendar = Calendar::findOrFail($calendarId);
479
480 $slot = $request->all();
481
482 $rules = [
483 'title' => 'required',
484 'duration' => 'required|numeric|min:5',
485 'status' => 'required',
486 'event_type' => 'required',
487 'location_settings.*.type' => 'required',
488 'location_settings.*.title' => 'required_if:location_settings.*.type,custom',
489 'location_settings.*.description' => 'required_if:location_settings.*.type,address_organizer',
490 'location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
491 ];
492
493 $messages = [
494 'title.required' => __('Event title field is required', 'fluent-booking'),
495 'duration.required' => __('Event duration field is required', 'fluent-booking'),
496 'status.required' => __('Event status field is required', 'fluent-booking'),
497 'event_type.required' => __('Event type field is required', 'fluent-booking'),
498 'location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
499 'location_settings.*.title.required_if' => __('Event location title field is required', 'fluent-booking'),
500 'location_settings.*.description.required_if' => __('Event location description field is required', 'fluent-booking'),
501 'location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
502 ];
503
504 $validationConfig = apply_filters('fluent_booking/create_calendar_event_validation_rule', [
505 'rules' => $rules,
506 'messages' => $messages
507 ], $slot);
508
509 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
510
511 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
512
513 $slotData = [
514 'title' => $slot['title'],
515 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
516 'calendar_id' => $calendar->id,
517 'user_id' => $calendar->user_id,
518 'duration' => (int)$slot['duration'],
519 'description' => wp_kses_post(Arr::get($slot, 'description')),
520 'settings' => [
521 'schedule_type' => sanitize_text_field($slot['settings']['schedule_type']),
522 'weekly_schedules' => SanitizeService::weeklySchedules($slot['settings']['weekly_schedules'], $calendar->author_timezone, 'UTC', true),
523 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($slot['settings'], 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
524 'range_type' => sanitize_text_field(Arr::get($slot['settings'], 'range_type')),
525 'range_days' => (int)(Arr::get($slot['settings'], 'range_days', 60)) ?: 60,
526 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($slot['settings'], 'range_date_between', ['', ''])),
527 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
528 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
529 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
530 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
531 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
532 ],
533 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
534 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
535 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
536 'availability_type' => 'existing_schedule',
537 'availability_id' => $availability ? $availability->id : null,
538 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
539 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
540 'max_book_per_slot' => (int)Arr::get($slot, 'max_book_per_slot', 1),
541 'is_display_spots' => (bool)Arr::get($slot, 'is_display_spots', false),
542 ];
543
544 $slotData = apply_filters('fluent_booking/create_calendar_event_data', $slotData, $calendar);
545
546 do_action('fluent_booking/before_create_event', $calendar, $slotData);
547
548 $createdSlot = CalendarSlot::create($slotData);
549
550 do_action('fluent_booking/after_create_event', $calendar, $createdSlot);
551
552 $calendar->updateEventOrder($createdSlot->id);
553
554 return [
555 'message' => __('New Event Type has been created successfully', 'fluent-booking'),
556 'slot' => $createdSlot
557 ];
558 }
559
560 public function updateEventDetails(Request $request, $calendarId, $eventId)
561 {
562 $data = $request->all();
563
564 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
565
566 $rules = [
567 'title' => 'required',
568 'duration' => 'required|numeric',
569 'location_settings.*.type' => 'required',
570 'location_settings.*.title' => 'required_if:location_settings.*.type,in_person_organizer',
571 'location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
572 ];
573
574 $messages = [
575 'title.required' => __('Event title field is required', 'fluent-booking'),
576 'duration.required' => __('Event duration field is required', 'fluent-booking'),
577 'location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
578 'location_settings.*.title.required_if' => __('Event location title field is required', 'fluent-booking'),
579 'location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
580 ];
581
582 if ('group' === $event->event_type) {
583 $rules = array_merge($rules, [
584 'max_book_per_slot' => 'required|numeric|min:1',
585 'is_display_spots' => 'required|min:0|max:1',
586 ]);
587 $messages = array_merge($messages, [
588 'max_book_per_slot.required' => __('Event max book per slot field is required', 'fluent-booking'),
589 'is_display_spots.required' => __('Event is display spots field is required', 'fluent-booking')
590 ]);
591 } else {
592 $rules = array_merge($rules, [
593 'multi_duration.default_duration' => 'required_if:multi_duration.enabled,true',
594 'multi_duration.available_durations' => 'required_if:multi_duration.enabled,true'
595 ]);
596 $messages = array_merge($messages, [
597 'multi_duration.default_duration.required_if' => __('Event default duration is required', 'fluent-booking'),
598 'multi_duration.available_durations.required_if' => __('Event available durations is required', 'fluent-booking')
599 ]);
600 }
601
602 $validationConfig = apply_filters('fluent_booking/update_event_details_validation_rule', [
603 'rules' => $rules,
604 'messages' => $messages
605 ], $event);
606
607 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
608
609 $event->title = sanitize_text_field($data['title']);
610 $event->duration = (int)$data['duration'];
611 $event->status = SanitizeService::checkCollection($data['status'], ['active', 'draft']);
612 $event->color_schema = sanitize_text_field(Arr::get($data, 'color_schema', '#0099ff'));
613 $event->description = wp_kses_post(Arr::get($data, 'description'));
614 $event->max_book_per_slot = (int)Arr::get($data, 'max_book_per_slot');
615 $event->is_display_spots = (bool)Arr::get($data, 'is_display_spots');
616 $event->location_settings = SanitizeService::locationSettings(Arr::get($data, 'location_settings', []));
617
618 $event->settings = [
619 'multi_duration' => [
620 'enabled' => Arr::isTrue($data, 'multi_duration.enabled'),
621 'default_duration' => Arr::get($data, 'multi_duration.default_duration', ''),
622 'available_durations' => array_map('sanitize_text_field', Arr::get($data, 'multi_duration.available_durations', []))
623 ]
624 ];
625
626 $event->save();
627
628 do_action('fluent_booking/after_update_event_details', $event);
629
630 return [
631 'message' => __('Data has been updated', 'fluent-booking'),
632 'event' => $event
633 ];
634 }
635
636 public function updateEventAvailability(Request $request, $calendarId, $eventId)
637 {
638 $data = $request->all();
639
640 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
641
642 $eventSettings = [
643 'schedule_type' => sanitize_text_field(Arr::get($data, 'schedule_type')),
644 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($data, 'weekly_schedules'), $event->calendar->author_timezone, 'UTC', true),
645 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($data, 'date_overrides', []), $event->calendar->author_timezone, 'UTC', null, true),
646 'range_type' => sanitize_text_field(Arr::get($data, 'range_type')),
647 'range_days' => (int)(Arr::get($data, 'range_days', 60)) ?: 60,
648 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
649 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
650 ];
651
652 if ($event->isTeamEvent()) {
653 $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
654 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
655 array_map('intval', Arr::get($data, 'hosts_schedules', []))
656 ));
657 }
658
659 $event->settings = $eventSettings;
660
661 $event->availability_id = (int)Arr::get($data, 'availability_id');
662 $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
663
664 $event->save();
665
666 return [
667 'message' => __('Data has been updated', 'fluent-booking'),
668 'event' => $event
669 ];
670 }
671
672 public function updateEventLimits(Request $request, $calendarId, $eventId)
673 {
674 $data = $request->all();
675
676 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
677
678 $event->settings = [
679 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($data['settings'], 'schedule_conditions', [])),
680 'buffer_time_before' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_before', '0')),
681 'buffer_time_after' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_after', '0')),
682 'slot_interval' => sanitize_text_field(Arr::get($data, 'settings.slot_interval', '')),
683 'booking_frequency' => [
684 'enabled' => Arr::isTrue($data, 'settings.booking_frequency.enabled'),
685 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_frequency.limits'))
686 ],
687 'booking_duration' => [
688 'enabled' => Arr::isTrue($data, 'settings.booking_duration.enabled'),
689 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_duration.limits'))
690 ],
691 'lock_timezone' => [
692 'enabled' => Arr::isTrue($data, 'settings.lock_timezone.enabled'),
693 'timezone' => sanitize_text_field(Arr::get($data, 'settings.lock_timezone.timezone'))
694 ],
695 ];
696
697 $event->save();
698
699 return [
700 'message' => __('Data has been updated', 'fluent-booking'),
701 'event' => $event
702 ];
703 }
704
705 public function patchCalendarEvent(Request $request, $calendarId, $eventId)
706 {
707 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
708
709 $status = $request->get('status');
710
711 if ($status) {
712 $slot->status = $status;
713 $slot->save();
714 }
715
716 return [
717 'message' => __('Data has been updated', 'fluent-booking')
718 ];
719
720 }
721
722 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
723 {
724 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
725
726 if (!PermissionManager::canWriteCalendar($newCalendarId)) {
727 return $this->sendError([
728 'message' => __('You do not have permission to write to the destination calendar.', 'fluent-booking')
729 ], 403);
730 }
731
732 $calendar = Calendar::findOrFail($newCalendarId);
733
734 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
735
736 $clonedEvent = $originalEvent->replicate();
737
738 $clonedEvent->hash = null;
739
740 $clonedEvent->calendar_id = $calendar->id;
741
742 $clonedEvent->user_id = $calendar->user_id;
743
744 $clonedEvent->title = $originalEvent->title . ' (clone)';
745
746 $clonedEvent->slug = Helper::generateSlotSlug($clonedEvent->duration . 'min', $calendar);
747
748 $clonedEvent->save();
749
750 $calendar->updateEventOrder($clonedEvent->id);
751
752 $eventsMeta = $originalEvent->event_metas;
753
754 foreach ($eventsMeta as $meta) {
755 $clonedMeta = $meta->replicate();
756 $clonedMeta->object_id = $clonedEvent->id;
757 $clonedMeta->save();
758 }
759
760 return [
761 'slot' => $clonedEvent,
762 'message' => __('The Event Type has been cloned successfully', 'fluent-booking')
763 ];
764 }
765
766 public function saveCalendarEventOrder(Request $request, $calendarId)
767 {
768 $calendar = Calendar::findOrFail($calendarId);
769
770 $eventOrder = array_map('intval', $request->get('event_order', []));
771
772 $calendar->updateMeta('event_order', array_filter($eventOrder));
773
774 return [
775 'calendar' => $calendar,
776 'message' => __('Event order has been updated', 'fluent-booking')
777 ];
778 }
779
780 public function cloneEventEmailNotification(Request $request, $calendarId, $eventId)
781 {
782 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
783
784 $fromEventId = intval($request->get('from_event_id'));
785
786 if (!$fromEventId || !PermissionManager::canUpdateCalendarEvent($fromEventId)) {
787 return $this->sendError([
788 'message' => __('You do not have permission to clone from the selected event.', 'fluent-booking')
789 ], 403);
790 }
791
792 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
793
794 $notification = $fromCalendarEvent->getNotifications(true);
795
796 $calendarEvent->setNotifications($notification);
797
798 $calendarEvent->save();
799
800 return [
801 'message' => __('The Notification has been cloned successfully', 'fluent-booking'),
802 'notifications' => $notification
803 ];
804 }
805
806 public function getEventEmailNotifications(Request $request, $calendarId, $eventId)
807 {
808 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
809
810 /*
811 * Confirmation Email to Attendee
812 * Confirmation Email to Organizer
813 * Reminder Email to Attendee [before 1 day, 1 hour, 30 minutes, 5 minutes]
814 * Cancelled By Organizer to Attendee
815 * Cancelled By Attendee to Organizer
816 */
817 $data = [
818 'notifications' => $calendarEvent->getNotifications(true)
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 saveEventEmailNotifications(Request $request, $calendarId, $eventId)
832 {
833 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
834
835 $notifications = $request->get('notifications', []);
836
837 $formattedNotifications = [];
838
839 foreach ($notifications as $key => $value) {
840 $formattedNotifications[$key] = [
841 'title' => sanitize_text_field(Arr::get($value, 'title')),
842 'enabled' => Arr::isTrue($value, 'enabled'),
843 'email' => $this->sanitize_mapped_data(Arr::get($value, 'email')),
844 'is_host' => Arr::isTrue($value, 'is_host')
845 ];
846 }
847
848 $slot->setNotifications($formattedNotifications);
849
850 return [
851 'message' => __('Notifications has been saved', 'fluent-booking')
852 ];
853 }
854
855 public function getEventBookingFields(Request $request, $calendarId, $eventId)
856 {
857 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
858
859 $data = [
860 'fields' => $calendarEvent->getBookingFields()
861 ];
862
863 if (in_array('smart_codes', $request->get('with', []))) {
864 $data['smart_codes'] = [
865 'texts' => Helper::getEditorShortCodes($calendarEvent),
866 'html' => Helper::getEditorShortCodes($calendarEvent, true)
867 ];
868 }
869
870 return $data;
871 }
872
873 public function saveEventBookingFields(Request $request, $calendarId, $eventId)
874 {
875 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
876
877 $bookingFields = $request->get('booking_fields');
878
879 $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
880
881 $formattedFields = [];
882
883 $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
884 $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
885
886 foreach ($bookingFields as $value) {
887 if (empty($value['name'])) {
888 $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
889 } else {
890 $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
891 }
892
893 $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
894
895 $booleanValues = array_map(function ($valueItem) {
896 return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
897 }, Arr::only($value, $booleanFields));
898
899 $formattedField = array_merge($textValues, $booleanValues);
900
901 $fieldType = Arr::get($value, 'type');
902
903 $formattedField['index'] = (int)Arr::get($value, 'index');
904 if (in_array($fieldType, $optionRequiredFields)) {
905 $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
906 $formattedField['options'] = $sanitizedOptions;
907 }
908 if ($fieldType == 'file') {
909 $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
910 $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
911 $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
912 $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
913 }
914 if ($fieldType == 'hidden') {
915 $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
916 }
917 if ($fieldType == 'terms-and-conditions') {
918 $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
919 }
920
921 $formattedField = apply_filters('fluent_booking/save_event_booking_field_' . $fieldType, $formattedField, $value, $calendarEvent);
922
923 $formattedFields[] = $formattedField;
924 }
925
926 $calendarEvent->setBookingFields($formattedFields);
927
928 return [
929 'message' => __('Fields has been updated', 'fluent-booking')
930 ];
931 }
932
933 public function getEventPaymentSettings($calendarId, $eventId)
934 {
935 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
936
937 $config = [
938 'native_enabled' => Helper::isPaymentEnabled(),
939 'stripe_configured' => Helper::isPaymentConfigured('stripe'),
940 'paypal_configured' => Helper::isPaymentConfigured('paypal'),
941 'offline_configured' => Helper::isPaymentConfigured('offline'),
942 'native_config_link' => Helper::getAppBaseUrl('settings/payment-methods/stripe'),
943 'woo_config_link' => Helper::getAppBaseUrl('settings/configure-integrations/global-modules'),
944 'has_cart' => defined('FLUENTCART_VERSION'),
945 'has_woo' => defined('WC_PLUGIN_FILE'),
946 'woo_enabled' => defined('WC_PLUGIN_FILE') && Helper::isModuleEnabled('woo')
947 ];
948
949 $data = apply_filters('fluent_booking/payment/get_payment_settings', [
950 'settings' => $calendarEvent->getPaymentSettings(),
951 'config' => $config
952 ], $calendarEvent);
953
954 return $data;
955 }
956
957 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
958 {
959 $calendar = Calendar::query()->findOrFail($calendarId);
960
961 $calendarEvent = CalendarSlot::query()->where('calendar_id', $calendar->id)->findOrFail($calendarEventId);
962
963 $calendar->updateEventOrder($calendarEvent->id);
964
965 do_action('fluent_booking/before_delete_calendar_event', $calendarEvent, $calendar);
966
967 $calendarEvent->delete();
968
969 do_action('fluent_booking/after_delete_calendar_event', $calendarEventId, $calendar);
970
971 return [
972 'message' => __('Calendar Event has been deleted', 'fluent-booking')
973 ];
974 }
975
976 private function sanitize_mapped_data($settings)
977 {
978 $sanitizerMap = [
979 'value' => 'intval',
980 'unit' => 'sanitize_text_field',
981 'subject' => 'sanitize_text_field',
982 'body' => 'fcal_sanitize_html',
983 'additional_recipients' => 'sanitize_text_field'
984 ];
985
986 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
987 }
988
989 public function deleteCalendar(Request $request, $calendarId)
990 {
991 $calendar = Calendar::findOrFail($calendarId);
992
993 do_action('fluent_booking/before_delete_calendar', $calendar);
994
995 $calendar->delete();
996
997 do_action('fluent_booking/after_delete_calendar', $calendarId);
998
999 return [
1000 'message' => __('Calendar Deleted Successfully!', 'fluent-booking')
1001 ];
1002 }
1003 }
1004