PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Http/Controllers/CalendarController.php +301 -88 1.5.23 → 2.5.0 View file →
@@ -1,19 +1,19 @@
1 1 <?php
2 2
3 3 namespace FluentBooking\App\Http\Controllers;
4 4
5 -use FluentBooking\App\Models\Booking;
5 +use FluentBooking\App\Models\Availability;
6 6 use FluentBooking\App\Models\Calendar;
7 7 use FluentBooking\App\Models\CalendarSlot;
8 -use FluentBooking\App\Models\Availability;
9 8 use FluentBooking\App\Services\Helper;
10 -use FluentBooking\App\Services\CurrenciesHelper;
11 9 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
12 10 use FluentBooking\App\Services\PermissionManager;
13 11 use FluentBooking\App\Services\AvailabilityService;
14 12 use FluentBooking\App\Services\SanitizeService;
15 13 use FluentBooking\App\Services\CalendarService;
14 +use FluentBooking\App\Services\OnboardingService;
15 +use FluentBooking\App\Services\CalendarEventService;
16 16 use FluentBooking\App\Services\BookingFieldService;
17 17 use FluentBooking\App\Hooks\Handlers\AdminMenuHandler;
18 18 use FluentBooking\Framework\Http\Request\Request;
19 19 use FluentBooking\Framework\Support\Arr;
@@ -33,9 +33,9 @@
33 33 $query->where('title', 'LIKE', '%' . $search . '%');
34 34 }
35 35 };
36 36
37 - $calendarsQuery = Calendar::with(['slots' => function($query) use ($applySearchFilter) {
37 + $calendarsQuery = Calendar::with(['metas', 'slots' => function($query) use ($applySearchFilter) {
38 38 $query->where($applySearchFilter);
39 39 }])
40 40 ->where('status', '!=', 'expired');
41 41
@@ -48,9 +48,11 @@
48 48 }
49 49
50 50 $calendarsQuery = $calendarsQuery->latest();
51 51
52 - if (!PermissionManager::hasAllCalendarAccess(true)) {
52 + $hasPermission = PermissionManager::hasAllCalendarAccess(true);
53 +
54 + if (!$hasPermission) {
53 55 $attachedCalendarIds = CalendarService::getAttachedCalendarIds($calendarsQuery);
54 56 $calendarsQuery->whereIn('id', $attachedCalendarIds);
55 57 }
56 58
@@ -59,21 +61,29 @@
59 61 foreach ($calendars as $calendar) {
60 62 $calendar->author_profile = $calendar->getAuthorProfile();
61 63 $calendar->public_url = $calendar->getLandingPageUrl();
62 64 $calendar->event_order = $calendar->getMeta('event_order');
65 +
66 + if (!$hasPermission) {
67 + $calendar->setRelation('slots', $calendar->slots->filter(function ($slot) {
68 + return CalendarEventService::isSharedCalendarEvent($slot);
69 + })->values());
70 + }
71 +
63 72 foreach ($calendar->slots as $slot) {
73 + $slot->setRelation('calendar', $calendar);
64 74 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
65 75 $slot->public_url = $slot->getPublicUrl();
66 76 $slot->duration = $slot->getDefaultDuration();
67 - $slot->price_total = $slot->getPricingTotal();
77 + $slot->price_total = $slot->getEventPrice();
68 78 $slot->location_fields = $slot->getLocationFields();
69 - $slot->short_description = Helper::excerpt($slot->getDescription());
70 79 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
71 80 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
81 + $slot->unsetRelation('calendar');
72 82 }
73 83
74 84 if(empty($calendar->author_profile['ID'])) {
75 - $calendar->generic_error = '<p style="color: red; margin:0;">Connected Host user is missing</p>';
85 + $calendar->generic_error = '<p style="color: var(--fcal-danger-fg); margin:0;">Connected Host user is missing</p>';
76 86 }
77 87
78 88 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
79 89 }
@@ -103,12 +113,59 @@
103 113 ], 422);
104 114 }
105 115
106 116 return [
107 - 'status' => true
117 + 'status' => true,
118 + 'message' => __('The provided slug is available', 'fluent-booking')
108 119 ];
109 120 }
110 121
122 + public function getNewEventLocationFields(Request $request)
123 + {
124 + $eventType = SanitizeService::checkCollection(
125 + sanitize_text_field($request->get('event_type', 'single')),
126 + CalendarSlot::getEventTypes(),
127 + 'single'
128 + );
129 +
130 + // Resolve the organizer the same way createCalendar() does, so the
131 + // connection checks run against the host the event will be saved under.
132 + $canAssignOthers = PermissionManager::canManageOtherHosts();
133 +
134 + $userId = get_current_user_id();
135 + $requestedUserId = (int) $request->get('user_id');
136 + if ($requestedUserId && $canAssignOthers) {
137 + $userId = $requestedUserId;
138 + }
139 +
140 + $calendarEvent = new CalendarSlot();
141 + $calendarEvent->event_type = $eventType;
142 +
143 + if ($calendarEvent->isMultiHostEvent()) {
144 + $teamMembers = array_values(array_unique(array_filter(
145 + array_map('intval', (array) $request->get('team_members', []))
146 + )));
147 +
148 + if (!PermissionManager::canAssignHosts($teamMembers, [$userId])) {
149 + return $this->sendError([
150 + 'message' => __('You are not allowed to create a calendar for another user', 'fluent-booking')
151 + ], 403);
152 + }
153 +
154 + if ($teamMembers && !in_array($userId, $teamMembers, true)) {
155 + $userId = reset($teamMembers);
156 + }
157 +
158 + $calendarEvent->settings = ['team_members' => $teamMembers];
159 + }
160 +
161 + $calendarEvent->user_id = $userId;
162 +
163 + return [
164 + 'location_fields' => $calendarEvent->getLocationFields()
165 + ];
166 + }
167 +
111 168 public function createCalendar(Request $request)
112 169 {
113 170 $data = $request->get('calendar');
114 171
@@ -144,9 +201,9 @@
144 201 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
145 202
146 203 do_action('fluent_booking/before_create_calendar', $data, $this);
147 204
148 - if (!empty($data['user_id']) && PermissionManager::userCan('invite_team_members')) {
205 + if (!empty($data['user_id']) && PermissionManager::canManageOtherHosts()) {
149 206 $user = get_user_by('ID', $data['user_id']);
150 207 } else {
151 208 $user = get_user_by('ID', get_current_user_id());
152 209 }
@@ -156,15 +213,25 @@
156 213 'message' => __('User not found', 'fluent-booking')
157 214 ], 422);
158 215 }
159 216
160 - $type = sanitize_text_field(Arr::get($data, 'type', 'simple'));
217 + $onboardinFeatures = $request->get('features');
218 + if (!empty($onboardinFeatures)) {
219 + $installableAddons = SanitizeService::sanitizeAddons($onboardinFeatures);
220 + OnboardingService::installAddons($installableAddons);
221 + }
161 222
223 + $type = SanitizeService::checkCollection(
224 + sanitize_text_field(Arr::get($data, 'type', 'simple')),
225 + ['simple', 'team', 'event'],
226 + 'simple'
227 + );
228 +
162 229 $isHostCalendar = $type == 'simple' ? true : false;
163 230
164 231 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
165 232 return $this->sendError([
166 - 'message' => __('The user already have a calendar. Please delete it first to create a new one', 'fluent-booking')
233 + 'message' => __('The user already has a calendar. Please delete it first to create a new one', 'fluent-booking')
167 234 ], 422);
168 235 }
169 236
170 237 if ($isHostCalendar) {
@@ -180,10 +247,30 @@
180 247
181 248 if (!$isHostCalendar) {
182 249 $title = sanitize_text_field(Arr::get($data, 'title', ''));
183 250 $data['slug'] = sanitize_title($title, '', 'display');
184 - $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
185 - if (!in_array($user->ID, $teamMembers)) {
251 + $teamMembers = array_values(array_filter(
252 + array_map('intval', (array) Arr::get($slot, 'settings.team_members', []))
253 + ));
254 +
255 + cache_users($teamMembers);
256 +
257 + foreach ($teamMembers as $memberId) {
258 + if (!get_user_by('ID', $memberId)) {
259 + return $this->sendError([
260 + 'message' => __('Invalid Team Member', 'fluent-booking')
261 + ], 422);
262 + }
263 + }
264 +
265 + // Gated even when the creator is listed too, not only when they are absent.
266 + if (!PermissionManager::canAssignHosts($teamMembers, [$user->ID])) {
267 + return $this->sendError([
268 + 'message' => __('You are not allowed to create a calendar for another user', 'fluent-booking')
269 + ], 403);
270 + }
271 +
272 + if (!in_array($user->ID, $teamMembers, true)) {
186 273 $user = get_user_by('ID', reset($teamMembers));
187 274 if (!$user) {
188 275 return $this->sendError([
189 276 'message' => __('Invalid Team Member', 'fluent-booking')
@@ -238,13 +325,13 @@
238 325 'slug' => Helper::generateSlotSlug((int)$slot['duration'] . 'min', $calendar),
239 326 'calendar_id' => $calendar->id,
240 327 'user_id' => $calendar->user_id,
241 328 'duration' => (int)$slot['duration'],
242 - 'description' => sanitize_textarea_field(Arr::get($slot, 'description')),
329 + 'description' => wp_kses_post(Arr::get($slot, 'description')),
243 330 'settings' => [
244 331 'team_members' => !$isHostCalendar ? $teamMembers : [],
245 332 'schedule_type' => sanitize_text_field($slot['schedule_type']),
246 - 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC')
333 + 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC', true)
247 334 ],
248 335 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft']),
249 336 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
250 337 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
@@ -278,9 +365,28 @@
278 365 $query->where('status', '!=', 'expired');
279 366 }])->findOrFail($calendarId);
280 367
281 368 $calendar->author_profile = $calendar->getAuthorProfile();
369 + $calendar->event_order = $calendar->getMeta('event_order');
282 370
371 + if (!PermissionManager::hasAllCalendarAccess(true)) {
372 + $calendar->setRelation('slots', $calendar->slots->filter(function ($slot) {
373 + return CalendarEventService::isSharedCalendarEvent($slot);
374 + })->values());
375 + }
376 +
377 + foreach ($calendar->slots as $slot) {
378 + $slot->setRelation('calendar', $calendar);
379 + $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
380 + $slot->public_url = $slot->getPublicUrl();
381 + $slot->duration = $slot->getDefaultDuration();
382 + $slot->price_total = $slot->getEventPrice();
383 + $slot->location_fields = $slot->getLocationFields();
384 + $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
385 + do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
386 + $slot->unsetRelation('calendar');
387 + }
388 +
283 389 $data = [
284 390 'calendar' => $calendar
285 391 ];
286 392
@@ -287,8 +393,12 @@
287 393 if (in_array('settings_menu', $request->get('with', []))) {
288 394 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
289 395 }
290 396
397 + if (in_array('public_url', $request->get('with', []))) {
398 + $data['public_url'] = $calendar->getLandingPageUrl();
399 + }
400 +
291 401 return $data;
292 402 }
293 403
294 404 public function getSharingSettings(Request $request, $calendarId)
@@ -295,10 +405,11 @@
295 405 {
296 406 $calendar = Calendar::findOrFail($calendarId);
297 407
298 408 return [
299 - 'settings' => LandingPageHelper::getSettings($calendar),
300 - 'share_url' => $calendar->getLandingPageUrl(true)
409 + 'settings' => LandingPageHelper::getSettings($calendar),
410 + 'share_url' => $calendar->getLandingPageUrl(true),
411 + 'public_url' => $this->getSharePublicUrl($calendar, intval($request->get('event_id')))
301 412 ];
302 413 }
303 414
304 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -309,9 +420,10 @@
309 420
310 421 if ($calendarDataItems) {
311 422 $this->validate($calendarDataItems, [
312 423 'title' => 'required',
313 - 'calendar_avatar' => 'url'
424 + 'calendar_avatar' => 'nullable|url',
425 + 'featured_image' => 'nullable|url'
314 426 ]);
315 427
316 428 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
317 429 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
@@ -320,11 +432,11 @@
320 432 }
321 433
322 434 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
323 435 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
324 - $calendar->save();
325 436 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
326 437 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
438 + $calendar->save();
327 439
328 440 if ($calendar->user) {
329 441 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
330 442 }
@@ -333,12 +445,27 @@
333 445 $sharingSettings = $request->get('landing_page_settings', []);
334 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
335 447
336 448 return [
337 - 'message' => __('Landing Page settings has been updated', 'fluent-booking')
449 + 'message' => __('Landing Page settings has been updated', 'fluent-booking'),
450 + 'public_url' => $this->getSharePublicUrl($calendar, intval($request->get('event_id')))
338 451 ];
339 452 }
340 453
454 + private function getSharePublicUrl($calendar, $eventId)
455 + {
456 + if ($eventId) {
457 + $event = CalendarSlot::where('calendar_id', $calendar->id)
458 + ->where('id', $eventId)
459 + ->first();
460 + if ($event) {
461 + return $event->getPublicUrl();
462 + }
463 + }
464 +
465 + return $calendar->getLandingPageUrl();
466 + }
467 +
341 468 public function updateCalendar(Request $request, $calendarId)
342 469 {
343 470 $data = $request->all();
344 471
@@ -359,11 +486,11 @@
359 486 'message' => __('Calendar has been updated successfully', 'fluent-booking')
360 487 ];
361 488 }
362 489
363 - public function getEvent(Request $request, $calendarId, $slotId)
490 + public function getEvent(Request $request, $calendarId, $eventId)
364 491 {
365 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($slotId);
492 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($eventId);
366 493
367 494 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
368 495
369 496 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
@@ -377,8 +504,10 @@
377 504 $eventSettings['date_overrides'] = (object)SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), 'UTC', $calendarEvent->calendar->author_timezone, $calendarEvent);
378 505
379 506 $eventSettings['location_fields'] = $calendarEvent->getLocationFields();
380 507
508 + $eventSettings['hosts_schedules'] = $calendarEvent->getHostsSchedules();
509 +
381 510 $calendarEvent->settings = apply_filters('fluent_booking/get_calendar_event_settings', $eventSettings, $calendarEvent, $calendarEvent->calendar);
382 511
383 512 $data = [
384 513 'calendar_event' => $calendarEvent
@@ -418,9 +547,9 @@
418 547 'slot' => $schema
419 548 ];
420 549 }
421 550
422 - public function getAvailabilitySettings(Request $request, $calendarId, $slotId)
551 + public function getAvailabilitySettings(Request $request, $calendarId, $eventId)
423 552 {
424 553 $availableSchedules = AvailabilityService::availabilitySchedules();
425 554
426 555 $scheduleOptions = AvailabilityService::getScheduleOptions();
@@ -465,21 +594,41 @@
465 594 ], $slot);
466 595
467 596 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
468 597
598 + $teamMembers = array_values(array_unique(array_filter(
599 + array_map('intval', (array) Arr::get($slot, 'settings.team_members', []))
600 + )));
601 +
602 + cache_users($teamMembers);
603 +
604 + foreach ($teamMembers as $memberId) {
605 + if (!get_user_by('ID', $memberId)) {
606 + return $this->sendError([
607 + 'message' => __('Invalid Team Member', 'fluent-booking')
608 + ], 422);
609 + }
610 + }
611 +
612 + if ($teamMembers && !PermissionManager::canAssignHosts($teamMembers, $calendar->getMemberIds())) {
613 + return $this->sendError([
614 + 'message' => __('You are not allowed to create a calendar for another user', 'fluent-booking')
615 + ], 403);
616 + }
617 +
469 618 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
470 619
471 620 $slotData = [
472 - 'title' => $slot['title'],
621 + 'title' => sanitize_text_field($slot['title']),
473 622 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
474 623 'calendar_id' => $calendar->id,
475 624 'user_id' => $calendar->user_id,
476 625 'duration' => (int)$slot['duration'],
477 - 'description' => sanitize_textarea_field(Arr::get($slot, 'description')),
626 + 'description' => wp_kses_post(Arr::get($slot, 'description')),
478 627 'settings' => [
479 628 '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'),
629 + 'weekly_schedules' => SanitizeService::weeklySchedules($slot['settings']['weekly_schedules'], $calendar->author_timezone, 'UTC', true),
630 + 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($slot['settings'], 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
482 631 'range_type' => sanitize_text_field(Arr::get($slot['settings'], 'range_type')),
483 632 'range_days' => (int)(Arr::get($slot['settings'], 'range_days', 60)) ?: 60,
484 633 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($slot['settings'], 'range_date_between', ['', ''])),
485 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
@@ -485,13 +634,13 @@
485 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
486 635 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
487 636 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
488 637 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
489 - 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
638 + 'team_members' => $teamMembers
490 639 ],
491 640 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
492 641 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
493 - 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
642 + 'event_type' => SanitizeService::checkCollection(sanitize_text_field(Arr::get($slot, 'event_type')), CalendarSlot::getEventTypes(), 'single'),
494 643 'availability_type' => 'existing_schedule',
495 644 'availability_id' => $availability ? $availability->id : null,
496 645 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
497 646 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
@@ -567,9 +716,9 @@
567 716 $event->title = sanitize_text_field($data['title']);
568 717 $event->duration = (int)$data['duration'];
569 718 $event->status = SanitizeService::checkCollection($data['status'], ['active', 'draft']);
570 719 $event->color_schema = sanitize_text_field(Arr::get($data, 'color_schema', '#0099ff'));
571 - $event->description = sanitize_textarea_field(Arr::get($data, 'description'));
720 + $event->description = wp_kses_post(Arr::get($data, 'description'));
572 721 $event->max_book_per_slot = (int)Arr::get($data, 'max_book_per_slot');
573 722 $event->is_display_spots = (bool)Arr::get($data, 'is_display_spots');
574 723 $event->location_settings = SanitizeService::locationSettings(Arr::get($data, 'location_settings', []));
575 724
@@ -596,12 +745,12 @@
596 745 $data = $request->all();
597 746
598 747 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
599 748
600 - $event->settings = [
749 + $eventSettings = [
601 750 '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'),
751 + 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($data, 'weekly_schedules'), $event->calendar->author_timezone, 'UTC', true),
752 + 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($data, 'date_overrides', []), $event->calendar->author_timezone, 'UTC', null, true),
604 753 'range_type' => sanitize_text_field(Arr::get($data, 'range_type')),
605 754 'range_days' => (int)(Arr::get($data, 'range_days', 60)) ?: 60,
606 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
607 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
@@ -606,11 +755,70 @@
606 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
607 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
608 757 ];
609 758
610 - $event->availability_id = (int)Arr::get($data, 'availability_id');
611 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
759 + $hostsSchedules = [];
612 760
761 + if ($event->isTeamEvent()) {
762 + $hostsSchedules = array_map('intval', array_combine(
763 + array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
764 + array_map('intval', Arr::get($data, 'hosts_schedules', []))
765 + ));
766 + }
767 +
768 + $availabilityId = (int)Arr::get($data, 'availability_id');
769 + $availabilityType = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
770 +
771 + $submittedIds = array_values(array_filter(array_unique(array_merge(
772 + [$availabilityType === 'existing_schedule' ? $availabilityId : 0],
773 + array_values($hostsSchedules)
774 + ))));
775 +
776 + $usableIds = [];
777 + $scheduleOwners = [];
778 +
779 + if ($submittedIds) {
780 + $usableIds = array_map('intval', AvailabilityService::usableAvailabilityQuery()
781 + ->whereIn('id', $submittedIds)->pluck('id')->toArray());
782 +
783 + $scheduleOwners = array_map('intval', Availability::whereIn('id', $submittedIds)
784 + ->pluck('object_id', 'id')->toArray());
785 + }
786 +
787 + foreach ($hostsSchedules as $hostId => $scheduleId) {
788 + if (($scheduleOwners[$scheduleId] ?? 0) === (int)$hostId) {
789 + continue;
790 + }
791 +
792 + if (!in_array($scheduleId, $usableIds, true)) {
793 + return $this->sendError([
794 + 'message' => __('You are not allowed to use the selected schedule', 'fluent-booking')
795 + ], 403);
796 + }
797 + }
798 +
799 + if ($hostsSchedules) {
800 + $eventSettings['hosts_schedules'] = $hostsSchedules;
801 + }
802 +
803 + $eventHostIds = array_map('intval', array_merge(
804 + $event->getHostIds(),
805 + [$event->user_id, $event->calendar->user_id]
806 + ));
807 +
808 + if ($availabilityType === 'existing_schedule' && $availabilityId
809 + && !in_array($availabilityId, $usableIds, true)
810 + && !in_array($scheduleOwners[$availabilityId] ?? 0, $eventHostIds, true)) {
811 + return $this->sendError([
812 + 'message' => __('You are not allowed to use the selected schedule', 'fluent-booking')
813 + ], 403);
814 + }
815 +
816 + $event->settings = $eventSettings;
817 +
818 + $event->availability_id = $availabilityId;
819 + $event->availability_type = $availabilityType;
820 +
613 821 $event->save();
614 822
615 823 return [
616 824 'message' => __('Data has been updated', 'fluent-booking'),
@@ -650,11 +858,11 @@
650 858 'event' => $event
651 859 ];
652 860 }
653 861
654 - public function patchCalendarEvent(Request $request, $calendarId, $slotId)
862 + public function patchCalendarEvent(Request $request, $calendarId, $eventId)
655 863 {
656 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
864 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
657 865
658 866 $status = $request->get('status');
659 867
660 868 if ($status) {
@@ -671,14 +879,32 @@
671 879 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
672 880 {
673 881 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
674 882
883 + if (!PermissionManager::canWriteCalendar($newCalendarId)) {
884 + return $this->sendError([
885 + 'message' => __('You do not have permission to write to the destination calendar.', 'fluent-booking')
886 + ], 403);
887 + }
888 +
675 889 $calendar = Calendar::findOrFail($newCalendarId);
676 890
677 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
678 892
893 + $teamMembers = Arr::get($originalEvent->settings, 'team_members', []);
894 +
895 + // Cloning into another calendar carries the source hosts along with it.
896 + if ($teamMembers && $calendar->id != $calendarId
897 + && !PermissionManager::canAssignHosts($teamMembers, $calendar->getMemberIds())) {
898 + return $this->sendError([
899 + 'message' => __('You are not allowed to create a calendar for another user', 'fluent-booking')
900 + ], 403);
901 + }
902 +
679 903 $clonedEvent = $originalEvent->replicate();
680 904
905 + $clonedEvent->hash = null;
906 +
681 907 $clonedEvent->calendar_id = $calendar->id;
682 908
683 909 $clonedEvent->user_id = $calendar->user_id;
684 910
@@ -723,8 +949,14 @@
723 949 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
724 950
725 951 $fromEventId = intval($request->get('from_event_id'));
726 952
953 + if (!$fromEventId || !PermissionManager::canUpdateCalendarEvent($fromEventId)) {
954 + return $this->sendError([
955 + 'message' => __('You do not have permission to clone from the selected event.', 'fluent-booking')
956 + ], 403);
957 + }
958 +
727 959 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
728 960
729 961 $notification = $fromCalendarEvent->getNotifications(true);
730 962
@@ -737,11 +969,11 @@
737 969 'notifications' => $notification
738 970 ];
739 971 }
740 972
741 - public function getEventEmailNotifications(Request $request, $calendarId, $slotId)
973 + public function getEventEmailNotifications(Request $request, $calendarId, $eventId)
742 974 {
743 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
975 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
744 976
745 977 /*
746 978 * Confirmation Email to Attendee
747 979 * Confirmation Email to Organizer
@@ -762,11 +994,11 @@
762 994
763 995 return $data;
764 996 }
765 997
766 - public function saveEventEmailNotifications(Request $request, $calendarId, $slotId)
998 + public function saveEventEmailNotifications(Request $request, $calendarId, $eventId)
767 999 {
768 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1000 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
769 1001
770 1002 $notifications = $request->get('notifications', []);
771 1003
772 1004 $formattedNotifications = [];
@@ -786,11 +1018,11 @@
786 1018 'message' => __('Notifications has been saved', 'fluent-booking')
787 1019 ];
788 1020 }
789 1021
790 - public function getEventBookingFields(Request $request, $calendarId, $slotId)
1022 + public function getEventBookingFields(Request $request, $calendarId, $eventId)
791 1023 {
792 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1024 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
793 1025
794 1026 $data = [
795 1027 'fields' => $calendarEvent->getBookingFields()
796 1028 ];
@@ -810,58 +1042,39 @@
810 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
811 1043
812 1044 $bookingFields = $request->get('booking_fields');
813 1045
814 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
815 1047
816 - $formattedFields = [];
1048 + $calendarEvent->setBookingFields($formattedFields);
817 1049
818 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format'];
819 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
1050 + return [
1051 + 'message' => __('Fields has been updated', 'fluent-booking')
1052 + ];
1053 + }
820 1054
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 - }
1055 + public function getEventPaymentSettings($calendarId, $eventId)
1056 + {
1057 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
827 1058
828 - $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
1059 + $config = [
1060 + 'native_enabled' => Helper::isPaymentEnabled(),
1061 + 'stripe_configured' => Helper::isPaymentConfigured('stripe'),
1062 + 'paypal_configured' => Helper::isPaymentConfigured('paypal'),
1063 + 'offline_configured' => Helper::isPaymentConfigured('offline'),
1064 + 'native_config_link' => Helper::getAppBaseUrl('settings/payment-methods/stripe'),
1065 + 'woo_config_link' => Helper::getAppBaseUrl('settings/configure-integrations/global-modules'),
1066 + 'has_cart' => defined('FLUENTCART_VERSION'),
1067 + 'has_woo' => defined('WC_PLUGIN_FILE'),
1068 + 'woo_enabled' => defined('WC_PLUGIN_FILE') && Helper::isModuleEnabled('woo')
1069 + ];
829 1070
830 - $booleanValues = array_map(function ($valueItem) {
831 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
832 - }, Arr::only($value, $booleanFields));
1071 + $data = apply_filters('fluent_booking/payment/get_payment_settings', [
1072 + 'settings' => $calendarEvent->getPaymentSettings(),
1073 + 'config' => $config
1074 + ], $calendarEvent);
833 1075
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 - ];
1076 + return $data;
864 1077 }
865 1078
866 1079 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
867 1080 {