PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.6.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.6.0
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / app / Http / Controllers / CalendarController.php

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

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