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

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