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

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