| @@ -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 | |
| @@ -146,9 +146,9 @@ | ||
| 146 | 146 | $firstCalendar = Calendar::where('user_id', $preparedData['user_id'])->where('type', 'simple')->first(); |
| 147 | 147 | |
| 148 | 148 | if ($isHostCalendar && $firstCalendar) { |
| 149 | 149 | if ($isFileInput) { |
| 150 | - 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')); | |
| 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 | 151 | } |
| 152 | 152 | return $firstCalendar; |
| 153 | 153 | } |
| 154 | 154 | |
| @@ -168,9 +168,11 @@ | ||
| 168 | 168 | |
| 169 | 169 | $preparedCalendarMetas[] = [ |
| 170 | 170 | 'key' => sanitize_text_field($calendarMeta['key']), |
| 171 | 171 | 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value), |
| 172 | - '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' | |
| 173 | 175 | ]; |
| 174 | 176 | } |
| 175 | 177 | |
| 176 | 178 | return $preparedCalendarMetas; |
| @@ -180,9 +182,9 @@ | ||
| 180 | 182 | { |
| 181 | 183 | $preparedEventData = [ |
| 182 | 184 | 'title' => sanitize_text_field(Arr::get($eventData, 'title')), |
| 183 | 185 | 'duration' => (int)Arr::get($eventData, 'duration', 30), |
| 184 | - 'description' => sanitize_textarea_field(Arr::get($eventData, 'description')), | |
| 186 | + 'description' => wp_kses_post(Arr::get($eventData, 'description')), | |
| 185 | 187 | 'type' => sanitize_text_field(Arr::get($eventData, 'type')), |
| 186 | 188 | 'status' => sanitize_text_field(Arr::get($eventData, 'status', 'active')), |
| 187 | 189 | 'color_schema' => sanitize_text_field(Arr::get($eventData, 'color_schema', '#0099ff')), |
| 188 | 190 | 'event_type' => sanitize_text_field(Arr::get($eventData, 'event_type')), |
| @@ -208,10 +210,10 @@ | ||
| 208 | 210 | } |
| 209 | 211 | |
| 210 | 212 | $preparedEventData['settings'] = [ |
| 211 | 213 | 'schedule_type' => sanitize_text_field(Arr::get($eventSettings, 'schedule_type')), |
| 212 | - 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($eventSettings, 'weekly_schedules', []), $calendar->author_timezone, 'UTC'), | |
| 213 | - '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), | |
| 214 | 216 | 'range_type' => sanitize_text_field(Arr::get($eventSettings, 'range_type')), |
| 215 | 217 | 'range_days' => (int)(Arr::get($eventSettings, 'range_days', 60)) ?: 60, |
| 216 | 218 | 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($eventSettings, 'range_date_between', ['', ''])), |
| 217 | 219 | 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($eventSettings, 'schedule_conditions', [])), |
| @@ -236,14 +238,52 @@ | ||
| 236 | 238 | 'lock_timezone' => [ |
| 237 | 239 | 'enabled' => Arr::isTrue($eventSettings, 'lock_timezone.enabled'), |
| 238 | 240 | 'timezone' => sanitize_text_field(Arr::get($eventSettings, 'lock_timezone.timezone')) |
| 239 | 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 | + ] | |
| 240 | 280 | ]; |
| 241 | 281 | |
| 242 | 282 | return $preparedEventData; |
| 243 | 283 | } |
| 244 | 284 | |
| 245 | - protected static function prepareEventMetas($eventMetasData) | |
| 285 | + protected static function prepareEventMetas($eventMetasData, $event = null) | |
| 246 | 286 | { |
| 247 | 287 | $preparedEventMetas = []; |
| 248 | 288 | |
| 249 | 289 | foreach ($eventMetasData as $eventMeta) { |
| @@ -252,16 +292,28 @@ | ||
| 252 | 292 | } |
| 253 | 293 | |
| 254 | 294 | $value = $eventMeta['value']; |
| 255 | 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 | + | |
| 256 | 303 | if ($eventMeta['key'] == 'email_notification') { |
| 257 | 304 | $value = self::updateNotificationImageUrl($value); |
| 258 | 305 | } |
| 259 | 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 | + | |
| 260 | 312 | $preparedEventMetas[] = [ |
| 261 | 313 | 'key' => sanitize_text_field($eventMeta['key']), |
| 262 | 314 | 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value), |
| 263 | - 'object_type' => sanitize_text_field($eventMeta['object_type']) | |
| 315 | + 'object_type' => $objectType | |
| 264 | 316 | ]; |
| 265 | 317 | } |
| 266 | 318 | |
| 267 | 319 | return $preparedEventMetas; |
| @@ -301,12 +353,17 @@ | ||
| 301 | 353 | |
| 302 | 354 | return $formattedNotifications; |
| 303 | 355 | } |
| 304 | 356 | |
| 305 | - public static function getSlotOptions($calendarId) | |
| 357 | + public static function getSlotOptions($calendarId = null, $userId = null) | |
| 306 | 358 | { |
| 307 | 359 | $calendarSlots = CalendarSlot::select(['id', 'title']) |
| 308 | - ->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 | + }) | |
| 309 | 366 | ->where('status', '!=', 'expired') |
| 310 | 367 | ->latest() |
| 311 | 368 | ->get(); |
| 312 | 369 | |
| @@ -355,9 +412,9 @@ | ||
| 355 | 412 | } |
| 356 | 413 | |
| 357 | 414 | public static function getCalendarOptionsByTitle($condition = '') |
| 358 | 415 | { |
| 359 | - $calendarsQuery = Calendar::select(['id', 'title']) | |
| 416 | + $calendarsQuery = Calendar::select(['id', 'title', 'user_id']) | |
| 360 | 417 | ->where('status', '!=', 'expired') |
| 361 | 418 | ->with(['slots' => function ($query) { |
| 362 | 419 | $query->where('status', '!=', 'expired'); |
| 363 | 420 | }]); |
| @@ -428,8 +485,27 @@ | ||
| 428 | 485 | |
| 429 | 486 | return $calendarIds; |
| 430 | 487 | } |
| 431 | 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 | + | |
| 432 | 508 | public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone) |
| 433 | 509 | { |
| 434 | 510 | $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get(); |
| 435 | 511 | |
| @@ -435,14 +511,14 @@ | ||
| 435 | 511 | |
| 436 | 512 | foreach ($calendarEvents as $event) { |
| 437 | 513 | if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) { |
| 438 | 514 | $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone); |
| 439 | - $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC'); | |
| 515 | + $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC', true); | |
| 440 | 516 | } |
| 441 | 517 | |
| 442 | 518 | if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) { |
| 443 | 519 | $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone); |
| 444 | - $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC'); | |
| 520 | + $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC', null, true); | |
| 445 | 521 | } |
| 446 | 522 | |
| 447 | 523 | $event->settings = [ |
| 448 | 524 | 'weekly_schedules' => $weeklySchedule, |