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 +160 -31 1.5.21 → 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,13 +214,39 @@
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
226 + $customField = BookingFieldService::getBookingFieldByName($booking->calendar_event, $key);
227 +
228 + if (Arr::get($customField, 'type') == 'file') {
229 + return self::getUploadedFileUrl(Arr::get(self::$store['custom_booking_data'], $key));
230 + }
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 +
191 249 return Arr::get(self::$store['custom_booking_data'], $key);
192 250 }
193 251
194 252 return '';
@@ -203,9 +261,9 @@
203 261 }
204 262
205 263 if ($key == 'timezone') {
206 264 $booking = static::$store['booking'];
207 - return $booking->getHostTimezone();
265 + return $booking ? $booking->getHostTimezone() : null;
208 266 }
209 267
210 268 return Arr::get($host, $key, '');
211 269 }
@@ -233,22 +291,59 @@
233 291 if ('full_name' == $key) {
234 292 return $guest['first_name'] . ' ' . $guest['last_name'];
235 293 }
236 294 if ('timezone' == $key) {
237 - $booking = static::$store['booking'];
238 - return $booking->person_time_zone;
295 + return $guest->person_time_zone;
239 296 }
240 297 if ('note' == $key) {
241 298 return $guest->getMessage();
242 299 }
243 300
301 + if ($key == 'total_guest') {
302 + return $guest->getTotalGuestCount();
303 + }
304 +
244 305 if ($key == 'form_data_html') {
245 - 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 + );
246 311 }
247 312
248 - return Arr::get($guest, $key, '');
313 + return self::resolveScalarAttribute($guest, $key);
249 314 }
250 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 +
251 346 protected static function getBookingEventData($key)
252 347 {
253 348 $bookingEvent = static::$store['booking_event'];
254 349
@@ -261,9 +356,9 @@
261 356 if (in_array($key, $fillables)) {
262 357 return $bookingEvent->{$key};
263 358 }
264 359
265 - return '';
360 + return self::resolveScalarAttribute($bookingEvent, $key);
266 361 }
267 362
268 363 protected static function getCalendarData($key)
269 364 {
@@ -278,11 +373,26 @@
278 373 if (in_array($key, $fillables)) {
279 374 return $calendar->{$key};
280 375 }
281 376
282 - return '';
377 + return self::resolveScalarAttribute($calendar, $key);
283 378 }
284 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 +
285 395 protected static function getPaymentData($key)
286 396 {
287 397 $booking = static::$store['booking'];
288 398
@@ -303,9 +413,9 @@
303 413 }
304 414
305 415 $order = static::$store['payment_order'];
306 416
307 - if ($key == 'payment_total') {
417 + if ($key == 'payment_total' && $order) {
308 418 $isZeroDecimal = CurrenciesHelper::isZeroDecimal($order->currency);
309 419 if ($isZeroDecimal) {
310 420 return $order->total_amount;
311 421 } else {
@@ -317,23 +427,23 @@
317 427 return apply_filters('fluent_booking/payment_receipt_html', '', $booking->hash);
318 428 }
319 429
320 430 if ($key == 'payment_status') {
321 - return $order->status;
431 + return $order ? $order->status : '';
322 432 }
323 433
324 434 if ($key == 'payment_currency') {
325 - return $order->currency;
435 + return $order ? $order->currency : '';
326 436 }
327 437
328 438 if ($key == 'payment_date') {
329 - return $order->created_at;
439 + return $order ? $order->created_at : '';
330 440 }
331 441
332 442 $fillables = (new \FluentBookingPro\App\Models\Order())->getFillable();
333 443
334 444 if (in_array($key, $fillables)) {
335 - return $order->{$key};
445 + return $order ? $order->{$key} : '';
336 446 }
337 447
338 448 return '';
339 449 }
@@ -339,12 +449,13 @@
339 449 }
340 450
341 451 protected static function getUserData($key)
342 452 {
343 - if (is_null(static::$store['user'])) {
344 - static::$store['user'] = wp_get_current_user();
453 + $user = static::$store['user'];
454 + if (is_null($user)) {
455 + $user = wp_get_current_user();
345 456 }
346 - return static::$store['user']->{$key};
457 + return $user ? $user->{$key} : '';
347 458 }
348 459
349 460 protected static function getWPData($key)
350 461 {
@@ -361,9 +472,14 @@
361 472 }
362 473
363 474 protected static function getOtherData($key)
364 475 {
365 - 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 + }
366 482
367 483 if (0 === strpos($key, 'date.')) {
368 484 $format = str_replace('date.', '', $key);
369 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
@@ -369,18 +485,31 @@
369 485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
370 486 } elseif ('add_booking_to_calendar' === $key) {
371 487 return static::parseShortCodes(Helper::getAddToCalendarHtml());
372 488 } elseif ('add_to_g_calendar_url' === $key) {
373 - return Arr::get(self::$store['meeting_bookmarks'], 'google.url');
489 + return Arr::get($meetingBookmarks, 'google.url');
374 490 } elseif ('add_to_ol_calendar_url' === $key) {
375 - return Arr::get(self::$store['meeting_bookmarks'], 'outlook.url');
491 + return Arr::get($meetingBookmarks, 'outlook.url');
376 492 } elseif ('add_to_ms_calendar_url' === $key) {
377 - return Arr::get(self::$store['meeting_bookmarks'], 'msoffice.url');
493 + return Arr::get($meetingBookmarks, 'msoffice.url');
378 494 } elseif ('add_to_ics_calendar_url' === $key) {
379 - return Arr::get(self::$store['meeting_bookmarks'], 'other.url');
495 + return Arr::get($meetingBookmarks, 'other.url');
380 496 }
381 497
382 498 return $key;
499 + }
500 +
501 + protected static function getUploadedFileUrl($fieldValue)
502 + {
503 + if (empty($fieldValue)) {
504 + return '';
505 + }
506 +
507 + $files = array_map(function($file) {
508 + return '<a href="' . esc_url($file) . '" style="color:#222;font-size:15px;" target="_blank" download="' . esc_attr(basename($file)) . '">' . esc_html(basename($file)) . '</a>';
509 + }, $fieldValue);
510 +
511 + return '<ul><li>' . implode('</li><li>', $files) . '</li></ul>';
383 512 }
384 513
385 514 protected static function parseShortCodes($parsable)
386 515 {