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