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 +210 -65 2.1.1 → 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;
@@ -32,9 +33,9 @@
32 33 $query->where('title', 'LIKE', '%' . $search . '%');
33 34 }
34 35 };
35 36
36 - $calendarsQuery = Calendar::with(['slots' => function($query) use ($applySearchFilter) {
37 + $calendarsQuery = Calendar::with(['metas', 'slots' => function($query) use ($applySearchFilter) {
37 38 $query->where($applySearchFilter);
38 39 }])
39 40 ->where('status', '!=', 'expired');
40 41
@@ -60,12 +61,16 @@
60 61 foreach ($calendars as $calendar) {
61 62 $calendar->author_profile = $calendar->getAuthorProfile();
62 63 $calendar->public_url = $calendar->getLandingPageUrl();
63 64 $calendar->event_order = $calendar->getMeta('event_order');
64 - foreach ($calendar->slots as $key => $slot) {
65 - if (!$hasPermission && !CalendarEventService::isSharedCalendarEvent($slot)) {
66 - unset($calendar->slots[$key]);
67 - }
65 +
66 + if (!$hasPermission) {
67 + $calendar->setRelation('slots', $calendar->slots->filter(function ($slot) {
68 + return CalendarEventService::isSharedCalendarEvent($slot);
69 + })->values());
70 + }
71 +
72 + foreach ($calendar->slots as $slot) {
68 73 $slot->setRelation('calendar', $calendar);
69 74 $slot->shortcode = '[fluent_booking id="' . $slot->id . '"]';
70 75 $slot->public_url = $slot->getPublicUrl();
71 76 $slot->duration = $slot->getDefaultDuration();
@@ -76,9 +81,9 @@
76 81 $slot->unsetRelation('calendar');
77 82 }
78 83
79 84 if(empty($calendar->author_profile['ID'])) {
80 - $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>';
81 86 }
82 87
83 88 do_action_ref_array('fluent_booking/calendar', [&$calendar, 'lists']);
84 89 }
@@ -113,8 +118,54 @@
113 118 'message' => __('The provided slug is available', 'fluent-booking')
114 119 ];
115 120 }
116 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 +
117 168 public function createCalendar(Request $request)
118 169 {
119 170 $data = $request->get('calendar');
120 171
@@ -150,9 +201,9 @@
150 201 $this->validate($data, $validationConfig['rules'], $validationConfig['messages']);
151 202
152 203 do_action('fluent_booking/before_create_calendar', $data, $this);
153 204
154 - if (!empty($data['user_id']) && PermissionManager::userCan(['manage_all_data', 'invite_team_members'])) {
205 + if (!empty($data['user_id']) && PermissionManager::canManageOtherHosts()) {
155 206 $user = get_user_by('ID', $data['user_id']);
156 207 } else {
157 208 $user = get_user_by('ID', get_current_user_id());
158 209 }
@@ -168,9 +219,13 @@
168 219 $installableAddons = SanitizeService::sanitizeAddons($onboardinFeatures);
169 220 OnboardingService::installAddons($installableAddons);
170 221 }
171 222
172 - $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 + );
173 228
174 229 $isHostCalendar = $type == 'simple' ? true : false;
175 230
176 231 if ($isHostCalendar && Calendar::where('user_id', $user->ID)->where('type', 'simple')->first()) {
@@ -192,10 +247,30 @@
192 247
193 248 if (!$isHostCalendar) {
194 249 $title = sanitize_text_field(Arr::get($data, 'title', ''));
195 250 $data['slug'] = sanitize_title($title, '', 'display');
196 - $teamMembers = array_map('intval', Arr::get($slot, 'settings.team_members', []));
197 - 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)) {
198 273 $user = get_user_by('ID', reset($teamMembers));
199 274 if (!$user) {
200 275 return $this->sendError([
201 276 'message' => __('Invalid Team Member', 'fluent-booking')
@@ -290,9 +365,28 @@
290 365 $query->where('status', '!=', 'expired');
291 366 }])->findOrFail($calendarId);
292 367
293 368 $calendar->author_profile = $calendar->getAuthorProfile();
369 + $calendar->event_order = $calendar->getMeta('event_order');
294 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 +
295 389 $data = [
296 390 'calendar' => $calendar
297 391 ];
298 392
@@ -311,10 +405,11 @@
311 405 {
312 406 $calendar = Calendar::findOrFail($calendarId);
313 407
314 408 return [
315 - 'settings' => LandingPageHelper::getSettings($calendar),
316 - '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')))
317 412 ];
318 413 }
319 414
320 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -350,12 +445,27 @@
350 445 $sharingSettings = $request->get('landing_page_settings', []);
351 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
352 447
353 448 return [
354 - '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')))
355 451 ];
356 452 }
357 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 +
358 468 public function updateCalendar(Request $request, $calendarId)
359 469 {
360 470 $data = $request->all();
361 471
@@ -484,12 +594,32 @@
484 594 ], $slot);
485 595
486 596 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
487 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 +
488 618 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
489 619
490 620 $slotData = [
491 - 'title' => $slot['title'],
621 + 'title' => sanitize_text_field($slot['title']),
492 622 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
493 623 'calendar_id' => $calendar->id,
494 624 'user_id' => $calendar->user_id,
495 625 'duration' => (int)$slot['duration'],
@@ -504,13 +634,13 @@
504 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
505 635 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
506 636 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
507 637 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
508 - 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
638 + 'team_members' => $teamMembers
509 639 ],
510 640 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
511 641 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
512 - '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'),
513 643 'availability_type' => 'existing_schedule',
514 644 'availability_id' => $availability ? $availability->id : null,
515 645 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
516 646 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
@@ -625,19 +755,69 @@
625 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
626 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
627 757 ];
628 758
759 + $hostsSchedules = [];
760 +
629 761 if ($event->isTeamEvent()) {
630 - $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
762 + $hostsSchedules = array_map('intval', array_combine(
631 763 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
632 764 array_map('intval', Arr::get($data, 'hosts_schedules', []))
633 765 ));
634 766 }
635 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 +
636 816 $event->settings = $eventSettings;
637 817
638 - $event->availability_id = (int)Arr::get($data, 'availability_id');
639 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
818 + $event->availability_id = $availabilityId;
819 + $event->availability_type = $availabilityType;
640 820
641 821 $event->save();
642 822
643 823 return [
@@ -709,8 +889,18 @@
709 889 $calendar = Calendar::findOrFail($newCalendarId);
710 890
711 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
712 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 +
713 903 $clonedEvent = $originalEvent->replicate();
714 904
715 905 $clonedEvent->hash = null;
716 906
@@ -852,54 +1042,9 @@
852 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
853 1043
854 1044 $bookingFields = $request->get('booking_fields');
855 1045
856 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
857 -
858 - $formattedFields = [];
859 -
860 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
861 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
862 -
863 - foreach ($bookingFields as $value) {
864 - if (empty($value['name'])) {
865 - $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
866 - } else {
867 - $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
868 - }
869 -
870 - $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
871 -
872 - $booleanValues = array_map(function ($valueItem) {
873 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
874 - }, Arr::only($value, $booleanFields));
875 -
876 - $formattedField = array_merge($textValues, $booleanValues);
877 -
878 - $fieldType = Arr::get($value, 'type');
879 -
880 - $formattedField['index'] = (int)Arr::get($value, 'index');
881 - if (in_array($fieldType, $optionRequiredFields)) {
882 - $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
883 - $formattedField['options'] = $sanitizedOptions;
884 - }
885 - if ($fieldType == 'file') {
886 - $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
887 - $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
888 - $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
889 - $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
890 - }
891 - if ($fieldType == 'hidden') {
892 - $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
893 - }
894 - if ($fieldType == 'terms-and-conditions') {
895 - $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
896 - }
897 -
898 - $formattedField = apply_filters('fluent_booking/save_event_booking_field_' . $fieldType, $formattedField, $value, $calendarEvent);
899 -
900 - $formattedFields[] = $formattedField;
901 - }
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
902 1047
903 1048 $calendarEvent->setBookingFields($formattedFields);
904 1049
905 1050 return [