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 +285 -84 1.6.0 → 2.5.0 View file →
@@ -1,17 +1,19 @@
1 1 <?php
2 2
3 3 namespace FluentBooking\App\Http\Controllers;
4 4
5 +use FluentBooking\App\Models\Availability;
5 6 use FluentBooking\App\Models\Calendar;
6 7 use FluentBooking\App\Models\CalendarSlot;
7 8 use FluentBooking\App\Services\Helper;
8 -use FluentBooking\App\Services\CurrenciesHelper;
9 9 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
10 10 use FluentBooking\App\Services\PermissionManager;
11 11 use FluentBooking\App\Services\AvailabilityService;
12 12 use FluentBooking\App\Services\SanitizeService;
13 13 use FluentBooking\App\Services\CalendarService;
14 +use FluentBooking\App\Services\OnboardingService;
15 +use FluentBooking\App\Services\CalendarEventService;
14 16 use FluentBooking\App\Services\BookingFieldService;
15 17 use FluentBooking\App\Hooks\Handlers\AdminMenuHandler;
16 18 use FluentBooking\Framework\Http\Request\Request;
17 19 use FluentBooking\Framework\Support\Arr;
@@ -31,9 +33,9 @@
31 33 $query->where('title', 'LIKE', '%' . $search . '%');
32 34 }
33 35 };
34 36
35 - $calendarsQuery = Calendar::with(['slots' => function($query) use ($applySearchFilter) {
37 + $calendarsQuery = Calendar::with(['metas', 'slots' => function($query) use ($applySearchFilter) {
36 38 $query->where($applySearchFilter);
37 39 }])
38 40 ->where('status', '!=', 'expired');
39 41
@@ -46,9 +48,11 @@
46 48 }
47 49
48 50 $calendarsQuery = $calendarsQuery->latest();
49 51
50 - if (!PermissionManager::hasAllCalendarAccess(true)) {
52 + $hasPermission = PermissionManager::hasAllCalendarAccess(true);
53 +
54 + if (!$hasPermission) {
51 55 $attachedCalendarIds = CalendarService::getAttachedCalendarIds($calendarsQuery);
52 56 $calendarsQuery->whereIn('id', $attachedCalendarIds);
53 57 }
54 58
@@ -57,20 +61,29 @@
57 61 foreach ($calendars as $calendar) {
58 62 $calendar->author_profile = $calendar->getAuthorProfile();
59 63 $calendar->public_url = $calendar->getLandingPageUrl();
60 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 +
61 72 foreach ($calendar->slots as $slot) {
73 + $slot->setRelation('calendar', $calendar);
62 74 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
63 75 $slot->public_url = $slot->getPublicUrl();
64 76 $slot->duration = $slot->getDefaultDuration();
65 - $slot->price_total = $slot->getPricingTotal();
77 + $slot->price_total = $slot->getEventPrice();
66 78 $slot->location_fields = $slot->getLocationFields();
67 79 $slot->author_profiles = $slot->isMultiHostEvent() ? $slot->getAuthorProfiles() : [];
68 80 do_action_ref_array('fluent_booking/calendar_slot', [&$slot]);
81 + $slot->unsetRelation('calendar');
69 82 }
70 83
71 84 if(empty($calendar->author_profile['ID'])) {
72 - $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>';
73 86 }
74 87
75 88 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
76 89 }
@@ -100,12 +113,59 @@
100 113 ], 422);
101 114 }
102 115
103 116 return [
104 - 'status' => true
117 + 'status' => true,
118 + 'message' => __('The provided slug is available', 'fluent-booking')
105 119 ];
106 120 }
107 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 +
108 168 public function createCalendar(Request $request)
109 169 {
110 170 $data = $request->get('calendar');
111 171
@@ -141,9 +201,9 @@
141 201 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
142 202
143 203 do_action('fluent_booking/before_create_calendar', $data, $this);
144 204
145 - if (!empty($data['user_id']) && PermissionManager::userCan('invite_team_members')) {
205 + if (!empty($data['user_id']) && PermissionManager::canManageOtherHosts()) {
146 206 $user = get_user_by('ID', $data['user_id']);
147 207 } else {
148 208 $user = get_user_by('ID', get_current_user_id());
149 209 }
@@ -153,15 +213,25 @@
153 213 'message' => __('User not found', 'fluent-booking')
154 214 ], 422);
155 215 }
156 216
157 - $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 + }
158 222
223 + $type = SanitizeService::checkCollection(
224 + sanitize_text_field(Arr::get($data, 'type', 'simple')),
225 + ['simple', 'team', 'event'],
226 + 'simple'
227 + );
228 +
159 229 $isHostCalendar = $type == 'simple' ? true : false;
160 230
161 231 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
162 232 return $this->sendError([
163 - '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')
164 234 ], 422);
165 235 }
166 236
167 237 if ($isHostCalendar) {
@@ -177,10 +247,30 @@
177 247
178 248 if (!$isHostCalendar) {
179 249 $title = sanitize_text_field(Arr::get($data, 'title', ''));
180 250 $data['slug'] = sanitize_title($title, '', 'display');
181 - $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
182 - 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)) {
183 273 $user = get_user_by('ID', reset($teamMembers));
184 274 if (!$user) {
185 275 return $this->sendError([
186 276 'message' => __('Invalid Team Member', 'fluent-booking')
@@ -239,9 +329,9 @@
239 329 'description' => wp_kses_post(Arr::get($slot, 'description')),
240 330 'settings' => [
241 331 'team_members' => !$isHostCalendar ? $teamMembers : [],
242 332 'schedule_type' => sanitize_text_field($slot['schedule_type']),
243 - 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC')
333 + 'weekly_schedules' => SanitizeService::weeklySchedules($slot['weekly_schedules'], $calendar->author_timezone, 'UTC', true)
244 334 ],
245 335 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft']),
246 336 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
247 337 'event_type' => sanitize_text_field(Arr::get($slot, 'event_type')),
@@ -275,9 +365,28 @@
275 365 $query->where('status', '!=', 'expired');
276 366 }])->findOrFail($calendarId);
277 367
278 368 $calendar->author_profile = $calendar->getAuthorProfile();
369 + $calendar->event_order = $calendar->getMeta('event_order');
279 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 +
280 389 $data = [
281 390 'calendar' => $calendar
282 391 ];
283 392
@@ -284,8 +393,12 @@
284 393 if (in_array('settings_menu', $request->get('with', []))) {
285 394 $data['settings_menu'] = AdminMenuHandler::getCalendarSettingsMenuItems($calendar);
286 395 }
287 396
397 + if (in_array('public_url', $request->get('with', []))) {
398 + $data['public_url'] = $calendar->getLandingPageUrl();
399 + }
400 +
288 401 return $data;
289 402 }
290 403
291 404 public function getSharingSettings(Request $request, $calendarId)
@@ -292,10 +405,11 @@
292 405 {
293 406 $calendar = Calendar::findOrFail($calendarId);
294 407
295 408 return [
296 - 'settings' => LandingPageHelper::getSettings($calendar),
297 - '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')))
298 412 ];
299 413 }
300 414
301 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -306,9 +420,10 @@
306 420
307 421 if ($calendarDataItems) {
308 422 $this->validate($calendarDataItems, [
309 423 'title' => 'required',
310 - 'calendar_avatar' => 'url'
424 + 'calendar_avatar' => 'nullable|url',
425 + 'featured_image' => 'nullable|url'
311 426 ]);
312 427
313 428 $updatedTimezone = sanitize_text_field(Arr::get($calendarDataItems, 'timezone'));
314 429 if ($updatedTimezone && $updatedTimezone != $calendar->author_timezone) {
@@ -317,11 +432,11 @@
317 432 }
318 433
319 434 $calendar->title = sanitize_text_field(Arr::get($calendarDataItems, 'title'));
320 435 $calendar->description = wp_kses_post(Arr::get($calendarDataItems, 'description'));
321 - $calendar->save();
322 436 $calendar->updateMeta('profile_photo_url', sanitize_url(Arr::get($calendarDataItems, 'calendar_avatar')));
323 437 $calendar->updateMeta('featured_image_url', sanitize_url(Arr::get($calendarDataItems, 'featured_image')));
438 + $calendar->save();
324 439
325 440 if ($calendar->user) {
326 441 $calendar->user->updateMeta('host_phone', sanitize_text_field(Arr::get($calendarDataItems, 'phone')));
327 442 }
@@ -330,12 +445,27 @@
330 445 $sharingSettings = $request->get('landing_page_settings', []);
331 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
332 447
333 448 return [
334 - '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')))
335 451 ];
336 452 }
337 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 +
338 468 public function updateCalendar(Request $request, $calendarId)
339 469 {
340 470 $data = $request->all();
341 471
@@ -356,11 +486,11 @@
356 486 'message' => __('Calendar has been updated successfully', 'fluent-booking')
357 487 ];
358 488 }
359 489
360 - public function getEvent(Request $request, $calendarId, $slotId)
490 + public function getEvent(Request $request, $calendarId, $eventId)
361 491 {
362 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($slotId);
492 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->with(['calendar.user'])->findOrFail($eventId);
363 493
364 494 $calendarEvent->author_profile = $calendarEvent->getAuthorProfile();
365 495
366 496 $calendarEvent->calendar->author_profile = $calendarEvent->calendar->getAuthorProfile();
@@ -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,12 +594,32 @@
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'],
@@ -475,10 +625,10 @@
475 625 'duration' => (int)$slot['duration'],
476 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', [])),
@@ -597,10 +747,10 @@
597 747 $event = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
598 748
599 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,19 +755,69 @@
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
759 + $hostsSchedules = [];
760 +
609 761 if ($event->isTeamEvent()) {
610 - $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
762 + $hostsSchedules = array_map('intval', array_combine(
611 763 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
612 764 array_map('intval', Arr::get($data, 'hosts_schedules', []))
613 765 ));
614 766 }
615 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 +
616 816 $event->settings = $eventSettings;
617 817
618 - $event->availability_id = (int)Arr::get($data, 'availability_id');
619 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
818 + $event->availability_id = $availabilityId;
819 + $event->availability_type = $availabilityType;
620 820
621 821 $event->save();
622 822
623 823 return [
@@ -658,11 +858,11 @@
658 858 'event' => $event
659 859 ];
660 860 }
661 861
662 - public function patchCalendarEvent(Request $request, $calendarId, $slotId)
862 + public function patchCalendarEvent(Request $request, $calendarId, $eventId)
663 863 {
664 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
864 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
665 865
666 866 $status = $request->get('status');
667 867
668 868 if ($status) {
@@ -679,12 +879,28 @@
679 879 public function cloneCalendarEvent(Request $request, $calendarId, $eventId)
680 880 {
681 881 $newCalendarId = intval($request->get('new_calendar_id')) ?: $calendarId;
682 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 +
683 889 $calendar = Calendar::findOrFail($newCalendarId);
684 890
685 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
686 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 +
687 903 $clonedEvent = $originalEvent->replicate();
688 904
689 905 $clonedEvent->hash = null;
690 906
@@ -733,8 +949,14 @@
733 949 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
734 950
735 951 $fromEventId = intval($request->get('from_event_id'));
736 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 +
737 959 $fromCalendarEvent = CalendarSlot::findOrFail($fromEventId);
738 960
739 961 $notification = $fromCalendarEvent->getNotifications(true);
740 962
@@ -747,11 +969,11 @@
747 969 'notifications' => $notification
748 970 ];
749 971 }
750 972
751 - public function getEventEmailNotifications(Request $request, $calendarId, $slotId)
973 + public function getEventEmailNotifications(Request $request, $calendarId, $eventId)
752 974 {
753 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
975 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
754 976
755 977 /*
756 978 * Confirmation Email to Attendee
757 979 * Confirmation Email to Organizer
@@ -772,11 +994,11 @@
772 994
773 995 return $data;
774 996 }
775 997
776 - public function saveEventEmailNotifications(Request $request, $calendarId, $slotId)
998 + public function saveEventEmailNotifications(Request $request, $calendarId, $eventId)
777 999 {
778 - $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1000 + $slot = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
779 1001
780 1002 $notifications = $request->get('notifications', []);
781 1003
782 1004 $formattedNotifications = [];
@@ -796,11 +1018,11 @@
796 1018 'message' => __('Notifications has been saved', 'fluent-booking')
797 1019 ];
798 1020 }
799 1021
800 - public function getEventBookingFields(Request $request, $calendarId, $slotId)
1022 + public function getEventBookingFields(Request $request, $calendarId, $eventId)
801 1023 {
802 - $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($slotId);
1024 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
803 1025
804 1026 $data = [
805 1027 'fields' => $calendarEvent->getBookingFields()
806 1028 ];
@@ -820,60 +1042,39 @@
820 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
821 1043
822 1044 $bookingFields = $request->get('booking_fields');
823 1045
824 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
825 1047
826 - $formattedFields = [];
1048 + $calendarEvent->setBookingFields($formattedFields);
827 1049
828 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
829 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
1050 + return [
1051 + 'message' => __('Fields has been updated', 'fluent-booking')
1052 + ];
1053 + }
830 1054
831 - foreach ($bookingFields as $value) {
832 - if (empty($value['name'])) {
833 - $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
834 - } else {
835 - $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
836 - }
1055 + public function getEventPaymentSettings($calendarId, $eventId)
1056 + {
1057 + $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
837 1058
838 - $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 + ];
839 1070
840 - $booleanValues = array_map(function ($valueItem) {
841 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
842 - }, Arr::only($value, $booleanFields));
1071 + $data = apply_filters('fluent_booking/payment/get_payment_settings', [
1072 + 'settings' => $calendarEvent->getPaymentSettings(),
1073 + 'config' => $config
1074 + ], $calendarEvent);
843 1075
844 - $formattedField = array_merge($textValues, $booleanValues);
845 -
846 - $formattedField['index'] = (int)Arr::get($value, 'index');
847 - if (in_array(Arr::get($value, 'type'), $optionRequiredFields)) {
848 - $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
849 - $formattedField['options'] = $sanitizedOptions;
850 - }
851 - if ($value['type'] == 'payment' && $calendarEvent->type === 'paid') {
852 - $formattedField['payment_items'] = Arr::get($value, 'payment_items');
853 - $formattedField['currency_sign'] = CurrenciesHelper::getGlobalCurrencySign();
854 - }
855 - if ($value['type'] == 'file') {
856 - $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
857 - $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
858 - $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
859 - $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
860 - }
861 - if ($value['type'] == 'hidden') {
862 - $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
863 - }
864 - if ($value['type'] == 'terms-and-conditions') {
865 - $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
866 - }
867 -
868 - $formattedFields[] = $formattedField;
869 - }
870 -
871 - $calendarEvent->setBookingFields($formattedFields);
872 -
873 - return [
874 - 'message' => __('Fields has been updated', 'fluent-booking')
875 - ];
1076 + return $data;
876 1077 }
877 1078
878 1079 public function deleteCalendarEvent(Request $request, $calendarId, $calendarEventId)
879 1080 {