| @@ -28,9 +28,9 @@ | ||
| 28 | 28 | static::setData($booking); |
| 29 | 29 | return static::parseShortCodes($parsable); |
| 30 | 30 | } catch (\Exception $e) { |
| 31 | 31 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 32 | - error_log($e->getTraceAsString()); | |
| 32 | + error_log($e->getTraceAsString()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 33 | 33 | } |
| 34 | 34 | return ''; |
| 35 | 35 | } |
| 36 | 36 | } |
| @@ -88,30 +88,50 @@ | ||
| 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 ($key == 'start_date_time_for_attendee') { | |
| 97 | - return DateTimeHelper::convertFromUtc($booking->start_time, $booking->person_time_zone, 'Y-m-d H:i:s'); | |
| 112 | + if (strpos($key, 'start_date_time_for_attendee') === 0) { | |
| 113 | + $format = preg_match('/format\.([a-zA-Z\-: ]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s'; | |
| 114 | + $dateTime = DateTimeHelper::convertFromUtc($booking->start_time, $booking->person_time_zone, 'Y-m-d H:i:s'); | |
| 115 | + return date_i18n($format, strtotime($dateTime)); | |
| 98 | 116 | } |
| 99 | 117 | |
| 100 | - if ($key == 'start_date_time_for_host') { | |
| 101 | - return DateTimeHelper::convertFromUtc($booking->start_time, $booking->getHostTimezone(), 'Y-m-d H:i:s'); | |
| 118 | + if (strpos($key, 'start_date_time_for_host') === 0) { | |
| 119 | + $format = preg_match('/format\.([a-zA-Z\-: ]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s'; | |
| 120 | + $dateTime = DateTimeHelper::convertFromUtc($booking->start_time, $booking->getHostTimezone(), 'Y-m-d H:i:s'); | |
| 121 | + return date_i18n($format, strtotime($dateTime)); | |
| 102 | 122 | } |
| 103 | 123 | |
| 104 | 124 | if ($key == 'cancel_reason') { |
| 105 | - return $booking->getCancelReason(true); | |
| 125 | + return $booking->getCancelReason(false, true); | |
| 106 | 126 | } |
| 107 | 127 | |
| 108 | 128 | if ($key == 'reject_reason') { |
| 109 | - return $booking->getRejectReason(true); | |
| 129 | + return $booking->getRejectReason(false, true); | |
| 110 | 130 | } |
| 111 | 131 | |
| 112 | 132 | if ($key == 'reschedule_reason') { |
| 113 | - return $booking->getRescheduleReason(); | |
| 133 | + return $booking->getRescheduleReason(true); | |
| 114 | 134 | } |
| 115 | 135 | |
| 116 | 136 | if ($key == 'previous_meeting_time') { |
| 117 | 137 | return $booking->getPreviousMeetingTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')'; |
| @@ -116,8 +136,20 @@ | ||
| 116 | 136 | if ($key == 'previous_meeting_time') { |
| 117 | 137 | return $booking->getPreviousMeetingTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')'; |
| 118 | 138 | } |
| 119 | 139 | |
| 140 | + if ($key == 'previous_meeting_time_guest_timezone') { | |
| 141 | + return $booking->getPreviousMeetingTime($booking->person_time_zone) . ' (' . $booking->person_time_zone . ')'; | |
| 142 | + } | |
| 143 | + | |
| 144 | + if ($key == 'previous_meeting_date_time_host_timezone') { | |
| 145 | + return $booking->getPreviousMeetingDateTimeText($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')'; | |
| 146 | + } | |
| 147 | + | |
| 148 | + if ($key == 'previous_meeting_date_time_guest_timezone') { | |
| 149 | + return $booking->getPreviousMeetingDateTimeText($booking->person_time_zone) . ' (' . $booking->person_time_zone . ')'; | |
| 150 | + } | |
| 151 | + | |
| 120 | 152 | if ($key == 'start_time_human_format') { |
| 121 | 153 | if (time() > strtotime($booking->start_time)) { |
| 122 | 154 | $suffix = __(' ago', 'fluent-booking'); |
| 123 | 155 | } else { |
| @@ -182,11 +214,14 @@ | ||
| 182 | 214 | self::$store['custom_booking_data'] = $booking->getMeta('custom_fields_data', []); |
| 183 | 215 | } |
| 184 | 216 | |
| 185 | 217 | if (self::$store['custom_booking_data']) { |
| 186 | - if (preg_match('/format\.([a-zA-Z\-]+)/', $key, $matches)) { | |
| 187 | - $value = Arr::get(self::$store['custom_booking_data'], preg_split('/\.format\./', $key)[0]); | |
| 188 | - return gmdate($matches[1], strtotime($value)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date | |
| 218 | + if (preg_match('/format\.([a-zA-Z\-: ]+)/', $key, $matches)) { | |
| 219 | + $fieldKey = preg_split('/\.format\./', $key)[0]; | |
| 220 | + $value = Arr::get(self::$store['custom_booking_data'], $fieldKey); | |
| 221 | + $customField = BookingFieldService::getBookingFieldByName($booking->calendar_event, $fieldKey); | |
| 222 | + $formattedDate = DateTimeHelper::getFormattedDate($value, Arr::get($customField, 'date_format')); | |
| 223 | + return $formattedDate ? date_i18n($matches[1], strtotime($formattedDate)) : ''; | |
| 189 | 224 | } |
| 190 | 225 | |
| 191 | 226 | $customField = BookingFieldService::getBookingFieldByName($booking->calendar_event, $key); |
| 192 | 227 | |
| @@ -193,8 +228,25 @@ | ||
| 193 | 228 | if (Arr::get($customField, 'type') == 'file') { |
| 194 | 229 | return self::getUploadedFileUrl(Arr::get(self::$store['custom_booking_data'], $key)); |
| 195 | 230 | } |
| 196 | 231 | |
| 232 | + if (Arr::get($customField, 'type') == 'hidden') { | |
| 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 | + } | |
| 247 | + } | |
| 248 | + | |
| 197 | 249 | return Arr::get(self::$store['custom_booking_data'], $key); |
| 198 | 250 | } |
| 199 | 251 | |
| 200 | 252 | return ''; |
| @@ -209,9 +261,9 @@ | ||
| 209 | 261 | } |
| 210 | 262 | |
| 211 | 263 | if ($key == 'timezone') { |
| 212 | 264 | $booking = static::$store['booking']; |
| 213 | - return $booking->getHostTimezone(); | |
| 265 | + return $booking ? $booking->getHostTimezone() : null; | |
| 214 | 266 | } |
| 215 | 267 | |
| 216 | 268 | return Arr::get($host, $key, ''); |
| 217 | 269 | } |
| @@ -239,20 +291,23 @@ | ||
| 239 | 291 | if ('full_name' == $key) { |
| 240 | 292 | return $guest['first_name'] . ' ' . $guest['last_name']; |
| 241 | 293 | } |
| 242 | 294 | if ('timezone' == $key) { |
| 243 | - $booking = static::$store['booking']; | |
| 244 | - return $booking->person_time_zone; | |
| 295 | + return $guest->person_time_zone; | |
| 245 | 296 | } |
| 246 | 297 | if ('note' == $key) { |
| 247 | 298 | return $guest->getMessage(); |
| 248 | 299 | } |
| 249 | 300 | |
| 301 | + if ($key == 'total_guest') { | |
| 302 | + return $guest->getTotalGuestCount(); | |
| 303 | + } | |
| 304 | + | |
| 250 | 305 | if ($key == 'form_data_html') { |
| 251 | 306 | return __('will be available soon', 'fluent-booking'); |
| 252 | 307 | } |
| 253 | 308 | |
| 254 | - return Arr::get($guest, $key, ''); | |
| 309 | + return self::resolveScalarAttribute($guest, $key); | |
| 255 | 310 | } |
| 256 | 311 | |
| 257 | 312 | protected static function getBookingEventData($key) |
| 258 | 313 | { |
| @@ -267,9 +322,9 @@ | ||
| 267 | 322 | if (in_array($key, $fillables)) { |
| 268 | 323 | return $bookingEvent->{$key}; |
| 269 | 324 | } |
| 270 | 325 | |
| 271 | - return ''; | |
| 326 | + return self::resolveScalarAttribute($bookingEvent, $key); | |
| 272 | 327 | } |
| 273 | 328 | |
| 274 | 329 | protected static function getCalendarData($key) |
| 275 | 330 | { |
| @@ -284,11 +339,26 @@ | ||
| 284 | 339 | if (in_array($key, $fillables)) { |
| 285 | 340 | return $calendar->{$key}; |
| 286 | 341 | } |
| 287 | 342 | |
| 288 | - return ''; | |
| 343 | + return self::resolveScalarAttribute($calendar, $key); | |
| 289 | 344 | } |
| 290 | 345 | |
| 346 | + /** | |
| 347 | + * Resolve a scalar attribute only; never a relation (dumps as JSON) or a | |
| 348 | + * dotted hop into a relation's columns. | |
| 349 | + */ | |
| 350 | + protected static function resolveScalarAttribute($model, $key) | |
| 351 | + { | |
| 352 | + if (strpos($key, '.') !== false) { | |
| 353 | + return ''; | |
| 354 | + } | |
| 355 | + | |
| 356 | + $value = isset($model[$key]) ? $model[$key] : ''; | |
| 357 | + | |
| 358 | + return is_scalar($value) ? $value : ''; | |
| 359 | + } | |
| 360 | + | |
| 291 | 361 | protected static function getPaymentData($key) |
| 292 | 362 | { |
| 293 | 363 | $booking = static::$store['booking']; |
| 294 | 364 | |
| @@ -309,9 +379,9 @@ | ||
| 309 | 379 | } |
| 310 | 380 | |
| 311 | 381 | $order = static::$store['payment_order']; |
| 312 | 382 | |
| 313 | - if ($key == 'payment_total') { | |
| 383 | + if ($key == 'payment_total' && $order) { | |
| 314 | 384 | $isZeroDecimal = CurrenciesHelper::isZeroDecimal($order->currency); |
| 315 | 385 | if ($isZeroDecimal) { |
| 316 | 386 | return $order->total_amount; |
| 317 | 387 | } else { |
| @@ -323,23 +393,23 @@ | ||
| 323 | 393 | return apply_filters('fluent_booking/payment_receipt_html', '', $booking->hash); |
| 324 | 394 | } |
| 325 | 395 | |
| 326 | 396 | if ($key == 'payment_status') { |
| 327 | - return $order->status; | |
| 397 | + return $order ? $order->status : ''; | |
| 328 | 398 | } |
| 329 | 399 | |
| 330 | 400 | if ($key == 'payment_currency') { |
| 331 | - return $order->currency; | |
| 401 | + return $order ? $order->currency : ''; | |
| 332 | 402 | } |
| 333 | 403 | |
| 334 | 404 | if ($key == 'payment_date') { |
| 335 | - return $order->created_at; | |
| 405 | + return $order ? $order->created_at : ''; | |
| 336 | 406 | } |
| 337 | 407 | |
| 338 | 408 | $fillables = (new \FluentBookingPro\App\Models\Order())->getFillable(); |
| 339 | 409 | |
| 340 | 410 | if (in_array($key, $fillables)) { |
| 341 | - return $order->{$key}; | |
| 411 | + return $order ? $order->{$key} : ''; | |
| 342 | 412 | } |
| 343 | 413 | |
| 344 | 414 | return ''; |
| 345 | 415 | } |
| @@ -345,12 +415,13 @@ | ||
| 345 | 415 | } |
| 346 | 416 | |
| 347 | 417 | protected static function getUserData($key) |
| 348 | 418 | { |
| 349 | - if (is_null(static::$store['user'])) { | |
| 350 | - static::$store['user'] = wp_get_current_user(); | |
| 419 | + $user = static::$store['user']; | |
| 420 | + if (is_null($user)) { | |
| 421 | + $user = wp_get_current_user(); | |
| 351 | 422 | } |
| 352 | - return static::$store['user']->{$key}; | |
| 423 | + return $user ? $user->{$key} : ''; | |
| 353 | 424 | } |
| 354 | 425 | |
| 355 | 426 | protected static function getWPData($key) |
| 356 | 427 | { |
| @@ -367,9 +438,14 @@ | ||
| 367 | 438 | } |
| 368 | 439 | |
| 369 | 440 | protected static function getOtherData($key) |
| 370 | 441 | { |
| 371 | - self::$store['meeting_bookmarks'] ??= static::$store['booking']->getMeetingBookmarks(); | |
| 442 | + $meetingBookmarks = self::$store['meeting_bookmarks']; | |
| 443 | + if (!$meetingBookmarks) { | |
| 444 | + $booking = static::$store['booking']; | |
| 445 | + $meetingBookmarks = $booking ? $booking->getMeetingBookmarks() : null; | |
| 446 | + self::$store['meeting_bookmarks'] = $meetingBookmarks; | |
| 447 | + } | |
| 372 | 448 | |
| 373 | 449 | if (0 === strpos($key, 'date.')) { |
| 374 | 450 | $format = str_replace('date.', '', $key); |
| 375 | 451 | return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| @@ -375,15 +451,15 @@ | ||
| 375 | 451 | return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 376 | 452 | } elseif ('add_booking_to_calendar' === $key) { |
| 377 | 453 | return static::parseShortCodes(Helper::getAddToCalendarHtml()); |
| 378 | 454 | } elseif ('add_to_g_calendar_url' === $key) { |
| 379 | - return Arr::get(self::$store['meeting_bookmarks'], 'google.url'); | |
| 455 | + return Arr::get($meetingBookmarks, 'google.url'); | |
| 380 | 456 | } elseif ('add_to_ol_calendar_url' === $key) { |
| 381 | - return Arr::get(self::$store['meeting_bookmarks'], 'outlook.url'); | |
| 457 | + return Arr::get($meetingBookmarks, 'outlook.url'); | |
| 382 | 458 | } elseif ('add_to_ms_calendar_url' === $key) { |
| 383 | - return Arr::get(self::$store['meeting_bookmarks'], 'msoffice.url'); | |
| 459 | + return Arr::get($meetingBookmarks, 'msoffice.url'); | |
| 384 | 460 | } elseif ('add_to_ics_calendar_url' === $key) { |
| 385 | - return Arr::get(self::$store['meeting_bookmarks'], 'other.url'); | |
| 461 | + return Arr::get($meetingBookmarks, 'other.url'); | |
| 386 | 462 | } |
| 387 | 463 | |
| 388 | 464 | return $key; |
| 389 | 465 | } |