PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.10.02
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.10.02
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 / Integrations / FluentCart / Bootstrap.php

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

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