PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.3.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.3.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 1.7.2 All 33 releases
fluent-booking / app / Services / Integrations / FluentCart / Bootstrap.php

Bootstrap.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.3.0, at app/Services/Integrations/FluentCart/Bootstrap.php

448 lines 16.8 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\Integrations\FluentCart;
4
5 use FluentBooking\Framework\Foundation\Application;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Services\Renderer\Receipt\ThankYouRender;
9 use FluentBooking\App\Models\Booking;
10 use FluentBooking\App\Models\CalendarSlot;
11 use FluentBooking\App\Services\DateTimeHelper;
12 use FluentBooking\App\Services\Helper;
13 use FluentBooking\Framework\Support\Arr;
14
15 class Bootstrap
16 {
17 public function __construct(Application $app)
18 {
19 $app->router->group(function ($router) {
20 require_once __DIR__ . '/Http/cart_api.php';
21 });
22
23 $this->registerHooks();
24 }
25
26 public function registerHooks()
27 {
28 add_filter('fluent_booking/public_event_vars', [$this, 'maybePushPaymentVars'], 11, 2);
29 add_filter('fluent_booking/booking_data', [$this, 'maybePushPaymentData'], 10, 2);
30
31 add_action('fluent_cart/receipt/thank_you/after_order_items', [$this, 'maybeShowBookingDetailsInReceipt'], 10, 1);
32 add_action('fluent_booking/booking_meta_info_main_meta_cart', [$this, 'pushOrderDataToBookingView'], 10, 2);
33
34 // new API
35 add_action('fluent_cart/cart/line_item/line_meta', [$this, 'maybeRenderBookingInfoOnCartItem'], 10, 1);
36 // We are adding booking id to the order config after draft is created
37 add_action('fluent_booking/cart/booking_order_created', function ($eventData) {
38 $cart = Arr::get($eventData, 'cart');
39 if (empty($cart->checkout_data['fluent_booking_data'])) {
40 return;
41 }
42
43 $order = Arr::get($eventData, 'order');
44 $config = $order->config;
45 $bookingId = Arr::get($cart->checkout_data, 'fluent_booking_data.booking_id');
46 if ($bookingId) {
47 $config['fcal_booking_id'] = $bookingId;
48 $order->config = $config;
49 $order->save();
50 }
51 });
52
53 // after order confirmation
54 add_action('fluent_booking/cart/booking_order_completed', [$this, 'maybeScheduleBooking'], 10, 1);
55
56 add_filter('fluent_cart/checkout_page_name_fields_schema', [$this, 'maybeFillSplitNameFields'], 10, 2);
57 }
58
59 public function maybeFillSplitNameFields($nameFields, $context)
60 {
61 $cart = Arr::get($context, 'cart');
62 if (!$cart || empty($cart->checkout_data['fluent_booking_data'])) {
63 return $nameFields;
64 }
65
66 if (isset($nameFields['billing_first_name']) && empty($nameFields['billing_first_name']['value'])) {
67 $nameFields['billing_first_name']['value'] = $cart->first_name;
68 }
69
70 if (isset($nameFields['billing_last_name']) && empty($nameFields['billing_last_name']['value'])) {
71 $nameFields['billing_last_name']['value'] = $cart->last_name;
72 }
73
74 return $nameFields;
75 }
76
77
78 public function maybePushPaymentVars($eventVars, CalendarSlot $calendarEvent)
79 {
80 if (!CartHelper::isEnabled($calendarEvent)) {
81 return $eventVars;
82 }
83
84 $paymentSettings = $calendarEvent->getPaymentSettings();
85 $defaultDuration = $calendarEvent->getDefaultDuration();
86
87 $eventVars['slot']['total_payment'] = CartHelper::getCartProductPrice($paymentSettings, $defaultDuration);
88
89 $isMultiEnabled = Arr::get($paymentSettings, 'multi_payment_enabled') === 'yes';
90 if ($calendarEvent->isMultiDurationEnabled() && $isMultiEnabled) {
91 $productIds = Arr::get($paymentSettings, 'multi_payment_cart_ids', []);
92 $eventVars['multi_payment_cart_ids'] = CartHelper::getCartProductPriceByDuration($productIds);
93 }
94
95 return $eventVars;
96 }
97
98 public function maybePushPaymentData($bookingData, $calendarEvent)
99 {
100 if (Arr::get($bookingData, 'source') != 'web') {
101 return $bookingData;
102 }
103
104 if (!CartHelper::isEnabled($calendarEvent)) {
105 return $bookingData;
106 }
107
108 $duration = Arr::get($bookingData, 'slot_minutes');
109
110 $variationId = CartHelper::getEventProductId($calendarEvent, $duration);
111 if (!$variationId) {
112 return $bookingData;
113 }
114
115 $product = CartHelper::getProduct($variationId);
116 if (!$product) {
117 return $bookingData;
118 }
119
120 $bookingData['source'] = 'cart';
121 $bookingData['payment_method'] = 'fluent_cart';
122 $bookingData['payment_status'] = 'pending';
123 $bookingData['status'] = 'pending';
124
125 add_filter('fluent_booking/booking_confirmation_response', function ($response, $booking) use ($product) {
126 if ($booking->status != 'pending' || $booking->source != 'cart') {
127 return $response;
128 }
129
130 $quantity = $booking->getMeta('quantity', 1);
131 $newItem = $product->toArray();
132
133 $instantCart = \FluentCart\App\Helpers\CartHelper::generateCartFromCustomVariation($newItem, $quantity);
134
135 $cartData = $instantCart->cart_data;
136 $cartData[0]['fcal_booking_id'] = $booking->id;
137 $instantCart->cart_data = $cartData;
138
139 $instantCart->cart_group = 'instant';
140 $instantCart->first_name = $booking->first_name;
141 $instantCart->last_name = $booking->last_name;
142 $instantCart->email = $booking->email;
143 $instantCart->user_id = $booking->person_user_id;
144 $instantCart->cart_hash = md5('booking_cart_' . wp_generate_uuid4() . time());
145 $instantCart->checkout_data = [
146 'is_locked' => 'yes',
147 'fluent_booking_data' => [
148 'booking_id' => $booking->id,
149 'target_variant_id' => $product->id
150 ],
151 '__on_success_actions__' => [
152 'fluent_booking/cart/booking_order_completed'
153 ],
154 '__after_draft_created_actions__' => [
155 'fluent_booking/cart/booking_order_created'
156 ],
157 '__cart_notices' => [
158
159 ]
160 ];
161
162 $instantCart->save();
163
164 $cartHash = $instantCart->cart_hash;
165 $checkoutUrl = add_query_arg(
166 [
167 'fct_cart_hash' => $cartHash
168 ],
169 (new StoreSettings())->getCheckoutPage()
170 );
171
172 $response['data']['redirect_to'] = $checkoutUrl;
173 $response['data']['redirect_message'] = __('You are redirecting to checkout page to complete the appointment.', 'fluent-booking');
174
175 do_action('fluent_booking/log_booking_activity', [
176 'booking_id' => $booking->id,
177 'status' => 'closed',
178 'type' => 'info',
179 'title' => __('Redirect to FluentCart checkout page', 'fluent-booking'),
180 'description' => __('User redirected to FluentCart checkout page to comeplete the order.', 'fluent-booking')
181 ]);
182
183 return $response;
184 }, 10, 2);
185
186 return $bookingData;
187 }
188
189 public function maybeRenderBookingInfoOnCartItem($eventInfo)
190 {
191 $item = Arr::get($eventInfo, 'item', []);
192
193 if (empty($item['fcal_booking_id'])) {
194 return;
195 }
196
197 $bookingId = $item['fcal_booking_id'];
198 $booking = Booking::find($bookingId);
199 if (!$booking || !$booking->calendar_event || $booking->status !== 'pending') {
200 return;
201 }
202
203 $bookingTime = $booking->getFullBookingDateTimeText($booking->person_time_zone, true) . ' (' . $booking->person_time_zone . ')';
204
205 if ($booking->calendar_event->allowMultiBooking()) {
206 $bookingTime = array_merge((array)$bookingTime, $booking->getOtherBookingTimes());
207 }
208 ?>
209 <div class="fct_item_title booking_meta">
210 <div class="fcal_meta_label"
211 style="font-weight: 600;"><?php echo esc_html__('Appointment:', 'fluent-booking'); ?></div>
212 <div class="fcal_meta_value"
213 style="font-weight: 400;"><?php echo esc_html(implode(', ', (array)$bookingTime)); ?></div>
214 </div>
215 <?php
216 }
217
218 public function maybeScheduleBooking($eventData)
219 {
220 $order = Arr::get($eventData, 'order');
221 $bookingId = Arr::get($order->config, 'fcal_booking_id', '');
222 if (empty($order) || empty($bookingId)) {
223 return;
224 }
225
226 $cart = Arr::get($eventData, 'cart');
227
228 if (!$cart) {
229 return;
230 }
231
232 $targetProductId = Arr::get($cart->checkout_data, 'fluent_booking_data.target_variant_id', 0);
233
234 if (!$targetProductId) {
235 return;
236 }
237
238 $checkoutItem = array_filter($cart->cart_data, function ($item) use ($targetProductId) {
239 return Arr::get($item, 'object_id', 0) == $targetProductId;
240 });
241
242 if (!$checkoutItem) {
243 return;
244 }
245
246 $booking = Booking::find($bookingId);
247 if (!$booking || $booking->source_id) {
248 return;
249 }
250
251 $calendarEvent = $booking->calendar_event;
252 if (!CartHelper::isEnabled($calendarEvent)) {
253 return;
254 }
255
256 if ($booking->status != 'pending') {
257 do_action('fluent_booking/log_booking_activity', [
258 'booking_id' => $booking->id,
259 'status' => 'closed',
260 'type' => 'success',
261 'title' => __('Cart: Booking status could not be changed', 'fluent-booking'),
262 'description' => sprintf(
263 /* translators: Notification message when the booking status could not be changed due to its current status. %1$s is the status, %2$s and %3$s are the HTML link tags for "View Order" */
264 __('Booking status could not be changed as it is in %1$s status. %2$sView Order%3$s', 'fluent-booking'),
265 $booking->status,
266 '<a target="_blank" href="' . $order->getViewUrl('admin') . '">',
267 '</a>')
268 ]);
269 return;
270 }
271
272 $isRequireConfirmation = $calendarEvent->isConfirmationRequired($booking->start_time, $booking->created_at);
273
274 if (!$isRequireConfirmation) {
275 $booking->status = 'scheduled';
276 }
277
278 $booking->payment_status = 'paid';
279 $booking->source_id = $order->id;
280 $booking->save();
281
282 $this->maybeUpdateChildBookings($booking, $calendarEvent, $order);
283
284 do_action('fluent_booking/log_booking_activity', CartHelper::getSuccessLog($booking->id, $order));
285
286 $bookingData = [
287 'name' => $booking->first_name . ' ' . $booking->last_name,
288 'email' => $booking->email,
289 'phone' => $booking->phone
290 ];
291
292 $order->addLog(
293 __('Booking Confirmation', 'fluent-booking'),
294 sprintf(
295 /* translators: Order log message for the change of booking status to scheduled. %1$s is the booking ID, %2$s is the booking status, %3$s is the date/time+timezone, %4$s is a link open tag, %5$s is the link close tag */
296 __('Booking #%1$s status changed to %2$s at %3$s. %4$sView Booking%5$s', 'fluent-booking'),
297 $booking->id,
298 $booking->status,
299 $booking->getFullBookingDateTimeText($booking->calendar->author_timezone, true) . ' (' . $booking->calendar->author_timezone . ')',
300 '<a target="_blank" href="' . esc_url(Helper::getAppBaseUrl('scheduled-events?period=upcoming&booking_id=' . $booking->id)) . '">',
301 '</a>'
302 ),
303 'info',
304 'FluentBooking'
305 );
306
307 // this pre hook is for early actions that require for remote calendars and locations
308 do_action('fluent_booking/pre_after_booking_' . $booking->status, $booking, $booking->calendar_event, $bookingData);
309
310 do_action('fluent_booking/after_booking_' . $booking->status, $booking, $booking->calendar_event, $bookingData);
311 }
312
313 public function maybeShowBookingDetailsInReceipt($event)
314 {
315 $order = Arr::get($event, 'order');
316 $bookingId = Arr::get($order->config, 'fcal_booking_id', '');
317
318 if (empty($order) || empty($bookingId)) {
319 return;
320 }
321
322 $booking = Booking::find($bookingId);
323 if (!$booking) {
324 return;
325 }
326
327 $redirectUrl = $booking->getRedirectUrlWithQuery();
328
329 if ($redirectUrl && in_array($order->payment_status, Status::getOrderPaymentSuccessStatuses()) && Arr::get($event, 'is_first_time', false)) {
330 add_action('wp_footer', function () use ($redirectUrl) {
331 ?>
332 <script type="text/javascript">
333 document.addEventListener('DOMContentLoaded', function () {
334 window.location.href = "<?php echo esc_url($redirectUrl); ?>";
335 });
336 </script>
337 <?php
338 });
339 }
340 ?>
341 <style>
342 .fcal_receipt_booking_details h5 {
343 margin: 0 0 10px;
344 border-bottom: 1px solid #dee2e6;
345 padding-bottom: 5px;
346 font-size: 15px;
347 font-weight: 700;
348 color: #495057;
349 }
350
351 .fcal_receipt_booking_info p {
352 margin: 0 0 4px;
353 color: #111111;
354 font-size: 14px;
355 }
356 </style>
357 <div class="fcal_receipt_booking_details">
358 <h5><?php esc_html_e('Booking Details', 'fluent-booking'); ?></h5>
359 <div class="fcal_receipt_booking_info">
360 <p>
361 <b><?php esc_html_e('Meeting Info:', 'fluent-booking'); ?></b> <?php echo esc_html($booking->getBookingTitle()); ?>
362 </p>
363 <p>
364 <b><?php esc_html_e('Date & Time:', 'fluent-booking'); ?></b> <?php echo esc_html(implode(', ', $booking->getAllBookingShortTimes($booking->person_time_zone))); ?>
365 (<?php echo esc_html($booking->person_time_zone); ?>)
366 </p>
367 <p>
368 <b><?php esc_html_e('Status:', 'fluent-booking'); ?></b> <?php echo esc_html(ucfirst($booking->status)); ?>
369 </p>
370 <p>
371 <a href="<?php echo esc_url($booking->getConfirmationUrl()); ?>"><?php esc_html_e('View Full Meeting Details', 'fluent-booking'); ?></a>
372 </p>
373 </div>
374 </div>
375 <?php
376 }
377
378 public function pushOrderDataToBookingView($meta, $booking)
379 {
380 $orderId = $booking->source_id;
381 if (!$orderId) {
382 return $meta;
383 }
384
385 $order = CartHelper::getOrder($orderId);
386 if (!$order) {
387 return $meta;
388 }
389
390 // Get FluentCart Order Summary as html
391 ob_start();
392 printf(
393 /* translators: 1: order number 2: order date 3: order status */
394 esc_html__('Order %1$s was placed on %2$s and is currently %3$s.', 'fluent-booking'),
395 '<span class="order-number"><a href="' . esc_url($order->getViewUrl('admin')) . '">' . '#' . esc_html($order->id) . '</a></span>',
396 '<span class="order-date">' . esc_html(DateTimeHelper::formatToLocale($order->created_at, 'date_time')) . '</span>',
397 '<span class="order-status">' . esc_html($order->status) . '</span>'
398 );
399
400 (new ThankYouRender(['order' => $order]))->renderOrderItems();
401
402 $orderSummary = ob_get_clean();
403
404 $meta[] = [
405 'id' => 'cart-order-summary',
406 'title' => __('Order Summary', 'fluent-booking'),
407 'content' => $orderSummary
408 ];
409
410 return $meta;
411 }
412
413 private function maybeUpdateChildBookings($booking, $calendarEvent, $order)
414 {
415 $childBookingIds = Booking::where('parent_id', $booking->id)
416 ->where('status', 'pending')
417 ->pluck('id')
418 ->toArray();
419
420 if (!$childBookingIds) {
421 return;
422 }
423
424 foreach ($childBookingIds as $childBookingId) {
425 $childBooking = Booking::find($childBookingId);
426
427 if (!$childBooking) {
428 continue;
429 }
430
431 $childBooking->update([
432 'status' => $booking->status,
433 'payment_status' => $booking->payment_status,
434 ]);
435
436 if ($booking->status == 'scheduled') {
437 do_action('fluent_booking/pre_after_booking_scheduled', $childBooking, $calendarEvent, $childBooking);
438
439 $childBooking = Booking::with(['calendar_event', 'calendar'])->find($childBooking->id);
440
441 do_action('fluent_booking/after_booking_scheduled', $childBooking, $calendarEvent, $childBooking);
442 }
443
444 do_action('fluent_booking/log_booking_activity', CartHelper::getSuccessLog($childBookingId, $order));
445 }
446 }
447 }
448