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 +79 -24 2.1.2 → 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 }
@@ -219,8 +222,29 @@
219 222 {
220 223 return $this->hosts()->pluck('user_id')->toArray();
221 224 }
222 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 +
223 247 public function scopeUpcoming($query)
224 248 {
225 249 return $query->where('end_time', '>=', gmdate('Y-m-d H:i:s')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
226 250 }
@@ -266,17 +290,19 @@
266 290 ->where('status', 'scheduled');
267 291 }
268 292
269 293 if ($status == 'completed') {
270 - return $query->where('end_time', '<', gmdate('Y-m-d H:i:s')) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
271 - ->where('status', '!=', 'cancelled')
272 - ->where('status', '!=', 'rejected')
273 - ->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 + });
274 301 }
275 302
276 303 if ($status == 'cancelled') {
277 - return $query->where('status', 'cancelled')
278 - ->orWhere('status', 'rejected');
304 + return $query->whereIn('status', ['cancelled', 'rejected']);
279 305 }
280 306
281 307 if ($status == 'pending') {
282 308 return $query->whereIn('status', ['pending', 'reserved']);
@@ -377,9 +403,9 @@
377 403 }
378 404
379 405 public function getAllBookingShortTimes($timeZone = 'UTC', $withTimeZone = false)
380 406 {
381 - $otherBookings = self::where('parent_id', $this->id)->get();
407 + $otherBookings = $this->getOwnChildBookings();
382 408
383 409 $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
384 410 return $otherBooking->formatBookingDateTime($otherBooking->start_time, $timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
385 411 })->toArray();
@@ -390,9 +416,9 @@
390 416 }
391 417
392 418 public function getAllBookingFullTimes($timeZone = 'UTC', $withTimeZone = false)
393 419 {
394 - $otherBookings = self::where('parent_id', $this->id)->get();
420 + $otherBookings = $this->getOwnChildBookings();
395 421
396 422 $otherTimes = $otherBookings->map(function ($otherBooking) use ($timeZone, $withTimeZone) {
397 423 return $otherBooking->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '');
398 424 })->toArray();
@@ -401,8 +427,17 @@
401 427 $this->getFullBookingDateTimeText($timeZone) . ($withTimeZone ? ' (' . $timeZone . ')' : '')
402 428 ]);
403 429 }
404 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 +
405 440 public function getHostAndGuestDetailsHtml()
406 441 {
407 442 $authors = $this->getHostsDetails();
408 443
@@ -420,13 +455,13 @@
420 455 $authorListHtml = '<ul class="fcal_listed">';
421 456
422 457 foreach ($authors as $author) {
423 458 $authorBadge = ($author['id'] == $hostUserId) ? '<span class="fcal_host_badge">' . __('Host', 'fluent-booking') . '</span>' : '';
424 - $authorListHtml .= '<li class="fcal_host_name">' . $author['name'] . $authorBadge . '</li>';
459 + $authorListHtml .= '<li class="fcal_host_name">' . esc_html($author['name']) . $authorBadge . '</li>';
425 460 }
426 461
427 462 foreach ($guestNames as $guestName) {
428 - $authorListHtml .= '<li class="fcal_guest_name">' . $guestName . '</li>';
463 + $authorListHtml .= '<li class="fcal_guest_name">' . esc_html($guestName) . '</li>';
429 464 }
430 465 $authorListHtml .= '</ul>';
431 466
432 467 return $authorListHtml;
@@ -513,9 +548,11 @@
513 548 }
514 549
515 550 public function getLocationDetailsAttribute($locationDetails)
516 551 {
517 - return \maybe_unserialize($locationDetails);
552 + $value = \maybe_unserialize($locationDetails);
553 +
554 + return is_array($value) ? $value : [];
518 555 }
519 556
520 557 public function setOtherInfoAttribute($otherInfo)
521 558 {
@@ -521,9 +558,12 @@
521 558 {
522 559 $originalOtherInfo = $this->getOriginal('other_info');
523 560
524 561 $originalOtherInfo = \maybe_unserialize($originalOtherInfo);
562 + $originalOtherInfo = is_array($originalOtherInfo) ? $originalOtherInfo : [];
525 563
564 + $otherInfo = is_array($otherInfo) ? $otherInfo : (array) \maybe_unserialize($otherInfo);
565 +
526 566 foreach ($otherInfo as $key => $value) {
527 567 $originalOtherInfo[$key] = $value;
528 568 }
529 569
@@ -614,9 +654,9 @@
614 654 if ($isText) {
615 655 return $row->description;
616 656 }
617 657 if ($isHtml) {
618 - return wp_unslash($row->description);
658 + return esc_html(wp_unslash($row->description));
619 659 }
620 660 }
621 661
622 662 return $row;
@@ -632,9 +672,9 @@
632 672 if ($isText) {
633 673 return $row->description;
634 674 }
635 675 if ($isHtml) {
636 - return wp_unslash($row->description);
676 + return esc_html(wp_unslash($row->description));
637 677 }
638 678 }
639 679
640 680 return $row;
@@ -775,10 +815,15 @@
775 815 $bookingTitle = EditorShortCodeParser::parse($bookingTitle, $this);
776 816
777 817 $bookingTitle = $bookingTitle ?: $this->generateBookingTitle($eventTitle, $authorName, $guestName);
778 818
779 - if ($html && strpos($bookingTitle, $eventTitle) !== false) {
780 - $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 + }
781 826 }
782 827
783 828 return apply_filters('fluent_booking/booking_meeting_title', $bookingTitle, $authorName, $guestName, $calendarEvent, $this);
784 829 }
@@ -996,8 +1041,10 @@
996 1041
997 1042 $conditionTime = $conditionValue * 60;
998 1043 if ($conditionUnit == 'hours') {
999 1044 $conditionTime = $conditionTime * 60;
1045 + } elseif ($conditionUnit == 'days') {
1046 + $conditionTime = $conditionTime * 60 * 24;
1000 1047 }
1001 1048
1002 1049 return $bookingStartTime - $currentTime > $conditionTime;
1003 1050 }
@@ -1042,12 +1089,20 @@
1042 1089 public function getHostProfiles($public = true)
1043 1090 {
1044 1091 $hostIds = $this->getHostIds();
1045 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 +
1046 1102 $hosts = [];
1047 1103 foreach ($hostIds as $hostId) {
1048 - $calendar = Calendar::where('user_id', $hostId)->where('type', 'simple')->first();
1049 - if ($calendar) {
1104 + if ($calendar = $calendars->get($hostId)) {
1050 1105 $hosts[] = $calendar->getAuthorProfile($public);
1051 1106 }
1052 1107 }
1053 1108
@@ -1079,9 +1134,9 @@
1079 1134 if ($message) {
1080 1135 return $message;
1081 1136 }
1082 1137
1083 - return __('Sorry! you can not cancel this', 'fluent-booking');
1138 + return __('Sorry! you cannot cancel this', 'fluent-booking');
1084 1139 }
1085 1140
1086 1141 public function getRescheduleMessage()
1087 1142 {
@@ -1092,9 +1147,9 @@
1092 1147 if ($message) {
1093 1148 return $message;
1094 1149 }
1095 1150
1096 - return __('Sorry! you can not reschedule this', 'fluent-booking');
1151 + return __('Sorry! you cannot reschedule this', 'fluent-booking');
1097 1152 }
1098 1153
1099 1154 public function getHostDetails($isPublic = true, $hostId = null)
1100 1155 {
@@ -1212,9 +1267,9 @@
1212 1267 if (empty($data['value'])) {
1213 1268 continue;
1214 1269 }
1215 1270 $html .= '<tr>';
1216 - $html .= '<td><b>' . $data['label'] . '</b></td>';
1271 + $html .= '<td><b>' . esc_html($data['label']) . '</b></td>';
1217 1272 $html .= '<td>' . $data['value'] . '</td>';
1218 1273 $html .= '</tr>';
1219 1274 }
1220 1275 $html .= '</table>';
@@ -1298,9 +1353,9 @@
1298 1353
1299 1354 return apply_filters('fluent_booking/meeting_bookmarks', [
1300 1355 'google' => [
1301 1356 'title' => __('Google Calendar', 'fluent-booking'),
1302 - 'url' => 'https://calendar.google.com/calendar/r/eventedit?' . $googleParams,
1357 + 'url' => 'https://calendar.google.com/calendar/render?action=TEMPLATE&' . $googleParams,
1303 1358 'icon' => $assetsUrl . 'images/g-icon.svg'
1304 1359 ],
1305 1360 'outlook' => [
1306 1361 'title' => __('Outlook', 'fluent-booking'),
@@ -1319,5 +1374,5 @@
1319 1374 ]
1320 1375 ], $this);
1321 1376 }
1322 1377
1323 -}
1378 +}