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

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