HEX
Server: Apache
System: Linux web5.visualcom.es 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: ensaco.es (10019)
PHP: 8.3.32
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/ensaco.es/httpdocs/wp-content/plugins/woo-product-bundle/includes/class-woosb.php
<?php
declare( strict_types=1 );
defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'WPCleverWoosb' ) && class_exists( 'WC_Product' ) ) {
    class WPCleverWoosb {
        protected static $instance = null;
        protected $helper = null;

        public static function instance() {
            if ( is_null( self::$instance ) ) {
                self::$instance = new self();
            }

            return self::$instance;
        }

        function __construct() {
            // Cache helper instance
            $this->helper = WPCleverWoosb_Helper();

            // Init
            add_action( 'init', [ $this, 'init' ] );

            // Add image to variation
            add_filter( 'woocommerce_available_variation', [ $this, 'available_variation' ], 10, 3 );

            // Enqueue frontend scripts
            add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );

            // Product tab
            if ( ( $this->helper->get_setting( 'bundled_position', 'above' ) === 'tab' ) || ( $this->helper->get_setting( 'bundles_position', 'no' ) === 'tab' ) ) {
                add_filter( 'woocommerce_product_tabs', [ $this, 'product_tabs' ] );
            }

            // Bundled products position
            switch ( $this->helper->get_setting( 'bundled_position', 'above' ) ) {
                case 'below_title':
                    add_action( 'woocommerce_single_product_summary', [ $this, 'product_summary_bundled' ], 6 );
                    break;
                case 'below_price':
                    add_action( 'woocommerce_single_product_summary', [ $this, 'product_summary_bundled' ], 11 );
                    break;
                case 'below_excerpt':
                    add_action( 'woocommerce_single_product_summary', [ $this, 'product_summary_bundled' ], 21 );
                    break;
            }

            // Bundles position
            switch ( $this->helper->get_setting( 'bundles_position', 'no' ) ) {
                case 'above':
                    add_action( 'woocommerce_single_product_summary', [ $this, 'product_summary_bundles' ], 29 );
                    break;
                case 'below':
                    add_action( 'woocommerce_single_product_summary', [ $this, 'product_summary_bundles' ], 31 );
                    break;
            }

            // Product price class
            add_filter( 'woocommerce_product_price_class', [ $this, 'product_price_class' ] );

            // Add-to-cart form & button
            add_action( 'woocommerce_woosb_add_to_cart', [ $this, 'add_to_cart_form' ] );
            add_action( 'woocommerce_before_add_to_cart_button', [ $this, 'add_to_cart_button' ] );

            // Add to cart
            add_filter( 'woocommerce_add_to_cart_sold_individually_found_in_cart', [ $this, 'found_in_cart' ], 10, 2 );
            add_filter( 'woocommerce_add_to_cart_validation', [ $this, 'add_to_cart_validation' ], 10, 3 );
            add_filter( 'woocommerce_add_cart_item_data', [ $this, 'add_cart_item_data' ], 10, 2 );
            add_action( 'woocommerce_add_to_cart', [ $this, 'add_to_cart' ], 10, 6 );
            add_filter( 'woocommerce_get_cart_item_from_session', [ $this, 'get_cart_item_from_session' ], 10, 2 );

            // Cart item
            add_filter( 'woocommerce_cart_item_name', [ $this, 'cart_item_name' ], 10, 2 );
            add_action( 'woocommerce_after_cart_item_name', [ $this, 'cart_item_edit' ], 10, 2 );
            add_filter( 'woocommerce_cart_item_quantity', [ $this, 'cart_item_quantity' ], 10, 3 );
            add_filter( 'woocommerce_cart_item_remove_link', [ $this, 'cart_item_remove_link' ], 10, 2 );
            add_filter( 'woocommerce_cart_contents_count', [ $this, 'cart_contents_count' ] );
            add_action( 'woocommerce_cart_item_removed', [ $this, 'cart_item_removed' ], 10, 2 );
            add_filter( 'woocommerce_cart_item_price', [ $this, 'cart_item_price' ], 9999, 2 );
            add_filter( 'woocommerce_cart_item_subtotal', [ $this, 'cart_item_subtotal' ], 9999, 2 );

            // Order
            add_filter( 'woocommerce_get_item_count', [ $this, 'get_item_count' ], 10, 3 );

            // Mini cart item visible
            add_filter( 'woocommerce_widget_cart_item_visible', [ $this, 'mini_cart_item_visible' ], 10, 2 );

            // Cart item visible
            add_filter( 'woocommerce_cart_item_visible', [ $this, 'cart_item_visible' ], 10, 2 );
            add_filter( 'woocommerce_checkout_cart_item_visible', [ $this, 'cart_item_visible' ], 10, 2 );

            // Order item visible
            add_filter( 'woocommerce_order_item_visible', [ $this, 'order_item_visible' ], 10, 2 );

            // Item class
            if ( $this->helper->get_setting( 'hide_bundled', 'no' ) !== 'yes' ) {
                add_filter( 'woocommerce_cart_item_class', [ $this, 'cart_item_class' ], 10, 2 );
                add_filter( 'woocommerce_mini_cart_item_class', [ $this, 'cart_item_class' ], 10, 2 );
                add_filter( 'woocommerce_order_item_class', [ $this, 'cart_item_class' ], 10, 2 );
            }

            // Get item data
            if ( $this->helper->get_setting( 'hide_bundled', 'no' ) === 'yes_text' || $this->helper->get_setting( 'hide_bundled', 'no' ) === 'yes_list' ) {
                add_filter( 'woocommerce_get_item_data', [ $this, 'cart_item_meta' ], 10, 2 );
            }

            // Order item
            add_action( 'woocommerce_checkout_create_order_line_item', [ $this, 'create_order_line_item' ], 10, 3 );
            add_filter( 'woocommerce_order_item_name', [ $this, 'cart_item_name' ], 10, 2 );
            add_filter( 'woocommerce_order_formatted_line_subtotal', [ $this, 'formatted_line_subtotal' ], 10, 2 );

            if ( $this->helper->get_setting( 'hide_bundled_order', 'no' ) === 'yes_text' || $this->helper->get_setting( 'hide_bundled_order', 'no' ) === 'yes_list' ) {
                // Hide bundled products, just show the main product on order details (order confirmation or emails)
                add_action( 'woocommerce_order_item_meta_start', [ $this, 'order_item_meta_start' ], 10, 2 );
            }

            // Undo remove
            add_action( 'woocommerce_restore_cart_item', [ $this, 'restore_cart_item' ] );

            // Loop add-to-cart
            add_filter( 'woocommerce_loop_add_to_cart_link', [ $this, 'loop_add_to_cart_link' ], 99, 2 );

            // Before calculate totals
            add_action( 'woocommerce_before_mini_cart_contents', [ $this, 'before_mini_cart_contents' ], 9999 );
            add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_calculate_totals' ], 9999 );

            // Shipping
            add_filter( 'woocommerce_cart_shipping_packages', [ $this, 'cart_shipping_packages' ], 9 );
            add_filter( 'woocommerce_cart_contents_weight', [ $this, 'cart_contents_weight' ], 9 );

            // Price HTML
            add_filter( 'woocommerce_get_price_html', [ $this, 'get_price_html' ], 99, 2 );

            // Order again
            add_filter( 'woocommerce_order_again_cart_item_data', [ $this, 'order_again_cart_item_data' ], 10, 2 );
            add_action( 'woocommerce_cart_loaded_from_session', [ $this, 'cart_loaded_from_session' ] );

            // Coupons
            add_filter( 'woocommerce_coupon_is_valid_for_product', [ $this, 'coupon_is_valid_for_product' ], 10, 4 );

            // Update stock status
            add_action( 'woocommerce_product_set_stock_status', [ $this, 'update_stock_status' ], 10, 3 );
            add_action( 'woocommerce_variation_set_stock_status', [ $this, 'update_stock_status' ], 10, 3 );
        }

        function init() {

            // shortcode
            add_shortcode( 'woosb_form', [ $this, 'shortcode_form' ] );
            add_shortcode( 'woosb_bundled', [ $this, 'shortcode_bundled' ] );
            add_shortcode( 'woosb_bundles', [ $this, 'shortcode_bundles' ] );
        }

        function available_variation( $data, $variable, $variation ) {
            if ( apply_filters( 'woosb_add_variation_image', true ) && ( $image_id = $variation->get_image_id() ) ) {
                $data['woosb_image'] = wp_get_attachment_image( $image_id, $this->helper::get_image_size() );
            }

            return $data;
        }

        function enqueue_scripts() {
            wp_enqueue_style( 'woosb-frontend', WOOSB_URI . 'assets/css/frontend.css', [], WOOSB_VERSION );
            wp_enqueue_script( 'woosb-frontend', WOOSB_URI . 'assets/js/frontend.js', [ 'jquery' ], WOOSB_VERSION, true );
            wp_localize_script(
                    'woosb-frontend',
                    'woosb_vars',
                    apply_filters( 'woosb_vars', [
                            'wc_price_decimals'           => wc_get_price_decimals(),
                            'wc_price_format'             => get_woocommerce_price_format(),
                            'wc_price_thousand_separator' => wc_get_price_thousand_separator(),
                            'wc_price_decimal_separator'  => wc_get_price_decimal_separator(),
                            'wc_currency_symbol'          => get_woocommerce_currency_symbol(),
                            'price_decimals'              => apply_filters( 'woosb_price_decimals', wc_get_price_decimals() ),
                            'price_format'                => get_woocommerce_price_format(),
                            'price_thousand_separator'    => wc_get_price_thousand_separator(),
                            'price_decimal_separator'     => wc_get_price_decimal_separator(),
                            'currency_symbol'             => get_woocommerce_currency_symbol(),
                            'trim_zeros'                  => apply_filters( 'woosb_price_trim_zeros', apply_filters( 'woocommerce_price_trim_zeros', false ) ),
                            'round_price'                 => apply_filters( 'woosb_round_price', ! apply_filters( 'woosb_ignore_round_price', false ) ),
                            'recalc_price'                => apply_filters( 'woosb_recalc_price', false ),
                            'change_image'                => $this->helper->get_setting( 'change_image', 'yes' ),
                            'bundled_price'               => $this->helper->get_setting( 'bundled_price', 'price' ),
                            'bundled_price_from'          => $this->helper->get_setting( 'bundled_price_from', 'sale_price' ),
                            'change_price'                => $this->helper->get_setting( 'change_price', 'yes' ),
                            'price_selector'              => $this->helper->get_setting( 'change_price_custom', '' ),
                            'saved_text'                  => $this->helper->localization( 'saved', esc_html__( '(saved [d])', 'woo-product-bundle' ) ),
                            'price_text'                  => $this->helper->localization( 'total', esc_html__( 'Bundle price:', 'woo-product-bundle' ) ),
                            'selected_text'               => $this->helper->localization( 'selected', esc_html__( 'Selected:', 'woo-product-bundle' ) ),
                            'alert_selection'             => $this->helper->localization( 'alert_selection', esc_html__( 'Please select a purchasable variation for [name] before adding this bundle to the cart.', 'woo-product-bundle' ) ),
                            'alert_unpurchasable'         => $this->helper->localization( 'alert_unpurchasable', esc_html__( 'Product [name] is unpurchasable. Please remove it before adding the bundle to the cart.', 'woo-product-bundle' ) ),
                            'alert_empty'                 => $this->helper->localization( 'alert_empty', esc_html__( 'Please choose at least one product before adding this bundle to the cart.', 'woo-product-bundle' ) ),
                            'alert_min'                   => $this->helper->localization( 'alert_min', esc_html__( 'Please choose at least a total quantity of [min] products before adding this bundle to the cart.', 'woo-product-bundle' ) ),
                            'alert_max'                   => $this->helper->localization( 'alert_max', esc_html__( 'Sorry, you can only choose at max a total quantity of [max] products before adding this bundle to the cart.', 'woo-product-bundle' ) ),
                            'alert_total_min'             => $this->helper->localization( 'alert_total_min', esc_html__( 'The total must meet the minimum amount of [min].', 'woo-product-bundle' ) ),
                            'alert_total_max'             => $this->helper->localization( 'alert_total_max', esc_html__( 'The total must meet the maximum amount of [max].', 'woo-product-bundle' ) ),
                    ] )
            );
        }

        function cart_contents_count( $count ) {
            // count for cart contents
            $cart_count = $this->helper->get_setting( 'cart_contents_count', 'bundle' );

            if ( $cart_count !== 'both' ) {
                foreach ( WC()->cart->get_cart() as $cart_item ) {
                    if ( ( $cart_count === 'bundled_products' ) && ! empty( $cart_item['woosb_ids'] ) ) {
                        $count -= $cart_item['quantity'];
                    }

                    if ( ( $cart_count === 'bundle' ) && ! empty( $cart_item['woosb_parent_id'] ) ) {
                        $count -= $cart_item['quantity'];
                    }
                }
            }

            return apply_filters( 'woosb_cart_contents_count', $count );
        }

        function get_item_count( $count, $type, $order ) {
            // count for order items
            $cart_count    = $this->helper->get_setting( 'cart_contents_count', 'bundle' );
            $order_bundles = $order_bundled = 0;

            if ( $cart_count !== 'both' ) {
                $order_items = $order->get_items( 'line_item' );

                foreach ( $order_items as $order_item ) {
                    if ( $order_item->get_meta( '_woosb_parent_id' ) ) {
                        $order_bundled += $order_item->get_quantity();
                    }

                    if ( $order_item->get_meta( '_woosb_ids' ) ) {
                        $order_bundles += $order_item->get_quantity();
                    }
                }

                if ( ( $cart_count === 'bundled_products' ) && ( $order_bundled > 0 ) ) {
                    return $count - $order_bundles;
                }

                if ( ( $cart_count === 'bundle' ) && ( $order_bundles > 0 ) ) {
                    return $count - $order_bundled;
                }
            }

            return apply_filters( 'woosb_get_item_count', $count );
        }

        function cart_item_name( $name, $item ) {
            if ( empty( $item['woosb_parent_id'] ) ) {
                return $name;
            }

            $show_bundle_name = $this->helper->get_setting( 'hide_bundle_name', 'no' ) === 'no';
            $parent_id        = apply_filters( 'woosb_item_id', $item['woosb_parent_id'] );

            if ( ( str_contains( $name, '</a>' ) ) && ( $this->helper->get_setting( 'bundled_link', 'yes' ) !== 'no' ) ) {
                if ( $show_bundle_name ) {
                    $_name = '<a href="' . esc_url( get_permalink( $parent_id ) ) . '">' . esc_html( get_the_title( $parent_id ) ) . '</a>' . apply_filters( 'woosb_name_separator', ' &rarr; ' ) . apply_filters( 'woosb_item_product_name', $name, $item );
                } else {
                    $_name = apply_filters( 'woosb_item_product_name', $name, $item );
                }
            } else {
                if ( $show_bundle_name ) {
                    $_name = esc_html( get_the_title( $parent_id ) ) . apply_filters( 'woosb_name_separator', ' &rarr; ' ) . wp_strip_all_tags( apply_filters( 'woosb_item_product_name', $name, $item ) );
                } else {
                    $_name = wp_strip_all_tags( apply_filters( 'woosb_item_product_name', $name, $item ) );
                }
            }

            return apply_filters( 'woosb_cart_item_name', $_name, $name, $item );
        }

        function cart_item_edit( $cart_item, $cart_item_key ) {
            $edit_link = $this->helper->get_setting( 'edit_link', 'no' ) === 'yes';

            if ( ! $edit_link ) {
                return;
            }

            if ( ! empty( $cart_item['woosb_ids'] ) && ( is_a( $cart_item['data'], 'WC_Product_Woosb' ) && ( $cart_item['data']->has_optional() || $cart_item['data']->has_variables() ) ) ) {
                $edit_url  = apply_filters( 'woosb_cart_item_edit_url', add_query_arg( [
                        'edit' => base64_encode( $cart_item['woosb_ids'] ),
                        'key'  => $cart_item_key
                ], $cart_item['data']->get_permalink() ), $cart_item, $cart_item_key );
                $edit_link = ' <a class="woosb-cart-item-edit" href="' . esc_url( $edit_url ) . '">' . esc_html( $this->helper->localization( 'cart_item_edit', esc_html__( 'Edit', 'woo-product-bundle' ) ) ) . '</a>';

                echo apply_filters( 'woosb_cart_item_edit_link', $edit_link, $cart_item, $cart_item_key );
            }
        }

        function cart_item_removed( $cart_item_key, $cart ) {
            $new_keys = [];

            foreach ( $cart->cart_contents as $cart_key => $cart_item ) {
                if ( ! empty( $cart_item['woosb_key'] ) ) {
                    $new_keys[ $cart_key ] = $cart_item['woosb_key'];
                }
            }

            if ( isset( $cart->removed_cart_contents[ $cart_item_key ]['woosb_keys'] ) ) {
                // remove all bundled sub-products when removing the main bundle
                $keys = $cart->removed_cart_contents[ $cart_item_key ]['woosb_keys'];

                foreach ( $keys as $key ) {
                    WC()->cart->remove_cart_item( $key );

                    if ( $new_key = array_search( $key, $new_keys ) ) {
                        WC()->cart->remove_cart_item( $new_key );
                    }
                }
            }

            if ( isset( $cart->removed_cart_contents[ $cart_item_key ]['woosb_parent_key'] ) ) {
                // remove the main bundle when removing any bundled sub-product
                $parent_key = $cart->removed_cart_contents[ $cart_item_key ]['woosb_parent_key'];

                WC()->cart->remove_cart_item( $parent_key );
            }
        }

        function check_in_cart( $product_id ) {
            foreach ( WC()->cart->get_cart() as $cart_item ) {
                if ( $cart_item['product_id'] == $product_id ) {
                    return true;
                }
            }

            return false;
        }

        function found_in_cart( $found_in_cart, $product_id ) {
            if ( apply_filters( 'woosb_sold_individually_found_in_cart', true ) && self::check_in_cart( $product_id ) ) {
                return true;
            }

            return $found_in_cart;
        }

        function add_to_cart_validation( $passed, $product_id, $qty ) {
            if ( ! apply_filters( 'woosb_add_to_cart_validation', true ) || isset( $_REQUEST['order_again'] ) ) {
                return $passed;
            }

            if ( ( $product = wc_get_product( $product_id ) ) && is_a( $product, 'WC_Product_Woosb' ) ) {
                // get settings before re-building items
                $has_optional  = $product->has_optional();
                $has_variables = $product->has_variables();

                // get original items for validating
                $ori_items = $product->get_items();
                $ori_ids   = array_filter( array_column( $ori_items, 'id' ) );

                if ( isset( $_REQUEST['woosb_ids'] ) ) {
                    $ids = $this->helper->clean_ids( wp_unslash( $_REQUEST['woosb_ids'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
                    $product->build_items( $ids );
                }

                if ( ( $items = $product->get_items() ) && ! empty( $items ) ) {
                    $count                 = $total = $purchasable = 0;
                    $min_whole             = (float) ( $product->get_meta( 'woosb_limit_whole_min' ) ?: 1 );
                    $max_whole             = (float) ( $product->get_meta( 'woosb_limit_whole_max' ) ?: - 1 );
                    $total_min             = (float) ( $product->get_meta( 'woosb_total_limits_min' ) ?: 0 );
                    $total_max             = (float) ( $product->get_meta( 'woosb_total_limits_max' ) ?: - 1 );
                    $total_limits          = $product->get_meta( 'woosb_total_limits' ) === 'on';
                    $check_total           = ! $product->is_fixed_price() && ( $has_optional || $has_variables ) && $total_limits;
                    $exclude_unpurchasable = $product->exclude_unpurchasable();

                    foreach ( $items as $key => $item ) {
                        $_id = $item['id'];

                        if ( ! $_id ) {
                            // exclude heading/paragraph
                            continue;
                        }

                        $_qty     = $item['qty'];
                        $count    += $_qty;
                        $_parent  = 0;
                        $_product = wc_get_product( $_id );

                        if ( ! $_product ) {
                            if ( ! $exclude_unpurchasable ) {
                                wc_add_notice( esc_html__( 'One of the bundled products is unavailable.', 'woo-product-bundle' ), 'error' );
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            } else {
                                continue;
                            }
                        }

                        if ( $check_total ) {
                            $total += wc_get_price_to_display( $_product, [ 'qty' => $_qty ] );
                        }

                        if ( $_product->is_type( 'variation' ) ) {
                            $_parent = $_product->get_parent_id();
                        }

                        if ( ! function_exists( 'wpml_loaded' ) ) {
                            // don't check it with wpml
                            $ori_item_id = isset( $ori_items[ $key ]['id'] ) ? (int) $ori_items[ $key ]['id'] : 0;

                            if ( ! isset( $ori_items[ $key ] ) || ( $ori_item_id !== (int) $_id && $ori_item_id !== $_parent ) ) {
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }
                        }

                        // validate qty
                        if ( ! isset( $ori_items[ $key ] ) ) {
                            // key doesn't exist in original items, already handled above for non-WPML
                            continue;
                        }

                        if ( ! empty( $ori_items[ $key ]['optional'] ) ) {
                            $_min = ! empty( $ori_items[ $key ]['min'] ) ? (float) $ori_items[ $key ]['min'] : 0;
                            $_max = ! empty( $ori_items[ $key ]['max'] ) ? (float) $ori_items[ $key ]['max'] : 10000;

                            if ( $_qty < $_min || $_qty > $_max ) {
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }
                        } else {
                            if ( (float) $_qty !== (float) $ori_items[ $key ]['qty'] ) {
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }
                        }

                        if ( ! $exclude_unpurchasable ) {
                            if ( $_product->is_type( 'variable' ) || $_product->is_type( 'woosb' ) || ! $_product->is_in_stock() || ! $_product->is_purchasable() ) {
                                /* translators: product name */
                                wc_add_notice( sprintf( esc_html__( '"%s" is un-purchasable.', 'woo-product-bundle' ), esc_html( $_product->get_name() ) ), 'error' );
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }

                            if ( $_product->is_sold_individually() && apply_filters( 'woosb_sold_individually_found_in_cart', true ) && self::check_in_cart( $_id ) ) {
                                /* translators: product name */
                                wc_add_notice( sprintf( esc_html__( 'You cannot add another "%s" to the cart.', 'woo-product-bundle' ), esc_html( $_product->get_name() ) ), 'error' );
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }

                            if ( post_password_required( $_id ) ) {
                                /* translators: product name */
                                wc_add_notice( sprintf( esc_html__( '"%s" is protected and cannot be purchased.', 'woo-product-bundle' ), esc_html( $_product->get_name() ) ), 'error' );
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }

                            if ( $_product->managing_stock() ) {
                                $qty_in_cart  = ( method_exists( WC()->cart, 'get_cart_item_quantities' ) ) && ( $quantities = WC()->cart->get_cart_item_quantities() ) && method_exists( $_product, 'get_stock_managed_by_id' ) && isset( $quantities[ $_product->get_stock_managed_by_id() ] ) ? $quantities[ $_product->get_stock_managed_by_id() ] : 0;
                                $qty_to_check = 0;

                                // use already-fetched $items instead of calling get_items() again
                                foreach ( $items as $_item ) {
                                    if ( (int) $_item['id'] === (int) $_id ) {
                                        $qty_to_check += $_item['qty'];
                                    }
                                }

                                if ( ! $_product->has_enough_stock( $qty_in_cart + $qty_to_check * $qty ) ) {
                                    /* translators: product name */
                                    wc_add_notice( sprintf( esc_html__( '"%s" has not enough stock.', 'woo-product-bundle' ), esc_html( $_product->get_name() ) ), 'error' );
                                    wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                    return false;
                                }
                            }
                        }

                        $purchasable ++;
                    }

                    if ( ! $purchasable || ( $purchasable > count( $ori_ids ) ) ) {
                        wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                        return false;
                    }

                    if ( ! $exclude_unpurchasable && ! $has_optional && ( $purchasable < count( $ori_ids ) ) ) {
                        wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                        return false;
                    }

                    // validate that all required (non-optional) items are present in submitted items
                    if ( $has_optional ) {
                        foreach ( $ori_items as $ori_key => $ori_item ) {
                            if ( empty( $ori_item['id'] ) || ! empty( $ori_item['optional'] ) ) {
                                // skip heading/paragraph and optional items
                                continue;
                            }

                            // required item must exist with the exact same key
                            if ( ! isset( $items[ $ori_key ] ) ) {
                                wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                                return false;
                            }
                        }
                    }

                    if ( $has_optional && ( ( $min_whole > 0 && $count < $min_whole ) || ( $max_whole > 0 && $count > $max_whole ) ) ) {
                        wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                        return false;
                    }

                    if ( $check_total ) {
                        if ( $discount_amount = $product->get_discount_amount() ) {
                            $total -= $discount_amount;
                        } elseif ( $discount_percentage = $product->get_discount_percentage() ) {
                            $total = $total * ( 100 - $discount_percentage ) / 100;
                        }

                        if ( $total_min > 0 && $total < $total_min ) {
                            wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                            return false;
                        }

                        if ( $total_max > 0 && $total > $total_max ) {
                            wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                            return false;
                        }
                    }
                } else {
                    wc_add_notice( esc_html__( 'You cannot add this bundle to the cart.', 'woo-product-bundle' ), 'error' );

                    return false;
                }
            }

            return $passed;
        }

        function add_cart_item_data( $cart_item_data, $product_id ) {
            if ( empty( $cart_item_data['woosb_ids'] ) && ( $_product = wc_get_product( $product_id ) ) && $_product->is_type( 'woosb' ) && ( $ids = $_product->get_ids_str() ) ) {
                // get woosb_ids in custom data array
                $custom_request = apply_filters( 'woosb_custom_request_data', 'data' );

                if ( ! empty( $custom_request ) && ! empty( $_REQUEST[ $custom_request ]['woosb_ids'] ) ) {
                    $ids = $this->helper->clean_ids( wp_unslash( $_REQUEST[ $custom_request ]['woosb_ids'] ) );
                    unset( $_REQUEST[ $custom_request ]['woosb_ids'] );
                }

                // make sure that is a bundle
                if ( isset( $_REQUEST['woosb_ids'] ) ) {
                    $ids = $this->helper->clean_ids( wp_unslash( $_REQUEST['woosb_ids'] ?? '' ) );
                    unset( $_REQUEST['woosb_ids'] );
                }

                if ( ! empty( $ids ) ) {
                    $cart_item_data['woosb_ids'] = $ids;
                }
            }

            return $cart_item_data;
        }

        function add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
            $edit_link = $this->helper->get_setting( 'edit_link', 'no' ) === 'yes';
            $edit_key  = sanitize_key( wp_unslash( $_REQUEST['woosb_update'] ?? '' ) );

            if ( $edit_link && ! empty( $edit_key ) && ( $edit_item = WC()->cart->get_cart_item( $edit_key ) ) ) {
                unset( $_REQUEST['woosb_update'] );

                if ( $edit_key === $cart_item_key ) {
                    WC()->cart->set_quantity( $cart_item_key, $edit_item['quantity'] - $quantity );
                } else {
                    WC()->cart->remove_cart_item( $edit_key );
                }
            }

            if ( ! empty( $cart_item_data['woosb_ids'] ) && isset( WC()->cart->cart_contents[ $cart_item_key ] ) ) {
                unset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'] );
                WC()->cart->cart_contents[ $cart_item_key ]['data']->build_items( $cart_item_data['woosb_ids'] );
                $items = WC()->cart->cart_contents[ $cart_item_key ]['data']->get_items();

                self::add_to_cart_items( $items, $cart_item_key, $product_id, $quantity );
            }
        }

        function restore_cart_item( $cart_item_key ) {
            if ( isset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_ids'] ) ) {
                unset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'] );
                WC()->cart->cart_contents[ $cart_item_key ]['data']->build_items( WC()->cart->cart_contents[ $cart_item_key ]['woosb_ids'] );
                $items      = WC()->cart->cart_contents[ $cart_item_key ]['data']->get_items();
                $product_id = WC()->cart->cart_contents[ $cart_item_key ]['product_id'];
                $quantity   = WC()->cart->cart_contents[ $cart_item_key ]['quantity'];

                self::add_to_cart_items( $items, $cart_item_key, $product_id, $quantity );
            }
        }

        function add_to_cart_items( $items, $cart_item_key, $product_id, $quantity ) {
            if ( apply_filters( 'woosb_exclude_bundled', false ) ) {
                return;
            }

            $items = $this->helper->minify_items( $items );

            $fixed_price           = WC()->cart->cart_contents[ $cart_item_key ]['data']->is_fixed_price();
            $discount_amount       = WC()->cart->cart_contents[ $cart_item_key ]['data']->get_discount_amount();
            $discount_percentage   = WC()->cart->cart_contents[ $cart_item_key ]['data']->get_discount_percentage();
            $exclude_unpurchasable = WC()->cart->cart_contents[ $cart_item_key ]['data']->exclude_unpurchasable();

            // save the current key associated with woosb_parent_key
            WC()->cart->cart_contents[ $cart_item_key ]['woosb_key']             = $cart_item_key;
            WC()->cart->cart_contents[ $cart_item_key ]['woosb_fixed_price']     = $fixed_price;
            WC()->cart->cart_contents[ $cart_item_key ]['woosb_discount_amount'] = $discount_amount;
            WC()->cart->cart_contents[ $cart_item_key ]['woosb_discount']        = $discount_percentage;

            if ( is_array( $items ) && ( count( $items ) > 0 ) ) {
                foreach ( $items as $item ) {
                    $_id           = $item['id'];
                    $_qty          = $item['qty'];
                    $_variation    = $item['attrs'];
                    $_variation_id = 0;

                    $_product = wc_get_product( $item['id'] );

                    if ( ! $_product || ( $_qty <= 0 ) || in_array( $_product->get_type(), $this->helper::get_types(), true ) ) {
                        continue;
                    }

                    if ( ( ! $_product->is_purchasable() || ! $_product->is_in_stock() ) && $exclude_unpurchasable ) {
                        // exclude unpurchasable
                        continue;
                    }

                    if ( $_product instanceof WC_Product_Variation ) {
                        // ensure we don't add a variation to the cart directly by variation ID
                        $_variation_id = $_id;
                        $_id           = $_product->get_parent_id();

                        if ( empty( $_variation ) ) {
                            $_variation = $_product->get_variation_attributes();
                        }
                    }

                    // add to cart
                    $_data = [
                            'woosb_qty'             => $_qty,
                            'woosb_parent_id'       => $product_id,
                            'woosb_parent_key'      => $cart_item_key,
                            'woosb_fixed_price'     => $fixed_price,
                            'woosb_discount_amount' => $discount_amount,
                            'woosb_discount'        => $discount_percentage
                    ];

                    $_key = WC()->cart->add_to_cart( $_id, $_qty * $quantity, $_variation_id, $_variation, $_data );

                    if ( empty( $_key ) ) {
                        if ( ! $exclude_unpurchasable ) {
                            // can't add the bundled product
                            if ( isset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'] ) ) {
                                $keys = WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'];

                                foreach ( $keys as $key ) {
                                    // remove all bundled products
                                    WC()->cart->remove_cart_item( $key );
                                }

                                // remove the bundle
                                WC()->cart->remove_cart_item( $cart_item_key );

                                // break out of the loop
                                break;
                            }
                        }
                    } elseif ( ! isset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'] ) || ! in_array( $_key, WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'], true ) ) {
                        // save the current key
                        WC()->cart->cart_contents[ $_key ]['woosb_key'] = $_key;

                        // add keys for parent
                        WC()->cart->cart_contents[ $cart_item_key ]['woosb_keys'][] = $_key;
                    }
                } // end foreach
            }
        }

        function get_cart_item_from_session( $cart_item, $session_values ) {
            if ( ! empty( $session_values['woosb_ids'] ) ) {
                $cart_item['woosb_ids'] = $session_values['woosb_ids'];
            }

            if ( ! empty( $session_values['woosb_parent_id'] ) ) {
                $cart_item['woosb_parent_id']  = $session_values['woosb_parent_id'];
                $cart_item['woosb_parent_key'] = $session_values['woosb_parent_key'];
                $cart_item['woosb_qty']        = $session_values['woosb_qty'];
            }

            return $cart_item;
        }

        function before_mini_cart_contents() {
            WC()->cart->calculate_totals();
        }

        function before_calculate_totals( $cart_object ) {
            if ( ! defined( 'DOING_AJAX' ) && is_admin() ) {
                // This is necessary for WC 3.0+
                return;
            }

            $cart_contents = $cart_object->cart_contents;
            $new_keys      = [];

            foreach ( $cart_contents as $cart_item_key => $cart_item ) {
                if ( ! empty( $cart_item['woosb_key'] ) ) {
                    $new_keys[ $cart_item_key ] = $cart_item['woosb_key'];
                }
            }

            foreach ( $cart_contents as $cart_item_key => $cart_item ) {
                // bundled products
                if ( ! empty( $cart_item['woosb_parent_key'] ) ) {
                    $parent_new_key = array_search( $cart_item['woosb_parent_key'], $new_keys );

                    // remove orphaned bundled products
                    if ( apply_filters( 'woosb_remove_orphaned_bundled_products', true ) ) {
                        if ( ! $parent_new_key || ! isset( $cart_contents[ $parent_new_key ] ) || ( isset( $cart_contents[ $parent_new_key ]['woosb_keys'] ) && ! in_array( $cart_item_key, $cart_contents[ $parent_new_key ]['woosb_keys'] ) ) ) {
                            unset( $cart_contents[ $cart_item_key ] );
                            continue;
                        }
                    }

                    // sync quantity
                    if ( ! empty( $cart_item['woosb_qty'] ) ) {
                        WC()->cart->cart_contents[ $cart_item_key ]['quantity'] = $cart_item['woosb_qty'] * $cart_contents[ $parent_new_key ]['quantity'];
                    }

                    // set price
                    if ( isset( $cart_item['woosb_fixed_price'] ) && $cart_item['woosb_fixed_price'] ) {
                        $cart_item['data']->set_price( 0 );
                    } else {
                        $_product = wc_get_product( $cart_item['variation_id'] ?: $cart_item['product_id'] );
                        $_price   = (float) $this->helper->get_price( $_product );

                        if ( ! empty( $cart_item['woosb_discount'] ) ) {
                            $_price *= ( 100 - (float) $cart_item['woosb_discount'] ) / 100;
                        }

                        $_price = $this->helper->round_price( $_price );
                        $_price = apply_filters( 'woosb_item_price_add_to_cart', $_price, $cart_item );
                        $_price = apply_filters( 'woosb_item_price_before_set', $_price, $cart_item );
                        $cart_item['data']->set_price( $_price );
                    }
                }

                // bundles
                if ( ! empty( $cart_item['woosb_ids'] ) && isset( $cart_item['woosb_fixed_price'] ) && ! $cart_item['woosb_fixed_price'] ) {
                    // set tax status 'none'
                    $cart_item['data']->set_tax_status( 'none' );

                    // set price zero, calculate later
                    if ( isset( $cart_item['woosb_discount_amount'] ) && $cart_item['woosb_discount_amount'] ) {
                        $bundles_price = - (float) $cart_item['woosb_discount_amount'];
                    } else {
                        $bundles_price = 0;
                    }

                    $cart_item['data']->set_price( apply_filters( 'woosb_bundles_price', $bundles_price, $cart_item ) );

                    if ( ! empty( $cart_item['woosb_keys'] ) ) {
                        $bundles_display_price = 0;

                        foreach ( $cart_item['woosb_keys'] as $key ) {
                            if ( isset( $cart_contents[ $key ], $cart_contents[ $key ]['data'] ) ) {
                                $_product = wc_get_product( $cart_contents[ $key ]['variation_id'] ?: $cart_contents[ $key ]['product_id'] );
                                $_price   = (float) $this->helper->get_price( $_product );

                                if ( ! empty( $cart_contents[ $key ]['woosb_discount'] ) ) {
                                    $_price *= ( 100 - (float) $cart_item['woosb_discount'] ) / 100;
                                }

                                $_price = $this->helper->round_price( $_price );
                                $_price = apply_filters( 'woosb_item_price_add_to_cart', $_price, $cart_contents[ $key ] );
                                $_price = apply_filters( 'woosb_item_price_before_set', $_price, $cart_contents[ $key ] );

                                if ( ! is_null( WC()->cart ) && WC()->cart->display_prices_including_tax() ) {
                                    $_price = wc_get_price_including_tax( $cart_contents[ $key ]['data'], [
                                            'price' => $_price,
                                            'qty'   => $cart_contents[ $key ]['woosb_qty']
                                    ] );
                                } else {
                                    $_price = wc_get_price_excluding_tax( $cart_contents[ $key ]['data'], [
                                            'price' => $_price,
                                            'qty'   => $cart_contents[ $key ]['woosb_qty']
                                    ] );
                                }

                                $bundles_display_price += $this->helper->round_price( $_price );
                            }
                        }

                        if ( ! empty( $cart_item['woosb_discount_amount'] ) ) {
                            $bundles_display_price -= (float) $cart_item['woosb_discount_amount'];
                        }

                        $bundles_display_price = apply_filters( 'woosb_bundles_display_price', $bundles_display_price, $cart_item );

                        if ( $cart_item['quantity'] > 0 ) {
                            // store bundles total
                            WC()->cart->cart_contents[ $cart_item_key ]['woosb_price'] = $this->helper->round_price( $bundles_display_price );
                        }
                    }
                }
            }
        }

        function cart_item_price( $price, $cart_item ) {
            if ( isset( $cart_item['woosb_ids'], $cart_item['woosb_price'], $cart_item['woosb_fixed_price'] ) && ! $cart_item['woosb_fixed_price'] ) {
                return apply_filters( 'woosb_cart_item_price', wc_price( $cart_item['woosb_price'] ), $price, $cart_item );
            }

            if ( isset( $cart_item['woosb_parent_id'], $cart_item['woosb_fixed_price'] ) && $cart_item['woosb_fixed_price'] ) {
                $_product = wc_get_product( $cart_item['variation_id'] ?: $cart_item['product_id'] );

                if ( ! is_null( WC()->cart ) && WC()->cart->display_prices_including_tax() ) {
                    $_price = wc_price( wc_get_price_including_tax( $_product ) );
                } else {
                    $_price = wc_price( wc_get_price_excluding_tax( $_product ) );
                }

                return apply_filters( 'woosb_cart_item_price', $_price, $price, $cart_item );
            }

            return $price;
        }

        function cart_item_subtotal( $subtotal, $cart_item = null ) {
            if ( isset( $cart_item['woosb_ids'], $cart_item['woosb_price'], $cart_item['woosb_fixed_price'] ) && ! $cart_item['woosb_fixed_price'] ) {
                $_subtotal = wc_price( $cart_item['woosb_price'] * $cart_item['quantity'] );

                if ( wc_tax_enabled() && ! is_null( WC()->cart ) && WC()->cart->display_prices_including_tax() && ! wc_prices_include_tax() ) {
                    $_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
                }

                return apply_filters( 'woosb_cart_item_subtotal', $_subtotal, $subtotal, $cart_item );
            }

            if ( isset( $cart_item['woosb_parent_id'], $cart_item['woosb_fixed_price'] ) && $cart_item['woosb_fixed_price'] ) {
                $_product = wc_get_product( $cart_item['variation_id'] ?: $cart_item['product_id'] );

                if ( ! is_null( WC()->cart ) && WC()->cart->display_prices_including_tax() ) {
                    $_subtotal = wc_price( wc_get_price_including_tax( $_product, [ 'qty' => $cart_item['quantity'] ] ) );
                } else {
                    $_subtotal = wc_price( wc_get_price_excluding_tax( $_product, [ 'qty' => $cart_item['quantity'] ] ) );
                }

                if ( wc_tax_enabled() && ! is_null( WC()->cart ) && WC()->cart->display_prices_including_tax() && ! wc_prices_include_tax() ) {
                    $_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
                }

                return apply_filters( 'woosb_cart_item_subtotal', $_subtotal, $subtotal, $cart_item );
            }

            return $subtotal;
        }

        function mini_cart_item_visible( $visible, $cart_item ) {
            if ( isset( $cart_item['woosb_parent_id'] ) ) {
                if ( ! apply_filters( 'woosb_item_visible', true, $cart_item['data'], $cart_item['woosb_parent_id'] ) ) {
                    return false;
                }

                if ( $this->helper->get_setting( 'hide_bundled_mini_cart', 'no' ) === 'yes' ) {
                    return false;
                }
            }

            return $visible;
        }

        function cart_item_visible( $visible, $cart_item ) {
            if ( isset( $cart_item['woosb_parent_id'] ) ) {
                if ( ! apply_filters( 'woosb_item_visible', true, $cart_item['data'], $cart_item['woosb_parent_id'] ) ) {
                    return false;
                }

                if ( $this->helper->get_setting( 'hide_bundled', 'no' ) !== 'no' ) {
                    return false;
                }
            }

            return $visible;
        }

        function order_item_visible( $visible, $order_item ) {
            if ( ! is_a( $order_item, 'WC_Order_Item' ) ) {
                return $visible;
            }

            if ( $parent_id = $order_item->get_meta( '_woosb_parent_id' ) ) {
                if ( ! apply_filters( 'woosb_item_visible', true, $order_item->get_product(), $parent_id ) ) {
                    return false;
                }

                if ( $this->helper->get_setting( 'hide_bundled_order', 'no' ) !== 'no' ) {
                    return false;
                }
            }

            return $visible;
        }

        function cart_item_class( $class, $cart_item ) {
            if ( isset( $cart_item['woosb_parent_id'] ) ) {
                $class .= ' woosb-cart-item woosb-cart-child woosb-item-child';
            } elseif ( isset( $cart_item['woosb_ids'] ) ) {
                $class .= ' woosb-cart-item woosb-cart-parent woosb-item-parent';
            }

            return $class;
        }

        function cart_item_meta( $data, $cart_item ) {
            if ( empty( $cart_item['woosb_ids'] ) ) {
                return $data;
            }

            $cart_item['data']->build_items( $cart_item['woosb_ids'] );
            $items     = $cart_item['data']->get_items();
            $parent_id = $cart_item['product_id'];

            if ( $this->helper->get_setting( 'hide_bundled', 'no' ) === 'yes_list' ) {
                $items_str = [];

                if ( is_array( $items ) && ! empty( $items ) ) {
                    foreach ( $items as $item ) {
                        if ( ! apply_filters( 'woosb_item_visible', true, $item['id'], $parent_id ) ) {
                            continue;
                        }

                        $items_str[] = apply_filters( 'woosb_order_bundled_product_name', '<li>' . $item['qty'] . ' × ' . esc_html( get_the_title( $item['id'] ) ) . '</li>', $item );
                    }
                }

                $data['woosb_data'] = [
                        'key'     => $this->helper->localization( 'bundled_products', esc_html__( 'Bundled products', 'woo-product-bundle' ) ),
                        'value'   => esc_html( $cart_item['woosb_ids'] ),
                        'display' => apply_filters( 'woosb_order_bundled_product_names', '<ul>' . implode( '', $items_str ) . '</ul>', $items ),
                ];
            } else {
                $items_str = [];

                if ( is_array( $items ) && ! empty( $items ) ) {
                    foreach ( $items as $item ) {
                        if ( ! apply_filters( 'woosb_item_visible', true, $item['id'], $parent_id ) ) {
                            continue;
                        }

                        $items_str[] = apply_filters( 'woosb_order_bundled_product_name', $item['qty'] . ' × ' . esc_html( get_the_title( $item['id'] ) ), $item );
                    }
                }

                $data['woosb_data'] = [
                        'key'     => $this->helper->localization( 'bundled_products', esc_html__( 'Bundled products', 'woo-product-bundle' ) ),
                        'value'   => esc_html( $cart_item['woosb_ids'] ),
                        'display' => apply_filters( 'woosb_order_bundled_product_names', implode( '; ', $items_str ), $items ),
                ];
            }

            return $data;
        }

        function create_order_line_item( $order_item, $cart_item_key, $values ) {
            if ( isset( $values['woosb_parent_id'] ) ) {
                // use _ to hide the data
                $order_item->update_meta_data( '_woosb_parent_id', $values['woosb_parent_id'] );
            }

            if ( isset( $values['woosb_ids'] ) ) {
                // use _ to hide the data
                $order_item->update_meta_data( '_woosb_ids', $values['woosb_ids'] );
            }

            if ( isset( $values['woosb_price'] ) ) {
                // use _ to hide the data
                $order_item->update_meta_data( '_woosb_price', $values['woosb_price'] );
            }
        }

        function order_item_meta_start( $order_item_id, $order_item ) {
            if ( $ids = $order_item->get_meta( '_woosb_ids' ) ) {
                $parent    = $order_item->get_product();
                $parent_id = $parent->get_id();
                $items     = self::get_bundled( $ids, $parent );

                if ( $this->helper->get_setting( 'hide_bundled_order', 'no' ) === 'yes_list' ) {
                    $items_str = [];

                    if ( is_array( $items ) && ! empty( $items ) ) {
                        foreach ( $items as $item ) {
                            if ( ! empty( $item['id'] ) ) {
                                if ( ! apply_filters( 'woosb_item_visible', true, $item['id'], $parent_id ) ) {
                                    continue;
                                }

                                $items_str[] = apply_filters( 'woosb_order_bundled_product_name', '<li>' . $item['qty'] . ' × ' . esc_html( get_the_title( $item['id'] ) ) . '</li>', $item );
                            }
                        }
                    }

                    $items_str = apply_filters( 'woosb_order_bundled_product_names', '<ul>' . implode( '', $items_str ) . '</ul>', $items );
                } else {
                    $items_str = [];

                    if ( is_array( $items ) && ! empty( $items ) ) {
                        foreach ( $items as $item ) {
                            if ( ! empty( $item['id'] ) ) {
                                if ( ! apply_filters( 'woosb_item_visible', true, $item['id'], $parent_id ) ) {
                                    continue;
                                }

                                $items_str[] = apply_filters( 'woosb_order_bundled_product_name', $item['qty'] . ' × ' . esc_html( get_the_title( $item['id'] ) ), $item );
                            }
                        }
                    }

                    $items_str = apply_filters( 'woosb_order_bundled_product_names', implode( '; ', $items_str ), $items );
                }

                echo apply_filters( 'woosb_before_order_itemmeta_bundles', '<div class="woosb-itemmeta-bundles">' . /* translators: bundled products */ sprintf( $this->helper->localization( 'bundled_products_s', esc_html__( 'Bundled products: %s', 'woo-product-bundle' ) ), $items_str ) . '</div>', $order_item_id, $order_item );
            }
        }

        function formatted_line_subtotal( $subtotal, $order_item ) {
            if ( isset( $order_item['_woosb_ids'], $order_item['_woosb_price'] ) ) {
                return apply_filters( 'woosb_order_item_subtotal', wc_price( $order_item['_woosb_price'] * $order_item['quantity'] ), $subtotal, $order_item );
            }

            return $subtotal;
        }

        function cart_item_remove_link( $link, $cart_item_key ) {
            if ( isset( WC()->cart->cart_contents[ $cart_item_key ]['woosb_parent_key'] ) ) {
                $parent_key = WC()->cart->cart_contents[ $cart_item_key ]['woosb_parent_key'];

                if ( isset( WC()->cart->cart_contents[ $parent_key ] ) || array_search( $parent_key, array_column( WC()->cart->cart_contents, 'woosb_key', 'key' ) ) ) {
                    return '';
                }
            }

            return $link;
        }

        function cart_item_quantity( $quantity, $cart_item_key, $cart_item ) {
            // add qty as text - not input
            if ( isset( $cart_item['woosb_parent_id'] ) ) {
                return $cart_item['quantity'];
            }

            return $quantity;
        }

        function product_summary_bundles() {
            self::show_bundles();
        }

        function product_summary_bundled() {
            self::show_bundled();
        }

        function product_tabs( $tabs ) {
            global $product;
            $product_id = $product->get_id();

            if ( ( $this->helper->get_setting( 'bundled_position', 'above' ) === 'tab' ) && $product->is_type( 'woosb' ) ) {
                $tabs['woosb_bundled'] = apply_filters( 'woosb_bundled_tab', [
                        'title'    => $this->helper->localization( 'bundled_products', esc_html__( 'Bundled products', 'woo-product-bundle' ) ),
                        'priority' => 50,
                        'callback' => [ $this, 'product_tab_bundled' ]
                ], $product );
            }

            if ( ( $this->helper->get_setting( 'bundles_position', 'no' ) === 'tab' ) && ! $product->is_type( 'woosb' ) && self::get_bundles( $product_id ) ) {
                $tabs['woosb_bundles'] = apply_filters( 'woosb_bundles_tab', [
                        'title'    => $this->helper->localization( 'bundles', esc_html__( 'Bundles', 'woo-product-bundle' ) ),
                        'priority' => 50,
                        'callback' => [ $this, 'product_tab_bundles' ]
                ], $product );
            }

            return $tabs;
        }

        function product_tab_bundled() {
            self::show_bundled();
        }

        function product_tab_bundles() {
            self::show_bundles();
        }

        function product_price_class( $class ) {
            global $product;

            if ( $product && is_a( $product, 'WC_Product_Woosb' ) ) {
                $class .= ' woosb-price-' . $product->get_id();
            }

            return $class;
        }

        function add_to_cart_form() {
            global $product;

            if ( ! $product || ! is_a( $product, 'WC_Product_Woosb' ) ) {
                return;
            }

            if ( $product->has_variables() ) {
                wp_enqueue_script( 'wc-add-to-cart-variation' );
            }

            do_action( 'woosb_before_add_to_cart_form', $product );

            if ( ( $this->helper->get_setting( 'bundled_position', 'above' ) === 'above' ) && apply_filters( 'woosb_show_bundled', true, $product->get_id() ) ) {
                self::show_bundled();
            }

            $edit_link = $this->helper->get_setting( 'edit_link', 'no' ) === 'yes';
            $edit_ids  = isset( $_GET['edit'] ) ? explode( ',', base64_decode( sanitize_text_field( wp_unslash( $_GET['edit'] ?? '' ) ) ) ) : [];
            $edit_key  = sanitize_key( wp_unslash( $_GET['key'] ?? '' ) );

            if ( $edit_link && ! empty( $edit_ids ) && ! empty( $edit_key ) && ( $product->has_optional() || $product->has_variables() ) && ( $edit_item = WC()->cart->get_cart_item( $edit_key ) ) ) {
                // edit cart item
                global $product;

                if ( is_a( $product, 'WC_Product_Woosb' ) ) {
                    $product_id = $product->get_id();
                    $quantity   = $edit_item['quantity'] ?: 1;
                    echo '<form class="cart" action="' . esc_url( wc_get_cart_url() ) . '" method="post" enctype="multipart/form-data">';
                    echo '<input type="hidden" name="woosb_ids" class="woosb-ids woosb-ids-' . esc_attr( $product_id ) . '" value=""/>';
                    echo '<input type="hidden" name="woosb_update" value="' . esc_attr( $edit_key ) . '"/>';
                    echo '<input type="hidden" name="quantity" value="' . esc_attr( $quantity ) . '"/>';
                    echo '<button type="submit" name="add-to-cart" value="' . esc_attr( $product_id ) . '" class="single_add_to_cart_button button alt">' . esc_html( $this->helper->localization( 'cart_item_update', esc_html__( 'Update', 'woo-product-bundle' ) ) ) . '</button>';
                    echo '</form>';
                }
            } else {
                wc_get_template( 'single-product/add-to-cart/simple.php' );
            }

            if ( ( $this->helper->get_setting( 'bundled_position', 'above' ) === 'below' ) && apply_filters( 'woosb_show_bundled', true, $product->get_id() ) ) {
                self::show_bundled();
            }

            do_action( 'woosb_after_add_to_cart_form', $product );
        }

        function add_to_cart_button() {
            global $product;

            if ( $product && is_a( $product, 'WC_Product_Woosb' ) && ( $ids = $product->get_ids_str() ) ) {
                echo '<input name="woosb_ids" class="woosb-ids woosb-ids-' . esc_attr( $product->get_id() ) . '" type="hidden" value="' . esc_attr( $ids ) . '"/>';
            }
        }

        function loop_add_to_cart_link( $link, $product ) {
            if ( $product->is_type( 'woosb' ) && ( $product->has_variables() || $product->has_optional() ) ) {
                $link = str_replace( 'ajax_add_to_cart', '', $link );
            }

            return $link;
        }

        function cart_shipping_packages( $packages ) {
            if ( ! empty( $packages ) ) {
                foreach ( $packages as $package_key => $package ) {
                    if ( ! empty( $package['contents'] ) ) {
                        foreach ( $package['contents'] as $cart_item_key => $cart_item ) {
                            if ( ! empty( $cart_item['woosb_parent_id'] ) ) {
                                $_parent = wc_get_product( $cart_item['woosb_parent_id'] );
                                if ( $_parent && ( $_parent->get_meta( 'woosb_shipping_fee' ) === 'whole' ) ) {
                                    unset( $packages[ $package_key ]['contents'][ $cart_item_key ] );
                                }
                            }

                            if ( ! empty( $cart_item['woosb_ids'] ) && ( $cart_item['data']->get_meta( 'woosb_shipping_fee' ) === 'each' ) ) {
                                unset( $packages[ $package_key ]['contents'][ $cart_item_key ] );
                            }
                        }
                    }
                }
            }

            return $packages;
        }

        function cart_contents_weight( $weight ) {
            if ( apply_filters( 'woosb_ignore_calc_weight', false ) ) {
                return $weight;
            }

            $weight = 0;

            foreach ( WC()->cart->get_cart() as $cart_item ) {
                if ( isset( $cart_item['data'] ) && is_a( $cart_item['data'], 'WC_Product' ) && $cart_item['data']->has_weight() ) {
                    $woosb_parent_id = ! empty( $cart_item['woosb_parent_id'] ) ? $cart_item['woosb_parent_id'] : 0;
                    $_parent         = $woosb_parent_id ? wc_get_product( $woosb_parent_id ) : null;

                    if ( ( $_parent && ( $_parent->get_meta( 'woosb_shipping_fee' ) === 'whole' ) ) || ( ! empty( $cart_item['woosb_ids'] ) && ( $cart_item['data']->get_meta( 'woosb_shipping_fee' ) === 'each' ) ) ) {
                        $weight += 0;
                    } else {
                        $weight += (float) $cart_item['data']->get_weight() * $cart_item['quantity'];
                    }
                }
            }

            return $weight;
        }

        function get_price_html( $price_html, $product ) {
            if ( is_admin() ) {
                return $price_html;
            }

            if ( $product->is_type( 'woosb' ) && ( $items = $product->get_items() ) ) {
                $exclude_unpurchasable = $product->exclude_unpurchasable();
                $custom_price          = $product->get_meta( 'woosb_custom_price' );
                $price_format          = $this->helper->get_setting( 'price_format', 'from_min' );
                $default_custom_price  = $this->helper->get_setting( 'price_format_custom', /* translators: dynamic price */ esc_html__( 'before %s after', 'woo-product-bundle' ) );

                if ( ! $product->is_fixed_price() && ! apply_filters( 'woosb_ignore_get_price_html', false ) ) {
                    $discount_amount     = $product->get_discount_amount();
                    $discount_percentage = $product->get_discount_percentage();

                    if ( $product->has_optional() ) {
                        if ( $price_format === 'min_only' || $price_format === 'from_min' ) {
                            $items_optional = []; // collect optional items only

                            foreach ( $items as $k => $item ) {
                                if ( $_product = wc_get_product( $item['id'] ) ) {
                                    // exclude heading/paragraph
                                    $_unpurchasable = ! $_product->is_purchasable() || ! $_product->is_in_stock() || ! $_product->has_enough_stock( $item['qty'] );

                                    if ( $exclude_unpurchasable && ! $_unpurchasable ) {
                                        $items[ $k ]['price'] = 0;
                                    } else {
                                        $items[ $k ]['price'] = $this->helper->get_price_to_display( $_product );
                                    }

                                    if ( ! empty( $item['optional'] ) && ! $_unpurchasable ) {
                                        $items_optional[ $k ] = $items[ $k ];
                                    }
                                }
                            }

                            // min price
                            $total_min = $total_qty = 0;

                            foreach ( $items as $item ) {
                                if ( isset( $item['price'] ) ) {
                                    // exclude heading/paragraph
                                    if ( ! empty( $item['optional'] ) && apply_filters( 'woosb_get_price_from_min_qty', true ) ) {
                                        $item_min  = ! empty( $item['min'] ) ? (float) $item['min'] : 0;
                                        $total_min += (float) $item['price'] * $item_min;
                                        $total_qty += $item_min;
                                    } else {
                                        $total_min += (float) $item['price'] * (float) $item['qty'];
                                        $total_qty += (float) $item['qty'];
                                    }

                                    if ( ! $discount_amount && $discount_percentage ) {
                                        $total_min *= ( 100 - (float) $discount_percentage ) / 100;
                                        $total_min = $this->helper->round_price( $total_min );
                                    }
                                }
                            }

                            // min whole
                            $min_whole = (float) ( $product->get_meta( 'woosb_limit_whole_min' ) ?: 1 );
                            $min_price = (float) ( ! empty( $items_optional ) ? min( array_column( $items_optional, 'price' ) ) : 0 );

                            if ( ! $discount_amount && $discount_percentage ) {
                                $min_price *= ( 100 - (float) $discount_percentage ) / 100;
                                $min_price = $this->helper->round_price( $min_price );
                            }

                            if ( $total_qty > 0 ) {
                                // has min each
                                if ( $min_whole > $total_qty ) {
                                    $total_min += ( $min_whole - $total_qty ) * $min_price;
                                }
                            } else {
                                $total_min += $min_whole * $min_price;
                            }

                            // discount
                            if ( $discount_amount ) {
                                $total_min -= (float) $discount_amount;
                            }

                            switch ( $price_format ) {
                                case 'min_only':
                                    $price_html = apply_filters( 'woosb_get_price_html_min_only', wc_price( $total_min ) . $product->get_price_suffix(), $total_min, $product );
                                    break;
                                case 'from_min':
                                    $price_html = apply_filters( 'woosb_get_price_html_from_min', '<span>' . esc_html__( 'From', 'woo-product-bundle' ) . '</span> ' . wc_price( $total_min ) . $product->get_price_suffix(), $total_min, $product );
                                    break;
                            }
                        }
                    } elseif ( $product->has_variables() ) {
                        if ( $price_format === 'min_only' || $price_format === 'min_max' || $price_format === 'from_min' ) {
                            $min_price = $max_price = 0;

                            foreach ( $items as $item ) {
                                if ( $_product = wc_get_product( $item['id'] ) ) {
                                    if ( $exclude_unpurchasable && ( ! $_product->is_purchasable() || ! $_product->is_in_stock() || ! $_product->has_enough_stock( $item['qty'] ) ) ) {
                                        continue;
                                    }

                                    $min_price += $this->helper->get_price_to_display( $_product, $item['qty'] );
                                    $max_price += $this->helper->get_price_to_display( $_product, $item['qty'], 'max' );
                                }
                            }

                            if ( $discount_amount ) {
                                $min_price -= (float) $discount_amount;
                                $max_price -= (float) $discount_amount;
                            } elseif ( $discount_percentage ) {
                                $min_price *= (float) ( 100 - $discount_percentage ) / 100;
                                $max_price *= (float) ( 100 - $discount_percentage ) / 100;
                            }

                            switch ( $price_format ) {
                                case 'min_only':
                                    $price_html = apply_filters( 'woosb_get_price_html_min_only', wc_price( $min_price ) . $product->get_price_suffix(), $min_price, $product );
                                    break;
                                case 'min_max':
                                    $price_html = apply_filters( 'woosb_get_price_html_min_max', wc_price( $min_price ) . ' - ' . wc_price( $max_price ) . $product->get_price_suffix(), $min_price, $max_price, $product );
                                    break;
                                case 'from_min':
                                    $price_html = apply_filters( 'woosb_get_price_html_from_min', '<span>' . esc_html__( 'From', 'woo-product-bundle' ) . '</span> ' . wc_price( $min_price ) . $product->get_price_suffix(), $min_price, $product );
                                    break;
                            }
                        }
                    } else {
                        // auto calculated price
                        $price_regular = $price_sale = $price = 0;

                        foreach ( $items as $item ) {
                            if ( $_product = wc_get_product( $item['id'] ) ) {
                                if ( $exclude_unpurchasable && ( ! $_product->is_purchasable() || ! $_product->is_in_stock() || ! $_product->has_enough_stock( $item['qty'] ) ) ) {
                                    continue;
                                }

                                $_price = $this->helper->get_price( $_product );

                                if ( $discount_percentage ) {
                                    // when haven't discount_amount, apply the discount percentage
                                    $_price *= ( 100 - (float) $discount_percentage ) / 100;
                                    $_price = $this->helper->round_price( $_price );
                                }

                                $_price        = apply_filters( 'woosb_item_price_add_to_cart', $_price, $_product );
                                $price         += $this->helper->get_price_to_display( $_product, [
                                        'price' => $_price,
                                        'qty'   => $item['qty']
                                ] );
                                $price_sale    += $this->helper->get_price_to_display( $_product, [ 'qty' => $item['qty'] ] );
                                $price_regular += wc_get_price_to_display( $_product, [
                                        'qty'   => $item['qty'],
                                        'price' => $_product->get_regular_price()
                                ] );
                            }
                        }

                        if ( $discount_amount ) {
                            $price = $price_sale - $discount_amount;
                        }

                        do_action( 'woosb_after_calculate_prices', $product, $price_regular, $price_sale, $price );

                        if ( $discount_amount || $discount_percentage ) {
                            $price_html = wc_format_sale_price( wc_price( $price_regular ), wc_price( $price ) ) . $product->get_price_suffix();
                        } else {
                            if ( $price < $price_regular ) {
                                $price_html = wc_format_sale_price( wc_price( $price_regular ), wc_price( $price ) ) . $product->get_price_suffix();
                            } else {
                                $price_html = wc_price( $price ) . $product->get_price_suffix();
                            }
                        }

                        $price_html = apply_filters( 'woosb_get_price_html_auto', $price_html, $price_regular, $price_sale, $price, $product );
                    }
                }

                if ( ! empty( $custom_price ) ) {
                    return str_replace( '%s', $price_html, $custom_price );
                }

                if ( ( $price_format === 'custom' ) && ! empty( $default_custom_price ) ) {
                    return str_replace( '%s', $price_html, $default_custom_price );
                }
            }

            return apply_filters( 'woosb_get_price_html', $price_html, $product );
        }

        function order_again_cart_item_data( $data, $item ) {
            if ( $ids = $item->get_meta( '_woosb_ids' ) ) {
                $data['woosb_order_again'] = 'yes';
                $data['woosb_ids']         = $ids;
            }

            if ( $parent_id = $item->get_meta( '_woosb_parent_id' ) ) {
                $data['woosb_order_again'] = 'yes';
                $data['woosb_parent_id']   = $parent_id;
            }

            return $data;
        }

        function cart_loaded_from_session( $cart ) {
            foreach ( $cart->cart_contents as $cart_item_key => $cart_item ) {
                // remove orphaned products
                if ( isset( $cart_item['woosb_parent_key'] ) && ( $parent_key = $cart_item['woosb_parent_key'] ) && ! isset( $cart->cart_contents[ $parent_key ] ) ) {
                    WC()->cart->remove_cart_item( $cart_item_key );
                }

                // if order again, remove bundled products first
                if ( isset( $cart_item['woosb_order_again'], $cart_item['woosb_parent_id'] ) ) {
                    WC()->cart->remove_cart_item( $cart_item_key );
                }
            }

            foreach ( $cart->cart_contents as $cart_item_key => $cart_item ) {
                // if order again, add bundled products again
                if ( isset( $cart_item['woosb_order_again'], $cart_item['woosb_ids'] ) ) {
                    unset( $cart->cart_contents[ $cart_item_key ]['woosb_order_again'] );
                    unset( $cart->cart_contents[ $cart_item_key ]['woosb_keys'] );
                    $cart_item['data']->build_items( $cart_item['woosb_ids'] );
                    $items     = $cart_item['data']->get_items();
                    $add_items = true;

                    foreach ( $items as $item ) {
                        $_product = wc_get_product( $item['id'] );

                        if ( ! $_product || in_array( $_product->get_type(), $this->helper::get_types(), true ) || ! $_product->is_in_stock() || ! $_product->is_purchasable() || empty( $item['qty'] ) || ! $_product->has_enough_stock( $item['qty'] * $cart_item['quantity'] ) ) {
                            $add_items = false;
                            break;
                        }
                    }

                    if ( $add_items ) {
                        self::add_to_cart_items( $items, $cart_item_key, $cart_item['product_id'], $cart_item['quantity'] );
                    } else {
                        unset( $cart->cart_contents[ $cart_item_key ] );
                    }
                }
            }
        }

        function coupon_is_valid_for_product( $valid, $product, $coupon, $cart_item ) {
            if ( ( $this->helper->get_setting( 'coupon_restrictions', 'no' ) === 'both' ) && ( isset( $cart_item['woosb_parent_id'] ) || isset( $cart_item['woosb_ids'] ) ) ) {
                // exclude both bundles and bundled products
                return false;
            }

            if ( ( $this->helper->get_setting( 'coupon_restrictions', 'no' ) === 'bundles' ) && isset( $cart_item['woosb_ids'] ) ) {
                // exclude bundles
                return false;
            }

            if ( ( $this->helper->get_setting( 'coupon_restrictions', 'no' ) === 'bundled' ) && isset( $cart_item['woosb_parent_id'] ) ) {
                // exclude bundled products
                return false;
            }

            /*
            if ( isset( $cart_item['woosb_parent_id'] ) && ( $parent = wc_get_product( $cart_item['woosb_parent_id'] ) ) ) {
                return $coupon->is_valid_for_product( $parent );
            }
            */

            return $valid;
        }

        function update_stock_status( $product_id, $stock_status, $product ) {
            if ( ! $product->is_type( 'woosb' ) ) {
                $bundles = self::get_bundles( $product_id, 500, 0, 'edit' );

                if ( ! empty( $bundles ) ) {
                    foreach ( $bundles as $bundle ) {
                        $bundle_id      = $bundle->get_id();
                        $visibility     = get_the_terms( $bundle_id, 'product_visibility' );
                        $visibility_arr = array_values( wp_list_pluck( $visibility, 'name' ) );

                        if ( $bundle->is_in_stock() ) {
                            $visibility_new = array_diff( $visibility_arr, [ 'outofstock' ] );
                        } else {
                            $visibility_new = array_merge( $visibility_arr, [ 'outofstock' ] );
                        }

                        wp_set_post_terms( $bundle_id, $visibility_new, 'product_visibility' );
                    }
                }
            }
        }

        function show_bundled( $product = null ) {
            if ( ! $product ) {
                global $product;
            }

            if ( ! $product || ! is_a( $product, 'WC_Product_Woosb' ) ) {
                return;
            }

            $edit_ids = isset( $_GET['edit'] ) ? explode( ',', base64_decode( sanitize_text_field( wp_unslash( $_GET['edit'] ?? '' ) ) ) ) : [];

            if ( $items = $product->get_items() ) {
                $order                 = 1;
                $product_id            = $product->get_id();
                $fixed_price           = $product->is_fixed_price();
                $has_variables         = $product->has_variables();
                $has_optional          = $product->has_optional();
                $discount_amount       = $product->get_discount_amount();
                $discount_percentage   = $product->get_discount_percentage();
                $exclude_unpurchasable = $product->exclude_unpurchasable();
                $total_limit           = $product->get_meta( 'woosb_total_limits' ) === 'on';
                $total_min             = $product->get_meta( 'woosb_total_limits_min' );
                $total_max             = $product->get_meta( 'woosb_total_limits_max' );
                $whole_min             = $product->get_meta( 'woosb_limit_whole_min' ) ?: 1;
                $whole_max             = $product->get_meta( 'woosb_limit_whole_max' ) ?: '-1';
                $layout                = $product->get_meta( 'woosb_layout' ) ?: 'unset';
                $layout                = $layout !== 'unset' ? $layout : $this->helper->get_setting( 'layout', 'list' );
                $bundled_price         = $this->helper->get_setting( 'bundled_price', 'price' );
                $products_class        = apply_filters( 'woosb_products_class', 'woosb-products woosb-products-layout-' . $layout, $product );
                $products_attrs        = apply_filters( 'woosb_products_attrs', [
                        'discount-amount'       => $discount_amount,
                        'discount'              => $discount_percentage,
                        'fixed-price'           => $fixed_price ? 'yes' : 'no',
                        'price'                 => wc_get_price_to_display( $product ),
                        'price-suffix'          => htmlentities( $product->get_price_suffix() ),
                        'variables'             => $has_variables ? 'yes' : 'no',
                        'optional'              => $has_optional ? 'yes' : 'no',
                        'min'                   => $whole_min,
                        'max'                   => $whole_max,
                        'total-min'             => $total_limit && $total_min ? $total_min : 0,
                        'total-max'             => $total_limit && $total_max ? $total_max : '-1',
                        'exclude-unpurchasable' => $exclude_unpurchasable ? 'yes' : 'no',
                ], $product );

                do_action( 'woosb_before_wrap', $product );

                echo '<div class="woosb-wrap woosb-bundled" data-id="' . esc_attr( $product_id ) . '">';

                if ( $before_text = apply_filters( 'woosb_before_text', $product->get_meta( 'woosb_before_text' ), $product_id ) ) {
                    echo '<div class="woosb-before-text woosb-text">' . wp_kses_post( do_shortcode( $before_text ) ) . '</div>';
                }

                do_action( 'woosb_before_table', $product );

                echo '<div class="' . esc_attr( $products_class ) . '" ' . $this->helper->data_attributes( $products_attrs ) . '>';
                // store global $product
                $global_product    = $product;
                $global_product_id = $product_id;

                foreach ( $items as $key => $item ) {
                    if ( ! empty( $item['id'] ) ) {
                        // exclude heading/paragraph
                        $product = wc_get_product( $item['id'] );

                        if ( ! $product || in_array( $product->get_type(), $this->helper::get_types(), true ) ) {
                            continue;
                        }

                        if ( apply_filters( 'woosb_item_exclude', false, $product, $global_product ) ) {
                            continue;
                        }

                        $item_qty = (float) ( $item['qty'] ?? 1 );
                        $item_min = ! empty( $item['min'] ) ? (float) $item['min'] : 0;
                        $item_max = ! empty( $item['max'] ) ? (float) $item['max'] : 10000;
                        $optional = ! empty( $item['optional'] );

                        if ( ! empty( $edit_ids ) ) {
                            foreach ( $edit_ids as $edit_id ) {
                                if ( str_contains( $edit_id, '/' . $key ) ) {
                                    $edit_id_arr = explode( '/', $edit_id );
                                    $item_qty    = (float) ( $edit_id_arr[2] ?? 1 );
                                }
                            }
                        }

                        if ( $optional ) {
                            if ( ( $max_purchase = $product->get_max_purchase_quantity() ) && ( $max_purchase > 0 ) && ( $max_purchase < $item_max ) ) {
                                // get_max_purchase_quantity can return -1
                                $item_max = $max_purchase;
                            }

                            if ( $item_qty < $item_min ) {
                                $item_qty = $item_min;
                            }

                            if ( ( $item_max > $item_min ) && ( $item_qty > $item_max ) ) {
                                $item_qty = $item_max;
                            }
                        }

                        $item_class = 'woosb-product woosb-product-type-' . $product->get_type();

                        if ( $optional ) {
                            $item_class .= ' woosb-product-optional';
                        }

                        if ( ! apply_filters( 'woosb_item_visible', true, $product, $global_product_id ) ) {
                            $item_class .= ' woosb-product-hidden';
                        }

                        if ( ( ! $product->is_type( 'variable' ) && ( ! $product->is_in_stock() || ! $product->has_enough_stock( $item_qty ) || ! $product->is_purchasable() ) ) || ( $product->is_type( 'variable' ) && ! $product->child_is_in_stock() && ! $product->child_is_on_backorder() ) ) {
                            if ( ! apply_filters( 'woosb_allow_unpurchasable_qty', false ) ) {
                                $item_qty = 0;
                            }

                            $item_class .= ' woosb-product-unpurchasable';
                        }

                        $item_price = $this->helper->get_price_to_display( $product );

                        $item_attrs = apply_filters( 'woosb_item_attrs', [
                                'key'          => $key,
                                'name'         => $product->get_name(),
                                'id'           => $product->is_type( 'variable' ) ? 0 : $item['id'],
                                'price'        => $item_price,
                                'o_price'      => $item_price,
                                'price-suffix' => htmlentities( $product->get_price_suffix() ),
                                'stock'        => $product->get_max_purchase_quantity(),
                                'qty'          => $item_qty,
                                'order'        => $order,
                        ], $product, $global_product, $order );

                        do_action( 'woosb_above_item', $product, $global_product, $order );
                        echo '<div class="' . esc_attr( apply_filters( 'woosb_item_class', $item_class, $product, $global_product, $order ) ) . '" ' . $this->helper->data_attributes( $item_attrs ) . '>';
                        do_action( 'woosb_before_item', $product, $global_product, $order );

                        if ( $this->helper->get_setting( 'bundled_thumb', 'yes' ) !== 'no' ) { ?>
                            <div class="woosb-thumb">
                                <?php if ( $product->is_visible() && ( $this->helper->get_setting( 'bundled_link', 'yes' ) !== 'no' ) ) {
                                    echo '<a ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_popup' ? 'class="woosq-link no-ajaxy" data-id="' . esc_attr( $item['id'] ) . '" data-context="woosb"' : '' ) . ' href="' . esc_url( $product->get_permalink() ) . '" ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_blank' ? 'target="_blank"' : '' ) . '>';
                                } ?>
                                <div class="woosb-thumb-ori">
                                    <?php echo apply_filters( 'woosb_item_thumbnail', $product->get_image( $this->helper::get_image_size() ), $product ); ?>
                                </div>
                                <div class="woosb-thumb-new"></div>
                                <?php if ( $product->is_visible() && ( $this->helper->get_setting( 'bundled_link', 'yes' ) !== 'no' ) ) {
                                    echo '</a>';
                                } ?>
                            </div>
                        <?php } ?>

                        <div class="woosb-title">
                            <?php
                            do_action( 'woosb_before_item_name', $product );

                            echo '<div class="woosb-name">';

                            if ( ( $this->helper->get_setting( 'bundled_qty', 'yes' ) === 'yes' ) && ! $optional ) {
                                echo apply_filters( 'woosb_item_qty', $item['qty'] . ' × ', $item['qty'], $product );
                            }

                            $item_name    = '';
                            $product_name = apply_filters( 'woosb_item_product_name', $product->get_name(), $product );

                            if ( $product->is_visible() && ( $this->helper->get_setting( 'bundled_link', 'yes' ) !== 'no' ) ) {
                                $item_name .= '<a ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_popup' ? 'class="woosq-link no-ajaxy" data-id="' . $item['id'] . '" data-context="woosb"' : '' ) . ' href="' . esc_url( $product->get_permalink() ) . '" ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_blank' ? 'target="_blank"' : '' ) . '>';
                            }

                            if ( $product->is_type( 'variable' ) || ( $product->is_in_stock() && $product->has_enough_stock( $item_qty ) ) ) {
                                $item_name .= $product_name;
                            } else {
                                $item_name .= '<s>' . $product_name . '</s>';
                            }

                            if ( $product->is_visible() && ( $this->helper->get_setting( 'bundled_link', 'yes' ) !== 'no' ) ) {
                                $item_name .= '</a>';
                            }

                            echo apply_filters( 'woosb_item_name', $item_name, $product, $global_product, $order );
                            echo '</div>';

                            do_action( 'woosb_after_item_name', $product );

                            if ( $bundled_price === 'price_under_name' || $bundled_price === 'subtotal_under_name' ) {
                                self::show_bundled_price( $bundled_price, $fixed_price, $discount_percentage, $product, $item );
                            }

                            if ( $this->helper->get_setting( 'bundled_description', 'no' ) === 'yes' ) {
                                echo '<div class="woosb-description">' . apply_filters( 'woosb_item_description', $product->is_type( 'variation' ) ? $product->get_description() : $product->get_short_description(), $product ) . '</div>';
                            }

                            echo '<div class="woosb-availability">' . wp_kses_post( wc_get_stock_html( $product ) ) . '</div>';
                            ?>
                        </div>

                        <?php if ( $optional ) {
                            if ( ( $product->is_in_stock() && ( $product->is_type( 'variable' ) || $product->is_purchasable() ) ) || apply_filters( 'woosb_allow_unpurchasable_qty', false ) ) {
                                echo '<div class="' . esc_attr( $this->helper->get_setting( 'plus_minus', 'no' ) === 'yes' ? 'woosb-quantity woosb-quantity-plus-minus' : 'woosb-quantity' ) . '">';

                                if ( $this->helper->get_setting( 'plus_minus', 'no' ) === 'yes' ) {
                                    echo '<div class="woosb-quantity-input">';
                                    echo '<div class="woosb-quantity-input-minus">-</div>';
                                }

                                $qty_args = [
                                        'input_value' => $item_qty,
                                        'min_value'   => $item_min,
                                        'max_value'   => $item_max,
                                        'woosb_qty'   => [
                                                'input_value' => $item_qty,
                                                'min_value'   => $item_min,
                                                'max_value'   => $item_max
                                        ],
                                        'classes'     => apply_filters( 'woosb_qty_classes', [
                                                'input-text',
                                                'woosb-qty',
                                                'woosb_qty',
                                                'qty',
                                                'text'
                                        ] ),
                                        'input_name'  => 'woosb_qty_' . $order
                                    // compatible with WPC Product Quantity
                                ];

                                if ( apply_filters( 'woosb_use_woocommerce_quantity_input', true ) ) {
                                    woocommerce_quantity_input( $qty_args, $product );
                                } else {
                                    echo apply_filters( 'woosb_quantity_input', '<input type="number" class="input-text woosb-qty woosb_qty qty text" value="' . esc_attr( $item_qty ) . '" min="' . esc_attr( $item_min ) . '" max="' . esc_attr( $item_max ) . '" name="' . esc_attr( 'woosb_qty_' . $order ) . '" />', $qty_args, $product );
                                }

                                if ( $this->helper->get_setting( 'plus_minus', 'no' ) === 'yes' ) {
                                    echo '<div class="woosb-quantity-input-plus">+</div>';
                                    echo '</div>';
                                }

                                echo '</div>';
                            } else { ?>
                                <div class="woosb-quantity woosb-quantity-disabled">
                                    <div class="quantity">
                                        <label>
                                            <input type="number" class="input-text woosb-qty woosb_qty qty text"
                                                   value="0" disabled/>
                                        </label>
                                    </div>
                                </div>
                            <?php }
                        }

                        if ( $bundled_price === 'price' || $bundled_price === 'subtotal' ) {
                            self::show_bundled_price( $bundled_price, $fixed_price, $discount_percentage, $product, $item );
                        }

                        do_action( 'woosb_after_item', $product, $global_product, $order );
                        echo '</div><!-- /woosb-product -->';
                        do_action( 'woosb_under_item', $product, $global_product, $order );
                    } elseif ( ! empty( $item['text'] ) ) {
                        $item_class = 'woosb-item-text';

                        if ( ! empty( $item['type'] ) ) {
                            $item_class .= ' woosb-item-text-type-' . $item['type'];
                        }

                        echo '<div class="' . esc_attr( apply_filters( 'woosb_item_text_class', $item_class, $item, $global_product, $order ) ) . '">';

                        if ( empty( $item['type'] ) || ( $item['type'] === 'none' ) ) {
                            echo wp_kses_post( $item['text'] );
                        } else {
                            echo '<' . esc_attr( $item['type'] ) . '>' . wp_kses_post( $item['text'] ) . '</' . esc_attr( $item['type'] ) . '>';
                        }

                        echo '</div>';
                    }

                    $order ++;
                }

                // restore global $product
                $product = $global_product;
                echo '</div><!-- /woosb-products -->';

                if ( ! $fixed_price && ( $has_variables || $has_optional ) ) {
                    echo '<div class="woosb-summary woosb-text"><span class="woosb-total"></span>';

                    if ( $has_optional ) {
                        echo '<span class="woosb-count"></span>';
                    }

                    echo '</div>';
                }

                echo '<div class="woosb-alert woosb-text" style="display: none"></div>';

                do_action( 'woosb_after_table', $product );

                if ( $after_text = apply_filters( 'woosb_after_text', $product->get_meta( 'woosb_after_text' ), $product_id ) ) {
                    echo '<div class="woosb-after-text woosb-text">' . wp_kses_post( do_shortcode( $after_text ) ) . '</div>';
                }

                echo '</div><!-- /woosb-wrap -->';

                do_action( 'woosb_after_wrap', $product );
            }
        }

        function show_bundled_price( $bundled_price, $fixed_price, $discount_percentage, $product, $item ) {
            ?>
            <div class="woosb-price">
                <?php do_action( 'woosb_before_item_price', $product ); ?>
                <div class="woosb-price-ori">
                    <?php
                    $ori_price = (float) $this->helper->round_price( $product->get_price() );
                    $get_price = (float) $this->helper->get_price( $product );

                    if ( ! $fixed_price && $discount_percentage ) {
                        $new_price     = true;
                        $product_price = $get_price * ( 100 - (float) $discount_percentage ) / 100;
                        $product_price = $this->helper->round_price( $product_price );
                        $product_price = apply_filters( 'woosb_item_price_add_to_cart', $product_price, $item );
                    } else {
                        $new_price     = false;
                        $product_price = $get_price;
                    }

                    switch ( $bundled_price ) {
                        case 'price':
                        case 'price_under_name':
                            if ( $new_price ) {
                                $item_price = wc_format_sale_price( wc_get_price_to_display( $product, [ 'price' => $get_price ] ), wc_get_price_to_display( $product, [ 'price' => $product_price ] ) );
                            } else {
                                if ( $get_price > $ori_price ) {
                                    $item_price = wc_price( $this->helper->get_price_to_display( $product ) ) . $product->get_price_suffix();
                                } else {
                                    $item_price = $product->get_price_html();
                                }
                            }

                            break;
                        case 'subtotal':
                        case 'subtotal_under_name':
                            if ( $new_price ) {
                                $item_price = wc_format_sale_price( wc_get_price_to_display( $product, [
                                                'price' => $get_price,
                                                'qty'   => $item['qty']
                                        ] ), wc_get_price_to_display( $product, [
                                                'price' => $product_price,
                                                'qty'   => $item['qty']
                                        ] ) ) . $product->get_price_suffix();
                            } else {
                                $item_price = wc_price( $this->helper->get_price_to_display( $product, $item['qty'] ) ) . $product->get_price_suffix();
                            }

                            break;
                        default:
                            $item_price = $product->get_price_html();
                    }

                    echo apply_filters( 'woosb_item_price', $item_price, $product );
                    ?>
                </div>
                <div class="woosb-price-new"></div>
                <?php do_action( 'woosb_after_item_price', $product ); ?>
            </div>
            <?php
        }

        function show_bundles( $product = null ) {
            if ( ! $product ) {
                global $product;
            }

            if ( ! $product || $product->is_type( 'woosb' ) ) {
                return;
            }

            $product_id = $product->get_id();
            $bundles    = self::get_bundles( $product_id ) ?: [];

            if ( $product->is_type( 'variable' ) && apply_filters( 'woosb_show_bundles_from_variation', false ) ) {
                $children = $product->get_children();

                if ( is_array( $children ) && count( $children ) > 0 ) {
                    foreach ( $children as $child ) {
                        if ( $child_bundles = self::get_bundles( $child ) ) {
                            foreach ( $child_bundles as $child_bundle ) {
                                $bundles[] = $child_bundle;
                            }
                        }
                    }
                }
            }

            if ( ! empty( $bundles ) ) {
                echo '<div class="woosb-bundles">';

                do_action( 'woosb_before_bundles', $product );

                echo '<div class="woosb-products">';

                foreach ( array_unique( $bundles ) as $bundle ) {
                    echo '<div class="woosb-product">';
                    do_action( 'woosb_before_bundles_item', $bundle, $product );
                    echo '<div class="woosb-thumb">' . wp_kses_post( $bundle->get_image( $this->helper::get_image_size() ) ) . '</div>';
                    echo '<div class="woosb-title"><a ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_popup' ? 'class="woosq-link no-ajaxy" data-id="' . esc_attr( $bundle->get_id() ) . '" data-context="woosb"' : '' ) . ' href="' . esc_url( $bundle->get_permalink() ) . '" ' . ( $this->helper->get_setting( 'bundled_link', 'yes' ) === 'yes_blank' ? 'target="_blank"' : '' ) . '>' . esc_html( $bundle->get_name() ) . '</a></div>';
                    echo '<div class="woosb-price">' . wp_kses_post( $bundle->get_price_html() ) . '</div>';
                    do_action( 'woosb_after_bundles_item', $bundle, $product );
                    echo '</div><!-- /woosb-product -->';
                }

                echo '</div><!-- /woosb-products -->';
                wp_reset_postdata();

                do_action( 'woosb_after_bundles', $product );

                echo '</div><!-- /woosb-bundles -->';
            }
        }

        function get_bundled( $ids, $product = null ) {
            // moved to helper
            return $this->helper->get_bundled( $ids, $product );
        }

        function get_bundles( $product_id, $per_page = 500, $offset = 0, $context = 'view' ) {
            // moved to helper
            return $this->helper->get_bundles( $product_id, $per_page, $offset, $context );
        }

        function shortcode_form() {
            ob_start();
            self::add_to_cart_form();

            return ob_get_clean();
        }

        function shortcode_bundled() {
            ob_start();
            self::show_bundled();

            return ob_get_clean();
        }

        function shortcode_bundles() {
            ob_start();
            self::show_bundles();

            return ob_get_clean();
        }
    }

    function WPCleverWoosb() {
        return WPCleverWoosb::instance();
    }
}