PATH:
home
/
thebhoeo
/
public_html
/
officepoint
/
wp-content
/
themes
/
woodmart-child
<?php /** * Enqueue script and styles for child theme */ function woodmart_child_enqueue_styles() { wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array( 'woodmart-style' ), woodmart_get_theme_info( 'Version' ) ); } add_action( 'wp_enqueue_scripts', 'woodmart_child_enqueue_styles', 1000 ); // Disable password change email notification for user add_filter('send_password_change_email', '__return_false'); add_filter( 'woocommerce_disable_password_change_notification', '__return_true' ); add_filter( 'manage_media_columns', 'sk_media_columns_filesize' ); add_action('init', 'pp_durne_init'); function pp_durne_init() { // Unhook the actions from wp-includes/default-filters.php remove_action('register_new_user', 'wp_send_new_user_notifications'); remove_action('edit_user_created_user', 'wp_send_new_user_notifications', 10, 2); // Replace with our action that sends the user email only add_action('register_new_user', 'pp_durne_send_notification'); add_action('edit_user_created_user', 'pp_durne_send_notification', 10, 2); } function pp_durne_send_notification($userId, $to='both') { if (empty($to) || $to == 'admin') { // Admin only, so we don't do anything return; } // For 'both' or 'user', we notify only the user wp_send_new_user_notifications($userId, 'user'); } function prefix_add_author_woocommerce() { add_post_type_support( 'product', 'author' ); } add_action( 'after_setup_theme', 'prefix_add_author_woocommerce' ); // Backend Custom CSS add_action('admin_head', 'my_custom_fonts'); function my_custom_fonts() { echo '<style> .bsf-update-nag, .vc_license-activation-notice, .aDBc-please-activate-msg, #redirect-panel .banner-wrap{ display:none !important; } .notice.is-dismissible.notice-info, #admin-menu-editor-pro-update { display: none !important; } #setting-error-tgmpa, .__A__To_Activate{ display: none !important; } .imported-full .import-base, .import-versions, .import-pages, .import-elements { cursor: default !important; } .imported-full .import-base .page-preview, .import-versions .page-preview, .import-pages .page-preview, .import-elements .page-preview, .imported-full .import-base .button, .import-versions .button, .import-pages .button, .import-elements .button { opacity: 1 !important; } .imported-full .import-base input, .imported-full .import-base select, .import-versions input, .import-versions select, .import-pages input, .import-pages select, .import-elements input, .import-elements select { pointer-events: auto !important; opacity: 1 !important; } .woodmart-msg.updated, .thwcfd-notice, .notice.notice-error, .wpcf7-redirect-pro-admin-notice.updated.notice.is-dismissible, .woodmart-msg.updated, li.woo-variation-swatches-pro_options.woo-variation-swatches-pro_tab { display:none !important; } .woocommerce_options_panel .checkbox, .woocommerce_variable_attributes .checkbox { float: none !important; } </style>'; } add_action( 'woocommerce_email_footer', 'bbloomer_add_css_to_emails' ); function bbloomer_add_css_to_emails() { ?> <style type="text/css"> p { color: black; } h1 { color: #fff !important; text-align: center; font-weight: bold !important; } </style> <?php } function get_togglehtml() { return '<label class="switch"> <input type="checkbox" name="taxincluded" id="taxincluded" type="checkbox" checked> <span class="slider round"></span> </label>'; } add_shortcode('taxtoggle', 'get_togglehtml'); /** * Block spam orders: * - Rejects if order total is exactly 5.21 * - Adds/validates a honeypot hidden field */ add_action( 'woocommerce_after_order_notes', function () { // Hidden field (honeypot) echo '<p class="form-row form-row-wide" style="display:none !important;"> <label for="hp_field">Secondry Hidden Mobile No</label> <input type="text" name="hp_field" id="hp_field" value="" autocomplete="off" /> </p>'; }); add_action( 'woocommerce_checkout_process', function () { // 1️⃣ Block if honeypot is filled if ( ! empty( $_POST['hp_field'] ) ) { wc_add_notice( __( 'Spam detected. Please try again.', 'Office-Point' ), 'error' ); } // 2️⃣ Block if total is exactly 5.21 if ( WC()->cart && floatval( WC()->cart->total ) === 5.21 ) { wc_add_notice( __( 'This order total is not allowed.', 'Office-Point' ), 'error' ); } }); /************** SHOW Including & Excluding VAT Prices via Toggle **************/ // Shortcode button for header add_shortcode('vat_toggle', function () { ob_start(); ?> <div id="vat-toggle-wrapper"> <div class="vat-toggle-title">Show prices</div> <div class="vat-toggle-row"> <span class="vat-side-label exvat-label">ex. VAT</span> <label class="vat-switch"> <input type="checkbox" id="vat-toggle"> <span class="vat-slider round"></span> </label> <span class="vat-side-label incvat-label">inc. VAT</span> </div> </div> <?php return ob_get_clean(); }); // JS + CSS add_action('wp_enqueue_scripts', function () { wp_enqueue_script('vat-toggle', get_stylesheet_directory_uri() . '/vat-toggle.js', array('jquery'), '2.0', true); wp_enqueue_style('vat-toggle-css', get_stylesheet_directory_uri() . '/vat-toggle.css', array(), '2.0'); }); // Set initial class ASAP (prevents flicker) // Set initial mode class + critical CSS very early to prevent flicker add_action('wp_head', function () { ?> <script> (function () { try { var mode = localStorage.getItem('vat_mode') || 'inc'; var cls = (mode === 'ex') ? 'vat-ex' : 'vat-inc'; // Add class before first paint document.documentElement.classList.add(cls); } catch (e) { document.documentElement.classList.add('vat-inc'); } })(); </script> <style> /* ===== Critical CSS (prevents price flash) ===== */ /* In INC mode: hide the Ex VAT suffix immediately */ html.vat-inc .price .woocommerce-price-suffix .woocommerce-Price-amount, html.vat-inc .price del .woocommerce-Price-amount { display:inline !important; } html.vat-inc .price .woocommerce-Price-amount, html.vat-inc .price ins .woocommerce-Price-amount, html.vat-ex .price ins { display:none !important; } /* In EX mode: show suffix and hide the currently visible amount (sale or non-sale) */ html.vat-ex .price .woocommerce-price-suffix { display:none !important;} html.vat-ex .price del .woocommerce-Price-amount, html.vat-ex .price ins .woocommerce-Price-amount, html.vat-ex .price ins { display:inline !important;} /* Optional: labels to avoid flicker on first paint too */ html.vat-inc .price .woocommerce-Price-amount:after, html.vat-ex .price .woocommerce-Price-amount:after{color: grey; font-size: 14px; font-weight: 500;} html.vat-inc .price del .woocommerce-Price-amount:after, html.vat-ex .price del .woocommerce-Price-amount:after {display:none;} html.vat-ex .price .woocommerce-Price-amount:after { content:" ex. VAT";} html.vat-inc .price .woocommerce-price-suffix .woocommerce-Price-amount:after {content: " inc. VAT";} </style> <?php }, 0); // priority 0 = as early as possible /***************** Change Text Create an account *****************/ function my_text_strings( $translated_text, $text, $domain ) { if ( 'woocommerce' !== $domain ) { return $translated_text; } switch ( $translated_text ) { case 'Create an account?' : $translated_text = __( 'Save your info for faster checkout next time!', 'woocommerce' ); break; } return $translated_text; } add_filter( 'gettext', 'my_text_strings', 20, 3 ); /***************** END of Change Text Create an account *****************/ /***************** Move Save your Info (Create an account) next to email field *****************/ function show_save_info_only_after_valid_email() { if (is_checkout()) { ?> <style> .woocommerce-account-fields { max-height: 0; opacity: 0; overflow: hidden; transition: max-height 0.4s ease, opacity 0.4s ease; } .woocommerce-account-fields.visible { max-height: 200px; opacity: 1; } </style> <script type="text/javascript"> jQuery(document).ready(function($) { function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(email); } function toggleSaveInfo(emailField) { const emailVal = emailField.val().trim(); const valid = isValidEmail(emailVal); const saveInfo = $('.woocommerce-account-fields'); if (valid) { saveInfo.addClass('visible'); } else { saveInfo.removeClass('visible'); } } function waitForEmailAndSaveInfo(callback) { const interval = setInterval(function () { const emailField = $('#billing_email'); const saveInfo = $('.woocommerce-account-fields'); if (emailField.length && saveInfo.length) { clearInterval(interval); callback(emailField, saveInfo); } }, 100); } waitForEmailAndSaveInfo(function(emailField, saveInfo) { // Move Save Info below email const emailParent = emailField.parent(); saveInfo.insertAfter(emailParent.closest('.form-row')); saveInfo.removeClass('visible'); // Initial check toggleSaveInfo(emailField); // Listen to changes emailField.on('input blur change', function () { toggleSaveInfo(emailField); }); // Fallback check for autofill or pre-filled let elapsed = 0; const autofillCheck = setInterval(function () { toggleSaveInfo(emailField); elapsed += 500; if (elapsed >= 5000) { clearInterval(autofillCheck); } }, 500); }); }); </script> <?php } } add_action('wp_footer', 'show_save_info_only_after_valid_email'); /***************** END Move Save your Info (Create an account) next to email field *****************/ /***************** Change Location of Order Notes before Shipping Address *****************/ add_filter( 'woocommerce_checkout_fields', 'move_additional_fields_before_shipping' ); function move_additional_fields_before_shipping( $fields ) { // Check if 'order' fields (additional fields) exist if ( isset( $fields['order'] ) ) { // Store additional fields $additional_fields = $fields['order']; // Remove additional fields from their original position unset( $fields['order'] ); // Insert additional fields before the shipping fields // Combine the additional fields before the shipping fields $fields['billing'] = array_merge($fields['billing'], $additional_fields); // Adding the additional fields after billing fields // Return the modified fields array } return $fields; } /***************** END change Location of Order Notes before Shipping Address *****************/ // Changing the checkout location for payment methods add_action( 'woocommerce_checkout_order_review', 'reordering_checkout_order_review', 1 ); function reordering_checkout_order_review(){ remove_action('woocommerce_checkout_order_review','woocommerce_checkout_payment', 20 ); } // BY Shoaib Jilani on 13Dec2020 for changing the checkout location for payment methods add_action( 'woocommerce_checkout_shipping', 'my_custom_display_payments', 20 ); function my_custom_display_payments() { if ( ! is_ajax() ) { do_action( 'woocommerce_review_order_before_payment' ); } ?> <div id="checkout_payments" class="woocommerce-checkout-payment"> <?php if ( WC()->cart->needs_payment() ) : ?> <h2><?php esc_html_e( 'Select Payment Methodd', 'woocommerce' );?></h2> <ul class="wc_payment_methods payment_methods methods ccicustompayment"> <?php if ( ! empty( $available_gateways ) ) { foreach ( $available_gateways as $gateway ) { echo '<button type="button" class="button paymentbtn" name="' . str_replace(" ", "_", strtolower($gateway->title)) . '_btn" data-index="' . $methodind . '" data-radioid="' . $gateway->id . '" value="' . $gateway->title . '">' . $gateway->title . '</button>'; } foreach ( $available_gateways as $gateway ) { wc_get_template( 'checkout/payment-method.php', array( 'gateway' => $gateway ) ); } } else { echo '<li class="woocommerce-notice woocommerce-notice--info woocommerce-info">' . apply_filters( 'woocommerce_no_available_payment_methods_message', WC()->customer->get_billing_country() ? esc_html__( 'Sorry, it seems that there are no available payment methods for your state. Please contact us if you require assistance or wish to make alternate arrangements.', 'woocommerce' ) : esc_html__( 'Please fill in your details above to see available payment methods.', 'woocommerce' ) ) . '</li>'; // @codingStandardsIgnoreLine } ?> </ul> <?php endif; ?> <div class="form-row place-order"> <noscript> <?php /* translators: $1 and $2 opening and closing emphasis tags respectively */ printf( esc_html__( 'Since your browser does not support JavaScript, or it is disabled, please ensure you click the %1$sUpdate Totals%2$s button before placing your order. You may be charged more than the amount stated above if you fail to do so.', 'woocommerce' ), '<em>', '</em>' ); ?> <br/><button type="submit" class="button alt" name="woocommerce_checkout_update_totals" value="<?php esc_attr_e( 'Update totals', 'woocommerce' ); ?>"><?php esc_html_e( 'Update totals', 'woocommerce' ); ?></button> </noscript> <?php wc_get_template( 'checkout/terms.php' ); ?> <?php do_action( 'woocommerce_review_order_before_submit' ); ?> <?php echo apply_filters( 'woocommerce_order_button_html', '<button type="submit" class="button alt" name="woocommerce_checkout_place_order" id="place_order" value="' . esc_attr( $order_button_text ) . '" data-value="' . esc_attr( $order_button_text ) . '">' . esc_html( $order_button_text ) . '</button>' ); // @codingStandardsIgnoreLine ?> <?php do_action( 'woocommerce_review_order_after_submit' ); ?> <?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?> </div> </div> <?php if ( ! is_ajax() ) { do_action( 'woocommerce_review_order_after_payment' ); } } /*****************MOVE Coupon under Proceed To Checkout Button*****************/ add_action( 'woocommerce_review_order_before_order_total', 'bbloomer_display_coupon_form_below_proceed_checkout', 25 ); function bbloomer_display_coupon_form_below_proceed_checkout() { ?> <tr> <td colspan="2"> <form class="woocommerce-coupon-form checkout" action="<?php echo esc_url( wc_get_checkout_url() ); ?>" method="post"> <?php if ( wc_coupons_enabled() ) { ?> <div class="coupon under-proceed"> <input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e( 'Coupon code', 'woocommerce' ); ?>" style="width: 100%" /> <button type="submit" class="button" name="apply_coupon" value="<?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?>" style="width: 100%"><?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?></button> </div> <?php } ?> </form> </td> </tr> <?php } //add_action( 'woocommerce_proceed_to_checkout', 'bbloomer_display_coupon_form_below_proceed_cart', 25 ); function bbloomer_display_coupon_form_below_proceed_cart() { ?> <form class="woocommerce-coupon-form" action="<?php echo esc_url( wc_get_cart_url() ); ?>" method="post"> <?php if ( wc_coupons_enabled() ) { ?> <div class="coupon under-proceed"> <input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="<?php esc_attr_e( 'Coupon code', 'woocommerce' ); ?>" style="width: 100%" /> <button type="submit" class="button" name="apply_coupon" value="<?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?>" style="width: 100%"><?php esc_attr_e( 'Apply coupon', 'woocommerce' ); ?></button> </div> <?php } ?> </form> <?php } remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10 ); /*****************END of MOVE Coupon under Proceed To Checkout Button*****************/ /***************** Add class to active payment method *****************/ add_action( 'wp_footer', 'highlight_active_payment_method_script', 100 ); function highlight_active_payment_method_script() { if ( is_checkout() && ! is_order_received_page() ) : ?> <script> function applyActivePaymentStyle() { const methods = document.querySelectorAll('.wc_payment_method input[type="radio"]'); methods.forEach(input => { const parent = input.closest('.wc_payment_method'); if (input.checked) { parent.classList.add('active-method'); } else { parent.classList.remove('active-method'); } // Add event listener (in case Woo re-renders and loses it) input.addEventListener('change', () => { methods.forEach(i => { const p = i.closest('.wc_payment_method'); p.classList.remove('active-method'); }); parent.classList.add('active-method'); }); }); } document.addEventListener('DOMContentLoaded', function () { applyActivePaymentStyle(); }); jQuery(document.body).on('updated_checkout', function() { applyActivePaymentStyle(); }); </script> <?php endif; } /***************** END class to active payment method *****************/ /***************** Testimonials in Checkout Page after Total *****************/ add_action( 'woocommerce_checkout_order_review', 'add_html_block_under_order_review', 20 ); function add_html_block_under_order_review() { echo do_shortcode('[html_block id="15413"]'); } /***************** END of Testimonials in Checkout Page after Total *****************/ /***************** Cross Sell + Upsell Side Column (2-Column Layout) with Bulk Add to Cart *****************/ add_action( 'init', function () { add_shortcode( 'crosssell-products', function( $atts = [] ){ return dattvape_crosssell_products_shortcode_handler( $atts ); } ); }); function dattvape_crosssell_products_shortcode_handler( $atts ) { if ( ! class_exists( 'WooCommerce' ) ) return ''; if ( ! is_product() ) return ''; $atts = shortcode_atts( [ 'category' => '', 'heading' => 'Frequently Bought Together', 'btn_label' => 'Add Selected Items', 'limit' => 4, // Right column (upsell mini) 'upsell_heading' => 'Often Bought Together', 'upsell_limit' => 4, ], $atts, 'crosssell-products' ); global $product; if ( ! $product instanceof WC_Product ) { $product = wc_get_product( get_the_ID() ); } if ( ! $product ) return ''; // Check if cross-sell section is enabled $show_crosssell = get_post_meta( $product->get_id(), '_show_crosssell_section', true ); if ( $show_crosssell !== 'yes' ) { // If cross-sell is disabled, still allow the upsell side panel to show (if enabled) $upsell_side = dattvape_render_upsell_sidepanel( $atts['upsell_heading'], (int) $atts['upsell_limit'] ); return $upsell_side ? '<div class="dvd-bundle-layout"><div class="dvd-bundle-col dvd-bundle-col--side">'.$upsell_side.'</div></div>' : ''; } $ids = []; $slug = $atts['category'] ? sanitize_title( $atts['category'] ) : ''; // Fetch cross-sells (no category restriction) $cross_sell_ids = array_filter( array_map( 'absint', (array) $product->get_cross_sell_ids() ) ); if ( $cross_sell_ids ) { $cross_sell_ids = array_values( array_filter( $cross_sell_ids, function( $pid ) { $purch = wc_get_product( $pid ); return ( $purch && $purch->is_purchasable() ); } ) ); if ( $cross_sell_ids ) { $ids = array_slice( $cross_sell_ids, 0, max(1, (int) $atts['limit']) ); } } // Fallback if ( empty( $ids ) && $slug ) { $ids = dattvape_get_products_from_category( $slug, max(1, (int) $atts['limit']) ); } // Even if cross-sells are empty: we still show bundle section (main product auto-included), // BUT if you want to hide the section when no FBT items exist, you can revert this behavior. $left = dattvape_render_bundle_section( $atts['heading'], $ids, // FBT items 'crosssell', $atts['btn_label'], max(1, (int) $atts['limit']), $product // pass main product object ); $right = dattvape_render_upsell_sidepanel( $atts['upsell_heading'], (int) $atts['upsell_limit'], $atts['category'] ); ob_start(); ?> <div class="dvd-bundle-layout"> <div class="dvd-bundle-col dvd-bundle-col--main"> <?php echo $left; ?> </div> <?php if ( $right ) : ?> <div class="dvd-bundle-col dvd-bundle-col--side"> <?php echo $right; ?> </div> <?php endif; ?> </div> <?php return ob_get_clean(); } /************** Add Upsell + Cross-sell toggles in product backend ***************/ add_action('woocommerce_product_options_general_product_data', function() { woocommerce_wp_checkbox([ 'id' => '_show_crosssell_section', 'label' => __('Show Cross-sell Section', 'dattvapes'), 'description' => __('Enable this to show the cross-sell section on product page.', 'dattvapes'), 'desc_tip' => true, ]); }); // Save their values add_action('woocommerce_process_product_meta', function($post_id) { update_post_meta($post_id, '_show_crosssell_section', isset($_POST['_show_crosssell_section']) ? 'yes' : 'no'); }); /** ---------------- Helpers ---------------- */ function dattvape_get_products_from_category( $slug, $limit = 4 ) { $limit = max(1, (int) $limit); $q = new WP_Query( [ 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => $limit, 'fields' => 'ids', 'orderby' => 'menu_order', 'order' => 'ASC', 'tax_query' => [[ 'taxonomy' => 'product_cat', 'field' => 'slug', 'terms' => [ $slug ], ]], ] ); $ids = (array) $q->posts; wp_reset_postdata(); $ids = array_values( array_filter( $ids, function( $pid ){ $p = wc_get_product( $pid ); return $p && $p->is_purchasable(); } ) ); return array_slice( $ids, 0, $limit ); } /** * Resolve a displayable/purchasable "item" for a product card. */ function dattvape_resolve_card_item( WC_Product $product ) { if ( $product->is_type( 'simple' ) ) { return [ 'type' => 'simple', 'display_id' => $product->get_id(), 'parent_id' => 0, 'attrs' => [], 'price_num' => (float) wc_get_price_to_display( $product ), 'price_html' => $product->get_price_html(), ]; } if ( $product->is_type( 'variable' ) ) { /** @var WC_Product_Variable $product */ $default_attrs = array_filter( (array) $product->get_default_attributes() ); $variation_id = 0; if ( $default_attrs ) { $variation_id = dattvape_find_matching_variation_id( $product, $default_attrs ); } if ( ! $variation_id ) { foreach ( $product->get_children() as $vid ) { $v = wc_get_product( $vid ); if ( $v && $v->is_purchasable() && $v->is_in_stock() ) { $variation_id = $vid; break; } } } if ( $variation_id ) { $variation_obj = wc_get_product( $variation_id ); $attrs_for_add = $variation_obj instanceof WC_Product_Variation ? (array) $variation_obj->get_attributes() : []; return [ 'type' => 'variation', 'display_id' => $variation_id, 'parent_id' => $product->get_id(), 'attrs' => $attrs_for_add, 'price_num' => (float) wc_get_price_to_display( $variation_obj ), 'price_html' => $variation_obj->get_price_html() ?: $product->get_price_html(), ]; } } return [ 'type' => 'simple', 'display_id' => $product->get_id(), 'parent_id' => 0, 'attrs' => [], 'price_num' => (float) wc_get_price_to_display( $product ), 'price_html' => $product->get_price_html(), ]; } function dattvape_find_matching_variation_id( WC_Product_Variable $product, array $attrs ) { $norm = []; foreach ( $attrs as $k => $v ) { $k = strpos( $k, 'attribute_' ) === 0 ? $k : 'attribute_' . $k; $norm[ $k ] = $v; } $data_store = WC_Data_Store::load( 'product' ); if ( method_exists( $data_store, 'find_matching_product_variation' ) ) { $vid = $data_store->find_matching_product_variation( $product, $norm ); return $vid ? absint( $vid ) : 0; } foreach ( $product->get_children() as $vid ) { $v = wc_get_product( $vid ); if ( ! $v instanceof WC_Product_Variation ) continue; $v_attrs = $v->get_attributes(); $match = true; foreach ( $norm as $k => $val ) { if ( ! isset( $v_attrs[ $k ] ) || wc_strtolower( $v_attrs[ $k ] ) !== wc_strtolower( $val ) ) { $match = false; break; } } if ( $match ) return absint( $vid ); } return 0; } /** ---------------- RIGHT COLUMN: Upsell mini list (Image + Name + INC VAT price) ---------------- */ function dattvape_render_upsell_sidepanel( $heading = 'Often Bought Together', $limit = 4, $fallback_category = '' ) { if ( ! is_product() ) return ''; global $product; if ( ! $product instanceof WC_Product ) { $product = wc_get_product( get_the_ID() ); } if ( ! $product ) return ''; $show_upsell = get_post_meta( $product->get_id(), '_show_upsell_section', true ); if ( $show_upsell !== 'yes' ) return ''; $limit = max(1, (int) $limit); // 1) Try upsells first $ids = array_filter( array_map( 'absint', (array) $product->get_upsell_ids() ) ); // 2) If no upsells, fallback to category (from shortcode) if ( empty($ids) && ! empty($fallback_category) ) { $slug = sanitize_title( $fallback_category ); $ids = dattvape_get_products_from_category( $slug, $limit ); } if ( empty($ids) ) return ''; $ids = array_values( array_filter( $ids, function($pid){ $p = wc_get_product($pid); return $p && $p->is_purchasable(); } ) ); if ( empty($ids) ) return ''; $ids = array_slice( $ids, 0, $limit ); ob_start(); ?> <div class="dvd-upsell-mini"> <div class="dvd-upsell-mini__title"><?php echo esc_html( $heading ); ?></div> <div class="dvd-upsell-mini__list"> <?php foreach ( $ids as $pid ) : $p = wc_get_product( $pid ); if ( ! $p ) continue; $item = dattvape_resolve_card_item( $p ); $display_product = wc_get_product( $item['display_id'] ); $price_incl = $display_product ? (float) wc_get_price_including_tax( $display_product ) : 0; $img = get_the_post_thumbnail_url( $pid, 'woocommerce_thumbnail' ); if ( ! $img ) $img = wc_placeholder_img_src( 'woocommerce_thumbnail' ); ?> <a class="dvd-upsell-mini__item" href="<?php echo esc_url( get_permalink( $pid ) ); ?>"> <img class="dvd-upsell-mini__img" src="<?php echo esc_url( $img ); ?>" alt="<?php echo esc_attr( $p->get_name() ); ?>"> <div class="dvd-upsell-mini__meta"> <div class="dvd-upsell-mini__name"><?php echo esc_html( $p->get_name() ); ?></div> <div class="dvd-upsell-mini__price"> <?php echo wp_kses_post( wc_price( $price_incl ) ); ?> <span class="dvd-upsell-mini__vat">inc. VAT</span> </div> </div> </a> <?php endforeach; ?> </div> </div> <?php return ob_get_clean(); } /** ---------------- LEFT COLUMN: Bundle Renderer (main product + FBT items) ---------------- */ function dattvape_render_bundle_section( $heading, $product_ids, $context_slug = 'bundle', $btn_label = 'Add Selected Items', $limit = 4, $main_product = null ) { static $did_inline = false; $section_id = 'bundle_' . wp_generate_uuid4(); $cols_class = 'cols-2'; // Resolve main product (current product) if ( ! $main_product instanceof WC_Product ) { $main_product = wc_get_product( get_the_ID() ); } if ( ! $main_product ) return ''; $main_item = dattvape_resolve_card_item( $main_product ); $main_display_product = wc_get_product( $main_item['display_id'] ); $main_price_incl = $main_display_product ? (float) wc_get_price_including_tax( $main_display_product ) : 0; $main_price_excl = $main_display_product ? (float) wc_get_price_excluding_tax( $main_display_product ) : 0; $main_img = get_the_post_thumbnail_url( $main_product->get_id(), 'woocommerce_single' ); if ( ! $main_img ) $main_img = wc_placeholder_img_src( 'woocommerce_single' ); $main_title = $main_product->get_name(); ob_start(); if ( ! $did_inline ) { $did_inline = true; ?> <script> (function(){ window.DVD_BUNDLE = window.DVD_BUNDLE || {}; DVD_BUNDLE.money = function(v){ // Keep £ formatting exactly like you were using return '£'+(Math.round(v*100)/100).toFixed(2); }; DVD_BUNDLE.ajaxUrlBatch = function(){ if (window.wc_add_to_cart_params && wc_add_to_cart_params.wc_ajax_url) { return wc_add_to_cart_params.wc_ajax_url.replace('%%endpoint%%','dvd_add_bundle'); } return (window.location.origin || '') + '/?wc-ajax=dvd_add_bundle'; }; DVD_BUNDLE.openMiniCart = function(){ try { if (window.woodmartThemeModule && woodmartThemeModule.cartPanel && typeof woodmartThemeModule.cartPanel.open === 'function') { woodmartThemeModule.cartPanel.open(); return; } } catch(e){} var $ = window.jQuery; if ($ && $('.cart-widget-side').length){ $('body').addClass('woodmart-cart-opened'); $('.cart-widget-side').addClass('wd-opened'); } }; })(); </script> <?php } ?> <div class="dvd-bundle" id="<?php echo esc_attr( $section_id ); ?>" data-bundle-type="<?php echo esc_attr( $context_slug ); ?>" data-main-id="<?php echo esc_attr( $main_item['display_id'] ); ?>" data-main-type="<?php echo esc_attr( $main_item['type'] ); ?>" data-main-parent="<?php echo esc_attr( $main_item['parent_id'] ); ?>" data-main-attrs="<?php echo esc_attr( wp_json_encode( $main_item['attrs'] ) ); ?>" data-main-price-incl="<?php echo esc_attr( $main_price_incl ); ?>" data-main-price-excl="<?php echo esc_attr( $main_price_excl ); ?>" > <div class="dvd-bundle__head"> <div class="dvd-bundle__title"><?php echo esc_html( $heading ); ?></div> </div> <div class="dvd-bundle__row"> <!-- LEFT: Current product card (image + title + price only) --> <div class="dvd-bundle__hero"> <div class="dvd-maincard"> <div class="main-img-big"> <img class="dvd-maincard__img" src="<?php echo esc_url( $main_img ); ?>" alt="<?php echo esc_attr( $main_title ); ?>"> </div> <div class="dvd-maincard__title"><?php echo esc_html( $main_title ); ?></div> <div class="price-rating"> <div class="dvd-maincard__price"> <span class="price"> <span class="woocommerce-Price-amount amount vat-current-amount"> <?php echo wp_kses_post( wc_price( $main_price_excl ) ); ?> </span> <small class="woocommerce-price-suffix"> <span class="woocommerce-Price-amount amount"> <?php echo wp_kses_post( wc_price( $main_price_incl ) ); ?> </span> </small> </span> </div> <div class="dvd-rating"> <?php $main_rating_html = function_exists('wc_get_rating_html') ? wc_get_rating_html( $main_product->get_average_rating(), $main_product->get_rating_count() ) : ''; ?> <?php if ( $main_rating_html ) : ?> <div class="dvd-maincard__rating"> <?php echo $main_rating_html; ?> </div> <?php endif; ?> </div> </div> </div> </div> <!-- RIGHT: FBT grid --> <div class="dvd-bundle__items"> <?php if ( ! empty( $product_ids ) ) : ?> <div class="dvd-grid <?php echo esc_attr( $cols_class ); ?>"> <?php foreach ( (array) array_slice( $product_ids, 0, $limit ) as $pid ) : $p = wc_get_product( $pid ); if ( ! $p || ! $p->is_purchasable() ) continue; $item = dattvape_resolve_card_item( $p ); $title = $p->get_name(); $display_product = wc_get_product( $item['display_id'] ); $price_incl = $display_product ? (float) wc_get_price_including_tax( $display_product ) : 0; $price_excl = $display_product ? (float) wc_get_price_excluding_tax( $display_product ) : 0; $desc_raw = $p->get_short_description(); $desc_clean = trim( wp_kses_post( $desc_raw ) ); if ( $desc_clean === '' ) { $desc_clean = trim( wp_trim_words( wp_strip_all_tags( $p->get_description() ), 16, '…' ) ); } $img = get_the_post_thumbnail_url( $pid, 'woocommerce_thumbnail' ); if ( ! $img ) $img = wc_placeholder_img_src( 'woocommerce_thumbnail' ); $rating_html = function_exists('wc_get_rating_html') ? wc_get_rating_html( $p->get_average_rating(), $p->get_rating_count() ) : ''; $select_id = 'qty_' . $section_id . '_' . $pid; $data_attrs_json = esc_attr( wp_json_encode( $item['attrs'] ) ); ?> <div class="dvd-card" data-id="<?php echo esc_attr( $item['display_id'] ); ?>" data-price-incl="<?php echo esc_attr( $price_incl ); ?>" data-price-excl="<?php echo esc_attr( $price_excl ); ?>" data-type="<?php echo esc_attr( $item['type'] ); ?>" data-parent="<?php echo esc_attr( $item['parent_id'] ); ?>" data-attrs='<?php echo $data_attrs_json; ?>'> <div class="dvd-check"></div> <div class="dvd-selectable"> <div class="dvd-rating"><?php echo $rating_html; ?></div> <div class="dvd-main"> <div class="dvd-media"> <img class="dvd-img" alt="<?php echo esc_attr( $title ); ?>" src="<?php echo esc_url( $img ); ?>"> </div> <div class="dvd-info"> <div class="dvd-title"><?php echo esc_html( $title ); ?></div> <?php if ( $desc_clean !== '' ) : ?> <div class="dvd-desc"><?php echo esc_html( $desc_clean ); ?></div> <?php endif; ?> <div class="price-qty"> <div class="dvd-meta"> <div class="dvd-price"> <span class="price"> <span class="woocommerce-Price-amount amount vat-current-amount"> <?php echo wp_kses_post( wc_price( $price_excl ) ); ?> </span> <small class="woocommerce-price-suffix"> <span class="woocommerce-Price-amount amount"> <?php echo wp_kses_post( wc_price( $price_incl ) ); ?> </span> </small> </span> </div> </div> <div class="dvd-qtywrap"> <div class="dvd-qtyctrl" data-step="1"> <button type="button" class="dvd-qty-btn minus" aria-label="Decrease quantity">−</button> <input type="number" class="dvd-qty" id="<?php echo esc_attr( $select_id ); ?>" min="0" step="1" value="1" inputmode="numeric" pattern="[0-9]*"> <button type="button" class="dvd-qty-btn plus" aria-label="Increase quantity">+</button> </div> </div> </div><!-- /.price-qty --> </div><!-- /.dvd-info --> </div><!-- /.dvd-main --> </div><!-- /.dvd-selectable --> </div> <?php endforeach; ?> </div> <?php endif; ?> <div class="dvd-addbar first-product-sec"> <div class="dvd-totalbox" aria-live="polite"> <div class="dvd-totalbox__amount"> <span class="small">Only</span> <span class="dvd-total-amount">£0.00</span> </div> <div class="dvd-totalbox__vat"> <span class="dvd-total-vatlabel">inc. VAT</span> </div> </div> <button type="button" class="dvd-addbtn wd-buy-now-btn"><?php echo esc_html( $btn_label ); ?></button> </div> </div><!-- /.dvd-bundle__items --> </div><!-- /.dvd-bundle__row --> </div> <script> (function($){ var $wrap = $('#<?php echo esc_js( $section_id ); ?>'); if(!$wrap.length) return; var labelBase = <?php echo json_encode( $btn_label ); ?>; var $btn = $wrap.find('.dvd-addbtn'); function getMainItem(){ var showIncl = $('#vat-toggle').is(':checked'); var id = parseInt($wrap.data('main-id'),10) || 0; var type = ($wrap.data('main-type') || 'simple') + ''; var parent= parseInt($wrap.data('main-parent'),10) || 0; var attrs = {}; try { attrs = JSON.parse($wrap.attr('data-main-attrs') || '{}'); } catch(e){ attrs = {}; } var price = showIncl ? (parseFloat($wrap.data('main-price-incl')) || 0) : (parseFloat($wrap.data('main-price-excl')) || 0); return { id: id, qty: 1, // fixed as requested price: price, type: type, parent: parent, attrs: attrs }; } function selectedItems(){ var items = []; // Always include main product (hidden auto-selected) var main = getMainItem(); if(main.id){ items.push(main); } // Optional selected cards $wrap.find('.dvd-card.selected').each(function(){ var $c = $(this); var qty = parseInt($c.find('.dvd-qty').val(),10) || 0; if(qty > 0){ var type = ($c.data('type') || 'simple') + ''; var parent = parseInt($c.data('parent'),10) || 0; var attrs = {}; try { attrs = JSON.parse($c.attr('data-attrs') || '{}'); } catch(e){ attrs = {}; } var showIncl = $('#vat-toggle').is(':checked'); var price = showIncl ? (parseFloat($c.data('price-incl')) || 0) : (parseFloat($c.data('price-excl')) || 0); items.push({ id: parseInt($c.data('id'),10), qty: qty, price: price, type: type, parent: parent, attrs: attrs }); } }); return items; } function updateTotal(){ var items = selectedItems(); var total = 0; items.forEach(function(it){ total += (it.price * it.qty); }); $wrap.find('.dvd-total-amount').text(DVD_BUNDLE.money(total)); var showIncl = $('#vat-toggle').is(':checked'); $wrap.find('.dvd-total-vatlabel').text(showIncl ? 'inc. VAT' : 'ex. VAT'); // ✅ Button always enabled (because main product is always included) $btn.text(labelBase); $btn.prop('disabled', false); } // Select/unselect FBT item $wrap.on('click', '.dvd-selectable', function(e){ if ($(e.target).closest('.dvd-qtywrap').length) return; $(this).closest('.dvd-card').toggleClass('selected'); updateTotal(); }); $wrap.on('click change focus', '.dvd-qty, .dvd-qty-btn', function(e){ e.stopPropagation(); }); function normalizeQty($input){ var val = parseInt($input.val(),10); if (isNaN(val) || val < 0) val = 0; $input.val(val); return val; } $wrap.on('input change', '.dvd-qty', function(){ var $input = $(this); var qty = normalizeQty($input); var $card = $input.closest('.dvd-card'); if(qty>0){ $card.addClass('selected'); } else { $card.removeClass('selected'); } updateTotal(); }); $wrap.on('click', '.dvd-qty-btn', function(){ var $btnEl = $(this); var $ctrl = $btnEl.closest('.dvd-qtyctrl'); var $input = $ctrl.find('.dvd-qty'); var val = parseInt($input.val(),10) || 0; if ($btnEl.hasClass('plus')) val += 1; else { val -= 1; if (val < 0) val = 0; } $input.val(val).trigger('change'); }); // Recalculate bundle total when VAT toggle changes $(document).on('change', '#vat-toggle', function(){ updateTotal(); }); $btn.on('click', function(){ var items = selectedItems(); if(!items.length) return; $btn.addClass('is-loading').prop('disabled', true); $.ajax({ url: DVD_BUNDLE.ajaxUrlBatch(), method: 'POST', dataType: 'json', data: { items: items }, success: function(resp){ if (resp && resp.fragments){ $.each(resp.fragments, function(key, value){ $(key).replaceWith(value); }); } try { $(document.body).trigger('wc_fragment_refresh'); $(document.body).trigger('added_to_cart', [resp.fragments, resp.cart_hash, $btn]); } catch(e){} setTimeout(function(){ DVD_BUNDLE.openMiniCart(); }, 0); }, complete: function(){ $btn.removeClass('is-loading').prop('disabled', false); updateTotal(); } }); }); // Initial total uses main product only updateTotal(); })(jQuery); </script> <?php return ob_get_clean(); } /** ---------------- AJAX: Add bundle items ---------------- */ add_action('wc_ajax_dvd_add_bundle', 'dattvape_ajax_add_bundle'); add_action('wc_ajax_nopriv_dvd_add_bundle', 'dattvape_ajax_add_bundle'); function dattvape_ajax_add_bundle() { if ( ! class_exists( 'WC_AJAX' ) ) wp_send_json_error( [ 'message' => 'WooCommerce missing' ] ); $items = isset($_POST['items']) ? (array) $_POST['items'] : []; foreach ( $items as $it ) { $id = isset($it['id']) ? absint($it['id']) : 0; $qty = isset($it['qty']) ? absint($it['qty']) : 0; $type = isset($it['type']) ? sanitize_text_field($it['type']) : 'simple'; if ( ! $id || $qty <= 0 ) continue; if ( $type === 'variation' ) { $parent = isset($it['parent']) ? absint($it['parent']) : 0; $attrs = isset($it['attrs']) && is_array($it['attrs']) ? array_map('wc_clean', $it['attrs']) : []; if ( $parent ) { $parent_product = wc_get_product( $parent ); $variation_object = wc_get_product( $id ); if ( $parent_product && $variation_object && $variation_object->is_purchasable() ) { WC()->cart->add_to_cart( $parent, $qty, $id, $attrs ); } } } else { $p = wc_get_product( $id ); if ( $p && $p->is_purchasable() ) { WC()->cart->add_to_cart( $id, $qty ); } } } WC_AJAX::get_refreshed_fragments(); wp_die(); } /***************** END *****************/ /***************** Coninue Shopping Button in Mini Cart *****************/ function mini_cart_continue_buttons () { // Adding customized Buttons $custom_link = home_url( '/#' ); // HERE replacing cart link echo '<a href="' . esc_url( $custom_link ) . '" class="button wc-forward closebtn">' . esc_html__( 'Continue Shopping', 'woocommerce' ) . '</a>'; } add_action ('woocommerce_widget_shopping_cart_buttons', 'mini_cart_continue_buttons', 50); /***************** END *****************/ /***************** Popup Login Light Box in Checkout Page *****************/ add_action( 'wp_footer', 'custom_checkout_login_modal_html' ); // Modal structure in footer add_action( 'woocommerce_before_checkout_form', 'display_login_trigger_button', 2 ); // Button at top function display_login_trigger_button() { if ( is_user_logged_in() ) return; echo '<div class="custom-login-trigger-wrapper"> <button id="open-login-lightbox" class="custom-login-btn">Click here to login</button> </div>'; } function custom_checkout_login_modal_html() { if ( !is_checkout() || is_user_logged_in() ) return; ?> <div id="login-lightbox-overlay" class="login-modal-overlay" style="display:none;"> <div class="login-modal-content"> <span id="close-lightbox" class="close-btn">×</span> <div class="modal-body"> <p class="modal-intro">If you have shopped with us before, please enter your details below. If you are a new customer, please proceed to the Billing section.</p> <form class="custom-lightbox-form" method="post" action="<?php echo esc_url( wp_login_url() ); ?>"> <div class="form-row"> <label for="lightbox_login_username">Username / Email ID</label> <input type="text" id="lightbox_login_username" name="log" placeholder="Enter your username or email" required> </div> <div class="form-row"> <label for="lightbox_login_password">Password</label> <input type="password" id="lightbox_login_password" name="pwd" placeholder="Enter your password" required> </div> <button type="submit" class="button">Log In</button> <input type="hidden" name="redirect_to" value="<?php echo wc_get_checkout_url(); ?>"> </form> </div> </div> </div> <style> /* Trigger Button Styling */ .custom-login-trigger-wrapper { margin: 20px 0; padding-left: 190px; text-align: left; } /* Modal Backdrop */ .login-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 999999; display: none; align-items: center; justify-content: center; } /* Modal Box */ .login-modal-content { background: #fff; padding: 40px; width: 90%; max-width: 450px; position: relative; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); } .close-btn { position: absolute; top: 10px; right: 15px; font-size: 28px; cursor: pointer; color: #333; } .modal-intro { font-size: 14px; margin-bottom: 20px; line-height: 1.5; color: #666; } /* Form Inputs */ .custom-lightbox-form input { width: 100%; padding: 12px; margin-bottom: 15px; border: 2px solid #c2c2c2a3; border-radius: 4px; display: block; } .woocommerce .woocommerce-form-login-toggle { display: none; } .woocommerce .custom-login-btn { color: #97be0d; background-color: transparent; font-weight: bold; font-size: 16px; padding: 0; text-transform: capitalize; border-bottom: 1px solid #97be0d; min-height: auto; } .woocommerce .custom-login-btn:hover { background-color: transparent; } .woocommerce button#open-login-lightbox:before { content: "Returning Customer?"; position: absolute; left: -175px; color: #242424; font-size: 16px; font-weight: 600; text-transform: capitalize; } .custom-lightbox-form label { margin-bottom: -12px; font-size: 14px; padding-left: 5px; margin-left: 6px; background-color: white; z-index: 99; position: relative; width: 160px; } .custom-lightbox-form .button { width: 100%; background: #2e8b57; color: white; border: none; margin-top: 5px; padding: 12px; font-weight: bold; cursor: pointer; text-transform: uppercase; border-radius: 5px; } </style> <script> (function($) { $(document).ready(function() { var $modal = $('#login-lightbox-overlay'); $('#open-login-lightbox').on('click', function(e) { e.preventDefault(); $modal.css('display', 'flex'); }); $('#close-lightbox').on('click', function() { $modal.hide(); }); $(window).on('click', function(event) { if (event.target == $modal[0]) { $modal.hide(); } }); }); })(jQuery); </script> <?php } /***************** END *****************/ /***************** Adding Product Original Price in Teir Plugin Boxes Cut Price *****************/ add_action('wp_footer', function () { ?> <script> document.addEventListener('DOMContentLoaded', function () { function formatMoney(amount, currencySymbol) { // 2 decimals, keeps it simple & stable return currencySymbol + ' ' + Number(amount).toFixed(2); } document.querySelectorAll('.tiered-pricing-blocks').forEach(function (wrap) { // Base / old price (the one to cut) var base = parseFloat(wrap.getAttribute('data-regular-price') || wrap.getAttribute('data-price') || '0'); if (!base || isNaN(base)) return; // Currency symbol (grab from any tier price already printed) var symbolEl = wrap.querySelector('.woocommerce-Price-currencySymbol'); var symbol = symbolEl ? symbolEl.textContent.trim() : '£'; wrap.querySelectorAll('.tiered-pricing-block').forEach(function (block) { // Avoid duplicating if script runs twice if (block.querySelector('.tiered-pricing-block__price-old')) return; var tier = parseFloat(block.getAttribute('data-tiered-price') || '0'); if (!tier || isNaN(tier)) return; // Only show cut price when discount exists if (tier >= base) return; // Where current price is rendered var priceWrap = block.querySelector('.tiered-pricing-block__price'); if (!priceWrap) return; // Inject old/cut price var oldSpan = document.createElement('span'); oldSpan.className = 'tiered-pricing-block__price-old'; oldSpan.innerHTML = '<del>' + formatMoney(base, symbol) + '</del>'; priceWrap.appendChild(oldSpan); }); }); }); </script> <?php }, 9999); /***************** END *****************/
[-] style.css
[edit]
[-] vat-toggle.css
[edit]
[-] functions.php
[edit]
[+]
woocommerce
[+]
..
[-] screenshot.png
[edit]
[-] vat-toggle.js
[edit]