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/EditorShortCodeParser.php +138 -34 1.6.0 → 2.5.0 View file →
@@ -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,20 +88,38 @@
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')) {
97 - $format = preg_match('/format\.([a-zA-Z\-]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s';
98 - return DateTimeHelper::convertFromUtc($booking->start_time, $booking->person_time_zone, $format);
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));
99 116 }
100 117
101 - if (str_starts_with($key, 'start_date_time_for_host')) {
102 - $format = preg_match('/format\.([a-zA-Z\-]+)/', $key, $matches) ? $matches[1] : 'Y-m-d H:i:s';
103 - return DateTimeHelper::convertFromUtc($booking->start_time, $booking->getHostTimezone(), $format);
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));
104 122 }
105 123
106 124 if ($key == 'cancel_reason') {
107 125 return $booking->getCancelReason(false, true);
@@ -111,9 +129,9 @@
111 129 return $booking->getRejectReason(false, true);
112 130 }
113 131
114 132 if ($key == 'reschedule_reason') {
115 - return $booking->getRescheduleReason();
133 + return $booking->getRescheduleReason(true);
116 134 }
117 135
118 136 if ($key == 'previous_meeting_time') {
119 137 return $booking->getPreviousMeetingTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')';
@@ -118,8 +136,20 @@
118 136 if ($key == 'previous_meeting_time') {
119 137 return $booking->getPreviousMeetingTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')';
120 138 }
121 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 +
122 152 if ($key == 'start_time_human_format') {
123 153 if (time() > strtotime($booking->start_time)) {
124 154 $suffix = __(' ago', 'fluent-booking');
125 155 } else {
@@ -184,11 +214,14 @@
184 214 self::$store['custom_booking_data'] = $booking->getMeta('custom_fields_data', []);
185 215 }
186 216
187 217 if (self::$store['custom_booking_data']) {
188 - if (preg_match('/format\.([a-zA-Z\-]+)/', $key, $matches)) {
189 - $value = Arr::get(self::$store['custom_booking_data'], preg_split('/\.format\./', $key)[0]);
190 - return $value ? 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)) : '';
191 224 }
192 225
193 226 $customField = BookingFieldService::getBookingFieldByName($booking->calendar_event, $key);
194 227
@@ -196,9 +229,22 @@
196 229 return self::getUploadedFileUrl(Arr::get(self::$store['custom_booking_data'], $key));
197 230 }
198 231
199 232 if (Arr::get($customField, 'type') == 'hidden') {
200 - 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 + }
201 247 }
202 248
203 249 return Arr::get(self::$store['custom_booking_data'], $key);
204 250 }
@@ -215,9 +261,9 @@
215 261 }
216 262
217 263 if ($key == 'timezone') {
218 264 $booking = static::$store['booking'];
219 - return $booking->getHostTimezone();
265 + return $booking ? $booking->getHostTimezone() : null;
220 266 }
221 267
222 268 return Arr::get($host, $key, '');
223 269 }
@@ -245,22 +291,59 @@
245 291 if ('full_name' == $key) {
246 292 return $guest['first_name'] . ' ' . $guest['last_name'];
247 293 }
248 294 if ('timezone' == $key) {
249 - $booking = static::$store['booking'];
250 - return $booking->person_time_zone;
295 + return $guest->person_time_zone;
251 296 }
252 297 if ('note' == $key) {
253 298 return $guest->getMessage();
254 299 }
255 300
301 + if ($key == 'total_guest') {
302 + return $guest->getTotalGuestCount();
303 + }
304 +
256 305 if ($key == 'form_data_html') {
257 - 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 + );
258 311 }
259 312
260 - return Arr::get($guest, $key, '');
313 + return self::resolveScalarAttribute($guest, $key);
261 314 }
262 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 +
263 346 protected static function getBookingEventData($key)
264 347 {
265 348 $bookingEvent = static::$store['booking_event'];
266 349
@@ -269,13 +352,13 @@
269 352 }
270 353
271 354 $fillables = (new CalendarSlot())->getFillable();
272 355
273 - if (in_array($key, $fillables) || isset($bookingEvent->{$key})) {
356 + if (in_array($key, $fillables)) {
274 357 return $bookingEvent->{$key};
275 358 }
276 359
277 - return '';
360 + return self::resolveScalarAttribute($bookingEvent, $key);
278 361 }
279 362
280 363 protected static function getCalendarData($key)
281 364 {
@@ -286,15 +369,30 @@
286 369 }
287 370
288 371 $fillables = (new Calendar())->getFillable();
289 372
290 - if (in_array($key, $fillables) || isset($calendar->{$key})) {
373 + if (in_array($key, $fillables)) {
291 374 return $calendar->{$key};
292 375 }
293 376
294 - return '';
377 + return self::resolveScalarAttribute($calendar, $key);
295 378 }
296 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 : '';
393 + }
394 +
297 395 protected static function getPaymentData($key)
298 396 {
299 397 $booking = static::$store['booking'];
300 398
@@ -315,9 +413,9 @@
315 413 }
316 414
317 415 $order = static::$store['payment_order'];
318 416
319 - if ($key == 'payment_total') {
417 + if ($key == 'payment_total' && $order) {
320 418 $isZeroDecimal = CurrenciesHelper::isZeroDecimal($order->currency);
321 419 if ($isZeroDecimal) {
322 420 return $order->total_amount;
323 421 } else {
@@ -329,23 +427,23 @@
329 427 return apply_filters('fluent_booking/payment_receipt_html', '', $booking->hash);
330 428 }
331 429
332 430 if ($key == 'payment_status') {
333 - return $order->status;
431 + return $order ? $order->status : '';
334 432 }
335 433
336 434 if ($key == 'payment_currency') {
337 - return $order->currency;
435 + return $order ? $order->currency : '';
338 436 }
339 437
340 438 if ($key == 'payment_date') {
341 - return $order->created_at;
439 + return $order ? $order->created_at : '';
342 440 }
343 441
344 442 $fillables = (new \FluentBookingPro\App\Models\Order())->getFillable();
345 443
346 444 if (in_array($key, $fillables)) {
347 - return $order->{$key};
445 + return $order ? $order->{$key} : '';
348 446 }
349 447
350 448 return '';
351 449 }
@@ -351,12 +449,13 @@
351 449 }
352 450
353 451 protected static function getUserData($key)
354 452 {
355 - if (is_null(static::$store['user'])) {
356 - static::$store['user'] = wp_get_current_user();
453 + $user = static::$store['user'];
454 + if (is_null($user)) {
455 + $user = wp_get_current_user();
357 456 }
358 - return static::$store['user']->{$key};
457 + return $user ? $user->{$key} : '';
359 458 }
360 459
361 460 protected static function getWPData($key)
362 461 {
@@ -373,9 +472,14 @@
373 472 }
374 473
375 474 protected static function getOtherData($key)
376 475 {
377 - self::$store['meeting_bookmarks'] ??= static::$store['booking']->getMeetingBookmarks();
476 + $meetingBookmarks = self::$store['meeting_bookmarks'];
477 + if (!$meetingBookmarks) {
478 + $booking = static::$store['booking'];
479 + $meetingBookmarks = $booking ? $booking->getMeetingBookmarks() : null;
480 + self::$store['meeting_bookmarks'] = $meetingBookmarks;
481 + }
378 482
379 483 if (0 === strpos($key, 'date.')) {
380 484 $format = str_replace('date.', '', $key);
381 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
@@ -381,15 +485,15 @@
381 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
382 486 } elseif ('add_booking_to_calendar' === $key) {
383 487 return static::parseShortCodes(Helper::getAddToCalendarHtml());
384 488 } elseif ('add_to_g_calendar_url' === $key) {
385 - return Arr::get(self::$store['meeting_bookmarks'], 'google.url');
489 + return Arr::get($meetingBookmarks, 'google.url');
386 490 } elseif ('add_to_ol_calendar_url' === $key) {
387 - return Arr::get(self::$store['meeting_bookmarks'], 'outlook.url');
491 + return Arr::get($meetingBookmarks, 'outlook.url');
388 492 } elseif ('add_to_ms_calendar_url' === $key) {
389 - return Arr::get(self::$store['meeting_bookmarks'], 'msoffice.url');
493 + return Arr::get($meetingBookmarks, 'msoffice.url');
390 494 } elseif ('add_to_ics_calendar_url' === $key) {
391 - return Arr::get(self::$store['meeting_bookmarks'], 'other.url');
495 + return Arr::get($meetingBookmarks, 'other.url');
392 496 }
393 497
394 498 return $key;
395 499 }