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 +314 -79 1.5.20 → 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,10 +48,13 @@
48 48 }
49 49
50 50 $calendarsQuery = $calendarsQuery->latest();
51 51
52 - if (!PermissionManager::hasAllCalendarAccess(true)) {
53 - $calendarsQuery->where('user_id', get_current_user_id());
52 + $hasPermission = PermissionManager::hasAllCalendarAccess(true);
53 +
54 + if (!$hasPermission) {
55 + $attachedCalendarIds = CalendarService::getAttachedCalendarIds($calendarsQuery);
56 + $calendarsQuery->whereIn('id', $attachedCalendarIds);
54 57 }
55 58
56 59 $calendars = $calendarsQuery->paginate();
57 60
@@ -58,21 +61,29 @@
58 61 foreach ($calendars as $calendar) {
59 62 $calendar->author_profile = $calendar->getAuthorProfile();
60 63 $calendar->public_url = $calendar->getLandingPageUrl();
61 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 +
62 72 foreach ($calendar->slots as $slot) {
73 + $slot->setRelation('calendar', $calendar);
63 74 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
64 75 $slot->public_url = $slot->getPublicUrl();
65 76 $slot->duration = $slot->getDefaultDuration();
66 - $slot->price_total = $slot->getPricingTotal();
77 + $slot->price_total = $slot->getEventPrice();
67 78 $slot->location_fields = $slot->getLocationFields();
68 - $slot->short_description = Helper::excerpt($slot->getDescription());
69 79 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
70 80 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
81 + $slot->unsetRelation('calendar');
71 82 }
72 83
73 84 if(empty($calendar->author_profile['ID'])) {
74 - $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>';
75 86 }
76 87
77 88 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
78 89 }
@@ -102,12 +113,59 @@
102 113 ], 422);
103 114 }
104 115
105 116 return [
106 - 'status' => true
117 + 'status' => true,
118 + 'message' => __('The provided slug is available', 'fluent-booking')
107 119 ];
108 120 }
109 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 +
110 168 public function createCalendar(Request $request)
111 169 {
112 170 $data = $request->get('calendar');
113 171
@@ -143,9 +201,9 @@
143 201 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
144 202
145 203 do_action('fluent_booking/before_create_calendar', $data, $this);
146 204
147 - if (!empty($data['user_id']) && PermissionManager::userCan('invite_team_members')) {
205 + if (!empty($data['user_id']) && PermissionManager::canManageOtherHosts()) {
148 206 $user = get_user_by('ID', $data['user_id']);
149 207 } else {
150 208 $user = get_user_by('ID', get_current_user_id());
151 209 }
@@ -155,15 +213,25 @@
155 213 'message' => __('User not found', 'fluent-booking')
156 214 ], 422);
157 215 }
158 216
159 - $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 + }
160 222
223 + $type = SanitizeService::checkCollection(
224 + sanitize_text_field(Arr::get($data, 'type', 'simple')),
225 + ['simple', 'team', 'event'],
226 + 'simple'
227 + );
228 +
161 229 $isHostCalendar = $type == 'simple' ? true : false;
162 230
163 231 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
164 232 return $this->sendError([
165 - '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')
166 234 ], 422);
167 235 }
168 236
169 237 if ($isHostCalendar) {
@@ -179,10 +247,30 @@
179 247
180 248 if (!$isHostCalendar) {
181 249 $title = sanitize_text_field(Arr::get($data, 'title', ''));
182 250 $data['slug'] = sanitize_title($title, '', 'display');
183 - $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
184 - 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)) {
185 273 $user = get_user_by('ID', reset($teamMembers));
186 274 if (!$user) {
187 275 return $this->sendError([
188 276 'message' => __('Invalid Team Member', 'fluent-booking')
@@ -237,13 +325,13 @@
237 325 'slug' => Helper::generateSlotSlug((int)$slot['duration'] . 'min', $calendar),
238 326 'calendar_id' => $calendar->id,
239 327 'user_id' => $calendar->user_id,
240 328 'duration' => (int)$slot['duration'],
241 - 'description' => sanitize_textarea_field(Arr::get($slot, 'description')),
329 + 'description' => wp_kses_post(Arr::get($slot, 'description')),
242 330 'settings' => [
243 331 'team_members' => !$isHostCalendar ? $teamMembers : [],
244 332 'schedule_type' => sanitize_text_field($slot['schedule_type']),
245 - 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC')
333 + 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC', true)
246 334 ],
247 335 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft']),
248 336 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
249 337 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
@@ -277,9 +365,28 @@
277 365 $query->where('status', '!=', 'expired');
278 366 }])->findOrFail($calendarId);
279 367
280 368 $calendar->author_profile = $calendar->getAuthorProfile();
369 + $calendar->event_order = $calendar->getMeta('event_order');
281 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 +
282 389 $data = [
283 390 'calendar' => $calendar
284 391 ];
285 392
@@ -286,8 +393,12 @@
286 393 if (in_array('settings_menu', $request->get('with', []))) {
287 394 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
288 395 }
289 396
397 + if (in_array('public_url', $request->get('with', []))) {
398 + $data['public_url'] = $calendar->getLandingPageUrl();
399 + }
400 +
290 401 return $data;
291 402 }
292 403
293 404 public function getSharingSettings(Request $request, $calendarId)
@@ -294,10 +405,11 @@
294 405 {
295 406 $calendar = Calendar::findOrFail($calendarId);
296 407
297 408 return [
298 - 'settings' => LandingPageHelper::getSettings($calendar),
299 - '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')))
300 412 ];
301 413 }
302 414
303 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -308,9 +420,10 @@
308 420
309 421 if ($calendarDataItems) {
310 422 $this->validate($calendarDataItems, [
311 423 'title' => 'required',
312 - 'calendar_avatar' => 'url'
424 + 'calendar_avatar' => 'nullable|url',
425 + 'featured_image' => 'nullable|url'
313 426 ]);
314 427
315 428 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
316 429 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
@@ -319,11 +432,11 @@
319 432 }
320 433
321 434 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
322 435 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
323 - $calendar->save();
324 436 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
325 437 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
438 + $calendar->save();
326 439
327 440 if ($calendar->user) {
328 441 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
329 442 }
@@ -332,12 +445,27 @@
332 445 $sharingSettings = $request->get('landing_page_settings', []);
333 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
334 447
335 448 return [
336 - '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')))
337 451 ];
338 452 }
339 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 +
340 468 public function updateCalendar(Request $request, $calendarId)
341 469 {
342 470 $data = $request->all();
343 471
@@ -358,11 +486,11 @@
358 486 'message' => __('Calendar has been updated successfully', 'fluent-booking')
359 487 ];
360 488 }
361 489
362 - public function getEvent(Request $request, $calendarId, $slotId)
490 + public function getEvent(Request $request, $calendarId, $eventId)
363 491 {
364 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($slotId);
492 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($eventId);
365 493
366 494 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
367 495
368 496 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
@@ -376,8 +504,10 @@
376 504 $eventSettings['date_overrides'] = (object)SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), 'UTC', $calendarEvent->calendar->author_timezone, $calendarEvent);
377 505
378 506 $eventSettings['location_fields'] = $calendarEvent->getLocationFields();
379 507
508 + $eventSettings['hosts_schedules'] = $calendarEvent->getHostsSchedules();
509 +
380 510 $calendarEvent->settings = apply_filters('fluent_booking/get_calendar_event_settings', $eventSettings, $calendarEvent, $calendarEvent->calendar);
381 511
382 512 $data = [
383 513 'calendar_event' => $calendarEvent
@@ -417,9 +547,9 @@
417 547 'slot' => $schema
418 548 ];
419 549 }
420 550
421 - public function getAvailabilitySettings(Request $request, $calendarId, $slotId)
551 + public function getAvailabilitySettings(Request $request, $calendarId, $eventId)
422 552 {
423 553 $availableSchedules = AvailabilityService::availabilitySchedules();
424 554
425 555 $scheduleOptions = AvailabilityService::getScheduleOptions();
@@ -464,21 +594,41 @@
464 594 ], $slot);
465 595
466 596 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
467 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 +
468 618 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
469 619
470 620 $slotData = [
471 - 'title' => $slot['title'],
621 + 'title' => sanitize_text_field($slot['title']),
472 622 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
473 623 'calendar_id' => $calendar->id,
474 624 'user_id' => $calendar->user_id,
475 625 'duration' => (int)$slot['duration'],
476 - 'description' => sanitize_textarea_field(Arr::get($slot, 'description')),
626 + 'description' => wp_kses_post(Arr::get($slot, 'description')),
477 627 'settings' => [
478 628 'schedule_type' => sanitize_text_field($slot['settings']['schedule_type']),
479 - 'weekly_schedules' => SanitizeService::weeklySchedules($slot['settings']['weekly_schedules'], $calendar->author_timezone, 'UTC'),
480 - 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($slot['settings'], 'date_overrides', []), $calendar->author_timezone, 'UTC'),
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),
481 631 'range_type' => sanitize_text_field(Arr::get($slot['settings'], 'range_type')),
482 632 'range_days' => (int)(Arr::get($slot['settings'], 'range_days', 60)) ?: 60,
483 633 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($slot['settings'], 'range_date_between', ['', ''])),
484 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
@@ -484,13 +634,13 @@
484 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
485 635 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
486 636 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
487 637 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
488 - 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
638 + 'team_members' => $teamMembers
489 639 ],
490 640 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
491 641 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
492 - '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'),
493 643 'availability_type' => 'existing_schedule',
494 644 'availability_id' => $availability ? $availability->id : null,
495 645 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
496 646 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
@@ -566,9 +716,9 @@
566 716 $event->title = sanitize_text_field($data['title']);
567 717 $event->duration = (int)$data['duration'];
568 718 $event->status = SanitizeService::checkCollection($data['status'], ['active', 'draft']);
569 719 $event->color_schema = sanitize_text_field(Arr::get($data, 'color_schema', '#0099ff'));
570 - $event->description = sanitize_textarea_field(Arr::get($data, 'description'));
720 + $event->description = wp_kses_post(Arr::get($data, 'description'));
571 721 $event->max_book_per_slot = (int)Arr::get($data, 'max_book_per_slot');
572 722 $event->is_display_spots = (bool)Arr::get($data, 'is_display_spots');
573 723 $event->location_settings = SanitizeService::locationSettings(Arr::get($data, 'location_settings', []));
574 724
@@ -595,12 +745,12 @@
595 745 $data = $request->all();
596 746
597 747 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
598 748
599 - $event->settings = [
749 + $eventSettings = [
600 750 'schedule_type' => sanitize_text_field(Arr::get($data, 'schedule_type')),
601 - 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($data, 'weekly_schedules'), $event->calendar->author_timezone, 'UTC'),
602 - 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($data, 'date_overrides', []), $event->calendar->author_timezone, 'UTC'),
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),
603 753 'range_type' => sanitize_text_field(Arr::get($data, 'range_type')),
604 754 'range_days' => (int)(Arr::get($data, 'range_days', 60)) ?: 60,
605 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
606 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
@@ -605,11 +755,70 @@
605 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
606 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
607 757 ];
608 758
609 - $event->availability_id = (int)Arr::get($data, 'availability_id');
610 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
759 + $hostsSchedules = [];
611 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 +
612 821 $event->save();
613 822
614 823 return [
615 824 'message' => __('Data has been updated', 'fluent-booking'),
@@ -649,11 +858,11 @@
649 858 'event' => $event
650 859 ];
651 860 }
652 861
653 - public function patchCalendarEvent(Request $request, $calendarId, $slotId)
862 + public function patchCalendarEvent(Request $request, $calendarId, $eventId)
654 863 {
655 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
864 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
656 865
657 866 $status = $request->get('status');
658 867
659 868 if ($status) {
@@ -670,14 +879,32 @@
670 879 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
671 880 {
672 881 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
673 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 +
674 889 $calendar = Calendar::findOrFail($newCalendarId);
675 890
676 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
677 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 +
678 903 $clonedEvent = $originalEvent->replicate();
679 904
905 + $clonedEvent->hash = null;
906 +
680 907 $clonedEvent->calendar_id = $calendar->id;
681 908
682 909 $clonedEvent->user_id = $calendar->user_id;
683 910
@@ -722,8 +949,14 @@
722 949 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
723 950
724 951 $fromEventId = intval($request->get('from_event_id'));
725 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 +
726 959 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
727 960
728 961 $notification = $fromCalendarEvent->getNotifications(true);
729 962
@@ -736,11 +969,11 @@
736 969 'notifications' => $notification
737 970 ];
738 971 }
739 972
740 - public function getEventEmailNotifications(Request $request, $calendarId, $slotId)
973 + public function getEventEmailNotifications(Request $request, $calendarId, $eventId)
741 974 {
742 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
975 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
743 976
744 977 /*
745 978 * Confirmation Email to Attendee
746 979 * Confirmation Email to Organizer
@@ -761,11 +994,11 @@
761 994
762 995 return $data;
763 996 }
764 997
765 - public function saveEventEmailNotifications(Request $request, $calendarId, $slotId)
998 + public function saveEventEmailNotifications(Request $request, $calendarId, $eventId)
766 999 {
767 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1000 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
768 1001
769 1002 $notifications = $request->get('notifications', []);
770 1003
771 1004 $formattedNotifications = [];
@@ -785,15 +1018,24 @@
785 1018 'message' => __('Notifications has been saved', 'fluent-booking')
786 1019 ];
787 1020 }
788 1021
789 - public function getEventBookingFields(Request $request, $calendarId, $slotId)
1022 + public function getEventBookingFields(Request $request, $calendarId, $eventId)
790 1023 {
791 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1024 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
792 1025
793 - return [
794 - 'fields' => $slot->getBookingFields()
1026 + $data = [
1027 + 'fields' => $calendarEvent->getBookingFields()
795 1028 ];
1029 +
1030 + if (in_array('smart_codes', $request->get('with', []))) {
1031 + $data['smart_codes'] = [
1032 + 'texts' => Helper::getEditorShortCodes($calendarEvent),
1033 + 'html' => Helper::getEditorShortCodes($calendarEvent, true)
1034 + ];
1035 + }
1036 +
1037 + return $data;
796 1038 }
797 1039
798 1040 public function saveEventBookingFields(Request $request, $calendarId, $eventId)
799 1041 {
@@ -800,46 +1042,39 @@
800 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
801 1043
802 1044 $bookingFields = $request->get('booking_fields');
803 1045
804 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
805 1047
806 - $formattedFields = [];
1048 + $calendarEvent->setBookingFields($formattedFields);
807 1049
808 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format'];
809 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
1050 + return [
1051 + 'message' => __('Fields has been updated', 'fluent-booking')
1052 + ];
1053 + }
810 1054
811 - foreach ($bookingFields as $value) {
812 - if (empty($value['name'])) {
813 - $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
814 - }
1055 + public function getEventPaymentSettings($calendarId, $eventId)
1056 + {
1057 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
815 1058
816 - $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 + ];
817 1070
818 - $booleanValues = array_map(function ($valueItem) {
819 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
820 - }, Arr::only($value, $booleanFields));
1071 + $data = apply_filters('fluent_booking/payment/get_payment_settings', [
1072 + 'settings' => $calendarEvent->getPaymentSettings(),
1073 + 'config' => $config
1074 + ], $calendarEvent);
821 1075
822 - $formattedField = array_merge($textValues, $booleanValues);
823 -
824 - $formattedField['index'] = (int)Arr::get($value, 'index');
825 - if ($value['type'] == 'payment' && $calendarEvent->type === 'paid') {
826 - $formattedField['payment_items'] = Arr::get($value, 'payment_items');
827 - $formattedField['currency_sign'] = CurrenciesHelper::getGlobalCurrencySign();
828 - }
829 - if (in_array(Arr::get($value, 'type'), $optionRequiredFields)) {
830 - $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
831 - $formattedField['options'] = $sanitizedOptions;
832 - }
833 -
834 - $formattedFields[] = $formattedField;
835 - }
836 -
837 - $calendarEvent->setBookingFields($formattedFields);
838 -
839 - return [
840 - 'message' => __('Fields has been updated', 'fluent-booking')
841 - ];
1076 + return $data;
842 1077 }
843 1078
844 1079 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
845 1080 {