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
fluent-booking / app / Services / RescheduleService.php

RescheduleService.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.5.0, at app/Services/RescheduleService.php

133 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services;
4
5 use FluentBooking\App\Models\Booking;
6 use FluentBooking\App\Models\CalendarSlot;
7
8 /**
9 * Moves an existing booking to a new time. Shared by the public form and
10 * programmatic callers such as MCP, so all fire the same hooks. Failures come
11 * back as WP_Error. A reschedule changes the time and leaves `status` alone.
12 *
13 * @since 2.2.6
14 */
15 class RescheduleService
16 {
17 /**
18 * @param Booking $booking The booking being moved.
19 * @param CalendarSlot $calendarEvent The event the booking belongs to.
20 * @param string $startTime New start time, UTC 'Y-m-d H:i:s'.
21 * @param string $timezone The attendee's IANA timezone.
22 * @param array $args {
23 * @type string $reason Rescheduling reason, stored as booking meta.
24 * @type int $host_user_id Resolved host for round-robin events.
25 * @type string $source Where the request came from, used in the
26 * activity log. Defaults to 'Web UI'.
27 * @type string $rescheduled_by Force 'host' or 'guest' instead of
28 * deriving it from the current user.
29 * }
30 *
31 * @return Booking|\WP_Error The updated booking, or the reason it was refused.
32 */
33 public static function reschedule(Booking $booking, CalendarSlot $calendarEvent, $startTime, $timezone, $args = [])
34 {
35 // Availability must be validated against the booking's own event.
36 if ((int) $booking->event_id !== (int) $calendarEvent->id) {
37 return new \WP_Error('invalid_reschedule_request', __('Invalid rescheduling request', 'fluent-booking'), ['status' => 422]);
38 }
39
40 $rescheduleBy = isset($args['rescheduled_by']) ? $args['rescheduled_by'] : self::resolveRescheduledBy($booking);
41
42 if ($rescheduleBy == 'guest' && !$booking->canReschedule()) {
43 return new \WP_Error('reschedule_not_allowed', $booking->getRescheduleMessage(), ['status' => 422]);
44 }
45
46 if ($startTime == $booking->start_time) {
47 return new \WP_Error('same_time', __('Sorry! you can not reschedule to the same time.', 'fluent-booking'), ['status' => 422]);
48 }
49
50 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startTime) + ($booking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
51
52 $previousBooking = clone $booking;
53
54 $reason = isset($args['reason']) ? sanitize_textarea_field($args['reason']) : '';
55
56 // The host pivot syncs before the row saves, so keep both in one transaction.
57 try {
58 Helper::dbTransaction(function () use ($booking, $rescheduleBy, $startTime, $timezone, $endDateTime, $previousBooking, $reason, $args) {
59 // Written after the guards so a refused reschedule leaves no trace.
60 // NotificationHandler reads it to pick the host or attendee email.
61 $booking->updateMeta('rescheduled_by_type', $rescheduleBy);
62
63 if ($booking->isMultiGuestBooking()) {
64 // Join the existing group at the new time, if any.
65 $parent = Booking::where('status', 'scheduled')
66 ->where('event_id', $booking->event_id)
67 ->where('start_time', $startTime)
68 ->orderBy('id', 'ASC')
69 ->first();
70
71 if ($parent) {
72 $booking->group_id = $parent->group_id;
73 } else {
74 $booking->group_id = Helper::getNextBookingGroup();
75 }
76 }
77
78 if ($booking->isRoundRobinBooking() && !empty($args['host_user_id'])) {
79 $hostId = (int) $args['host_user_id'];
80 $booking->host_user_id = $hostId;
81 $booking->hosts()->sync([$hostId]);
82 }
83
84 $booking->start_time = $startTime;
85 $booking->person_time_zone = $timezone;
86 $booking->end_time = $endDateTime;
87 $booking->save();
88
89 $booking->updateMeta('previous_meeting_time', $previousBooking->start_time);
90
91 if ($reason) {
92 $booking->updateMeta('reschedule_reason', $reason);
93 }
94 });
95 } catch (\Throwable $e) {
96 return new \WP_Error('reschedule_failed', __('The booking could not be moved and was left where it was.', 'fluent-booking'), ['status' => 500]);
97 }
98
99 $source = isset($args['source']) ? $args['source'] : __('Web UI', 'fluent-booking');
100
101 do_action('fluent_booking/log_booking_activity', [
102 'booking_id' => $booking->id,
103 'type' => 'info',
104 'status' => 'closed',
105 'title' => __('Meeting Rescheduled', 'fluent-booking'),
106 /* translators: %1$s is the user who rescheduled the meeting, %2$s is where the request came from, %3$s is the previous date and time in UTC. */
107 'description' => sprintf(__('Meeting has been rescheduled by %1$s from %2$s. Previous date time: %3$s (UTC)', 'fluent-booking'), $rescheduleBy, $source, $previousBooking->start_time)
108 ]);
109
110 do_action('fluent_booking/after_booking_rescheduled', $booking, $previousBooking, $calendarEvent);
111
112 return $booking;
113 }
114
115 /**
116 * A reschedule is "by host" when the current user is one of the booking's
117 * hosts or holds site-wide booking permissions; otherwise it is the guest
118 * acting on their own booking link.
119 *
120 * @return string 'host'|'guest'
121 */
122 public static function resolveRescheduledBy(Booking $booking)
123 {
124 $hostIds = $booking->getHostIds();
125
126 if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan(['manage_all_data', 'manage_all_bookings'])) {
127 return 'host';
128 }
129
130 return 'guest';
131 }
132 }
133