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 +141 -31 1.5.23 → 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,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,22 +291,59 @@
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 - 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 + );
252 311 }
253 312
254 - return Arr::get($guest, $key, '');
313 + return self::resolveScalarAttribute($guest, $key);
255 314 }
256 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 +
257 346 protected static function getBookingEventData($key)
258 347 {
259 348 $bookingEvent = static::$store['booking_event'];
260 349
@@ -267,9 +356,9 @@
267 356 if (in_array($key, $fillables)) {
268 357 return $bookingEvent->{$key};
269 358 }
270 359
271 - return '';
360 + return self::resolveScalarAttribute($bookingEvent, $key);
272 361 }
273 362
274 363 protected static function getCalendarData($key)
275 364 {
@@ -284,11 +373,26 @@
284 373 if (in_array($key, $fillables)) {
285 374 return $calendar->{$key};
286 375 }
287 376
288 - return '';
377 + return self::resolveScalarAttribute($calendar, $key);
289 378 }
290 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 +
291 395 protected static function getPaymentData($key)
292 396 {
293 397 $booking = static::$store['booking'];
294 398
@@ -309,9 +413,9 @@
309 413 }
310 414
311 415 $order = static::$store['payment_order'];
312 416
313 - if ($key == 'payment_total') {
417 + if ($key == 'payment_total' && $order) {
314 418 $isZeroDecimal = CurrenciesHelper::isZeroDecimal($order->currency);
315 419 if ($isZeroDecimal) {
316 420 return $order->total_amount;
317 421 } else {
@@ -323,23 +427,23 @@
323 427 return apply_filters('fluent_booking/payment_receipt_html', '', $booking->hash);
324 428 }
325 429
326 430 if ($key == 'payment_status') {
327 - return $order->status;
431 + return $order ? $order->status : '';
328 432 }
329 433
330 434 if ($key == 'payment_currency') {
331 - return $order->currency;
435 + return $order ? $order->currency : '';
332 436 }
333 437
334 438 if ($key == 'payment_date') {
335 - return $order->created_at;
439 + return $order ? $order->created_at : '';
336 440 }
337 441
338 442 $fillables = (new \FluentBookingPro\App\Models\Order())->getFillable();
339 443
340 444 if (in_array($key, $fillables)) {
341 - return $order->{$key};
445 + return $order ? $order->{$key} : '';
342 446 }
343 447
344 448 return '';
345 449 }
@@ -345,12 +449,13 @@
345 449 }
346 450
347 451 protected static function getUserData($key)
348 452 {
349 - if (is_null(static::$store['user'])) {
350 - static::$store['user'] = wp_get_current_user();
453 + $user = static::$store['user'];
454 + if (is_null($user)) {
455 + $user = wp_get_current_user();
351 456 }
352 - return static::$store['user']->{$key};
457 + return $user ? $user->{$key} : '';
353 458 }
354 459
355 460 protected static function getWPData($key)
356 461 {
@@ -367,9 +472,14 @@
367 472 }
368 473
369 474 protected static function getOtherData($key)
370 475 {
371 - 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 + }
372 482
373 483 if (0 === strpos($key, 'date.')) {
374 484 $format = str_replace('date.', '', $key);
375 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
@@ -375,15 +485,15 @@
375 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
376 486 } elseif ('add_booking_to_calendar' === $key) {
377 487 return static::parseShortCodes(Helper::getAddToCalendarHtml());
378 488 } elseif ('add_to_g_calendar_url' === $key) {
379 - return Arr::get(self::$store['meeting_bookmarks'], 'google.url');
489 + return Arr::get($meetingBookmarks, 'google.url');
380 490 } elseif ('add_to_ol_calendar_url' === $key) {
381 - return Arr::get(self::$store['meeting_bookmarks'], 'outlook.url');
491 + return Arr::get($meetingBookmarks, 'outlook.url');
382 492 } elseif ('add_to_ms_calendar_url' === $key) {
383 - return Arr::get(self::$store['meeting_bookmarks'], 'msoffice.url');
493 + return Arr::get($meetingBookmarks, 'msoffice.url');
384 494 } elseif ('add_to_ics_calendar_url' === $key) {
385 - return Arr::get(self::$store['meeting_bookmarks'], 'other.url');
495 + return Arr::get($meetingBookmarks, 'other.url');
386 496 }
387 497
388 498 return $key;
389 499 }