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 +182 -60 2.2.5 → 2.5.0 View file →
@@ -1,8 +1,9 @@
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 9 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
@@ -80,9 +81,9 @@
80 81 $slot->unsetRelation('calendar');
81 82 }
82 83
83 84 if(empty($calendar->author_profile['ID'])) {
84 - $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>';
85 86 }
86 87
87 88 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
88 89 }
@@ -117,8 +118,54 @@
117 118 'message' => __('The provided slug is available', 'fluent-booking')
118 119 ];
119 120 }
120 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 +
121 168 public function createCalendar(Request $request)
122 169 {
123 170 $data = $request->get('calendar');
124 171
@@ -154,9 +201,9 @@
154 201 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
155 202
156 203 do_action('fluent_booking/before_create_calendar', $data, $this);
157 204
158 - if (!empty($data['user_id']) && PermissionManager::userCan(['manage_all_data', 'invite_team_members'])) {
205 + if (!empty($data['user_id']) && PermissionManager::canManageOtherHosts()) {
159 206 $user = get_user_by('ID', $data['user_id']);
160 207 } else {
161 208 $user = get_user_by('ID', get_current_user_id());
162 209 }
@@ -172,9 +219,13 @@
172 219 $installableAddons = SanitizeService::sanitizeAddons($onboardinFeatures);
173 220 OnboardingService::installAddons($installableAddons);
174 221 }
175 222
176 - $type = sanitize_text_field(Arr::get($data, 'type', 'simple'));
223 + $type = SanitizeService::checkCollection(
224 + sanitize_text_field(Arr::get($data, 'type', 'simple')),
225 + ['simple', 'team', 'event'],
226 + 'simple'
227 + );
177 228
178 229 $isHostCalendar = $type == 'simple' ? true : false;
179 230
180 231 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
@@ -196,10 +247,30 @@
196 247
197 248 if (!$isHostCalendar) {
198 249 $title = sanitize_text_field(Arr::get($data, 'title', ''));
199 250 $data['slug'] = sanitize_title($title, '', 'display');
200 - $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
201 - 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)) {
202 273 $user = get_user_by('ID', reset($teamMembers));
203 274 if (!$user) {
204 275 return $this->sendError([
205 276 'message' => __('Invalid Team Member', 'fluent-booking')
@@ -334,10 +405,11 @@
334 405 {
335 406 $calendar = Calendar::findOrFail($calendarId);
336 407
337 408 return [
338 - 'settings' => LandingPageHelper::getSettings($calendar),
339 - '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')))
340 412 ];
341 413 }
342 414
343 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -373,12 +445,27 @@
373 445 $sharingSettings = $request->get('landing_page_settings', []);
374 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
375 447
376 448 return [
377 - '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')))
378 451 ];
379 452 }
380 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 +
381 468 public function updateCalendar(Request $request, $calendarId)
382 469 {
383 470 $data = $request->all();
384 471
@@ -507,12 +594,32 @@
507 594 ], $slot);
508 595
509 596 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
510 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 +
511 618 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
512 619
513 620 $slotData = [
514 - 'title' => $slot['title'],
621 + 'title' => sanitize_text_field($slot['title']),
515 622 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
516 623 'calendar_id' => $calendar->id,
517 624 'user_id' => $calendar->user_id,
518 625 'duration' => (int)$slot['duration'],
@@ -527,13 +634,13 @@
527 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
528 635 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
529 636 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
530 637 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
531 - 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
638 + 'team_members' => $teamMembers
532 639 ],
533 640 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
534 641 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
535 - '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'),
536 643 'availability_type' => 'existing_schedule',
537 644 'availability_id' => $availability ? $availability->id : null,
538 645 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
539 646 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
@@ -648,19 +755,69 @@
648 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
649 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
650 757 ];
651 758
759 + $hostsSchedules = [];
760 +
652 761 if ($event->isTeamEvent()) {
653 - $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
762 + $hostsSchedules = array_map('intval', array_combine(
654 763 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
655 764 array_map('intval', Arr::get($data, 'hosts_schedules', []))
656 765 ));
657 766 }
658 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 +
659 816 $event->settings = $eventSettings;
660 817
661 - $event->availability_id = (int)Arr::get($data, 'availability_id');
662 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
818 + $event->availability_id = $availabilityId;
819 + $event->availability_type = $availabilityType;
663 820
664 821 $event->save();
665 822
666 823 return [
@@ -732,8 +889,18 @@
732 889 $calendar = Calendar::findOrFail($newCalendarId);
733 890
734 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
735 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 +
736 903 $clonedEvent = $originalEvent->replicate();
737 904
738 905 $clonedEvent->hash = null;
739 906
@@ -875,54 +1042,9 @@
875 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
876 1043
877 1044 $bookingFields = $request->get('booking_fields');
878 1045
879 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
880 -
881 - $formattedFields = [];
882 -
883 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
884 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
885 -
886 - foreach ($bookingFields as $value) {
887 - if (empty($value['name'])) {
888 - $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
889 - } else {
890 - $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
891 - }
892 -
893 - $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
894 -
895 - $booleanValues = array_map(function ($valueItem) {
896 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
897 - }, Arr::only($value, $booleanFields));
898 -
899 - $formattedField = array_merge($textValues, $booleanValues);
900 -
901 - $fieldType = Arr::get($value, 'type');
902 -
903 - $formattedField['index'] = (int)Arr::get($value, 'index');
904 - if (in_array($fieldType, $optionRequiredFields)) {
905 - $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
906 - $formattedField['options'] = $sanitizedOptions;
907 - }
908 - if ($fieldType == 'file') {
909 - $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
910 - $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
911 - $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
912 - $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
913 - }
914 - if ($fieldType == 'hidden') {
915 - $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
916 - }
917 - if ($fieldType == 'terms-and-conditions') {
918 - $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
919 - }
920 -
921 - $formattedField = apply_filters('fluent_booking/save_event_booking_field_' . $fieldType, $formattedField, $value, $calendarEvent);
922 -
923 - $formattedFields[] = $formattedField;
924 - }
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
925 1047
926 1048 $calendarEvent->setBookingFields($formattedFields);
927 1049
928 1050 return [