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 +57 -23 2.2.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;
@@ -137,9 +138,11 @@
137 138 }
138 139
139 140 public static function assignNextGroupId()
140 141 {
141 - $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']);
142 145
143 146 return $lastEvent ? $lastEvent->group_id + 1 : 1;
144 147 }
145 148
@@ -168,9 +171,9 @@
168 171 return [];
169 172 }
170 173
171 174 if ($isHtml) {
172 - return wpautop(implode('<br>', $additionalGuests));
175 + return wpautop(implode('<br>', array_map('esc_html', $additionalGuests)));
173 176 }
174 177
175 178 return $additionalGuests;
176 179 }
@@ -287,17 +290,19 @@
287 290 ->where('status', 'scheduled');
288 291 }
289 292
290 293 if ($status == 'completed') {
291 - return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
292 - ->where('status', '!=', 'cancelled')
293 - ->where('status', '!=', 'rejected')
294 - ->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 + });
295 301 }
296 302
297 303 if ($status == 'cancelled') {
298 - return $query->where('status', 'cancelled')
299 - ->orWhere('status', 'rejected');
304 + return $query->whereIn('status', ['cancelled', 'rejected']);
300 305 }
301 306
302 307 if ($status == 'pending') {
303 308 return $query->whereIn('status', ['pending', 'reserved']);
@@ -398,9 +403,9 @@
398 403 }
399 404
400 405 public function getAllBookingShortTimes($timeZone = 'UTC', $withTimeZone = false)
401 406 {
402 - $otherBookings = self::where('parent_id', $this->id)->get();
407 + $otherBookings = $this->getOwnChildBookings();
403 408
404 409 $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
405 410 return $otherBooking->formatBookingDateTime($otherBooking->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
406 411 })->toArray();
@@ -411,9 +416,9 @@
411 416 }
412 417
413 418 public function getAllBookingFullTimes($timeZone = 'UTC', $withTimeZone = false)
414 419 {
415 - $otherBookings = self::where('parent_id', $this->id)->get();
420 + $otherBookings = $this->getOwnChildBookings();
416 421
417 422 $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
418 423 return $otherBooking->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
419 424 })->toArray();
@@ -422,8 +427,17 @@
422 427 $this->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
423 428 ]);
424 429 }
425 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 +
426 440 public function getHostAndGuestDetailsHtml()
427 441 {
428 442 $authors = $this->getHostsDetails();
429 443
@@ -441,13 +455,13 @@
441 455 $authorListHtml = '<ul class="fcal_listed">';
442 456
443 457 foreach ($authors as $author) {
444 458 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
445 - $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
459 + $authorListHtml .= '<li class="fcal_host_name">' . esc_html($author['name']) . $authorBadge . '</li>';
446 460 }
447 461
448 462 foreach ($guestNames as $guestName) {
449 - $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
463 + $authorListHtml .= '<li class="fcal_guest_name">' . esc_html($guestName) . '</li>';
450 464 }
451 465 $authorListHtml .= '</ul>';
452 466
453 467 return $authorListHtml;
@@ -534,9 +548,11 @@
534 548 }
535 549
536 550 public function getLocationDetailsAttribute($locationDetails)
537 551 {
538 - return \maybe_unserialize($locationDetails);
552 + $value = \maybe_unserialize($locationDetails);
553 +
554 + return is_array($value) ? $value : [];
539 555 }
540 556
541 557 public function setOtherInfoAttribute($otherInfo)
542 558 {
@@ -542,9 +558,12 @@
542 558 {
543 559 $originalOtherInfo = $this->getOriginal('other_info');
544 560
545 561 $originalOtherInfo = \maybe_unserialize($originalOtherInfo);
562 + $originalOtherInfo = is_array($originalOtherInfo) ? $originalOtherInfo : [];
546 563
564 + $otherInfo = is_array($otherInfo) ? $otherInfo : (array) \maybe_unserialize($otherInfo);
565 +
547 566 foreach ($otherInfo as $key => $value) {
548 567 $originalOtherInfo[$key] = $value;
549 568 }
550 569
@@ -635,9 +654,9 @@
635 654 if ($isText) {
636 655 return $row->description;
637 656 }
638 657 if ($isHtml) {
639 - return wp_unslash($row->description);
658 + return esc_html(wp_unslash($row->description));
640 659 }
641 660 }
642 661
643 662 return $row;
@@ -653,9 +672,9 @@
653 672 if ($isText) {
654 673 return $row->description;
655 674 }
656 675 if ($isHtml) {
657 - return wp_unslash($row->description);
676 + return esc_html(wp_unslash($row->description));
658 677 }
659 678 }
660 679
661 680 return $row;
@@ -796,10 +815,15 @@
796 815 $bookingTitle = EditorShortCodeParser::parse($bookingTitle, $this);
797 816
798 817 $bookingTitle = $bookingTitle ?: $this->generateBookingTitle($eventTitle, $authorName, $guestName);
799 818
800 - if ($html && strpos($bookingTitle, $eventTitle) !== false) {
801 - $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 + }
802 826 }
803 827
804 828 return apply_filters('fluent_booking/booking_meeting_title', $bookingTitle, $authorName, $guestName, $calendarEvent, $this);
805 829 }
@@ -1017,8 +1041,10 @@
1017 1041
1018 1042 $conditionTime = $conditionValue * 60;
1019 1043 if ($conditionUnit == 'hours') {
1020 1044 $conditionTime = $conditionTime * 60;
1045 + } elseif ($conditionUnit == 'days') {
1046 + $conditionTime = $conditionTime * 60 * 24;
1021 1047 }
1022 1048
1023 1049 return $bookingStartTime - $currentTime > $conditionTime;
1024 1050 }
@@ -1063,12 +1089,20 @@
1063 1089 public function getHostProfiles($public = true)
1064 1090 {
1065 1091 $hostIds = $this->getHostIds();
1066 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 +
1067 1102 $hosts = [];
1068 1103 foreach ($hostIds as $hostId) {
1069 - $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
1070 - if ($calendar) {
1104 + if ($calendar = $calendars->get($hostId)) {
1071 1105 $hosts[] = $calendar->getAuthorProfile($public);
1072 1106 }
1073 1107 }
1074 1108
@@ -1100,9 +1134,9 @@
1100 1134 if ($message) {
1101 1135 return $message;
1102 1136 }
1103 1137
1104 - return __('Sorry! you can not cancel this', 'fluent-booking');
1138 + return __('Sorry! you cannot cancel this', 'fluent-booking');
1105 1139 }
1106 1140
1107 1141 public function getRescheduleMessage()
1108 1142 {
@@ -1113,9 +1147,9 @@
1113 1147 if ($message) {
1114 1148 return $message;
1115 1149 }
1116 1150
1117 - return __('Sorry! you can not reschedule this', 'fluent-booking');
1151 + return __('Sorry! you cannot reschedule this', 'fluent-booking');
1118 1152 }
1119 1153
1120 1154 public function getHostDetails($isPublic = true, $hostId = null)
1121 1155 {
@@ -1233,9 +1267,9 @@
1233 1267 if (empty($data['value'])) {
1234 1268 continue;
1235 1269 }
1236 1270 $html .= '<tr>';
1237 - $html .= '<td><b>' . $data['label'] . '</b></td>';
1271 + $html .= '<td><b>' . esc_html($data['label']) . '</b></td>';
1238 1272 $html .= '<td>' . $data['value'] . '</td>';
1239 1273 $html .= '</tr>';
1240 1274 }
1241 1275 $html .= '</table>';
@@ -1340,5 +1374,5 @@
1340 1374 ]
1341 1375 ], $this);
1342 1376 }
1343 1377
1344 -}
1378 +}