PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
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 1.7.2 All 33 releases
← All changes | app/Models/Booking.php +318 -117 1.5.21trunk View file →
@@ -54,12 +54,12 @@
54 54 'source_url',
55 55 'utm_source',
56 56 'utm_medium',
57 57 'utm_campaign',
58 - 'utm_term'
58 + 'utm_term',
59 + 'utm_content'
59 60 ];
60 61
61 -
62 62 /**
63 63 * $searchable Columns in table to search
64 64 * @var array
65 65 */
@@ -142,12 +142,12 @@
142 142
143 143 return $lastEvent ? $lastEvent->group_id + 1 : 1;
144 144 }
145 145
146 - public function getCustomFormData($isFormatted = true)
146 + public function getCustomFormData($isFormatted = true, $isPublic = false)
147 147 {
148 148 if ($isFormatted) {
149 - return BookingFieldService::getFormattedCustomBookingData($this);
149 + return BookingFieldService::getFormattedCustomBookingData($this, true, $isPublic);
150 150 }
151 151
152 152 return $this->getMeta('custom_fields_data', []);
153 153 }
@@ -174,8 +174,18 @@
174 174
175 175 return $additionalGuests;
176 176 }
177 177
178 + public function getTotalGuestCount()
179 + {
180 + $additionalGuests = $this->getAdditionalGuests();
181 + $mainGuests = 1;
182 + if ($this->isMultiGuestBooking()) {
183 + $mainGuests = self::where('group_id', $this->group_id)->where('status', 'scheduled')->count();
184 + }
185 + return count($additionalGuests) + $mainGuests;
186 + }
187 +
178 188 public function getHostEmails($excludeHostId = null)
179 189 {
180 190 $hostIds = $this->getHostIds();
181 191
@@ -209,8 +219,29 @@
209 219 {
210 220 return $this->hosts()->pluck('user_id')->toArray();
211 221 }
212 222
223 + public function bookingHosts()
224 + {
225 + return $this->hasMany(BookingHost::class, 'booking_id');
226 + }
227 +
228 + /**
229 + * Limit to bookings the given user may access as a host: either they own
230 + * the booking's calendar, or they are a host on the booking (team events
231 + * such as round-robin/collective put non-owner hosts on fcal_booking_hosts).
232 + */
233 + public function scopeWhereHostAccess($query, $userId)
234 + {
235 + return $query->where(function ($q) use ($userId) {
236 + $q->whereHas('calendar', function ($c) use ($userId) {
237 + $c->where('user_id', $userId);
238 + })->orWhereHas('bookingHosts', function ($h) use ($userId) {
239 + $h->where('user_id', $userId);
240 + });
241 + });
242 + }
243 +
213 244 public function scopeUpcoming($query)
214 245 {
215 246 return $query->where('end_time', '>=', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
216 247 }
@@ -219,8 +250,24 @@
219 250 {
220 251 return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
221 252 }
222 253
254 + public function scopeApplyDateRangeFilter($query, $range)
255 + {
256 + if (empty($range['start_date']) || empty($range['end_date'])) {
257 + return $query;
258 + }
259 +
260 + if (!empty($range['time_zone']) && $range['time_zone'] != 'UTC') {
261 + if (in_array($range['time_zone'], timezone_identifiers_list(), true)) {
262 + $range['start_date'] = gmdate('Y-m-d H:i:s', strtotime($range['start_date'] . ' -1 day'));
263 + $range['end_date'] = gmdate('Y-m-d H:i:s', strtotime($range['end_date'] . ' +1 day'));
264 + }
265 + }
266 +
267 + return $query->whereBetween('start_time', [$range['start_date'], $range['end_date']]);
268 + }
269 +
223 270 public function scopeApplyComputedStatus($query, $status)
224 271 {
225 272 $validStatuses = [
226 273 'upcoming',
@@ -240,17 +287,19 @@
240 287 ->where('status', 'scheduled');
241 288 }
242 289
243 290 if ($status == 'completed') {
244 - return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
245 - ->where('status', '!=', 'cancelled')
246 - ->where('status', '!=', 'rejected')
247 - ->orWhere('status', 'completed'); // maybe cron did not mark few as completed yet
291 + return $query->where(function ($query) {
292 + $query->where(function ($query) {
293 + $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
294 + ->where('status', '!=', 'cancelled')
295 + ->where('status', '!=', 'rejected');
296 + })->orWhere('status', 'completed'); // maybe cron did not mark few as completed yet
297 + });
248 298 }
249 299
250 300 if ($status == 'cancelled') {
251 - return $query->where('status', 'cancelled')
252 - ->orWhere('status', 'rejected');
301 + return $query->whereIn('status', ['cancelled', 'rejected']);
253 302 }
254 303
255 304 if ($status == 'pending') {
256 305 return $query->whereIn('status', ['pending', 'reserved']);
@@ -262,8 +311,25 @@
262 311
263 312 return $query->where('status', $status);
264 313 }
265 314
315 + public function scopeApplyBookingOrderByStatus($query, $status)
316 + {
317 + if ($status == 'upcoming') {
318 + return $query->orderBy('start_time', 'ASC');
319 + }
320 +
321 + if ($status == 'latest_bookings') {
322 + return $query->orderBy('created_at', 'DESC');
323 + }
324 +
325 + if (in_array($status, ['completed', 'cancelled'])) {
326 + return $query->orderBy('updated_at', 'DESC');
327 + }
328 +
329 + return $query->orderBy('start_time', 'DESC');
330 + }
331 +
266 332 public function getFullBookingDateTimeText($timeZone = 'UTC', $isHtml = false)
267 333 {
268 334 $startDateTime = DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'Y-m-d H:i:s');
269 335 $endDateTime = DateTimeHelper::convertFromUtc($this->end_time, $timeZone, 'Y-m-d H:i:s');
@@ -277,24 +343,42 @@
277 343
278 344 return $html;
279 345 }
280 346
347 + public function getPreviousMeetingDateTimeText($timeZone = 'UTC')
348 + {
349 + $previousStartTime = $this->getMeta('previous_meeting_time');
350 + $previousEndTime = gmdate('Y-m-d H:i:s', strtotime($previousStartTime) + ($this->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
351 +
352 + $startDateTime = DateTimeHelper::convertFromUtc($previousStartTime, $timeZone, 'Y-m-d H:i:s');
353 + $endDateTime = DateTimeHelper::convertFromUtc($previousEndTime, $timeZone, 'Y-m-d H:i:s');
354 +
355 + $text = DateTimeHelper::formatToLocale($startDateTime, 'time') . ' - ' . DateTimeHelper::formatToLocale($endDateTime, 'time') . ', ';
356 + $text .= DateTimeHelper::formatToLocale($startDateTime, 'date');
357 +
358 + return $text;
359 + }
360 +
361 + protected function formatBookingDateTime($dateTime, $timeZone = 'UTC')
362 + {
363 + $startDate = DateTimeHelper::convertFromUtc($dateTime, $timeZone, 'D M d, Y');
364 + $startTime = DateTimeHelper::convertFromUtc($dateTime, $timeZone, 'h:ia');
365 +
366 + $localDate = date_i18n('D M d, Y', strtotime($startDate));
367 + $localTime = date_i18n('h:ia', strtotime($startTime));
368 +
369 + return $localDate . ' ' . $localTime;
370 + }
371 +
281 372 public function getShortBookingDateTime($timeZone = 'UTC')
282 373 {
283 - // date format for Fri Feb 10, 2023
284 - $html = DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'D M d, Y');
285 - $html .= ' ' . DateTimeHelper::convertFromUtc($this->start_time, $timeZone, 'h:ia');
286 -
287 - return $html;
374 + return $this->formatBookingDateTime($this->start_time, $timeZone);
288 375 }
289 376
290 377 public function getPreviousMeetingTime($timeZone = 'UTC')
291 378 {
292 379 $previousMeetingTime = $this->getMeta('previous_meeting_time');
293 - $html = DateTimeHelper::convertFromUtc($previousMeetingTime, $timeZone, 'D M d, Y');
294 - $html .= ' ' . DateTimeHelper::convertFromUtc($previousMeetingTime, $timeZone, 'h:ia');
295 -
296 - return $html;
380 + return $this->formatBookingDateTime($previousMeetingTime, $timeZone);
297 381 }
298 382
299 383 public function getAttendeeStartTime($format = 'Y-m-d H:i:s')
300 384 {
@@ -314,8 +398,34 @@
314 398 return $otherBooking->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')';
315 399 })->toArray();
316 400 }
317 401
402 + public function getAllBookingShortTimes($timeZone = 'UTC', $withTimeZone = false)
403 + {
404 + $otherBookings = self::where('parent_id', $this->id)->get();
405 +
406 + $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
407 + return $otherBooking->formatBookingDateTime($otherBooking->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
408 + })->toArray();
409 +
410 + return array_merge($otherTimes, [
411 + $this->formatBookingDateTime($this->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
412 + ]);
413 + }
414 +
415 + public function getAllBookingFullTimes($timeZone = 'UTC', $withTimeZone = false)
416 + {
417 + $otherBookings = self::where('parent_id', $this->id)->get();
418 +
419 + $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
420 + return $otherBooking->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
421 + })->toArray();
422 +
423 + return array_merge($otherTimes, [
424 + $this->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
425 + ]);
426 + }
427 +
318 428 public function getHostAndGuestDetailsHtml()
319 429 {
320 430 $authors = $this->getHostsDetails();
321 431
@@ -320,9 +430,9 @@
320 430 $authors = $this->getHostsDetails();
321 431
322 432 $guestNames = (array) trim($this->first_name . ' ' . $this->last_name);
323 433
324 - if ($this->isMultiGuestBooking()) {
434 + if ($this->isMultiGuestBooking() && !$this->isRecurringBooking()) {
325 435 $otherGuests = self::where('parent_id', $this->id)->get()->map(function ($guest) {
326 436 return trim($guest->first_name . ' ' . $guest->last_name);
327 437 })->toArray();
328 438 $guestNames = array_merge($guestNames, $otherGuests);
@@ -349,39 +459,25 @@
349 459 {
350 460 $details = $this->location_details;
351 461 $locationType = Arr::get($details, 'type');
352 462
353 - if (!$locationType) {
354 - return '--';
355 - }
356 -
357 - if ($locationType == 'in_person_guest') {
358 - return '<b>' . __('Invitee Address:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description');
359 - }
360 -
361 - if ($locationType == 'in_person_organizer') {
362 - $html = '<b>' . Arr::get($details, 'title') . ' </b>';
363 - if ($description = Arr::get($details, 'description')) {
364 - $html .= wpautop($description);
463 + $html = '';
464 + if ($locationType === 'in_person_guest') {
465 + $html = '<b>' . esc_html(__('Invitee Address:', 'fluent-booking')) . ' </b>' . esc_html(Arr::get($details, 'description'));
466 + } else if ($locationType === 'in_person_organizer') {
467 + $html = '<b>' . esc_html(Arr::get($details, 'title')) . ' </b>';
468 + $description = Arr::get($details, 'description');
469 + if ($description) {
470 + $html .= wpautop(wp_kses_post($description));
365 471 }
366 - return $html;
367 - }
368 -
369 - if ($locationType == 'phone_guest') {
370 - return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . $this->phone;
371 - }
372 -
373 - if ($locationType == 'phone_organizer') {
374 - return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description') . __(' (Host phone number)', 'fluent-booking');
375 - }
376 -
377 - if ($locationType == 'custom') {
378 - $html = '<b>' . Arr::get($details, 'title') . '</b>';
379 - $html .= wpautop(Arr::get($details, 'description'));
380 - return $html;
381 - }
382 -
383 - if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
472 + } else if ($locationType === 'phone_guest') {
473 + $html = '<b>' . esc_html(__('Phone Call:', 'fluent-booking')) . ' </b>' . esc_html($this->phone);
474 + } else if ($locationType === 'phone_organizer') {
475 + $html = '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . esc_html(Arr::get($details, 'description')) . esc_html(__(' (Host phone number)', 'fluent-booking'));
476 + } else if ($locationType === 'custom') {
477 + $html = '<b>' . esc_html(Arr::get($details, 'title')) . '</b>';
478 + $html .= wpautop(wp_kses_post(Arr::get($details, 'description')));
479 + } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
384 480 $platformLabels = [
385 481 'google_meet' => __('Google Meet', 'fluent-booking'),
386 482 'online_meeting' => __('Online Meeting', 'fluent-booking'),
387 483 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
@@ -387,18 +483,18 @@
387 483 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
388 484 'ms_teams' => __('MS Teams', 'fluent-booking'),
389 485 ];
390 486
391 - $html = '<b>' . $platformLabels[$locationType] . '</b> ';
392 -
393 - if ($meetingLink = Arr::get($details, 'online_platform_link')) {
394 - $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . __('Join Meeting', 'fluent-booking') . '</a>';
487 + $html = '<b>' . esc_html($platformLabels[$locationType]) . '</b> ';
488 + $meetingLink = Arr::get($details, 'online_platform_link');
489 + if ($meetingLink) {
490 + $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . esc_html(__('Join Meeting', 'fluent-booking')) . '</a>';
395 491 }
396 -
397 - return $html;
492 + } else {
493 + return '--';
398 494 }
399 495
400 - return '--';
496 + return apply_filters('fluent_booking/location_details_html', $html, $details);
401 497 }
402 498
403 499 public function getLocationAsText()
404 500 {
@@ -420,9 +516,11 @@
420 516 if ($locationType == 'phone_guest') {
421 517 return $this->phone;
422 518 }
423 519
424 - return wp_strip_all_tags($this->getLocationDetailsHtml());
520 + $text = wp_strip_all_tags($this->getLocationDetailsHtml());
521 +
522 + return apply_filters('fluent_booking/location_details_text', $text, $details);
425 523 }
426 524
427 525 public function getMessage()
428 526 {
@@ -438,11 +536,34 @@
438 536 }
439 537
440 538 public function getLocationDetailsAttribute($locationDetails)
441 539 {
442 - return \maybe_unserialize($locationDetails);
540 + $value = \maybe_unserialize($locationDetails);
541 +
542 + return is_array($value) ? $value : [];
443 543 }
444 544
545 + public function setOtherInfoAttribute($otherInfo)
546 + {
547 + $originalOtherInfo = $this->getOriginal('other_info');
548 +
549 + $originalOtherInfo = \maybe_unserialize($originalOtherInfo);
550 + $originalOtherInfo = is_array($originalOtherInfo) ? $originalOtherInfo : [];
551 +
552 + $otherInfo = is_array($otherInfo) ? $otherInfo : (array) \maybe_unserialize($otherInfo);
553 +
554 + foreach ($otherInfo as $key => $value) {
555 + $originalOtherInfo[$key] = $value;
556 + }
557 +
558 + $this->attributes['other_info'] = \maybe_serialize($originalOtherInfo);
559 + }
560 +
561 + public function getOtherInfoAttribute($otherInfo)
562 + {
563 + return \maybe_unserialize($otherInfo);
564 + }
565 +
445 566 public function getOngoingStatus()
446 567 {
447 568 if ($this->status != 'scheduled') {
448 569 return [];
@@ -508,11 +629,15 @@
508 629 }
509 630
510 631 public function getCancelReason($isText = false, $isHtml = false)
511 632 {
512 - $row = BookingActivity::where('booking_id', $this->id)
513 - ->where('type', 'cancel_reason')
514 - ->first();
633 + if ($this->relationLoaded('booking_activities')) {
634 + $row = $this->booking_activities->firstWhere('type', 'cancel_reason');
635 + } else {
636 + $row = BookingActivity::where('booking_id', $this->id)
637 + ->where('type', 'cancel_reason')
638 + ->first();
639 + }
515 640
516 641 if ($row) {
517 642 if ($isText) {
518 643 return $row->description;
@@ -643,11 +768,17 @@
643 768
644 769 do_action('fluent_booking/booking_schedule_rejected', $this, $this->calendar_event);
645 770 }
646 771
647 - public function getRescheduleReason()
772 + public function getRescheduleReason($html = false)
648 773 {
649 - return $this->getMeta('reschedule_reason', '');
774 + $rescheduleReason = $this->getMeta('reschedule_reason', '');
775 +
776 + if ($rescheduleReason && $html) {
777 + return wp_unslash($rescheduleReason);
778 + }
779 +
780 + return $rescheduleReason;
650 781 }
651 782
652 783 private function generateBookingTitle($eventTitle, $authorName, $guestName)
653 784 {
@@ -714,11 +845,15 @@
714 845 }
715 846
716 847 public function getMeta($key, $default = '')
717 848 {
718 - $exist = BookingMeta::where('booking_id', $this->id)
719 - ->where('meta_key', $key)
720 - ->first();
849 + if ($this->relationLoaded('booking_meta')) {
850 + $exist = $this->booking_meta->firstWhere('meta_key', $key);
851 + } else {
852 + $exist = BookingMeta::where('booking_id', $this->id)
853 + ->where('meta_key', $key)
854 + ->first();
855 + }
721 856
722 857 if ($exist) {
723 858 return $exist->value;
724 859 }
@@ -733,17 +868,23 @@
733 868 */
734 869 public function scopeSearchBy($query, $search)
735 870 {
736 871 if ($search) {
872 + $escape = function ($value) {
873 + return addcslashes((string) $value, '%_\\');
874 + };
875 +
737 876 $fields = $this->searchable;
738 - $query->where(function ($query) use ($fields, $search) {
739 - $query->where(array_shift($fields), 'LIKE', "%$search%");
877 + $searchEsc = $escape($search);
740 878
879 + $query->where(function ($query) use ($fields, $search, $searchEsc, $escape) {
880 + $query->where(array_shift($fields), 'LIKE', "%$searchEsc%");
881 +
741 882 $nameArray = explode(' ', $search);
742 883 if (count($nameArray) >= 2) {
743 - $query->orWhere(function ($q) use ($nameArray) {
744 - $fname = array_shift($nameArray);
745 - $lastName = implode(' ', $nameArray);
884 + $query->orWhere(function ($q) use ($nameArray, $escape) {
885 + $fname = $escape(array_shift($nameArray));
886 + $lastName = $escape(implode(' ', $nameArray));
746 887 $q->where('first_name', 'LIKE', "%$fname%")
747 888 ->orWhere('last_name', 'LIKE', "%$lastName%");
748 889 });
749 890 }
@@ -748,9 +889,9 @@
748 889 });
749 890 }
750 891
751 892 foreach ($fields as $field) {
752 - $query->orWhere($field, 'LIKE', "%$search%");
893 + $query->orWhere($field, 'LIKE', "%$searchEsc%");
753 894 }
754 895 });
755 896 }
756 897
@@ -844,13 +985,25 @@
844 985 'type' => 'cancel',
845 986 ], Helper::getBookingReceiptLandingBaseUrl());
846 987 }
847 988
989 + /**
990 + * Whether the current user may access this booking: a host of the booking,
991 + * or a manage_own_calendar user who hosts the booking's event.
992 + */
848 993 public function hasBookingAccess()
849 994 {
850 - $hostIds = $this->getHostIds();
851 - $hasAccess = PermissionManager::userCan('manage_all_bookings');
852 - return in_array(get_current_user_id(), $hostIds) || $hasAccess;
995 + $userId = get_current_user_id();
996 +
997 + if (in_array($userId, $this->getHostIds())) {
998 + return true;
999 + }
1000 +
1001 + if (!PermissionManager::userCan('manage_own_calendar')) {
1002 + return false;
1003 + }
1004 +
1005 + return $this->calendar_event && in_array($userId, $this->calendar_event->getHostIds());
853 1006 }
854 1007
855 1008 private function canPerformAction($settings)
856 1009 {
@@ -871,8 +1024,10 @@
871 1024
872 1025 $conditionTime = $conditionValue * 60;
873 1026 if ($conditionUnit == 'hours') {
874 1027 $conditionTime = $conditionTime * 60;
1028 + } elseif ($conditionUnit == 'days') {
1029 + $conditionTime = $conditionTime * 60 * 24;
875 1030 }
876 1031
877 1032 return $bookingStartTime - $currentTime > $conditionTime;
878 1033 }
@@ -898,21 +1053,39 @@
898 1053 {
899 1054 return $this->event_type == 'group' || $this->event_type == 'group_event';
900 1055 }
901 1056
1057 + public function isRoundRobinBooking()
1058 + {
1059 + return $this->event_type == 'round_robin';
1060 + }
1061 +
902 1062 public function isMultiHostBooking()
903 1063 {
904 - return $this->event_type == 'single_event' || $this->event_type == 'group_event';
1064 + return in_array($this->event_type, ['single_event', 'group_event', 'collective']);
905 1065 }
906 1066
1067 + public function isRecurringBooking()
1068 + {
1069 + return Arr::get($this->other_info, 'recurring_count', 0) > 1;
1070 + }
1071 +
907 1072 public function getHostProfiles($public = true)
908 1073 {
909 1074 $hostIds = $this->getHostIds();
910 1075
1076 + cache_users($hostIds);
1077 +
1078 + $calendars = Calendar::whereIn('user_id', $hostIds)
1079 + ->where('type', 'simple')
1080 + ->orderBy('id', 'desc')
1081 + ->with(['metas', 'user', 'user.metas'])
1082 + ->get()
1083 + ->keyBy('user_id');
1084 +
911 1085 $hosts = [];
912 1086 foreach ($hostIds as $hostId) {
913 - $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
914 - if ($calendar) {
1087 + if ($calendar = $calendars->get($hostId)) {
915 1088 $hosts[] = $calendar->getAuthorProfile($public);
916 1089 }
917 1090 }
918 1091
@@ -922,9 +1095,9 @@
922 1095 public function getInviteePhoneNumber($calendarEvent)
923 1096 {
924 1097 $customFormData = $this->getCustomFormData(false);
925 1098
926 - $customFields = BookingFieldService::getBookingFields($calendarEvent);
1099 + $customFields = BookingFieldService::getBookingFields($calendarEvent, true);
927 1100
928 1101 foreach ($customFields as $field) {
929 1102 $fieldValue = Arr::get($customFormData, $field['name']);
930 1103 if ($fieldValue && $field['type'] == 'phone' && Arr::isTrue($field, 'is_sms_number')) {
@@ -1010,15 +1183,30 @@
1010 1183 ->where('type', 'simple')
1011 1184 ->first();
1012 1185
1013 1186 if (!$calendar) {
1014 - return '';
1187 + return 'UTC';
1015 1188 }
1016 1189 return $calendar->author_timezone;
1017 1190 }
1018 - return '';
1191 + return 'UTC';
1019 1192 }
1020 1193
1194 + public function getCalendarLinkDescription()
1195 + {
1196 + $description = str_replace(PHP_EOL, '<br>', $this->getConfirmationData());
1197 +
1198 + if ($this->message) {
1199 + $description .= __('Note: ', 'fluent-booking') . '<br>' . esc_html($this->message) . '<br><br>';
1200 + }
1201 +
1202 + if ($additionalData = $this->getAdditionalData(false)) {
1203 + $description .= '<br>' . str_replace(PHP_EOL, '<br>', $additionalData);
1204 + }
1205 +
1206 + return $description;
1207 + }
1208 +
1021 1209 public function getIcsBookingDescription()
1022 1210 {
1023 1211 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
1024 1212
@@ -1042,9 +1230,9 @@
1042 1230 }
1043 1231
1044 1232 public function getAdditionalData($isHtml = false)
1045 1233 {
1046 - $customData = BookingFieldService::getFormattedCustomBookingData($this);
1234 + $customData = BookingFieldService::getFormattedCustomBookingData($this, $isHtml, true);
1047 1235
1048 1236 if (!$customData) {
1049 1237 return '';
1050 1238 }
@@ -1071,9 +1259,9 @@
1071 1259
1072 1260 return $html;
1073 1261 }
1074 1262
1075 - public function getConfirmationData()
1263 + public function getConfirmationData($html = false)
1076 1264 {
1077 1265 $author = $this->getHostDetails(false);
1078 1266
1079 1267 $guestName = trim($this->first_name . ' ' . $this->last_name);
@@ -1078,9 +1266,11 @@
1078 1266
1079 1267 $guestName = trim($this->first_name . ' ' . $this->last_name);
1080 1268
1081 1269 $bookingTitle = $this->getBookingTitle();
1082 -
1270 +
1271 + $separator = $html ? '<br>' : PHP_EOL;
1272 +
1083 1273 $sections = [
1084 1274 'what' => [
1085 1275 'title' => __('What', 'fluent-booking'),
1086 1276 'content' => $bookingTitle,
@@ -1086,13 +1276,13 @@
1086 1276 'content' => $bookingTitle,
1087 1277 ],
1088 1278 'when' => [
1089 1279 'title' => __('When', 'fluent-booking'),
1090 - 'content' => $this->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')',
1280 + 'content' => $this->getFullBookingDateTimeText($this->person_time_zone, !$html) . ' (' . $this->person_time_zone . ')',
1091 1281 ],
1092 1282 'who' => [
1093 1283 'title' => __('Who', 'fluent-booking'),
1094 - 'content' => $author['name'] . ' - ' . __('Organizer', 'fluent-booking') . PHP_EOL . $author['email'] . PHP_EOL . PHP_EOL . $guestName . PHP_EOL . $this->email
1284 + 'content' => $author['name'] . ' - ' . __('Organizer', 'fluent-booking') . $separator . $author['email'] . $separator . $separator . $guestName . $separator . $this->email
1095 1285 ],
1096 1286 'where' => [
1097 1287 'title' => __('Where', 'fluent-booking'),
1098 1288 'content' => $this->getLocationAsText()
@@ -1097,14 +1287,18 @@
1097 1287 'title' => __('Where', 'fluent-booking'),
1098 1288 'content' => $this->getLocationAsText()
1099 1289 ],
1100 1290 ];
1101 -
1102 - $lines = array_map(function ($section) {
1103 - return $section['title'] . ': ' . PHP_EOL . esc_html($section['content']);
1291 +
1292 + if ($html) {
1293 + unset($sections['who']);
1294 + }
1295 +
1296 + $lines = array_map(function ($section) use ($separator) {
1297 + return $section['title'] . ': ' . $separator . esc_html($section['content']);
1104 1298 }, $sections);
1105 -
1106 - return implode(PHP_EOL . PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
1299 +
1300 + return implode($separator . $separator, $lines) . $separator . $separator;
1107 1301 }
1108 1302
1109 1303 public function getMeetingBookmarks($assetsUrl = '')
1110 1304 {
@@ -1109,45 +1303,52 @@
1109 1303 public function getMeetingBookmarks($assetsUrl = '')
1110 1304 {
1111 1305 $bookingTitle = $this->getBookingTitle();
1112 1306
1113 - $eventTitle = $this->calendar_event->title;
1307 + $eventDescription = $this->getCalendarLinkDescription();
1114 1308
1309 + $eventLocation = LocationService::getBookingLocationUrl($this);
1310 +
1311 + $startTimestamp = strtotime($this->start_time);
1312 + $endTimestamp = strtotime($this->end_time);
1313 +
1314 + $compactStart = gmdate('Ymd\THis\Z', $startTimestamp);
1315 + $compactEnd = gmdate('Ymd\THis\Z', $endTimestamp);
1316 +
1317 + $isoStart = gmdate('Y-m-d\TH:i:s\Z', $startTimestamp);
1318 + $isoEnd = gmdate('Y-m-d\TH:i:s\Z', $endTimestamp);
1319 +
1320 + $googleParams = http_build_query([
1321 + 'dates' => $compactStart . '/' . $compactEnd,
1322 + 'text' => $bookingTitle,
1323 + 'details' => $eventDescription,
1324 + 'location' => $eventLocation,
1325 + ], '', '&', PHP_QUERY_RFC3986);
1326 +
1327 + $outlookParams = http_build_query([
1328 + 'path' => '/calendar/action/compose',
1329 + 'rru' => 'addevent',
1330 + 'startdt' => $isoStart,
1331 + 'enddt' => $isoEnd,
1332 + 'subject' => $bookingTitle,
1333 + 'body' => $eventDescription,
1334 + 'location' => $eventLocation,
1335 + ], '', '&', PHP_QUERY_RFC3986);
1336 +
1115 1337 return apply_filters('fluent_booking/meeting_bookmarks', [
1116 1338 'google' => [
1117 1339 'title' => __('Google Calendar', 'fluent-booking'),
1118 - 'url' => add_query_arg([
1119 - 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1120 - 'text' => $bookingTitle,
1121 - 'details' => $eventTitle,
1122 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1123 - ], 'https://calendar.google.com/calendar/r/eventedit'),
1340 + 'url' => 'https://calendar.google.com/calendar/render?action=TEMPLATE&' . $googleParams,
1124 1341 'icon' => $assetsUrl . 'images/g-icon.svg'
1125 1342 ],
1126 1343 'outlook' => [
1127 1344 'title' => __('Outlook', 'fluent-booking'),
1128 - 'url' => add_query_arg([
1129 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1130 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1131 - 'subject' => $bookingTitle,
1132 - 'path' => '/calendar/action/compose',
1133 - 'body' => $eventTitle,
1134 - 'rru' => 'addevent',
1135 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1136 - ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1345 + 'url' => 'https://outlook.live.com/calendar/0/deeplink/compose?' . $outlookParams,
1137 1346 'icon' => $assetsUrl . 'images/ol-icon.svg'
1138 1347 ],
1139 1348 'msoffice' => [
1140 1349 'title' => __('Microsoft Office', 'fluent-booking'),
1141 - 'url' => add_query_arg([
1142 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1143 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1144 - 'subject' => $bookingTitle,
1145 - 'path' => '/calendar/action/compose',
1146 - 'body' => $eventTitle,
1147 - 'rru' => 'addevent',
1148 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1149 - ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1350 + 'url' => 'https://outlook.office.com/calendar/0/deeplink/compose?' . $outlookParams,
1150 1351 'icon' => $assetsUrl . 'images/msoffice.svg'
1151 1352 ],
1152 1353 'other' => [
1153 1354 'title' => __('Other Calendar', 'fluent-booking'),