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 +344 -118 1.5.01trunk 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 {
@@ -305,14 +389,56 @@
305 389 {
306 390 return DateTimeHelper::convertFromUtc($this->end_time, $this->person_time_zone, $format);
307 391 }
308 392
393 + public function getOtherBookingTimes()
394 + {
395 + $otherBookings = self::where('parent_id', $this->id)->get();
396 +
397 + return $otherBookings->map(function ($otherBooking) {
398 + return $otherBooking->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')';
399 + })->toArray();
400 + }
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 +
309 428 public function getHostAndGuestDetailsHtml()
310 429 {
311 430 $authors = $this->getHostsDetails();
312 431
313 - $guestName = trim($this->first_name . ' ' . $this->last_name);
432 + $guestNames = (array) trim($this->first_name . ' ' . $this->last_name);
314 433
434 + if ($this->isMultiGuestBooking() && !$this->isRecurringBooking()) {
435 + $otherGuests = self::where('parent_id', $this->id)->get()->map(function ($guest) {
436 + return trim($guest->first_name . ' ' . $guest->last_name);
437 + })->toArray();
438 + $guestNames = array_merge($guestNames, $otherGuests);
439 + }
440 +
315 441 $hostUserId = $this->host_user_id;
316 442
317 443 $authorListHtml = '<ul class="fcal_listed">';
318 444
@@ -320,9 +446,11 @@
320 446 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
321 447 $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
322 448 }
323 449
324 - $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
450 + foreach ($guestNames as $guestName) {
451 + $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
452 + }
325 453 $authorListHtml .= '</ul>';
326 454
327 455 return $authorListHtml;
328 456 }
@@ -331,39 +459,25 @@
331 459 {
332 460 $details = $this->location_details;
333 461 $locationType = Arr::get($details, 'type');
334 462
335 - if (!$locationType) {
336 - return '--';
337 - }
338 -
339 - if ($locationType == 'in_person_guest') {
340 - return '<b>' . __('Invitee Address:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description');
341 - }
342 -
343 - if ($locationType == 'in_person_organizer') {
344 - $html = '<b>' . Arr::get($details, 'title') . ' </b>';
345 - if ($description = Arr::get($details, 'description')) {
346 - $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));
347 471 }
348 - return $html;
349 - }
350 -
351 - if ($locationType == 'phone_guest') {
352 - return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . $this->phone;
353 - }
354 -
355 - if ($locationType == 'phone_organizer') {
356 - return '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description') . __(' (Host phone number)', 'fluent-booking');
357 - }
358 -
359 - if ($locationType == 'custom') {
360 - $html = '<b>' . Arr::get($details, 'title') . '</b>';
361 - $html .= wpautop(Arr::get($details, 'description'));
362 - return $html;
363 - }
364 -
365 - 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'])) {
366 480 $platformLabels = [
367 481 'google_meet' => __('Google Meet', 'fluent-booking'),
368 482 'online_meeting' => __('Online Meeting', 'fluent-booking'),
369 483 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
@@ -369,18 +483,18 @@
369 483 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
370 484 'ms_teams' => __('MS Teams', 'fluent-booking'),
371 485 ];
372 486
373 - $html = '<b>' . $platformLabels[$locationType] . '</b> ';
374 -
375 - if ($meetingLink = Arr::get($details, 'online_platform_link')) {
376 - $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>';
377 491 }
378 -
379 - return $html;
492 + } else {
493 + return '--';
380 494 }
381 495
382 - return '--';
496 + return apply_filters('fluent_booking/location_details_html', $html, $details);
383 497 }
384 498
385 499 public function getLocationAsText()
386 500 {
@@ -402,9 +516,11 @@
402 516 if ($locationType == 'phone_guest') {
403 517 return $this->phone;
404 518 }
405 519
406 - 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);
407 523 }
408 524
409 525 public function getMessage()
410 526 {
@@ -420,11 +536,34 @@
420 536 }
421 537
422 538 public function getLocationDetailsAttribute($locationDetails)
423 539 {
424 - return \maybe_unserialize($locationDetails);
540 + $value = \maybe_unserialize($locationDetails);
541 +
542 + return is_array($value) ? $value : [];
425 543 }
426 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 +
427 566 public function getOngoingStatus()
428 567 {
429 568 if ($this->status != 'scheduled') {
430 569 return [];
@@ -490,11 +629,15 @@
490 629 }
491 630
492 631 public function getCancelReason($isText = false, $isHtml = false)
493 632 {
494 - $row = BookingActivity::where('booking_id', $this->id)
495 - ->where('type', 'cancel_reason')
496 - ->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 + }
497 640
498 641 if ($row) {
499 642 if ($isText) {
500 643 return $row->description;
@@ -625,11 +768,17 @@
625 768
626 769 do_action('fluent_booking/booking_schedule_rejected', $this, $this->calendar_event);
627 770 }
628 771
629 - public function getRescheduleReason()
772 + public function getRescheduleReason($html = false)
630 773 {
631 - 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;
632 781 }
633 782
634 783 private function generateBookingTitle($eventTitle, $authorName, $guestName)
635 784 {
@@ -687,13 +836,24 @@
687 836 'value' => $value
688 837 ]);
689 838 }
690 839
840 + public function deleteMeta($key)
841 + {
842 + return BookingMeta::where('booking_id', $this->id)
843 + ->where('meta_key', $key)
844 + ->delete();
845 + }
846 +
691 847 public function getMeta($key, $default = '')
692 848 {
693 - $exist = BookingMeta::where('booking_id', $this->id)
694 - ->where('meta_key', $key)
695 - ->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 + }
696 856
697 857 if ($exist) {
698 858 return $exist->value;
699 859 }
@@ -708,17 +868,23 @@
708 868 */
709 869 public function scopeSearchBy($query, $search)
710 870 {
711 871 if ($search) {
872 + $escape = function ($value) {
873 + return addcslashes((string) $value, '%_\\');
874 + };
875 +
712 876 $fields = $this->searchable;
713 - $query->where(function ($query) use ($fields, $search) {
714 - $query->where(array_shift($fields), 'LIKE', "%$search%");
877 + $searchEsc = $escape($search);
715 878
879 + $query->where(function ($query) use ($fields, $search, $searchEsc, $escape) {
880 + $query->where(array_shift($fields), 'LIKE', "%$searchEsc%");
881 +
716 882 $nameArray = explode(' ', $search);
717 883 if (count($nameArray) >= 2) {
718 - $query->orWhere(function ($q) use ($nameArray) {
719 - $fname = array_shift($nameArray);
720 - $lastName = implode(' ', $nameArray);
884 + $query->orWhere(function ($q) use ($nameArray, $escape) {
885 + $fname = $escape(array_shift($nameArray));
886 + $lastName = $escape(implode(' ', $nameArray));
721 887 $q->where('first_name', 'LIKE', "%$fname%")
722 888 ->orWhere('last_name', 'LIKE', "%$lastName%");
723 889 });
724 890 }
@@ -723,9 +889,9 @@
723 889 });
724 890 }
725 891
726 892 foreach ($fields as $field) {
727 - $query->orWhere($field, 'LIKE', "%$search%");
893 + $query->orWhere($field, 'LIKE', "%$searchEsc%");
728 894 }
729 895 });
730 896 }
731 897
@@ -819,13 +985,25 @@
819 985 'type' => 'cancel',
820 986 ], Helper::getBookingReceiptLandingBaseUrl());
821 987 }
822 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 + */
823 993 public function hasBookingAccess()
824 994 {
825 - $hostIds = $this->getHostIds();
826 - $hasAccess = PermissionManager::userCan('manage_all_bookings');
827 - 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());
828 1006 }
829 1007
830 1008 private function canPerformAction($settings)
831 1009 {
@@ -846,8 +1024,10 @@
846 1024
847 1025 $conditionTime = $conditionValue * 60;
848 1026 if ($conditionUnit == 'hours') {
849 1027 $conditionTime = $conditionTime * 60;
1028 + } elseif ($conditionUnit == 'days') {
1029 + $conditionTime = $conditionTime * 60 * 24;
850 1030 }
851 1031
852 1032 return $bookingStartTime - $currentTime > $conditionTime;
853 1033 }
@@ -873,21 +1053,39 @@
873 1053 {
874 1054 return $this->event_type == 'group' || $this->event_type == 'group_event';
875 1055 }
876 1056
1057 + public function isRoundRobinBooking()
1058 + {
1059 + return $this->event_type == 'round_robin';
1060 + }
1061 +
877 1062 public function isMultiHostBooking()
878 1063 {
879 - return $this->event_type == 'single_event' || $this->event_type == 'group_event';
1064 + return in_array($this->event_type, ['single_event', 'group_event', 'collective']);
880 1065 }
881 1066
1067 + public function isRecurringBooking()
1068 + {
1069 + return Arr::get($this->other_info, 'recurring_count', 0) > 1;
1070 + }
1071 +
882 1072 public function getHostProfiles($public = true)
883 1073 {
884 1074 $hostIds = $this->getHostIds();
885 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 +
886 1085 $hosts = [];
887 1086 foreach ($hostIds as $hostId) {
888 - $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
889 - if ($calendar) {
1087 + if ($calendar = $calendars->get($hostId)) {
890 1088 $hosts[] = $calendar->getAuthorProfile($public);
891 1089 }
892 1090 }
893 1091
@@ -897,9 +1095,9 @@
897 1095 public function getInviteePhoneNumber($calendarEvent)
898 1096 {
899 1097 $customFormData = $this->getCustomFormData(false);
900 1098
901 - $customFields = BookingFieldService::getBookingFields($calendarEvent);
1099 + $customFields = BookingFieldService::getBookingFields($calendarEvent, true);
902 1100
903 1101 foreach ($customFields as $field) {
904 1102 $fieldValue = Arr::get($customFormData, $field['name']);
905 1103 if ($fieldValue && $field['type'] == 'phone' && Arr::isTrue($field, 'is_sms_number')) {
@@ -985,15 +1183,30 @@
985 1183 ->where('type', 'simple')
986 1184 ->first();
987 1185
988 1186 if (!$calendar) {
989 - return '';
1187 + return 'UTC';
990 1188 }
991 1189 return $calendar->author_timezone;
992 1190 }
993 - return '';
1191 + return 'UTC';
994 1192 }
995 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 +
996 1209 public function getIcsBookingDescription()
997 1210 {
998 1211 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
999 1212
@@ -1017,9 +1230,9 @@
1017 1230 }
1018 1231
1019 1232 public function getAdditionalData($isHtml = false)
1020 1233 {
1021 - $customData = BookingFieldService::getFormattedCustomBookingData($this);
1234 + $customData = BookingFieldService::getFormattedCustomBookingData($this, $isHtml, true);
1022 1235
1023 1236 if (!$customData) {
1024 1237 return '';
1025 1238 }
@@ -1046,9 +1259,9 @@
1046 1259
1047 1260 return $html;
1048 1261 }
1049 1262
1050 - public function getConfirmationData()
1263 + public function getConfirmationData($html = false)
1051 1264 {
1052 1265 $author = $this->getHostDetails(false);
1053 1266
1054 1267 $guestName = trim($this->first_name . ' ' . $this->last_name);
@@ -1053,9 +1266,11 @@
1053 1266
1054 1267 $guestName = trim($this->first_name . ' ' . $this->last_name);
1055 1268
1056 1269 $bookingTitle = $this->getBookingTitle();
1057 -
1270 +
1271 + $separator = $html ? '<br>' : PHP_EOL;
1272 +
1058 1273 $sections = [
1059 1274 'what' => [
1060 1275 'title' => __('What', 'fluent-booking'),
1061 1276 'content' => $bookingTitle,
@@ -1061,13 +1276,13 @@
1061 1276 'content' => $bookingTitle,
1062 1277 ],
1063 1278 'when' => [
1064 1279 'title' => __('When', 'fluent-booking'),
1065 - '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 . ')',
1066 1281 ],
1067 1282 'who' => [
1068 1283 'title' => __('Who', 'fluent-booking'),
1069 - '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
1070 1285 ],
1071 1286 'where' => [
1072 1287 'title' => __('Where', 'fluent-booking'),
1073 1288 'content' => $this->getLocationAsText()
@@ -1072,14 +1287,18 @@
1072 1287 'title' => __('Where', 'fluent-booking'),
1073 1288 'content' => $this->getLocationAsText()
1074 1289 ],
1075 1290 ];
1076 -
1077 - $lines = array_map(function ($section) {
1078 - 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']);
1079 1298 }, $sections);
1080 -
1081 - return implode(PHP_EOL . PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
1299 +
1300 + return implode($separator . $separator, $lines) . $separator . $separator;
1082 1301 }
1083 1302
1084 1303 public function getMeetingBookmarks($assetsUrl = '')
1085 1304 {
@@ -1084,45 +1303,52 @@
1084 1303 public function getMeetingBookmarks($assetsUrl = '')
1085 1304 {
1086 1305 $bookingTitle = $this->getBookingTitle();
1087 1306
1088 - $eventTitle = $this->calendar_event->title;
1307 + $eventDescription = $this->getCalendarLinkDescription();
1089 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 +
1090 1337 return apply_filters('fluent_booking/meeting_bookmarks', [
1091 1338 'google' => [
1092 1339 'title' => __('Google Calendar', 'fluent-booking'),
1093 - 'url' => add_query_arg([
1094 - 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1095 - 'text' => $bookingTitle,
1096 - 'details' => $eventTitle,
1097 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1098 - ], 'https://calendar.google.com/calendar/r/eventedit'),
1340 + 'url' => 'https://calendar.google.com/calendar/render?action=TEMPLATE&' . $googleParams,
1099 1341 'icon' => $assetsUrl . 'images/g-icon.svg'
1100 1342 ],
1101 1343 'outlook' => [
1102 1344 'title' => __('Outlook', 'fluent-booking'),
1103 - 'url' => add_query_arg([
1104 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1105 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1106 - 'subject' => $bookingTitle,
1107 - 'path' => '/calendar/action/compose',
1108 - 'body' => $eventTitle,
1109 - 'rru' => 'addevent',
1110 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1111 - ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1345 + 'url' => 'https://outlook.live.com/calendar/0/deeplink/compose?' . $outlookParams,
1112 1346 'icon' => $assetsUrl . 'images/ol-icon.svg'
1113 1347 ],
1114 1348 'msoffice' => [
1115 1349 'title' => __('Microsoft Office', 'fluent-booking'),
1116 - 'url' => add_query_arg([
1117 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1118 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1119 - 'subject' => $bookingTitle,
1120 - 'path' => '/calendar/action/compose',
1121 - 'body' => $eventTitle,
1122 - 'rru' => 'addevent',
1123 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1124 - ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1350 + 'url' => 'https://outlook.office.com/calendar/0/deeplink/compose?' . $outlookParams,
1125 1351 'icon' => $assetsUrl . 'images/msoffice.svg'
1126 1352 ],
1127 1353 'other' => [
1128 1354 'title' => __('Other Calendar', 'fluent-booking'),