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 +202 -61 2.2.0 → 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
@@ -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')
@@ -294,9 +365,28 @@
294 365 $query->where('status', '!=', 'expired');
295 366 }])->findOrFail($calendarId);
296 367
297 368 $calendar->author_profile = $calendar->getAuthorProfile();
369 + $calendar->event_order = $calendar->getMeta('event_order');
298 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 +
299 389 $data = [
300 390 'calendar' => $calendar
301 391 ];
302 392
@@ -315,10 +405,11 @@
315 405 {
316 406 $calendar = Calendar::findOrFail($calendarId);
317 407
318 408 return [
319 - 'settings' => LandingPageHelper::getSettings($calendar),
320 - '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')))
321 412 ];
322 413 }
323 414
324 415 public function saveSharingSettings(Request $request, $calendarId)
@@ -354,12 +445,27 @@
354 445 $sharingSettings = $request->get('landing_page_settings', []);
355 446 LandingPageHelper::updateSettings($calendar, $sharingSettings);
356 447
357 448 return [
358 - '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')))
359 451 ];
360 452 }
361 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 +
362 468 public function updateCalendar(Request $request, $calendarId)
363 469 {
364 470 $data = $request->all();
365 471
@@ -488,12 +594,32 @@
488 594 ], $slot);
489 595
490 596 $this->validate($slot, $validationConfig['rules'], $validationConfig['messages']);
491 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 +
492 618 $availability = AvailabilityService::getDefaultSchedule($calendar->user_id);
493 619
494 620 $slotData = [
495 - 'title' => $slot['title'],
621 + 'title' => sanitize_text_field($slot['title']),
496 622 'slug' => Helper::generateSlotSlug($slot['duration'] . 'min', $calendar),
497 623 'calendar_id' => $calendar->id,
498 624 'user_id' => $calendar->user_id,
499 625 'duration' => (int)$slot['duration'],
@@ -508,13 +634,13 @@
508 634 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($slot['settings'], 'schedule_conditions', [])),
509 635 'buffer_time_before' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_before', '0')),
510 636 'buffer_time_after' => sanitize_text_field(Arr::get($slot['settings'], 'buffer_time_after', '0')),
511 637 'slot_interval' => sanitize_text_field(Arr::get($slot['settings'], 'slot_interval', '')),
512 - 'team_members' => array_map('intval', Arr::get($slot['settings'], 'team_members', []))
638 + 'team_members' => $teamMembers
513 639 ],
514 640 'status' => SanitizeService::checkCollection($slot['status'], ['active', 'draft'], 'active'),
515 641 'color_schema' => sanitize_text_field(Arr::get($slot, 'color_schema', '#0099ff')),
516 - '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'),
517 643 'availability_type' => 'existing_schedule',
518 644 'availability_id' => $availability ? $availability->id : null,
519 645 'location_type' => sanitize_text_field(Arr::get($slot, 'location_type')),
520 646 'location_settings' => SanitizeService::locationSettings(Arr::get($slot, 'location_settings', [])),
@@ -629,19 +755,69 @@
629 755 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($data, 'range_date_between', ['', ''])),
630 756 'common_schedule' => Arr::isTrue($data, 'common_schedule', false)
631 757 ];
632 758
759 + $hostsSchedules = [];
760 +
633 761 if ($event->isTeamEvent()) {
634 - $eventSettings['hosts_schedules'] = array_map('intval', array_combine(
762 + $hostsSchedules = array_map('intval', array_combine(
635 763 array_map('intval', array_keys(Arr::get($data, 'hosts_schedules', []))),
636 764 array_map('intval', Arr::get($data, 'hosts_schedules', []))
637 765 ));
638 766 }
639 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 +
640 816 $event->settings = $eventSettings;
641 817
642 - $event->availability_id = (int)Arr::get($data, 'availability_id');
643 - $event->availability_type = SanitizeService::checkCollection(Arr::get($data, 'availability_type'), ['existing_schedule', 'custom']);
818 + $event->availability_id = $availabilityId;
819 + $event->availability_type = $availabilityType;
644 820
645 821 $event->save();
646 822
647 823 return [
@@ -713,8 +889,18 @@
713 889 $calendar = Calendar::findOrFail($newCalendarId);
714 890
715 891 $originalEvent = CalendarSlot::with('event_metas')->where('calendar_id', $calendarId)->findOrFail($eventId);
716 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 +
717 903 $clonedEvent = $originalEvent->replicate();
718 904
719 905 $clonedEvent->hash = null;
720 906
@@ -856,54 +1042,9 @@
856 1042 $calendarEvent = CalendarSlot::where('calendar_id', $calendarId)->findOrFail($eventId);
857 1043
858 1044 $bookingFields = $request->get('booking_fields');
859 1045
860 - $optionRequiredFields = ['dropdown', 'radio', 'checkbox-group', 'multi-select'];
861 -
862 - $formattedFields = [];
863 -
864 - $textFields = ['type', 'name', 'label', 'placeholder', 'limit', 'help_text', 'date_format', 'min_date', 'max_date'];
865 - $booleanFields = ['enabled', 'required', 'system_defined', 'disable_alter', 'is_sms_number'];
866 -
867 - foreach ($bookingFields as $value) {
868 - if (empty($value['name'])) {
869 - $value['name'] = BookingFieldService::generateFieldName($calendarEvent, $value['label']);
870 - } else {
871 - $value['name'] = BookingFieldService::maybeGenerateFieldName($calendarEvent, $value);
872 - }
873 -
874 - $textValues = array_map('sanitize_text_field', Arr::only($value, $textFields));
875 -
876 - $booleanValues = array_map(function ($valueItem) {
877 - return $valueItem === true || $valueItem === 'true' || $valueItem == 1;
878 - }, Arr::only($value, $booleanFields));
879 -
880 - $formattedField = array_merge($textValues, $booleanValues);
881 -
882 - $fieldType = Arr::get($value, 'type');
883 -
884 - $formattedField['index'] = (int)Arr::get($value, 'index');
885 - if (in_array($fieldType, $optionRequiredFields)) {
886 - $sanitizedOptions = array_map('sanitize_text_field', Arr::get($value, 'options'));
887 - $formattedField['options'] = $sanitizedOptions;
888 - }
889 - if ($fieldType == 'file') {
890 - $formattedField['max_file_allow'] = intval(Arr::get($value, 'max_file_allow'));
891 - $formattedField['allow_file_types'] = array_map('sanitize_text_field', Arr::get($value, 'allow_file_types'));
892 - $formattedField['file_size_value'] = intval(Arr::get($value, 'file_size_value'));
893 - $formattedField['file_size_unit'] = SanitizeService::checkCollection(Arr::get($value, 'file_size_unit'), ['kb','mb']);
894 - }
895 - if ($fieldType == 'hidden') {
896 - $formattedField['default_value'] = sanitize_text_field(Arr::get($value, 'default_value'));
897 - }
898 - if ($fieldType == 'terms-and-conditions') {
899 - $formattedField['terms_and_conditions'] = wp_kses_post(Arr::get($value, 'terms_and_conditions'));
900 - }
901 -
902 - $formattedField = apply_filters('fluent_booking/save_event_booking_field_' . $fieldType, $formattedField, $value, $calendarEvent);
903 -
904 - $formattedFields[] = $formattedField;
905 - }
1046 + $formattedFields = BookingFieldService::sanitizeBookingFields($bookingFields, $calendarEvent);
906 1047
907 1048 $calendarEvent->setBookingFields($formattedFields);
908 1049
909 1050 return [