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

891 lines 38.4 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\Booking;
6 use FluentBooking\App\Models\Calendar;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\App\Models\Availability;
9 use FluentBooking\App\Services\Helper;
10 use FluentBooking\App\Services\CurrenciesHelper;
11 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
12 use FluentBooking\App\Services\PermissionManager;
13 use FluentBooking\App\Services\AvailabilityService;
14 use FluentBooking\App\Services\SanitizeService;
15 use FluentBooking\App\Services\CalendarService;
16 use FluentBooking\App\Services\BookingFieldService;
17 use FluentBooking\App\Hooks\Handlers\AdminMenuHandler;
18 use FluentBooking\Framework\Http\Request\Request;
19 use FluentBooking\Framework\Support\Arr;
20
21 class CalendarController extends Controller
22 {
23 public function getAllCalendars(Request $request)
24 {
25 do_action('fluent_booking/before_get_all_calendars', $request);
26
27 $search = sanitize_text_field(Arr::get($request->get('query'), 'search'));
28 $calendarType = sanitize_text_field(Arr::get($request->get('query'), 'calendarType'));
29
30 $applySearchFilter = function($query) use ($search) {
31 $query->where('status', '!=', 'expired');
32 if (!empty($search)) {
33 $query->where('title', 'LIKE', '%' . $search . '%');
34 }
35 };
36
37 $calendarsQuery = Calendar::with(['slots' => function($query) use ($applySearchFilter) {
38 $query->where($applySearchFilter);
39 }])
40 ->where('status', '!=', 'expired');
41
42 if (!empty($search)) {
43 $calendarsQuery->whereHas('slots', $applySearchFilter);
44 }
45
46 if (!empty($calendarType) && $calendarType != 'all') {
47 $calendarsQuery->where('type', $calendarType);
48 }
49
50 $calendarsQuery = $calendarsQuery->latest();
51
52 if (!PermissionManager::hasAllCalendarAccess(true)) {
53 $calendarsQuery->where('user_id', get_current_user_id());
54 }
55
56 $calendars = $calendarsQuery->paginate();
57
58 foreach ($calendars as $calendar) {
59 $calendar->author_profile = $calendar->getAuthorProfile();
60 $calendar->public_url = $calendar->getLandingPageUrl();
61 $calendar->event_order = $calendar->getMeta('event_order');
62 foreach ($calendar->slots as $slot) {
63 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
64 $slot->public_url = $slot->getPublicUrl();
65 $slot->duration = $slot->getDefaultDuration();
66 $slot->price_total = $slot->getPricingTotal();
67 $slot->location_fields = $slot->getLocationFields();
68 $slot->short_description = Helper::excerpt($slot->getDescription());
69 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
70 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
71 }
72
73 if(empty($calendar->author_profile['ID'])) {
74 $calendar->generic_error = '<p style="color: red; margin:0;">Connected Host user is missing</p>';
75 }
76
77 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
78 }
79
80 $data = [
81 'calendars' => $calendars
82 ];
83
84 if (in_array('calendar_event_lists', $request->get('with', []))) {
85 $data['calendar_event_lists'] = [
86 'hosts' => CalendarService::getCalendarOptionsByTitle('only_hosts'),
87 'teams' => CalendarService::getCalendarOptionsByTitle('only_teams'),
88 'events' => CalendarService::getCalendarOptionsByTitle('only_events')
89 ];
90 }
91
92 return $data;
93 }
94
95 public function checkSlug(Request $request)
96 {
97 $slug = sanitize_text_field(trim($request->get('slug')));
98
99 if (!Helper::isCalendarSlugAvailable($slug, true)) {
100 return $this->sendError([
101 'message' => __('The provided slug is not available. Please choose a different one', 'fluent-booking')
102 ], 422);
103 }
104
105 return [
106 'status' => true
107 ];
108 }
109
110 public function createCalendar(Request $request)
111 {
112 $data = $request->get('calendar');
113
114 $rules = [
115 'author_timezone' => 'required',
116 'slot.duration' => 'required|numeric|min:5',
117 'slot.event_type' => 'required',
118 'slot.availability_type' => 'required',
119 'slot.schedule_type' => 'required',
120 'slot.title' => 'required',
121 'slot.weekly_schedules' => 'required_if:slot.schedule_type,weekly_schedules',
122 'slot.location_settings.*.type' => 'required',
123 'slot.location_settings.*.host_phone_number' => 'required_if:location_settings.*.type,phone_organizer'
124 ];
125
126 $messages = [
127 'author_timezone.required' => __('Author timezone field is required', 'fluent-booking'),
128 'slot.duration.required' => __('Event duration field is required', 'fluent-booking'),
129 'slot.event_type.required' => __('Event type field is required', 'fluent-booking'),
130 'slot.availability_type.required' => __('Event availability type field is required', 'fluent-booking'),
131 'slot.schedule_type.required' => __('Event schedule type field is required', 'fluent-booking'),
132 'slot.title.required' => __('Event title field is required', 'fluent-booking'),
133 'slot.weekly_schedules.required_if' => __('Event weekly schedules field is required', 'fluent-booking'),
134 'slot.location_settings.*.type.required' => __('Event location type field is required', 'fluent-booking'),
135 'slot.location_settings.*.host_phone_number.required_if' => __('Event location host phone number field is required', 'fluent-booking')
136 ];
137
138 $validationConfig = apply_filters('fluent_booking/create_calendar_validation_rule', [
139 'rules' => $rules,
140 'messages' => $messages
141 ], $data);
142
143 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
144
145 do_action('fluent_booking/before_create_calendar', $data, $this);
146
147 if (!empty($data['user_id']) && PermissionManager::userCan('invite_team_members')) {
148 $user = get_user_by('ID', $data['user_id']);
149 } else {
150 $user = get_user_by('ID', get_current_user_id());
151 }
152
153 if (!$user) {
154 return $this->sendError([
155 'message' => __('User not found', 'fluent-booking')
156 ], 422);
157 }
158
159 $type = sanitize_text_field(Arr::get($data, 'type', 'simple'));
160
161 $isHostCalendar = $type == 'simple' ? true : false;
162
163 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
164 return $this->sendError([
165 'message' => __('The user already have a calendar. Please delete it first to create a new one', 'fluent-booking')
166 ], 422);
167 }
168
169 if ($isHostCalendar) {
170 $userName = $user->user_login;
171 if (is_email($userName)) {
172 $userName = explode('@', $userName);
173 $userName = $userName[0] . '-' . time();
174 }
175 $data['slug'] = sanitize_title($userName, '', 'display');
176 }
177
178 $slot = $data['slot'];
179
180 if (!$isHostCalendar) {
181 $title = sanitize_text_field(Arr::get($data, 'title', ''));
182 $data['slug'] = sanitize_title($title, '', 'display');
183 $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
184 if (!in_array($user->ID, $teamMembers)) {
185 $user = get_user_by('ID', reset($teamMembers));
186 if (!$user) {
187 return $this->sendError([
188 'message' => __('Invalid Team Member', 'fluent-booking')
189 ], 422);
190 }
191 }
192 }
193
194 if (!Helper::isCalendarSlugAvailable($data['slug'], true)) {
195 $data['slug'] .= '-' . time();
196 }
197
198 if (!empty($data['slug'])) {
199 $slug = trim(sanitize_text_field($data['slug']));
200 if (!Helper::isCalendarSlugAvailable($slug, true)) {
201 return $this->sendError([
202 'message' => __('The provided slug is not available. Please choose a different one', 'fluent-booking')
203 ], 422);
204 }
205
206 $personName = trim($user->first_name . ' ' . $user->last_name);
207 if (!$personName) {
208 $personName = $user->display_name;
209 }
210
211 $calendarData = [
212 'slug' => $slug,
213 'user_id' => $user->ID,
214 'title' => $isHostCalendar ? $personName : $title,
215 'type' => $type,
216 'author_timezone' => sanitize_text_field($data['author_timezone']) ?: 'UTC',
217 ];
218 $calendar = Calendar::create($calendarData);
219 } else {
220 $calendar = Calendar::where('user_id', $user->ID)->where('type', 'simple')->first();
221 }
222
223 if (!$calendar) {
224 return $this->sendError([
225 'message' => __('Calendar could not be found. Please try again', 'fluent-booking')
226 ], 422);
227 }
228
229 $weeklySchedule = Arr::get($data, 'slot.weekly_schedules', []);
230
231 $availability = AvailabilityService::maybeCreateAvailability($calendar, $weeklySchedule);
232
233 $title = (!empty($slot['title'])) ? sanitize_text_field($slot['title']) : $slot['duration'] . ' Minute Meeting';
234
235 $slotData = [
236 'title' => $title,
237 'slug' => Helper::generateSlotSlug((int)$slot['duration'] . 'min', $calendar),
238 'calendar_id' => $calendar->id,
239 'user_id' => $calendar->user_id,
240 'duration' => (int)$slot['duration'],
241 'description' => sanitize_textarea_field(Arr::get($slot, 'description')),
242 'settings' => [
243 'team_members' => !$isHostCalendar ? $teamMembers : [],
244 'schedule_type' => sanitize_text_field($slot['schedule_type']),
245 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC')
246 ],
247 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft']),
248 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
249 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
250 'availability_type' => SanitizeService::checkCollection($slot['availability_type'], ['existing_schedule', 'custom'], 'existing_schedule'),
251 'availability_id' => (int)$availability->id,
252 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
253 'location_heading' => wp_kses_post(Arr::get($slot, 'location_heading')),
254 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
255 ];
256
257 $slotData['settings'] = wp_parse_args($slotData['settings'], (new CalendarSlot())->getSlotSettingsSchema());
258
259 $slotData = apply_filters('fluent_booking/create_calendar_event_data', $slotData, $calendar);
260
261 $slot = CalendarSlot::create($slotData);
262
263 do_action('fluent_booking/after_create_calendar', $calendar);
264
265 do_action('fluent_booking/after_create_calendar_slot', $slot, $calendar);
266
267 return [
268 'calendar' => $calendar,
269 'slot' => $slot,
270 'redirect_url' => Helper::getAppBaseUrl('calendars/' . $calendar->id . '/slot-settings/' . $slot->id)
271 ];
272 }
273
274 public function getCalendar(Request $request, $calendarId)
275 {
276 $calendar = Calendar::with(['slots' => function ($query) {
277 $query->where('status', '!=', 'expired');
278 }])->findOrFail($calendarId);
279
280 $calendar->author_profile = $calendar->getAuthorProfile();
281
282 $data = [
283 'calendar' => $calendar
284 ];
285
286 if (in_array('settings_menu', $request->get('with', []))) {
287 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
288 }
289
290 return $data;
291 }
292
293 public function getSharingSettings(Request $request, $calendarId)
294 {
295 $calendar = Calendar::findOrFail($calendarId);
296
297 return [
298 'settings' => LandingPageHelper::getSettings($calendar),
299 'share_url' => $calendar->getLandingPageUrl(true)
300 ];
301 }
302
303 public function saveSharingSettings(Request $request, $calendarId)
304 {
305 $calendar = Calendar::findOrFail($calendarId);
306
307 $calendarDataItems = Arr::only($request->get('calendar_data', []), ['title', 'timezone', 'description', 'calendar_avatar', 'featured_image', 'phone']);
308
309 if ($calendarDataItems) {
310 $this->validate($calendarDataItems, [
311 'title' => 'required',
312 'calendar_avatar' => 'url'
313 ]);
314
315 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
316 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
317 CalendarService::updateCalendarEventsSchedule($calendarId, $calendar->author_timezone, $updatedTimezone);
318 $calendar->author_timezone = $updatedTimezone;
319 }
320
321 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
322 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
323 $calendar->save();
324 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
325 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
326
327 if ($calendar->user) {
328 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
329 }
330 }
331
332 $sharingSettings = $request->get('landing_page_settings', []);
333 LandingPageHelper::updateSettings($calendar, $sharingSettings);
334
335 return [
336 'message' => __('Landing Page settings has been updated', 'fluent-booking')
337 ];
338 }
339
340 public function updateCalendar(Request $request, $calendarId)
341 {
342 $data = $request->all();
343
344 $calendar = Calendar::findOrFail($calendarId);
345
346 do_action_ref_array('fluent_booking/before_update_calendar', [&$calendar, $data]);
347
348 $calendar->description = wp_kses_post($request->get('description'));
349 $calendar->save();
350 do_action('fluent_booking/after_update_calendar', $calendar, $data);
351
352 $calendar->author_profile = $calendar->getAuthorProfile();
353
354 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'update']);
355
356 return [
357 'calendar' => $calendar,
358 'message' => __('Calendar has been updated successfully', 'fluent-booking')
359 ];
360 }
361
362 public function getEvent(Request $request, $calendarId, $slotId)
363 {
364 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($slotId);
365
366 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
367
368 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
369
370 $calendarEvent->public_url = $calendarEvent->getPublicUrl();
371
372 $eventSettings = $calendarEvent->settings;
373
374 $eventSettings['weekly_schedules'] = SanitizeService::weeklySchedules($eventSettings['weekly_schedules'], 'UTC', $calendarEvent->calendar->author_timezone);
375
376 $eventSettings['date_overrides'] = (object)SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), 'UTC', $calendarEvent->calendar->author_timezone, $calendarEvent);
377
378 $eventSettings['location_fields'] = $calendarEvent->getLocationFields();
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' => sanitize_textarea_field(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 = sanitize_textarea_field(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 $event->settings = [
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 $event->availability_id = (int)Arr::get($data, 'availability_id');
610 $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
611
612 $event->save();
613
614 return [
615 'message' => __('Data has been updated', 'fluent-booking'),
616 'event' => $event
617 ];
618 }
619
620 public function updateEventLimits(Request $request, $calendarId, $eventId)
621 {
622 $data = $request->all();
623
624 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
625
626 $event->settings = [
627 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($data['settings'], 'schedule_conditions', [])),
628 'buffer_time_before' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_before', '0')),
629 'buffer_time_after' => sanitize_text_field(Arr::get($data, 'settings.buffer_time_after', '0')),
630 'slot_interval' => sanitize_text_field(Arr::get($data, 'settings.slot_interval', '')),
631 'booking_frequency' => [
632 'enabled' => Arr::isTrue($data, 'settings.booking_frequency.enabled'),
633 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_frequency.limits'))
634 ],
635 'booking_duration' => [
636 'enabled' => Arr::isTrue($data, 'settings.booking_duration.enabled'),
637 'limits' => $this->sanitize_mapped_data(Arr::get($data, 'settings.booking_duration.limits'))
638 ],
639 'lock_timezone' => [
640 'enabled' => Arr::isTrue($data, 'settings.lock_timezone.enabled'),
641 'timezone' => sanitize_text_field(Arr::get($data, 'settings.lock_timezone.timezone'))
642 ],
643 ];
644
645 $event->save();
646
647 return [
648 'message' => __('Data has been updated', 'fluent-booking'),
649 'event' => $event
650 ];
651 }
652
653 public function patchCalendarEvent(Request $request, $calendarId, $slotId)
654 {
655 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
656
657 $status = $request->get('status');
658
659 if ($status) {
660 $slot->status = $status;
661 $slot->save();
662 }
663
664 return [
665 'message' => __('Data has been updated', 'fluent-booking')
666 ];
667
668 }
669
670 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
671 {
672 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
673
674 $calendar = Calendar::findOrFail($newCalendarId);
675
676 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
677
678 $clonedEvent = $originalEvent->replicate();
679
680 $clonedEvent->calendar_id = $calendar->id;
681
682 $clonedEvent->user_id = $calendar->user_id;
683
684 $clonedEvent->title = $originalEvent->title . ' (clone)';
685
686 $clonedEvent->slug = Helper::generateSlotSlug($clonedEvent->duration . 'min', $calendar);
687
688 $clonedEvent->save();
689
690 $calendar->updateEventOrder($clonedEvent->id);
691
692 $eventsMeta = $originalEvent->event_metas;
693
694 foreach ($eventsMeta as $meta) {
695 $clonedMeta = $meta->replicate();
696 $clonedMeta->object_id = $clonedEvent->id;
697 $clonedMeta->save();
698 }
699
700 return [
701 'slot' => $clonedEvent,
702 'message' => __('The Event Type has been cloned successfully', 'fluent-booking')
703 ];
704 }
705
706 public function saveCalendarEventOrder(Request $request, $calendarId)
707 {
708 $calendar = Calendar::findOrFail($calendarId);
709
710 $eventOrder = array_map('intval', $request->get('event_order', []));
711
712 $calendar->updateMeta('event_order', array_filter($eventOrder));
713
714 return [
715 'calendar' => $calendar,
716 'message' => __('Event order has been updated', 'fluent-booking')
717 ];
718 }
719
720 public function cloneEventEmailNotification(Request $request, $calendarId, $eventId)
721 {
722 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
723
724 $fromEventId = intval($request->get('from_event_id'));
725
726 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
727
728 $notification = $fromCalendarEvent->getNotifications(true);
729
730 $calendarEvent->setNotifications($notification);
731
732 $calendarEvent->save();
733
734 return [
735 'message' => __('The Notification has been cloned successfully', 'fluent-booking'),
736 'notifications' => $notification
737 ];
738 }
739
740 public function getEventEmailNotifications(Request $request, $calendarId, $slotId)
741 {
742 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
743
744 /*
745 * Confirmation Email to Attendee
746 * Confirmation Email to Organizer
747 * Reminder Email to Attendee [before 1 day, 1 hour, 30 minutes, 5 minutes]
748 * Cancelled By Organizer to Attendee
749 * Cancelled By Attendee to Organizer
750 */
751 $data = [
752 'notifications' => $calendarEvent->getNotifications(true)
753 ];
754
755 if (in_array('smart_codes', $request->get('with', []))) {
756 $data['smart_codes'] = [
757 'texts' => Helper::getEditorShortCodes($calendarEvent),
758 'html' => Helper::getEditorShortCodes($calendarEvent, true)
759 ];
760 }
761
762 return $data;
763 }
764
765 public function saveEventEmailNotifications(Request $request, $calendarId, $slotId)
766 {
767 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
768
769 $notifications = $request->get('notifications', []);
770
771 $formattedNotifications = [];
772
773 foreach ($notifications as $key => $value) {
774 $formattedNotifications[$key] = [
775 'title' => sanitize_text_field(Arr::get($value, 'title')),
776 'enabled' => Arr::isTrue($value, 'enabled'),
777 'email' => $this->sanitize_mapped_data(Arr::get($value, 'email')),
778 'is_host' => Arr::isTrue($value, 'is_host')
779 ];
780 }
781
782 $slot->setNotifications($formattedNotifications);
783
784 return [
785 'message' => __('Notifications has been saved', 'fluent-booking')
786 ];
787 }
788
789 public function getEventBookingFields(Request $request, $calendarId, $slotId)
790 {
791 $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
792
793 return [
794 'fields' => $slot->getBookingFields()
795 ];
796 }
797
798 public function saveEventBookingFields(Request $request, $calendarId, $eventId)
799 {
800 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
801
802 $bookingFields = $request->get('booking_fields');
803
804 $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
805
806 $formattedFields = [];
807
808 $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format'];
809 $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
810
811 foreach ($bookingFields as $value) {
812 if (empty($value['name'])) {
813 $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
814 }
815
816 $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
817
818 $booleanValues = array_map(function ($valueItem) {
819 return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
820 }, Arr::only($value, $booleanFields));
821
822 $formattedField = array_merge($textValues, $booleanValues);
823
824 $formattedField['index'] = (int)Arr::get($value, 'index');
825 if ($value['type'] == 'payment' && $calendarEvent->type === 'paid') {
826 $formattedField['payment_items'] = Arr::get($value, 'payment_items');
827 $formattedField['currency_sign'] = CurrenciesHelper::getGlobalCurrencySign();
828 }
829 if (in_array(Arr::get($value, 'type'), $optionRequiredFields)) {
830 $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
831 $formattedField['options'] = $sanitizedOptions;
832 }
833
834 $formattedFields[] = $formattedField;
835 }
836
837 $calendarEvent->setBookingFields($formattedFields);
838
839 return [
840 'message' => __('Fields has been updated', 'fluent-booking')
841 ];
842 }
843
844 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
845 {
846 $calendar = Calendar::query()->findOrFail($calendarId);
847
848 $calendarEvent = CalendarSlot::query()->where('calendar_id', $calendar->id)->findOrFail($calendarEventId);
849
850 $calendar->updateEventOrder($calendarEvent->id);
851
852 do_action('fluent_booking/before_delete_calendar_event', $calendarEvent, $calendar);
853
854 $calendarEvent->delete();
855
856 do_action('fluent_booking/after_delete_calendar_event', $calendarEventId, $calendar);
857
858 return [
859 'message' => __('Calendar Event has been deleted', 'fluent-booking')
860 ];
861 }
862
863 private function sanitize_mapped_data($settings)
864 {
865 $sanitizerMap = [
866 'value' => 'intval',
867 'unit' => 'sanitize_text_field',
868 'subject' => 'sanitize_text_field',
869 'body' => 'fcal_sanitize_html',
870 'additional_recipients' => 'sanitize_text_field'
871 ];
872
873 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
874 }
875
876 public function deleteCalendar(Request $request, $calendarId)
877 {
878 $calendar = Calendar::findOrFail($calendarId);
879
880 do_action('fluent_booking/before_delete_calendar', $calendar);
881
882 $calendar->delete();
883
884 do_action('fluent_booking/after_delete_calendar', $calendarId);
885
886 return [
887 'message' => __('Calendar Deleted Successfully!', 'fluent-booking')
888 ];
889 }
890 }
891