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/Services/CalendarService.php +140 -31 1.5.20 → 2.5.0 View file →
@@ -9,11 +9,11 @@
9 9 use FluentBooking\Framework\Support\Arr;
10 10
11 11 class CalendarService
12 12 {
13 - public static function createCalendar($data, $useCurrentUser = false)
13 + public static function createCalendar($data, $useCurrentUser = false, $isFileInput = false)
14 14 {
15 - $calendarData = self::prepareCalendarData($data, $useCurrentUser);
15 + $calendarData = self::prepareCalendarData($data, $useCurrentUser, $isFileInput);
16 16
17 17 if (is_wp_error($calendarData)) {
18 18 return new \WP_Error($calendarData->get_error_code(), $calendarData->get_error_message());
19 19 }
@@ -92,9 +92,9 @@
92 92
93 93 foreach ($createdEvents as $index => $event) {
94 94 $eventMetasData = Arr::get($createEventMetasData, $index, []);
95 95
96 - $eventMetasData = self::prepareEventMetas($eventMetasData);
96 + $eventMetasData = self::prepareEventMetas($eventMetasData, $event);
97 97
98 98 $event->event_metas()->createMany($eventMetasData);
99 99 }
100 100
@@ -100,9 +100,9 @@
100 100
101 101 return $createdEvents;
102 102 }
103 103
104 - protected static function prepareCalendarData($calendarData, $useCurrentUser = false)
104 + protected static function prepareCalendarData($calendarData, $useCurrentUser = false, $isFileInput = false)
105 105 {
106 106 if (!$calendarData) {
107 107 return new \WP_Error('invalid_data', esc_html__('Invalid JSON Data', 'fluent-booking'));
108 108 }
@@ -111,9 +111,9 @@
111 111 'title' => sanitize_text_field(Arr::get($calendarData, 'title')),
112 112 'description' => sanitize_textarea_field(Arr::get($calendarData, 'description')),
113 113 'user_id' => intval(Arr::get($calendarData, 'user_id')),
114 114 'status' => sanitize_text_field(Arr::get($calendarData, 'status', 'active')),
115 - 'type' => sanitize_text_field(Arr::get($calendarData, 'type')),
115 + 'type' => sanitize_text_field(Arr::get($calendarData, 'type', 'simple')),
116 116 'event_type' => sanitize_text_field(Arr::get($calendarData, 'event_type')),
117 117 'account_type' => sanitize_text_field(Arr::get($calendarData, 'account_type')),
118 118 'visibility' => sanitize_text_field(Arr::get($calendarData, 'visibility')),
119 119 'author_timezone' => sanitize_text_field(Arr::get($calendarData, 'author_timezone')),
@@ -142,10 +142,15 @@
142 142 if ($isHostCalendar || !$preparedData['title']) {
143 143 $preparedData['title'] = is_email($user->user_login) ? explode('@', $user->user_login)[0] : $user->user_login;
144 144 }
145 145
146 - if ($isHostCalendar && Calendar::where('user_id', $preparedData['user_id'])->where('type', 'simple')->first()) {
147 - return new \WP_Error('calendar_exists', esc_html__('The user already have a calendar. Please delete it first to create a new one', 'fluent-booking'));
146 + $firstCalendar = Calendar::where('user_id', $preparedData['user_id'])->where('type', 'simple')->first();
147 +
148 + if ($isHostCalendar && $firstCalendar) {
149 + if ($isFileInput) {
150 + return new \WP_Error('calendar_exists', esc_html__('The user already has a calendar. Please delete it first to create a new one', 'fluent-booking'));
151 + }
152 + return $firstCalendar;
148 153 }
149 154
150 155 return $preparedData;
151 156 }
@@ -163,9 +168,11 @@
163 168
164 169 $preparedCalendarMetas[] = [
165 170 'key' => sanitize_text_field($calendarMeta['key']),
166 171 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
167 - 'object_type' => sanitize_text_field($calendarMeta['object_type'])
172 + // Bound to the imported calendar; never let the payload pick the type
173 + // (e.g. a user_meta / _access_permissions row).
174 + 'object_type' => 'Calendar'
168 175 ];
169 176 }
170 177
171 178 return $preparedCalendarMetas;
@@ -175,9 +182,9 @@
175 182 {
176 183 $preparedEventData = [
177 184 'title' => sanitize_text_field(Arr::get($eventData, 'title')),
178 185 'duration' => (int)Arr::get($eventData, 'duration', 30),
179 - 'description' => sanitize_textarea_field(Arr::get($eventData, 'description')),
186 + 'description' => wp_kses_post(Arr::get($eventData, 'description')),
180 187 'type' => sanitize_text_field(Arr::get($eventData, 'type')),
181 188 'status' => sanitize_text_field(Arr::get($eventData, 'status', 'active')),
182 189 'color_schema' => sanitize_text_field(Arr::get($eventData, 'color_schema', '#0099ff')),
183 190 'event_type' => sanitize_text_field(Arr::get($eventData, 'event_type')),
@@ -203,10 +210,10 @@
203 210 }
204 211
205 212 $preparedEventData['settings'] = [
206 213 'schedule_type' => sanitize_text_field(Arr::get($eventSettings, 'schedule_type')),
207 - 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($eventSettings, 'weekly_schedules', []), $calendar->author_timezone, 'UTC'),
208 - 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), $calendar->author_timezone, 'UTC'),
214 + 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($eventSettings, 'weekly_schedules', []), $calendar->author_timezone, 'UTC', true),
215 + 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
209 216 'range_type' => sanitize_text_field(Arr::get($eventSettings, 'range_type')),
210 217 'range_days' => (int)(Arr::get($eventSettings, 'range_days', 60)) ?: 60,
211 218 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($eventSettings, 'range_date_between', ['', ''])),
212 219 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($eventSettings, 'schedule_conditions', [])),
@@ -231,14 +238,52 @@
231 238 'lock_timezone' => [
232 239 'enabled' => Arr::isTrue($eventSettings, 'lock_timezone.enabled'),
233 240 'timezone' => sanitize_text_field(Arr::get($eventSettings, 'lock_timezone.timezone'))
234 241 ],
242 + 'booking_title' => sanitize_text_field(Arr::get($eventSettings, 'booking_title')),
243 + 'submit_button_text' => sanitize_text_field(Arr::get($eventSettings, 'submit_button_text')),
244 + 'custom_redirect' => [
245 + 'enabled' => Arr::isTrue($eventSettings, 'custom_redirect.enabled'),
246 + 'redirect_url' => sanitize_url(Arr::get($eventSettings, 'custom_redirect.redirect_url')),
247 + 'is_query_string' => Arr::get($eventSettings, 'custom_redirect.is_query_string') == 'yes' ? 'yes' : 'no',
248 + 'query_string' => sanitize_text_field(Arr::get($eventSettings, 'custom_redirect.query_string')),
249 + ],
250 + 'requires_confirmation' => [
251 + 'enabled' => Arr::isTrue($eventSettings, 'requires_confirmation.enabled'),
252 + 'type' => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.type')),
253 + 'condition' => [
254 + 'unit' => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.condition.unit')),
255 + 'value' => intval(Arr::get($eventSettings, 'requires_confirmation.condition.value'))
256 + ]
257 + ],
258 + 'multiple_booking' => [
259 + 'enabled' => Arr::isTrue($eventSettings, 'multiple_booking.enabled'),
260 + 'limit' => intval(Arr::get($eventSettings, 'multiple_booking.limit'))
261 + ],
262 + 'can_not_cancel' => [
263 + 'enabled' => Arr::isTrue($eventSettings, 'can_not_cancel.enabled'),
264 + 'type' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.type')),
265 + 'message' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.message')),
266 + 'condition' => [
267 + 'unit' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.condition.unit')),
268 + 'value' => intval(Arr::get($eventSettings, 'can_not_cancel.condition.value'))
269 + ]
270 + ],
271 + 'can_not_reschedule' => [
272 + 'enabled' => Arr::isTrue($eventSettings, 'can_not_reschedule.enabled'),
273 + 'type' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.type')),
274 + 'message' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.message')),
275 + 'condition' => [
276 + 'unit' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.condition.unit')),
277 + 'value' => intval(Arr::get($eventSettings, 'can_not_reschedule.condition.value'))
278 + ]
279 + ]
235 280 ];
236 281
237 282 return $preparedEventData;
238 283 }
239 284
240 - protected static function prepareEventMetas($eventMetasData)
285 + protected static function prepareEventMetas($eventMetasData, $event = null)
241 286 {
242 287 $preparedEventMetas = [];
243 288
244 289 foreach ($eventMetasData as $eventMeta) {
@@ -247,16 +292,28 @@
247 292 }
248 293
249 294 $value = $eventMeta['value'];
250 295
296 + // Only the two types an event actually stores; anything else in the
297 + // payload (e.g. user_meta / _access_permissions) is dropped.
298 + $objectType = sanitize_text_field(Arr::get($eventMeta, 'object_type'));
299 + if (!in_array($objectType, ['calendar_event', 'integration'], true)) {
300 + continue;
301 + }
302 +
251 303 if ($eventMeta['key'] == 'email_notification') {
252 304 $value = self::updateNotificationImageUrl($value);
253 305 }
254 306
307 + // Rendered on the public form via {@html}; sanitize as the admin path does.
308 + if ($eventMeta['key'] == 'booking_fields' && is_array($value)) {
309 + $value = BookingFieldService::sanitizeBookingFields($value, $event);
310 + }
311 +
255 312 $preparedEventMetas[] = [
256 313 'key' => sanitize_text_field($eventMeta['key']),
257 314 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
258 - 'object_type' => sanitize_text_field($eventMeta['object_type'])
315 + 'object_type' => $objectType
259 316 ];
260 317 }
261 318
262 319 return $preparedEventMetas;
@@ -296,12 +353,17 @@
296 353
297 354 return $formattedNotifications;
298 355 }
299 356
300 - public static function getSlotOptions($calendarId)
357 + public static function getSlotOptions($calendarId = null, $userId = null)
301 358 {
302 359 $calendarSlots = CalendarSlot::select(['id', 'title'])
303 - ->where('calendar_id', $calendarId)
360 + ->when($calendarId, function ($query) use ($calendarId) {
361 + return $query->where('calendar_id', $calendarId);
362 + })
363 + ->when($userId, function ($query) use ($userId) {
364 + return $query->where('user_id', $userId);
365 + })
304 366 ->where('status', '!=', 'expired')
305 367 ->latest()
306 368 ->get();
307 369
@@ -327,9 +389,9 @@
327 389 ->latest()
328 390 ->get();
329 391
330 392 $formattedCalendars = [];
331 - foreach ($calendars as $index => $calendar) {
393 + foreach ($calendars as $calendar) {
332 394 $slots = Arr::get($calendar, 'slots');
333 395 if (!empty($slots)) {
334 396 $options = [];
335 397 foreach ($slots as $slot) {
@@ -338,9 +400,9 @@
338 400 'value' => Arr::get($slot, 'id')
339 401 ];
340 402 }
341 403 if (!empty($options)) {
342 - $formattedCalendars[$index] = [
404 + $formattedCalendars[] = [
343 405 'label' => Arr::get($calendar, 'title'),
344 406 'options' => $options
345 407 ];
346 408 }
@@ -350,32 +412,35 @@
350 412 }
351 413
352 414 public static function getCalendarOptionsByTitle($condition = '')
353 415 {
354 - $calendarQuery = Calendar::select(['id', 'title'])
416 + $calendarsQuery = Calendar::select(['id', 'title', 'user_id'])
355 417 ->where('status', '!=', 'expired')
356 - ->when(!PermissionManager::hasAllCalendarAccess(true), function ($query) {
357 - return $query->where('user_id', get_current_user_id());
358 - });
418 + ->with(['slots' => function ($query) {
419 + $query->where('status', '!=', 'expired');
420 + }]);
359 421
360 422 switch ($condition) {
361 423 case 'only_hosts':
362 - $calendarQuery->where('type', 'simple');
424 + $calendarsQuery->where('type', 'simple');
363 425 break;
364 426 case 'only_teams':
365 - $calendarQuery->where('type', 'team');
427 + $calendarsQuery->where('type', 'team');
366 428 break;
367 429 case 'only_events':
368 - $calendarQuery->where('type', 'event');
430 + $calendarsQuery->where('type', 'event');
369 431 break;
370 432 }
371 433
372 - $calendars = $calendarQuery->with(['slots' => function ($query) {
373 - $query->where('status', '!=', 'expired');
374 - }])->latest()->get();
434 + if (!PermissionManager::hasAllCalendarAccess(true)) {
435 + $attachedCalendarIds = self::getAttachedCalendarIds($calendarsQuery);
436 + $calendarsQuery->whereIn('id', $attachedCalendarIds);
437 + }
375 438
439 + $calendars = $calendarsQuery->latest()->get();
440 +
376 441 $formattedCalendars = [];
377 - foreach ($calendars as $index => $calendar) {
442 + foreach ($calendars as $calendar) {
378 443 $slots = Arr::get($calendar, 'slots');
379 444 if (!empty($slots)) {
380 445 $options = [];
381 446 foreach ($slots as $slot) {
@@ -384,9 +449,9 @@
384 449 'title' => Arr::get($slot, 'title')
385 450 ];
386 451 }
387 452 if (!empty($options)) {
388 - $formattedCalendars[$index] = [
453 + $formattedCalendars[] = [
389 454 'id' => Arr::get($calendar, 'id'),
390 455 'title' => Arr::get($calendar, 'title'),
391 456 'options' => $options
392 457 ];
@@ -395,8 +460,52 @@
395 460 }
396 461 return apply_filters('fluent_booking/calendar_options_by_title', $formattedCalendars);
397 462 }
398 463
464 + public static function getAttachedCalendarIds($calendarsQuery)
465 + {
466 + $userId = get_current_user_id();
467 +
468 + $calendars = $calendarsQuery->get();
469 +
470 + $calendarIds = [];
471 + foreach ($calendars as $calendar) {
472 + if ($calendar->user_id == $userId) {
473 + $calendarIds[] = $calendar->id;
474 + continue;
475 + }
476 +
477 + $events = Arr::get($calendar, 'slots', []);
478 + foreach ($events as $event) {
479 + $teamMembers = Arr::get($event, 'settings.team_members', []);
480 + if (in_array($userId, $teamMembers)) {
481 + $calendarIds[] = $calendar->id;
482 + }
483 + }
484 + }
485 +
486 + return $calendarIds;
487 + }
488 +
489 + public static function isSharedCalendar($calendar)
490 + {
491 + $calendarEvents = $calendar->events;
492 +
493 + $userId = get_current_user_id();
494 +
495 + foreach ($calendarEvents as $event) {
496 + if ($event->user_id == $userId) {
497 + return true;
498 + }
499 + $teamMembers = Arr::get($event, 'settings.team_members', []);
500 + if (in_array($userId, $teamMembers)) {
501 + return true;
502 + }
503 + }
504 +
505 + return false;
506 + }
507 +
399 508 public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone)
400 509 {
401 510 $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get();
402 511
@@ -402,14 +511,14 @@
402 511
403 512 foreach ($calendarEvents as $event) {
404 513 if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) {
405 514 $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone);
406 - $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC');
515 + $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC', true);
407 516 }
408 517
409 518 if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) {
410 519 $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone);
411 - $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC');
520 + $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC', null, true);
412 521 }
413 522
414 523 $event->settings = [
415 524 'weekly_schedules' => $weeklySchedule,