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/Models/Booking.php +221 -81 1.10.0 → 2.5.0 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace FluentBooking\App\Models;
4 4
5 +use FluentBooking\App\App;
5 6 use FluentBooking\App\Models\Model;
6 7 use FluentBooking\App\Services\BookingFieldService;
7 8 use FluentBooking\App\Services\LocationService;
8 9 use FluentBooking\App\Services\DateTimeHelper;
@@ -58,9 +59,8 @@
58 59 'utm_term',
59 60 'utm_content'
60 61 ];
61 62
62 -
63 63 /**
64 64 * $searchable Columns in table to search
65 65 * @var array
66 66 */
@@ -138,9 +138,11 @@
138 138 }
139 139
140 140 public static function assignNextGroupId()
141 141 {
142 - $lastEvent = static::orderBy('group_id', 'desc')->first(['group_id']);
142 + // Queue on one row the insert never touches; locking the max row alone deadlocks.
143 + App::getInstance('db')->table('options')->where('option_name', 'fcal_booking_group_lock')->lockForUpdate()->first();
144 + $lastEvent = static::orderBy('group_id', 'desc')->lockForUpdate()->first(['group_id']);
143 145
144 146 return $lastEvent ? $lastEvent->group_id + 1 : 1;
145 147 }
146 148
@@ -169,9 +171,9 @@
169 171 return [];
170 172 }
171 173
172 174 if ($isHtml) {
173 - return wpautop(implode('<br>', $additionalGuests));
175 + return wpautop(implode('<br>', array_map('esc_html', $additionalGuests)));
174 176 }
175 177
176 178 return $additionalGuests;
177 179 }
@@ -220,8 +222,29 @@
220 222 {
221 223 return $this->hosts()->pluck('user_id')->toArray();
222 224 }
223 225
226 + public function bookingHosts()
227 + {
228 + return $this->hasMany(BookingHost::class, 'booking_id');
229 + }
230 +
231 + /**
232 + * Limit to bookings the given user may access as a host: either they own
233 + * the booking's calendar, or they are a host on the booking (team events
234 + * such as round-robin/collective put non-owner hosts on fcal_booking_hosts).
235 + */
236 + public function scopeWhereHostAccess($query, $userId)
237 + {
238 + return $query->where(function ($q) use ($userId) {
239 + $q->whereHas('calendar', function ($c) use ($userId) {
240 + $c->where('user_id', $userId);
241 + })->orWhereHas('bookingHosts', function ($h) use ($userId) {
242 + $h->where('user_id', $userId);
243 + });
244 + });
245 + }
246 +
224 247 public function scopeUpcoming($query)
225 248 {
226 249 return $query->where('end_time', '>=', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
227 250 }
@@ -237,10 +260,12 @@
237 260 return $query;
238 261 }
239 262
240 263 if (!empty($range['time_zone']) && $range['time_zone'] != 'UTC') {
241 - $range['start_date'] = gmdate('Y-m-d H:i:s', strtotime($range['start_date'] . ' -1 day'));
242 - $range['end_date'] = gmdate('Y-m-d H:i:s', strtotime($range['end_date'] . ' +1 day'));
264 + if (in_array($range['time_zone'], timezone_identifiers_list(), true)) {
265 + $range['start_date'] = gmdate('Y-m-d H:i:s', strtotime($range['start_date'] . ' -1 day'));
266 + $range['end_date'] = gmdate('Y-m-d H:i:s', strtotime($range['end_date'] . ' +1 day'));
267 + }
243 268 }
244 269
245 270 return $query->whereBetween('start_time', [$range['start_date'], $range['end_date']]);
246 271 }
@@ -265,17 +290,19 @@
265 290 ->where('status', 'scheduled');
266 291 }
267 292
268 293 if ($status == 'completed') {
269 - return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
270 - ->where('status', '!=', 'cancelled')
271 - ->where('status', '!=', 'rejected')
272 - ->orWhere('status', 'completed'); // maybe cron did not mark few as completed yet
294 + return $query->where(function ($query) {
295 + $query->where(function ($query) {
296 + $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
297 + ->where('status', '!=', 'cancelled')
298 + ->where('status', '!=', 'rejected');
299 + })->orWhere('status', 'completed'); // maybe cron did not mark few as completed yet
300 + });
273 301 }
274 302
275 303 if ($status == 'cancelled') {
276 - return $query->where('status', 'cancelled')
277 - ->orWhere('status', 'rejected');
304 + return $query->whereIn('status', ['cancelled', 'rejected']);
278 305 }
279 306
280 307 if ($status == 'pending') {
281 308 return $query->whereIn('status', ['pending', 'reserved']);
@@ -374,21 +401,43 @@
374 401 return $otherBooking->getFullBookingDateTimeText($this->person_time_zone, true) . ' (' . $this->person_time_zone . ')';
375 402 })->toArray();
376 403 }
377 404
378 - public function getAllBookingShortTimes($timeZone = 'UTC')
405 + public function getAllBookingShortTimes($timeZone = 'UTC', $withTimeZone = false)
379 406 {
380 - $otherBookings = self::where('parent_id', $this->id)->get();
407 + $otherBookings = $this->getOwnChildBookings();
381 408
382 - $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone) {
383 - return $otherBooking->formatBookingDateTime($otherBooking->start_time, $timeZone);
409 + $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
410 + return $otherBooking->formatBookingDateTime($otherBooking->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
384 411 })->toArray();
385 412
386 413 return array_merge($otherTimes, [
387 - $this->formatBookingDateTime($this->start_time, $timeZone)
414 + $this->formatBookingDateTime($this->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
388 415 ]);
389 416 }
390 417
418 + public function getAllBookingFullTimes($timeZone = 'UTC', $withTimeZone = false)
419 + {
420 + $otherBookings = $this->getOwnChildBookings();
421 +
422 + $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
423 + return $otherBooking->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
424 + })->toArray();
425 +
426 + return array_merge($otherTimes, [
427 + $this->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
428 + ]);
429 + }
430 +
431 + /**
432 + * Additional guests on a group booking share the parent link too, each
433 + * with their own email, so only this guest's other times are theirs.
434 + */
435 + private function getOwnChildBookings()
436 + {
437 + return self::where('parent_id', $this->id)->where('email', $this->email)->get();
438 + }
439 +
391 440 public function getHostAndGuestDetailsHtml()
392 441 {
393 442 $authors = $this->getHostsDetails();
394 443
@@ -393,9 +442,9 @@
393 442 $authors = $this->getHostsDetails();
394 443
395 444 $guestNames = (array) trim($this->first_name . ' ' . $this->last_name);
396 445
397 - if ($this->isMultiGuestBooking()) {
446 + if ($this->isMultiGuestBooking() && !$this->isRecurringBooking()) {
398 447 $otherGuests = self::where('parent_id', $this->id)->get()->map(function ($guest) {
399 448 return trim($guest->first_name . ' ' . $guest->last_name);
400 449 })->toArray();
401 450 $guestNames = array_merge($guestNames, $otherGuests);
@@ -406,13 +455,13 @@
406 455 $authorListHtml = '<ul class="fcal_listed">';
407 456
408 457 foreach ($authors as $author) {
409 458 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
410 - $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
459 + $authorListHtml .= '<li class="fcal_host_name">' . esc_html($author['name']) . $authorBadge . '</li>';
411 460 }
412 461
413 462 foreach ($guestNames as $guestName) {
414 - $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
463 + $authorListHtml .= '<li class="fcal_guest_name">' . esc_html($guestName) . '</li>';
415 464 }
416 465 $authorListHtml .= '</ul>';
417 466
418 467 return $authorListHtml;
@@ -424,22 +473,22 @@
424 473 $locationType = Arr::get($details, 'type');
425 474
426 475 $html = '';
427 476 if ($locationType === 'in_person_guest') {
428 - $html = '<b>' . __('Invitee Address:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description');
477 + $html = '<b>' . esc_html(__('Invitee Address:', 'fluent-booking')) . ' </b>' . esc_html(Arr::get($details, 'description'));
429 478 } else if ($locationType === 'in_person_organizer') {
430 - $html = '<b>' . Arr::get($details, 'title') . ' </b>';
479 + $html = '<b>' . esc_html(Arr::get($details, 'title')) . ' </b>';
431 480 $description = Arr::get($details, 'description');
432 481 if ($description) {
433 - $html .= wpautop($description);
482 + $html .= wpautop(wp_kses_post($description));
434 483 }
435 484 } else if ($locationType === 'phone_guest') {
436 - $html = '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . $this->phone;
485 + $html = '<b>' . esc_html(__('Phone Call:', 'fluent-booking')) . ' </b>' . esc_html($this->phone);
437 486 } else if ($locationType === 'phone_organizer') {
438 - $html = '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . Arr::get($details, 'description') . __(' (Host phone number)', 'fluent-booking');
487 + $html = '<b>' . __('Phone Call:', 'fluent-booking') . ' </b>' . esc_html(Arr::get($details, 'description')) . esc_html(__(' (Host phone number)', 'fluent-booking'));
439 488 } else if ($locationType === 'custom') {
440 - $html = '<b>' . Arr::get($details, 'title') . '</b>';
441 - $html .= wpautop(Arr::get($details, 'description'));
489 + $html = '<b>' . esc_html(Arr::get($details, 'title')) . '</b>';
490 + $html .= wpautop(wp_kses_post(Arr::get($details, 'description')));
442 491 } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
443 492 $platformLabels = [
444 493 'google_meet' => __('Google Meet', 'fluent-booking'),
445 494 'online_meeting' => __('Online Meeting', 'fluent-booking'),
@@ -446,12 +495,12 @@
446 495 'zoom_meeting' => __('Zoom Video', 'fluent-booking'),
447 496 'ms_teams' => __('MS Teams', 'fluent-booking'),
448 497 ];
449 498
450 - $html = '<b>' . $platformLabels[$locationType] . '</b> ';
499 + $html = '<b>' . esc_html($platformLabels[$locationType]) . '</b> ';
451 500 $meetingLink = Arr::get($details, 'online_platform_link');
452 501 if ($meetingLink) {
453 - $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . __('Join Meeting', 'fluent-booking') . '</a>';
502 + $html .= '<a target="_blank" href="' . esc_url($meetingLink) . '">' . esc_html(__('Join Meeting', 'fluent-booking')) . '</a>';
454 503 }
455 504 } else {
456 505 return '--';
457 506 }
@@ -499,11 +548,34 @@
499 548 }
500 549
501 550 public function getLocationDetailsAttribute($locationDetails)
502 551 {
503 - return \maybe_unserialize($locationDetails);
552 + $value = \maybe_unserialize($locationDetails);
553 +
554 + return is_array($value) ? $value : [];
504 555 }
505 556
557 + public function setOtherInfoAttribute($otherInfo)
558 + {
559 + $originalOtherInfo = $this->getOriginal('other_info');
560 +
561 + $originalOtherInfo = \maybe_unserialize($originalOtherInfo);
562 + $originalOtherInfo = is_array($originalOtherInfo) ? $originalOtherInfo : [];
563 +
564 + $otherInfo = is_array($otherInfo) ? $otherInfo : (array) \maybe_unserialize($otherInfo);
565 +
566 + foreach ($otherInfo as $key => $value) {
567 + $originalOtherInfo[$key] = $value;
568 + }
569 +
570 + $this->attributes['other_info'] = \maybe_serialize($originalOtherInfo);
571 + }
572 +
573 + public function getOtherInfoAttribute($otherInfo)
574 + {
575 + return \maybe_unserialize($otherInfo);
576 + }
577 +
506 578 public function getOngoingStatus()
507 579 {
508 580 if ($this->status != 'scheduled') {
509 581 return [];
@@ -569,11 +641,15 @@
569 641 }
570 642
571 643 public function getCancelReason($isText = false, $isHtml = false)
572 644 {
573 - $row = BookingActivity::where('booking_id', $this->id)
574 - ->where('type', 'cancel_reason')
575 - ->first();
645 + if ($this->relationLoaded('booking_activities')) {
646 + $row = $this->booking_activities->firstWhere('type', 'cancel_reason');
647 + } else {
648 + $row = BookingActivity::where('booking_id', $this->id)
649 + ->where('type', 'cancel_reason')
650 + ->first();
651 + }
576 652
577 653 if ($row) {
578 654 if ($isText) {
579 655 return $row->description;
@@ -578,9 +654,9 @@
578 654 if ($isText) {
579 655 return $row->description;
580 656 }
581 657 if ($isHtml) {
582 - return wp_unslash($row->description);
658 + return esc_html(wp_unslash($row->description));
583 659 }
584 660 }
585 661
586 662 return $row;
@@ -596,9 +672,9 @@
596 672 if ($isText) {
597 673 return $row->description;
598 674 }
599 675 if ($isHtml) {
600 - return wp_unslash($row->description);
676 + return esc_html(wp_unslash($row->description));
601 677 }
602 678 }
603 679
604 680 return $row;
@@ -739,10 +815,15 @@
739 815 $bookingTitle = EditorShortCodeParser::parse($bookingTitle, $this);
740 816
741 817 $bookingTitle = $bookingTitle ?: $this->generateBookingTitle($eventTitle, $authorName, $guestName);
742 818
743 - if ($html && strpos($bookingTitle, $eventTitle) !== false) {
744 - $bookingTitle = str_replace($eventTitle, "<strong>{$eventTitle}</strong>", $bookingTitle);
819 + if ($html) {
820 + $bookingTitle = esc_html($bookingTitle);
821 + $eventTitle = esc_html($eventTitle);
822 +
823 + if (strpos($bookingTitle, $eventTitle) !== false) {
824 + $bookingTitle = str_replace($eventTitle, "<strong>{$eventTitle}</strong>", $bookingTitle);
825 + }
745 826 }
746 827
747 828 return apply_filters('fluent_booking/booking_meeting_title', $bookingTitle, $authorName, $guestName, $calendarEvent, $this);
748 829 }
@@ -781,11 +862,15 @@
781 862 }
782 863
783 864 public function getMeta($key, $default = '')
784 865 {
785 - $exist = BookingMeta::where('booking_id', $this->id)
786 - ->where('meta_key', $key)
787 - ->first();
866 + if ($this->relationLoaded('booking_meta')) {
867 + $exist = $this->booking_meta->firstWhere('meta_key', $key);
868 + } else {
869 + $exist = BookingMeta::where('booking_id', $this->id)
870 + ->where('meta_key', $key)
871 + ->first();
872 + }
788 873
789 874 if ($exist) {
790 875 return $exist->value;
791 876 }
@@ -800,17 +885,23 @@
800 885 */
801 886 public function scopeSearchBy($query, $search)
802 887 {
803 888 if ($search) {
889 + $escape = function ($value) {
890 + return addcslashes((string) $value, '%_\\');
891 + };
892 +
804 893 $fields = $this->searchable;
805 - $query->where(function ($query) use ($fields, $search) {
806 - $query->where(array_shift($fields), 'LIKE', "%$search%");
894 + $searchEsc = $escape($search);
807 895
896 + $query->where(function ($query) use ($fields, $search, $searchEsc, $escape) {
897 + $query->where(array_shift($fields), 'LIKE', "%$searchEsc%");
898 +
808 899 $nameArray = explode(' ', $search);
809 900 if (count($nameArray) >= 2) {
810 - $query->orWhere(function ($q) use ($nameArray) {
811 - $fname = array_shift($nameArray);
812 - $lastName = implode(' ', $nameArray);
901 + $query->orWhere(function ($q) use ($nameArray, $escape) {
902 + $fname = $escape(array_shift($nameArray));
903 + $lastName = $escape(implode(' ', $nameArray));
813 904 $q->where('first_name', 'LIKE', "%$fname%")
814 905 ->orWhere('last_name', 'LIKE', "%$lastName%");
815 906 });
816 907 }
@@ -815,9 +906,9 @@
815 906 });
816 907 }
817 908
818 909 foreach ($fields as $field) {
819 - $query->orWhere($field, 'LIKE', "%$search%");
910 + $query->orWhere($field, 'LIKE', "%$searchEsc%");
820 911 }
821 912 });
822 913 }
823 914
@@ -911,13 +1002,25 @@
911 1002 'type' => 'cancel',
912 1003 ], Helper::getBookingReceiptLandingBaseUrl());
913 1004 }
914 1005
1006 + /**
1007 + * Whether the current user may access this booking: a host of the booking,
1008 + * or a manage_own_calendar user who hosts the booking's event.
1009 + */
915 1010 public function hasBookingAccess()
916 1011 {
917 - $hostIds = $this->getHostIds();
918 - $hasAccess = PermissionManager::userCan('manage_all_bookings');
919 - return in_array(get_current_user_id(), $hostIds) || $hasAccess;
1012 + $userId = get_current_user_id();
1013 +
1014 + if (in_array($userId, $this->getHostIds())) {
1015 + return true;
1016 + }
1017 +
1018 + if (!PermissionManager::userCan('manage_own_calendar')) {
1019 + return false;
1020 + }
1021 +
1022 + return $this->calendar_event && in_array($userId, $this->calendar_event->getHostIds());
920 1023 }
921 1024
922 1025 private function canPerformAction($settings)
923 1026 {
@@ -938,8 +1041,10 @@
938 1041
939 1042 $conditionTime = $conditionValue * 60;
940 1043 if ($conditionUnit == 'hours') {
941 1044 $conditionTime = $conditionTime * 60;
1045 + } elseif ($conditionUnit == 'days') {
1046 + $conditionTime = $conditionTime * 60 * 24;
942 1047 }
943 1048
944 1049 return $bookingStartTime - $currentTime > $conditionTime;
945 1050 }
@@ -975,16 +1080,29 @@
975 1080 {
976 1081 return in_array($this->event_type, ['single_event', 'group_event', 'collective']);
977 1082 }
978 1083
1084 + public function isRecurringBooking()
1085 + {
1086 + return Arr::get($this->other_info, 'recurring_count', 0) > 1;
1087 + }
1088 +
979 1089 public function getHostProfiles($public = true)
980 1090 {
981 1091 $hostIds = $this->getHostIds();
982 1092
1093 + cache_users($hostIds);
1094 +
1095 + $calendars = Calendar::whereIn('user_id', $hostIds)
1096 + ->where('type', 'simple')
1097 + ->orderBy('id', 'desc')
1098 + ->with(['metas', 'user', 'user.metas'])
1099 + ->get()
1100 + ->keyBy('user_id');
1101 +
983 1102 $hosts = [];
984 1103 foreach ($hostIds as $hostId) {
985 - $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
986 - if ($calendar) {
1104 + if ($calendar = $calendars->get($hostId)) {
987 1105 $hosts[] = $calendar->getAuthorProfile($public);
988 1106 }
989 1107 }
990 1108
@@ -1016,9 +1134,9 @@
1016 1134 if ($message) {
1017 1135 return $message;
1018 1136 }
1019 1137
1020 - return __('Sorry! you can not cancel this', 'fluent-booking');
1138 + return __('Sorry! you cannot cancel this', 'fluent-booking');
1021 1139 }
1022 1140
1023 1141 public function getRescheduleMessage()
1024 1142 {
@@ -1029,9 +1147,9 @@
1029 1147 if ($message) {
1030 1148 return $message;
1031 1149 }
1032 1150
1033 - return __('Sorry! you can not reschedule this', 'fluent-booking');
1151 + return __('Sorry! you cannot reschedule this', 'fluent-booking');
1034 1152 }
1035 1153
1036 1154 public function getHostDetails($isPublic = true, $hostId = null)
1037 1155 {
@@ -1082,15 +1200,30 @@
1082 1200 ->where('type', 'simple')
1083 1201 ->first();
1084 1202
1085 1203 if (!$calendar) {
1086 - return '';
1204 + return 'UTC';
1087 1205 }
1088 1206 return $calendar->author_timezone;
1089 1207 }
1090 - return '';
1208 + return 'UTC';
1091 1209 }
1092 1210
1211 + public function getCalendarLinkDescription()
1212 + {
1213 + $description = str_replace(PHP_EOL, '<br>', $this->getConfirmationData());
1214 +
1215 + if ($this->message) {
1216 + $description .= __('Note: ', 'fluent-booking') . '<br>' . esc_html($this->message) . '<br><br>';
1217 + }
1218 +
1219 + if ($additionalData = $this->getAdditionalData(false)) {
1220 + $description .= '<br>' . str_replace(PHP_EOL, '<br>', $additionalData);
1221 + }
1222 +
1223 + return $description;
1224 + }
1225 +
1093 1226 public function getIcsBookingDescription()
1094 1227 {
1095 1228 $description = str_replace(PHP_EOL, '\\n', $this->getConfirmationData());
1096 1229
@@ -1134,9 +1267,9 @@
1134 1267 if (empty($data['value'])) {
1135 1268 continue;
1136 1269 }
1137 1270 $html .= '<tr>';
1138 - $html .= '<td><b>' . $data['label'] . '</b></td>';
1271 + $html .= '<td><b>' . esc_html($data['label']) . '</b></td>';
1139 1272 $html .= '<td>' . $data['value'] . '</td>';
1140 1273 $html .= '</tr>';
1141 1274 }
1142 1275 $html .= '</table>';
@@ -1187,45 +1320,52 @@
1187 1320 public function getMeetingBookmarks($assetsUrl = '')
1188 1321 {
1189 1322 $bookingTitle = $this->getBookingTitle();
1190 1323
1191 - $eventTitle = $this->calendar_event->title;
1324 + $eventDescription = $this->getCalendarLinkDescription();
1192 1325
1326 + $eventLocation = LocationService::getBookingLocationUrl($this);
1327 +
1328 + $startTimestamp = strtotime($this->start_time);
1329 + $endTimestamp = strtotime($this->end_time);
1330 +
1331 + $compactStart = gmdate('Ymd\THis\Z', $startTimestamp);
1332 + $compactEnd = gmdate('Ymd\THis\Z', $endTimestamp);
1333 +
1334 + $isoStart = gmdate('Y-m-d\TH:i:s\Z', $startTimestamp);
1335 + $isoEnd = gmdate('Y-m-d\TH:i:s\Z', $endTimestamp);
1336 +
1337 + $googleParams = http_build_query([
1338 + 'dates' => $compactStart . '/' . $compactEnd,
1339 + 'text' => $bookingTitle,
1340 + 'details' => $eventDescription,
1341 + 'location' => $eventLocation,
1342 + ], '', '&', PHP_QUERY_RFC3986);
1343 +
1344 + $outlookParams = http_build_query([
1345 + 'path' => '/calendar/action/compose',
1346 + 'rru' => 'addevent',
1347 + 'startdt' => $isoStart,
1348 + 'enddt' => $isoEnd,
1349 + 'subject' => $bookingTitle,
1350 + 'body' => $eventDescription,
1351 + 'location' => $eventLocation,
1352 + ], '', '&', PHP_QUERY_RFC3986);
1353 +
1193 1354 return apply_filters('fluent_booking/meeting_bookmarks', [
1194 1355 'google' => [
1195 1356 'title' => __('Google Calendar', 'fluent-booking'),
1196 - 'url' => add_query_arg([
1197 - 'dates' => gmdate('Ymd\THis\Z', strtotime($this->start_time)) . '/' . gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1198 - 'text' => $bookingTitle,
1199 - 'details' => $eventTitle,
1200 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1201 - ], 'https://calendar.google.com/calendar/r/eventedit'),
1357 + 'url' => 'https://calendar.google.com/calendar/render?action=TEMPLATE&' . $googleParams,
1202 1358 'icon' => $assetsUrl . 'images/g-icon.svg'
1203 1359 ],
1204 1360 'outlook' => [
1205 1361 'title' => __('Outlook', 'fluent-booking'),
1206 - 'url' => add_query_arg([
1207 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1208 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1209 - 'subject' => $bookingTitle,
1210 - 'path' => '/calendar/action/compose',
1211 - 'body' => $eventTitle,
1212 - 'rru' => 'addevent',
1213 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1214 - ], 'https://outlook.live.com/calendar/0/deeplink/compose'),
1362 + 'url' => 'https://outlook.live.com/calendar/0/deeplink/compose?' . $outlookParams,
1215 1363 'icon' => $assetsUrl . 'images/ol-icon.svg'
1216 1364 ],
1217 1365 'msoffice' => [
1218 1366 'title' => __('Microsoft Office', 'fluent-booking'),
1219 - 'url' => add_query_arg([
1220 - 'startdt' => gmdate('Ymd\THis\Z', strtotime($this->start_time)),
1221 - 'enddt' => gmdate('Ymd\THis\Z', strtotime($this->end_time)),
1222 - 'subject' => $bookingTitle,
1223 - 'path' => '/calendar/action/compose',
1224 - 'body' => $eventTitle,
1225 - 'rru' => 'addevent',
1226 - 'location' => urlencode(LocationService::getBookingLocationUrl($this)),
1227 - ], 'https://outlook.office.com/calendar/0/deeplink/compose'),
1367 + 'url' => 'https://outlook.office.com/calendar/0/deeplink/compose?' . $outlookParams,
1228 1368 'icon' => $assetsUrl . 'images/msoffice.svg'
1229 1369 ],
1230 1370 'other' => [
1231 1371 'title' => __('Other Calendar', 'fluent-booking'),
@@ -1234,5 +1374,5 @@
1234 1374 ]
1235 1375 ], $this);
1236 1376 }
1237 1377
1238 -}
1378 +}