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
fluent-booking / app / Services / EditorShortCodeParser.php

EditorShortCodeParser.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.5.0, at app/Services/EditorShortCodeParser.php

587 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services;
4
5 use FluentBooking\App\Models\Booking;
6 use FluentBooking\App\Models\Calendar;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\Framework\Support\Arr;
9
10 class EditorShortCodeParser
11 {
12 protected static $requireHtml = true;
13
14 protected static $store = [
15 'booking' => null,
16 'calendar_booking' => null,
17 'calendar' => null,
18 'host' => null,
19 'user' => null,
20 'custom_booking_data' => null,
21 'payment_order' => null
22 ];
23
24 public static function parse($parsable, $booking, $requireHtml = true)
25 {
26 try {
27 static::$requireHtml = $requireHtml;
28 static::setData($booking);
29 return static::parseShortCodes($parsable);
30 } catch (\Exception $e) {
31 if (defined('WP_DEBUG') && WP_DEBUG) {
32 error_log($e->getTraceAsString()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
33 }
34 return '';
35 }
36 }
37
38 protected static function setData($booking)
39 {
40 static::$store['booking'] = $booking;
41 static::$store['booking_event'] = $booking->calendar_event;
42 static::$store['calendar'] = $booking->calendar;
43 static::$store['host'] = $booking->getHostDetails(false);
44 static::$store['team_members'] = $booking->getHostsDetails(false, $booking->host_user_id);
45 static::$store['custom_booking_data'] = null;
46 static::$store['payment_order'] = null;
47 static::$store['meeting_bookmarks'] = null;
48 }
49
50 protected static function getBookingData($key)
51 {
52 $bookingEvent = static::$store['booking_event'];
53 $calendar = static::$store['calendar'];
54 $booking = static::$store['booking'];
55
56 if (!$bookingEvent || !$calendar || !$booking) {
57 return '';
58 }
59
60 if ($key == 'event_name') {
61 return $bookingEvent->title;
62 }
63
64 if ($key == 'description') {
65 return $bookingEvent->description;
66 }
67
68 if ($key == 'booking_title') {
69 return $booking->getBookingTitle();
70 }
71
72 if ($key == 'additional_guests') {
73 return $booking->getAdditionalGuests(true);
74 }
75
76 if ($key == 'full_start_end_guest_timezone') {
77 return $booking->getShortBookingDateTime($booking->person_time_zone) . ' (' . $booking->person_time_zone . ')';
78 }
79
80 if ($key == 'full_start_end_host_timezone') {
81 return $booking->getShortBookingDateTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')';
82 }
83
84 if ($key == 'full_start_and_end_guest_timezone') {
85 return $booking->getFullBookingDateTimeText($booking->person_time_zone) . ' (' . $booking->person_time_zone . ')';
86 }
87
88 if ($key == 'full_start_and_end_host_timezone') {
89 return $booking->getFullBookingDateTimeText($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')';
90 }
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
108 if ($key == 'start_date_time') {
109 return $booking->start_time;
110 }
111
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));
116 }
117
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));
122 }
123
124 if ($key == 'cancel_reason') {
125 return $booking->getCancelReason(false, true);
126 }
127
128 if ($key == 'reject_reason') {
129 return $booking->getRejectReason(false, true);
130 }
131
132 if ($key == 'reschedule_reason') {
133 return $booking->getRescheduleReason(true);
134 }
135
136 if ($key == 'previous_meeting_time') {
137 return $booking->getPreviousMeetingTime($booking->getHostTimezone()) . ' (' . $booking->getHostTimezone() . ')';
138 }
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
152 if ($key == 'start_time_human_format') {
153 if (time() > strtotime($booking->start_time)) {
154 $suffix = __(' ago', 'fluent-booking');
155 } else {
156 $suffix = __(' from now', 'fluent-booking');
157 }
158
159 return human_time_diff(time(), strtotime($booking->start_time)) . ' ' . $suffix;
160 }
161
162 if ($key == 'cancelation_url') {
163 return $booking->getCancelUrl();
164 }
165
166 if ($key == 'reschedule_url') {
167 return $booking->getRescheduleUrl();
168 }
169
170 if ($key == 'admin_booking_url') {
171 return Helper::getAdminBookingUrl($booking->id) . '&period=upcoming';
172 }
173
174 if ($key == 'booking_confirm_url') {
175 return Helper::getAdminBookingUrl($booking->id) . '&period=pending&confirm_booking=true';
176 }
177
178 if ($key == 'booking_reject_url') {
179 return Helper::getAdminBookingUrl($booking->id) . '&period=pending&reject_booking=true';
180 }
181
182 if ($key == 'location_details_html') {
183 return $booking->getLocationDetailsHtml();
184 }
185
186 if ($key == 'location_details_text') {
187 return $booking->getLocationAsText();
188 }
189
190 if ($key == 'booking_hash') {
191 return $booking->hash;
192 }
193
194 $fillables = (new Booking())->getFillable();
195 $fillables[] = 'id';
196 $fillables[] = 'created_at';
197 $fillables[] = 'updated_at';
198
199 if (in_array($key, $fillables)) {
200 return $booking->{$key};
201 }
202
203 return '';
204 }
205
206 protected static function getBookingCustomData($key)
207 {
208 $booking = static::$store['booking'];
209 if (!$booking) {
210 return '';
211 }
212
213 if (self::$store['custom_booking_data'] === null) {
214 self::$store['custom_booking_data'] = $booking->getMeta('custom_fields_data', []);
215 }
216
217 if (self::$store['custom_booking_data']) {
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)) : '';
224 }
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
249 return Arr::get(self::$store['custom_booking_data'], $key);
250 }
251
252 return '';
253 }
254
255 protected static function getHostData($key)
256 {
257 $host = static::$store['host'];
258
259 if (is_null($host)) {
260 return '';
261 }
262
263 if ($key == 'timezone') {
264 $booking = static::$store['booking'];
265 return $booking ? $booking->getHostTimezone() : null;
266 }
267
268 return Arr::get($host, $key, '');
269 }
270
271 protected static function getTeamMembersData($key)
272 {
273 $teamMembers = static::$store['team_members'];
274
275 if (empty($teamMembers)) {
276 return '';
277 }
278
279 list($key, $value) = explode('.', $key);
280
281 return Arr::get($teamMembers, $key - 1 . '.' . $value, '');
282 }
283
284 protected static function getGuestData($key)
285 {
286 $guest = static::$store['booking'];
287 if (is_null($guest)) {
288 return '';
289 }
290
291 if ('full_name' == $key) {
292 return $guest['first_name'] . ' ' . $guest['last_name'];
293 }
294 if ('timezone' == $key) {
295 return $guest->person_time_zone;
296 }
297 if ('note' == $key) {
298 return $guest->getMessage();
299 }
300
301 if ($key == 'total_guest') {
302 return $guest->getTotalGuestCount();
303 }
304
305 if ($key == 'form_data_html') {
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 );
311 }
312
313 return self::resolveScalarAttribute($guest, $key);
314 }
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
346 protected static function getBookingEventData($key)
347 {
348 $bookingEvent = static::$store['booking_event'];
349
350 if (is_null($bookingEvent)) {
351 return '';
352 }
353
354 $fillables = (new CalendarSlot())->getFillable();
355
356 if (in_array($key, $fillables)) {
357 return $bookingEvent->{$key};
358 }
359
360 return self::resolveScalarAttribute($bookingEvent, $key);
361 }
362
363 protected static function getCalendarData($key)
364 {
365 $calendar = static::$store['calendar'];
366
367 if (is_null($calendar)) {
368 return '';
369 }
370
371 $fillables = (new Calendar())->getFillable();
372
373 if (in_array($key, $fillables)) {
374 return $calendar->{$key};
375 }
376
377 return self::resolveScalarAttribute($calendar, $key);
378 }
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
395 protected static function getPaymentData($key)
396 {
397 $booking = static::$store['booking'];
398
399 if (is_null($booking)) {
400 return '';
401 }
402
403 if (!$booking->payment_status) {
404 return '';
405 }
406
407 if (is_null(static::$store['payment_order'])) {
408 static::$store['payment_order'] = $booking->payment_order;
409 }
410
411 if (!static::$store['payment_order']) {
412 return '';
413 }
414
415 $order = static::$store['payment_order'];
416
417 if ($key == 'payment_total' && $order) {
418 $isZeroDecimal = CurrenciesHelper::isZeroDecimal($order->currency);
419 if ($isZeroDecimal) {
420 return $order->total_amount;
421 } else {
422 return $order->total_amount / 100;
423 }
424 }
425
426 if ($key == 'receipt_html') {
427 return apply_filters('fluent_booking/payment_receipt_html', '', $booking->hash);
428 }
429
430 if ($key == 'payment_status') {
431 return $order ? $order->status : '';
432 }
433
434 if ($key == 'payment_currency') {
435 return $order ? $order->currency : '';
436 }
437
438 if ($key == 'payment_date') {
439 return $order ? $order->created_at : '';
440 }
441
442 $fillables = (new \FluentBookingPro\App\Models\Order())->getFillable();
443
444 if (in_array($key, $fillables)) {
445 return $order ? $order->{$key} : '';
446 }
447
448 return '';
449 }
450
451 protected static function getUserData($key)
452 {
453 $user = static::$store['user'];
454 if (is_null($user)) {
455 $user = wp_get_current_user();
456 }
457 return $user ? $user->{$key} : '';
458 }
459
460 protected static function getWPData($key)
461 {
462 if ('site_url' == $key) {
463 return site_url();
464 }
465 if ('admin_email' == $key) {
466 return get_option('admin_email');
467 }
468 if ('site_title' == $key) {
469 return get_option('blogname');
470 }
471 return $key;
472 }
473
474 protected static function getOtherData($key)
475 {
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 }
482
483 if (0 === strpos($key, 'date.')) {
484 $format = str_replace('date.', '', $key);
485 return gmdate($format, strtotime(current_time('mysql'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
486 } elseif ('add_booking_to_calendar' === $key) {
487 return static::parseShortCodes(Helper::getAddToCalendarHtml());
488 } elseif ('add_to_g_calendar_url' === $key) {
489 return Arr::get($meetingBookmarks, 'google.url');
490 } elseif ('add_to_ol_calendar_url' === $key) {
491 return Arr::get($meetingBookmarks, 'outlook.url');
492 } elseif ('add_to_ms_calendar_url' === $key) {
493 return Arr::get($meetingBookmarks, 'msoffice.url');
494 } elseif ('add_to_ics_calendar_url' === $key) {
495 return Arr::get($meetingBookmarks, 'other.url');
496 }
497
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>';
512 }
513
514 protected static function parseShortCodes($parsable)
515 {
516 if (is_array($parsable)) {
517 return static::parseFromArray($parsable);
518 }
519
520 return static::parseFromString($parsable);
521 }
522
523 protected static function parseFromArray($parsable)
524 {
525 foreach ($parsable as $key => $value) {
526 if (is_array($value)) {
527 $parsable[$key] = static::parseFromArray($value);
528 } else {
529 $parsable[$key] = static::parseFromString($value);
530 }
531 }
532
533 return $parsable;
534 }
535
536 protected static function parseFromString($parsable)
537 {
538 if (!$parsable) {
539 return '';
540 }
541
542 return preg_replace_callback('/({{|##)+(.*?)(}}|##)/', function ($matches) {
543 $value = '';
544
545 if (empty($matches[2])) {
546 return '';
547 }
548
549 $match = $matches[2];
550
551 if (false !== strpos($match, 'guest.')) {
552 $guestProperty = substr($match, strlen('guest.'));
553 $value = static::getGuestData($guestProperty);
554 } elseif (false !== strpos($match, 'booking.custom.')) {
555 $customBookingProp = substr($match, strlen('booking.custom.'));
556 $value = static::getBookingCustomData($customBookingProp);
557 } elseif (false !== strpos($match, 'booking.')) {
558 $bookingProperty = substr($match, strlen('booking.'));
559 $value = static::getBookingData($bookingProperty);
560 } elseif (false !== strpos($match, 'host.')) {
561 $hostProperty = substr($match, strlen('host.'));
562 $value = static::getHostData($hostProperty);
563 } elseif (false !== strpos($match, 'team_member.')) {
564 $teamMemberProperty = substr($match, strlen('team_member.'));
565 $value = static::getTeamMembersData($teamMemberProperty);
566 } elseif (false !== strpos($match, 'event.')) {
567 $eventProperty = substr($match, strlen('event.'));
568 $value = static::getBookingEventData($eventProperty);
569 } elseif (false !== strpos($match, 'calendar.')) {
570 $calendarProperty = substr($match, strlen('calendar.'));
571 $value = static::getCalendarData($calendarProperty);
572 } elseif (false !== strpos($match, 'payment.')) {
573 $paymentProperty = substr($match, strlen('payment.'));
574 $value = static::getPaymentData($paymentProperty);
575 } else {
576 $value = static::getOtherData($match);
577 }
578
579 if (static::$requireHtml && is_array($value)) {
580 $value = Helper::fcalImplodeRecursive(', ', $value);
581 }
582
583 return $value;
584 }, $parsable);
585 }
586 }
587