| @@ -88,19 +88,35 @@ | ||
| 88 | 88 | if ($key == 'full_start_and_end_host_timezone') { |
| 89 | 89 | return $booking->getFullBookingDateTimeText($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')'; |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | + if ($key == 'all_bookings_short_times_guest_timezone') { | |
| 93 | + return implode("<br>", $booking->getAllBookingShortTimes($booking->person_time_zone, true)); | |
| 94 | + } | |
| 95 | + | |
| 96 | + if ($key == 'all_bookings_short_times_host_timezone') { | |
| 97 | + return implode("<br>", $booking->getAllBookingShortTimes($booking->getHostTimezone(), true)); | |
| 98 | + } | |
| 99 | + | |
| 100 | + if ($key == 'all_bookings_full_times_guest_timezone') { | |
| 101 | + return implode("<br>", $booking->getAllBookingFullTimes($booking->person_time_zone, true)); | |
| 102 | + } | |
| 103 | + | |
| 104 | + if ($key == 'all_bookings_full_times_host_timezone') { | |
| 105 | + return implode("<br>", $booking->getAllBookingFullTimes($booking->getHostTimezone(), true)); | |
| 106 | + } | |
| 107 | + | |
| 92 | 108 | if ($key == 'start_date_time') { |
| 93 | 109 | return $booking->start_time; |
| 94 | 110 | } |
| 95 | 111 | |
| 96 | - if (str_starts_with($key, 'start_date_time_for_attendee')) { | |
| 112 | + if (strpos($key, 'start_date_time_for_attendee') === 0) { | |
| 97 | 113 | $format = preg_match('/format\.([a-zA-Z\-: ]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s'; |
| 98 | 114 | $dateTime = DateTimeHelper::convertFromUtc($booking->start_time, $booking->person_time_zone, 'Y-m-d H:i:s'); |
| 99 | 115 | return date_i18n($format, strtotime($dateTime)); |
| 100 | 116 | } |
| 101 | 117 | |
| 102 | - if (str_starts_with($key, 'start_date_time_for_host')) { | |
| 118 | + if (strpos($key, 'start_date_time_for_host') === 0) { | |
| 103 | 119 | $format = preg_match('/format\.([a-zA-Z\-: ]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s'; |
| 104 | 120 | $dateTime = DateTimeHelper::convertFromUtc($booking->start_time, $booking->getHostTimezone(), 'Y-m-d H:i:s'); |
| 105 | 121 | return date_i18n($format, strtotime($dateTime)); |
| 106 | 122 | } |
| @@ -213,9 +229,22 @@ | ||
| 213 | 229 | return self::getUploadedFileUrl(Arr::get(self::$store['custom_booking_data'], $key)); |
| 214 | 230 | } |
| 215 | 231 | |
| 216 | 232 | if (Arr::get($customField, 'type') == 'hidden') { |
| 217 | - return self::parseShortCodes(Arr::get(self::$store['custom_booking_data'], $key)); | |
| 233 | + static $resolving = []; | |
| 234 | + | |
| 235 | + // Guest-supplied hidden values are parsed again, so a self-reference would recurse until the stack runs out. | |
| 236 | + if (isset($resolving[$key])) { | |
| 237 | + return Arr::get(self::$store['custom_booking_data'], $key); | |
| 238 | + } | |
| 239 | + | |
| 240 | + $resolving[$key] = true; | |
| 241 | + | |
| 242 | + try { | |
| 243 | + return self::parseShortCodes(Arr::get(self::$store['custom_booking_data'], $key)); | |
| 244 | + } finally { | |
| 245 | + unset($resolving[$key]); | |
| 246 | + } | |
| 218 | 247 | } |
| 219 | 248 | |
| 220 | 249 | return Arr::get(self::$store['custom_booking_data'], $key); |
| 221 | 250 | } |
| @@ -273,14 +302,48 @@ | ||
| 273 | 302 | return $guest->getTotalGuestCount(); |
| 274 | 303 | } |
| 275 | 304 | |
| 276 | 305 | if ($key == 'form_data_html') { |
| 277 | - return __('will be available soon', 'fluent-booking'); | |
| 306 | + // isPublic: hidden fields stay out, the recipient may be the guest. | |
| 307 | + return self::renderFormData( | |
| 308 | + BookingFieldService::getFormattedCustomBookingData($guest, static::$requireHtml, true), | |
| 309 | + static::$requireHtml | |
| 310 | + ); | |
| 278 | 311 | } |
| 279 | 312 | |
| 280 | - return Arr::get($guest, $key, ''); | |
| 313 | + return self::resolveScalarAttribute($guest, $key); | |
| 281 | 314 | } |
| 282 | 315 | |
| 316 | + protected static function renderFormData($fields, $isHtml) | |
| 317 | + { | |
| 318 | + $rows = ''; | |
| 319 | + | |
| 320 | + foreach ($fields as $field) { | |
| 321 | + $value = Arr::get($field, 'value'); | |
| 322 | + | |
| 323 | + if ($value === '' || $value === null || $value === []) { | |
| 324 | + continue; | |
| 325 | + } | |
| 326 | + | |
| 327 | + if (!$isHtml) { | |
| 328 | + $rows .= $field['label'] . ': ' . $value . PHP_EOL; | |
| 329 | + continue; | |
| 330 | + } | |
| 331 | + | |
| 332 | + // File values are download anchors; everything else is raw guest input. | |
| 333 | + $value = Arr::get($field, 'type') == 'file' ? wp_kses_post($value) : nl2br(esc_html($value)); | |
| 334 | + | |
| 335 | + $rows .= '<tr><th style="padding:6px 12px;background-color:#f8f8f8;text-align:' . (is_rtl() ? 'right' : 'left') . ';">' . esc_html($field['label']) . '</th></tr>' | |
| 336 | + . '<tr><td style="padding:6px 12px 12px 12px;">' . $value . '</td></tr>'; | |
| 337 | + } | |
| 338 | + | |
| 339 | + if (!$isHtml || !$rows) { | |
| 340 | + return trim($rows); | |
| 341 | + } | |
| 342 | + | |
| 343 | + return '<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;"><tbody>' . $rows . '</tbody></table>'; | |
| 344 | + } | |
| 345 | + | |
| 283 | 346 | protected static function getBookingEventData($key) |
| 284 | 347 | { |
| 285 | 348 | $bookingEvent = static::$store['booking_event']; |
| 286 | 349 | |
| @@ -289,13 +352,13 @@ | ||
| 289 | 352 | } |
| 290 | 353 | |
| 291 | 354 | $fillables = (new CalendarSlot())->getFillable(); |
| 292 | 355 | |
| 293 | - if (in_array($key, $fillables) || isset($bookingEvent->{$key})) { | |
| 356 | + if (in_array($key, $fillables)) { | |
| 294 | 357 | return $bookingEvent->{$key}; |
| 295 | 358 | } |
| 296 | 359 | |
| 297 | - return ''; | |
| 360 | + return self::resolveScalarAttribute($bookingEvent, $key); | |
| 298 | 361 | } |
| 299 | 362 | |
| 300 | 363 | protected static function getCalendarData($key) |
| 301 | 364 | { |
| @@ -306,13 +369,28 @@ | ||
| 306 | 369 | } |
| 307 | 370 | |
| 308 | 371 | $fillables = (new Calendar())->getFillable(); |
| 309 | 372 | |
| 310 | - if (in_array($key, $fillables) || isset($calendar->{$key})) { | |
| 373 | + if (in_array($key, $fillables)) { | |
| 311 | 374 | return $calendar->{$key}; |
| 312 | 375 | } |
| 313 | 376 | |
| 314 | - return ''; | |
| 377 | + return self::resolveScalarAttribute($calendar, $key); | |
| 378 | + } | |
| 379 | + | |
| 380 | + /** | |
| 381 | + * Resolve a scalar attribute only; never a relation (dumps as JSON) or a | |
| 382 | + * dotted hop into a relation's columns. | |
| 383 | + */ | |
| 384 | + protected static function resolveScalarAttribute($model, $key) | |
| 385 | + { | |
| 386 | + if (strpos($key, '.') !== false) { | |
| 387 | + return ''; | |
| 388 | + } | |
| 389 | + | |
| 390 | + $value = isset($model[$key]) ? $model[$key] : ''; | |
| 391 | + | |
| 392 | + return is_scalar($value) ? $value : ''; | |
| 315 | 393 | } |
| 316 | 394 | |
| 317 | 395 | protected static function getPaymentData($key) |
| 318 | 396 | { |