dayslot_session_get(), 'inputs' => apply_filters( 'dayslot_box_input_names', array( 'date', 'slot', 'variant', 'mode', 'location' ) ), ); } wp_add_inline_script( 'dys-picker', 'window.dayslotData = ' . wp_json_encode( $data ) . ';', 'before' ); $css = dayslot_style_css(); if ( $css ) { wp_add_inline_style( 'dys-picker', $css ); } do_action( 'dayslot_enqueue_picker', $products, $exclude_order ); } } /** * Time options for a date (AJAX, used everywhere the picker is). */ function dayslot_ajax_slots() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only availability lookup, no state change. $choice = dayslot_read_choice( $_GET ); $exclude = 0; $products = dayslot_cart_products(); if ( ! empty( $_GET['exclude'] ) && is_user_logged_in() ) { $order = wc_get_order( absint( $_GET['exclude'] ) ); if ( $order && ( (int) $order->get_customer_id() === get_current_user_id() || current_user_can( 'edit_shop_orders' ) ) ) { // Changing the date of this order: its own products and value count, not the cart's. $exclude = $order->get_id(); $products = dayslot_order_products( $order ); dayslot_worth_order( $order ); } } // phpcs:enable $ctx = dayslot_ctx_for_choice( $choice, $products, $exclude ); $groups = array(); $error = ''; if ( dayslot_parse_date( $choice['date'] ) ) { $error = dayslot_validate_choice( array_merge( $choice, array( 'slot' => '' ) ), $ctx ); if ( dayslot_get_settings()['slot_required_message'] === $error ) { $error = ''; } if ( '' === $error ) { $groups = dayslot_time_options( $choice['date'], $ctx ); } } nocache_headers(); wp_send_json_success( array( 'groups' => $groups, 'error' => $error, ) ); } add_action( 'wc_ajax_dayslot_slots', 'dayslot_ajax_slots' ); /** * Read the choice from a classic checkout POST. */ function dayslot_posted_choice( $src = null ) { // phpcs:disable WordPress.Security.NonceVerification.Missing -- WooCommerce verifies the checkout nonce. $src = null === $src ? $_POST : $src; $out = array(); foreach ( array( 'date', 'slot', 'variant', 'mode', 'location', 'fields' ) as $key ) { if ( isset( $src[ 'dayslot_' . $key ] ) && is_scalar( $src[ 'dayslot_' . $key ] ) ) { $value = wp_unslash( (string) $src[ 'dayslot_' . $key ] ); $out[ $key ] = 'fields' === $key ? sanitize_textarea_field( $value ) : sanitize_text_field( $value ); // Extra fields arrive as JSON text. } } // phpcs:enable return dayslot_read_choice( $out ); } /** * Keep the choice in the session while the customer is on the checkout (for the shipping price). */ add_action( 'woocommerce_checkout_update_order_review', function ( $post_data ) { parse_str( (string) $post_data, $data ); if ( isset( $data['dayslot_date'] ) ) { dayslot_session_set( dayslot_posted_choice( $data ) ); } } ); /** * Should the picker be inserted automatically at this hook? */ function dayslot_auto_box( $where ) { if ( ! empty( $GLOBALS['dayslot_box_rendered'] ) || ! apply_filters( 'dayslot_server_box', true ) || ! in_array( dayslot_get_settings()['placement'], (array) $where, true ) || dayslot_manual_placement() || ! dayslot_cart_needs_date() ) { return; } dayslot_render_box(); } foreach ( array( 'woocommerce_checkout_before_customer_details' => 'before_address', 'woocommerce_before_checkout_billing_form' => 'before_address', // Right after the address form; FunnelKit and most builders run this hook too. 'woocommerce_after_checkout_billing_form' => 'after_address', 'woocommerce_before_order_notes' => array( 'after_address', 'before_notes' ), 'woocommerce_after_order_notes' => 'after_notes', ) as $dayslot_hook => $dayslot_where ) { add_action( $dayslot_hook, function () use ( $dayslot_where ) { dayslot_auto_box( $dayslot_where ); } ); } unset( $dayslot_hook, $dayslot_where ); // Checkout layouts that skip the hooks above (some page builders): put it before the order review instead. // Page builders and FunnelKit render their own fields and skip the order-notes hooks: // the first of these hooks that runs inside the form places the picker (once). foreach ( array( 'woocommerce_checkout_after_customer_details', 'wfacp_before_payment_section', 'woocommerce_checkout_before_order_review' ) as $dayslot_hook ) { add_action( $dayslot_hook, function () { if ( empty( $GLOBALS['dayslot_box_rendered'] ) && apply_filters( 'dayslot_server_box', true ) && ! dayslot_manual_placement() && dayslot_cart_needs_date() ) { dayslot_render_box(); } } ); } unset( $dayslot_hook ); /** * The picker box. In the classic checkout the hidden inputs travel with the form; * the Checkout block script reads the same box when it is placed manually. */ function dayslot_render_box( $manual = false ) { if ( ! empty( $GLOBALS['dayslot_box_rendered'] ) ) { return; } $GLOBALS['dayslot_box_rendered'] = true; dayslot_enqueue_picker( dayslot_cart_products() ); $choice = dayslot_posted_choice(); if ( '' === $choice['date'] ) { $choice = dayslot_session_get(); } ?>
'info'|'error', 'text' => '' ). */ function dayslot_checkout_notice() { $notice = apply_filters( 'dayslot_checkout_notice', array( 'type' => '', 'text' => '', ), dayslot_session_get() ); return is_array( $notice ) ? wp_parse_args( $notice, array( 'type' => '', 'text' => '', ) ) : array( 'type' => '', 'text' => '', ); } function dayslot_checkout_notice_html() { $n = dayslot_checkout_notice(); return '' === $n['text'] ? '' : '
' . esc_html( $n['text'] ) . '
'; } add_filter( 'woocommerce_update_order_review_fragments', function ( $fragments ) { $fragments['#dayslot_field .dys-notice'] = '
' . dayslot_checkout_notice_html() . '
'; return $fragments; } ); /** * Before WooCommerce validates and prices the order: use what is being submitted. */ add_action( 'woocommerce_checkout_process', function () { if ( ! dayslot_cart_needs_date() ) { return; } dayslot_session_set( dayslot_posted_choice() ); WC()->cart->calculate_shipping(); WC()->cart->calculate_totals(); }, 5 ); /** * Server-side check — the real protection against double bookings. */ add_action( 'woocommerce_checkout_process', function () { if ( ! dayslot_cart_needs_date() ) { return; } $choice = dayslot_posted_choice(); $error = dayslot_validate_choice( $choice, dayslot_ctx_for_choice( $choice, dayslot_cart_products() ) ); if ( $error ) { wc_add_notice( $error, 'error' ); } } ); /** * Save on the order. */ add_action( 'woocommerce_checkout_create_order', function ( $order ) { if ( dayslot_cart_needs_date() ) { dayslot_save_order_values( $order, dayslot_posted_choice() ); } } ); /** * Forget the choice once the order is placed. */ add_action( 'woocommerce_thankyou', function () { if ( function_exists( 'WC' ) && WC()->session ) { WC()->session->set( 'dayslot_choice', null ); } } );