diff --git a/assets/js/frontend/cart.js b/assets/js/frontend/cart.js index e6ae0396dc0..0ccf9ec6bff 100644 --- a/assets/js/frontend/cart.js +++ b/assets/js/frontend/cart.js @@ -6,35 +6,237 @@ jQuery( function( $ ) { return false; } - // Shipping calculator - $( document ).on( 'click', '.shipping-calculator-button', function() { - $( '.shipping-calculator-form' ).slideToggle( 'slow' ); - return false; - }).on( 'change', 'select.shipping_method, input[name^=shipping_method]', function() { - var shipping_methods = []; + // Gets a url for a given AJAX endpoint. + var get_url = function( endpoint ) { + return wc_cart_params.wc_ajax_url.toString().replace( + '%%endpoint%%', + endpoint + ); + }; - $( 'select.shipping_method, input[name^=shipping_method][type=radio]:checked, input[name^=shipping_method][type=hidden]' ).each( function() { - shipping_methods[ $( this ).data( 'index' ) ] = $( this ).val(); - }); + // Check if a node is blocked for processing. + var is_blocked = function( $node ) { + return $node.is( '.processing' ); + }; - $( 'div.cart_totals' ).block({ + // Block a node for processing. + var block = function( $node ) { + $node.addClass( 'processing' ).block( { message: null, overlayCSS: { background: '#fff', opacity: 0.6 } - }); + } ); + }; + + // Unblock a node after processing is complete. + var unblock = function( $node ) { + $node.removeClass( 'processing' ).unblock(); + }; + + // Updates the .woocommerce div with a string of html. + var update_wc_div = function( html_str ) { + var $html = $.parseHTML( html_str ); + var $new_div = $( 'div.woocommerce', $html ); + $( 'div.woocommerce' ).replaceWith( $new_div ); + }; + + // Shipping calculator + $( document ).on( 'click', '.shipping-calculator-button', function() { + $( '.shipping-calculator-form' ).slideToggle( 'slow' ); + return false; + } ).on( 'change', 'select.shipping_method, input[name^=shipping_method]', function() { + var shipping_methods = []; + + $( 'select.shipping_method, input[name^=shipping_method][type=radio]:checked, input[name^=shipping_method][type=hidden]' ).each( function() { + shipping_methods[ $( this ).data( 'index' ) ] = $( this ).val(); + } ); + + block( $( 'div.cart_totals' ) ); var data = { security: wc_cart_params.update_shipping_method_nonce, shipping_method: shipping_methods }; - $.post( wc_cart_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'update_shipping_method' ), data, function( response ) { + $.post( get_url( 'update_shipping_method' ), data, function( response ) { $( 'div.cart_totals' ).replaceWith( response ); $( document.body ).trigger( 'updated_shipping_method' ); - }); - }); + } ); + } ); + + $( document ).on( 'submit', 'form.woocommerce-shipping-calculator', function( evt ) { + evt.preventDefault(); + + var $form = $( evt.target ); + + block( $form ); + + // Provide the submit button value because wc-form-handler expects it. + $( '' ).attr( 'type', 'hidden' ) + .attr( 'name', 'calc_shipping' ) + .attr( 'value', 'x' ) + .appendTo( $form ); + + // Make call to actual form post URL. + $.ajax( { + type: $form.attr( 'method' ), + url: $form.attr( 'action' ), + data: $form.serialize(), + dataType: 'html', + success: function( response ) { + update_wc_div(response ); + }, + complete: function() { + unblock( $form ); + } + } ); + } ); $( '.shipping-calculator-form' ).hide(); -}); + + // Update the cart after something has changed. + var update_cart_totals = function() { + block( $( 'div.cart_totals' ) ); + + $.ajax( { + url: get_url( 'get_cart_totals' ), + dataType: 'html', + success: function( response ) { + $( 'div.cart_totals' ).replaceWith( response ); + } + } ); + }; + + // clears previous notices and shows new one above form. + var show_notice = function( html_element ) { + var $form = $( 'div.woocommerce > form' ); + + $( '.woocommerce-error, .woocommerce-message' ).remove(); + $form.before( html_element ); + }; + + // Handle form submit and route to correct logic. + $( document ).on( 'submit', 'div.woocommerce > form', function( evt ) { + evt.preventDefault(); + + var $form = $( evt.target ); + var $submit = $( document.activeElement ); + + window.console.log( $submit ); + + if ( is_blocked( $form ) ) { + return false; + } + + if ( $submit.is( '[name="update_cart"]' ) || $submit.is( 'input.qty' ) ) { + window.console.log( 'update cart' ); + quantity_update( $form ); + + } else if ( $submit.is( '[name="apply_coupon"]' ) || $submit.is( '#coupon_code' ) ) { + window.console.log( 'apply coupon' ); + apply_coupon( $form ); + } + } ); + + // Coupon code + var apply_coupon = function( $form ) { + block( $form ); + + var $text_field = $( '#coupon_code' ); + var coupon_code = $text_field.val(); + + var data = { + security: wc_cart_params.apply_coupon_nonce, + coupon_code: coupon_code + }; + + $.ajax( { + type: 'POST', + url: get_url( 'apply_coupon' ), + data: data, + dataType: 'html', + success: function( response ) { + show_notice( response ); + }, + complete: function() { + unblock( $form ); + $text_field.val( '' ); + update_cart_totals(); + } + } ); + }; + + $( document ).on( 'click', 'a.woocommerce-remove-coupon', function( evt ) { + evt.preventDefault(); + + var $tr = $( this ).parents( 'tr' ); + var coupon = $( this ).attr( 'data-coupon' ); + + block( $tr.parents( 'table' ) ); + + var data = { + security: wc_cart_params.remove_coupon_nonce, + coupon: coupon + }; + + $.ajax( { + type: 'POST', + url: get_url( 'remove_coupon' ), + data: data, + dataType: 'html', + success: function( response ) { + show_notice( response ); + unblock( $tr.parents( 'table' ) ); + }, + complete: function() { + update_cart_totals(); + } + } ); + } ); + + // Quantity Update + var quantity_update = function( $form ) { + + // Provide the submit button value because wc-form-handler expects it. + $( '' ).attr( 'type', 'hidden' ) + .attr( 'name', 'update_cart' ) + .attr( 'value', 'Update Cart' ) + .appendTo( $form ); + + block( $form ); + + // Make call to actual form post URL. + $.ajax( { + type: $form.attr( 'method' ), + url: $form.attr( 'action' ), + data: $form.serialize(), + dataType: 'html', + success: update_wc_div, + complete: function() { + unblock( $form ); + } + } ); + }; + + // Item Remove + $( document ).on( 'click', 'td.product-remove > a', function( evt ) { + evt.preventDefault(); + + var $a = $( evt.target ); + var $form = $a.parents( 'form' ); + + block( $form ); + + $.ajax( { + type: 'GET', + url: $a.attr( 'href' ), + dataType: 'html', + success: update_wc_div, + complete: function() { + unblock( $form ); + } + } ); + } ); +} ); diff --git a/assets/js/frontend/cart.min.js b/assets/js/frontend/cart.min.js index a1a3317cac9..da0aa9928e4 100644 --- a/assets/js/frontend/cart.min.js +++ b/assets/js/frontend/cart.min.js @@ -1 +1 @@ -jQuery(function(a){return"undefined"==typeof wc_cart_params?!1:(a(document).on("click",".shipping-calculator-button",function(){return a(".shipping-calculator-form").slideToggle("slow"),!1}).on("change","select.shipping_method, input[name^=shipping_method]",function(){var b=[];a("select.shipping_method, input[name^=shipping_method][type=radio]:checked, input[name^=shipping_method][type=hidden]").each(function(){b[a(this).data("index")]=a(this).val()}),a("div.cart_totals").block({message:null,overlayCSS:{background:"#fff",opacity:.6}});var c={security:wc_cart_params.update_shipping_method_nonce,shipping_method:b};a.post(wc_cart_params.wc_ajax_url.toString().replace("%%endpoint%%","update_shipping_method"),c,function(b){a("div.cart_totals").replaceWith(b),a(document.body).trigger("updated_shipping_method")})}),void a(".shipping-calculator-form").hide())}); \ No newline at end of file +jQuery(function(a){if("undefined"==typeof wc_cart_params)return!1;var b=function(a){return wc_cart_params.wc_ajax_url.toString().replace("%%endpoint%%",a)},c=function(a){return a.is(".processing")},d=function(a){a.addClass("processing").block({message:null,overlayCSS:{background:"#fff",opacity:.6}})},e=function(a){a.removeClass("processing").unblock()},f=function(b){var c=a.parseHTML(b),d=a("div.woocommerce",c);a("div.woocommerce").replaceWith(d)};a(document).on("click",".shipping-calculator-button",function(){return a(".shipping-calculator-form").slideToggle("slow"),!1}).on("change","select.shipping_method, input[name^=shipping_method]",function(){var c=[];a("select.shipping_method, input[name^=shipping_method][type=radio]:checked, input[name^=shipping_method][type=hidden]").each(function(){c[a(this).data("index")]=a(this).val()}),d(a("div.cart_totals"));var e={security:wc_cart_params.update_shipping_method_nonce,shipping_method:c};a.post(b("update_shipping_method"),e,function(b){a("div.cart_totals").replaceWith(b),a(document.body).trigger("updated_shipping_method")})}),a(document).on("submit","form.woocommerce-shipping-calculator",function(b){b.preventDefault();var c=a(b.target);d(c),a("").attr("type","hidden").attr("name","calc_shipping").attr("value","x").appendTo(c),a.ajax({type:c.attr("method"),url:c.attr("action"),data:c.serialize(),dataType:"html",success:function(a){f(a)},complete:function(){e(c)}})}),a(".shipping-calculator-form").hide();var g=function(){d(a("div.cart_totals")),a.ajax({url:b("get_cart_totals"),dataType:"html",success:function(b){a("div.cart_totals").replaceWith(b)}})},h=function(b){var c=a("div.woocommerce > form");a(".woocommerce-error, .woocommerce-message").remove(),c.before(b)};a(document).on("submit","div.woocommerce > form",function(b){b.preventDefault();var d=a(b.target),e=a(document.activeElement);return window.console.log(e),c(d)?!1:void(e.is('[name="update_cart"]')||e.is("input.qty")?(window.console.log("update cart"),j(d)):(e.is('[name="apply_coupon"]')||e.is("#coupon_code"))&&(window.console.log("apply coupon"),i(d)))});var i=function(c){d(c);var f=a("#coupon_code"),i=f.val(),j={security:wc_cart_params.apply_coupon_nonce,coupon_code:i};a.ajax({type:"POST",url:b("apply_coupon"),data:j,dataType:"html",success:function(a){h(a)},complete:function(){e(c),f.val(""),g()}})};a(document).on("click","a.woocommerce-remove-coupon",function(c){c.preventDefault();var f=a(this).parents("tr"),i=a(this).attr("data-coupon");d(f.parents("table"));var j={security:wc_cart_params.remove_coupon_nonce,coupon:i};a.ajax({type:"POST",url:b("remove_coupon"),data:j,dataType:"html",success:function(a){h(a),e(f.parents("table"))},complete:function(){g()}})});var j=function(b){a("").attr("type","hidden").attr("name","update_cart").attr("value","Update Cart").appendTo(b),d(b),a.ajax({type:b.attr("method"),url:b.attr("action"),data:b.serialize(),dataType:"html",success:f,complete:function(){e(b)}})};a(document).on("click","td.product-remove > a",function(b){b.preventDefault();var c=a(b.target),g=c.parents("form");d(g),a.ajax({type:"GET",url:c.attr("href"),dataType:"html",success:f,complete:function(){e(g)}})})}); \ No newline at end of file diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index bb10caa2760..b2e90ddddb1 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -94,6 +94,7 @@ class WC_AJAX { 'apply_coupon' => true, 'remove_coupon' => true, 'update_shipping_method' => true, + 'get_cart_totals' => true, 'update_order_review' => true, 'add_to_cart' => true, 'checkout' => true, @@ -254,6 +255,22 @@ class WC_AJAX { die(); } + /** + * AJAX receive updated cart_totals div. + */ + public static function get_cart_totals() { + + if ( ! defined( 'WOOCOMMERCE_CART' ) ) { + define( 'WOOCOMMERCE_CART', true ); + } + + WC()->cart->calculate_totals(); + + woocommerce_cart_totals(); + + die(); + } + /** * AJAX update order review on checkout. */ diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php index 51fb97bdee1..7f99fef889b 100644 --- a/includes/class-wc-checkout.php +++ b/includes/class-wc-checkout.php @@ -765,18 +765,22 @@ class WC_Checkout { } // Get the billing_ and shipping_ address fields - $address_fields = array_merge( WC()->countries->get_address_fields(), WC()->countries->get_address_fields( '', 'shipping_' ) ); + if ( isset( $this->checkout_fields['shipping'] ) && isset( $this->checkout_fields['billing'] ) ) { - if ( is_user_logged_in() && array_key_exists( $input, $address_fields ) ) { - $current_user = wp_get_current_user(); + $address_fields = array_merge( $this->checkout_fields['billing'], $this->checkout_fields['shipping'] ); - if ( $meta = get_user_meta( $current_user->ID, $input, true ) ) { - return $meta; + if ( is_user_logged_in() && is_array( $address_fields ) && array_key_exists( $input, $address_fields ) ) { + $current_user = wp_get_current_user(); + + if ( $meta = get_user_meta( $current_user->ID, $input, true ) ) { + return $meta; + } + + if ( $input == 'billing_email' ) { + return $current_user->user_email; + } } - if ( $input == 'billing_email' ) { - return $current_user->user_email; - } } switch ( $input ) { diff --git a/includes/class-wc-frontend-scripts.php b/includes/class-wc-frontend-scripts.php index 10ca99535db..82a23703a47 100644 --- a/includes/class-wc-frontend-scripts.php +++ b/includes/class-wc-frontend-scripts.php @@ -289,6 +289,8 @@ class WC_Frontend_Scripts { 'ajax_url' => WC()->ajax_url(), 'wc_ajax_url' => WC_AJAX::get_endpoint( "%%endpoint%%" ), 'update_shipping_method_nonce' => wp_create_nonce( "update-shipping-method" ), + 'apply_coupon_nonce' => wp_create_nonce( "apply-coupon" ), + 'remove_coupon_nonce' => wp_create_nonce( "remove-coupon" ), ); break; case 'wc-cart-fragments' : diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index c8c09b7b07d..6fc6a549ae8 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -376,68 +376,54 @@ class WC_Product_Variable extends WC_Product { * @return array of attributes and their available values */ public function get_variation_attributes() { + global $wpdb; + $variation_attributes = array(); + $attributes = $this->get_attributes(); + $child_ids = $this->get_children( true ); - if ( ! $this->has_child() ) { - return $variation_attributes; - } - - $attributes = $this->get_attributes(); - - foreach ( $attributes as $attribute ) { - if ( ! $attribute['is_variation'] ) { - continue; - } - - $values = array(); - $attribute_field_name = 'attribute_' . sanitize_title( $attribute['name'] ); - - // Get used values from children variations - foreach ( $this->get_children() as $child_id ) { - $variation = $this->get_child( $child_id ); - - if ( ! empty( $variation->variation_id ) ) { - if ( ! $variation->variation_is_visible() ) { - continue; // Disabled or hidden - } - - $child_variation_attributes = $variation->get_variation_attributes(); - - if ( isset( $child_variation_attributes[ $attribute_field_name ] ) ) { - $values[] = $child_variation_attributes[ $attribute_field_name ]; - } + if ( ! empty( $child_ids ) ) { + foreach ( $attributes as $attribute ) { + if ( empty( $attribute['is_variation'] ) ) { + continue; } - } - // empty value indicates that all options for given attribute are available - if ( in_array( '', $values ) ) { - $values = $attribute['is_taxonomy'] ? wp_get_post_terms( $this->id, $attribute['name'], array( 'fields' => 'slugs' ) ) : wc_get_text_attributes( $attribute['value'] ); + // Get possible values for this attribute, for only visible variations. + $values = array_unique( $wpdb->get_col( $wpdb->prepare( + "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN (" . implode( ',', array_map( 'esc_sql', $child_ids ) ) . ")", + wc_variation_attribute_name( $attribute['name'] ) + ) ) ); - // Get custom attributes (non taxonomy) as defined - } elseif ( ! $attribute['is_taxonomy'] ) { - $text_attributes = wc_get_text_attributes( $attribute['value'] ); - $assigned_text_attributes = $values; - $values = array(); + // empty value indicates that all options for given attribute are available + if ( in_array( '', $values ) ) { + $values = $attribute['is_taxonomy'] ? wp_get_post_terms( $this->id, $attribute['name'], array( 'fields' => 'slugs' ) ) : wc_get_text_attributes( $attribute['value'] ); - // Pre 2.4 handling where 'slugs' were saved instead of the full text attribute - if ( version_compare( get_post_meta( $this->id, '_product_version', true ), '2.4.0', '<' ) ) { - $assigned_text_attributes = array_map( 'sanitize_title', $assigned_text_attributes ); + // Get custom attributes (non taxonomy) as defined + } elseif ( ! $attribute['is_taxonomy'] ) { + $text_attributes = wc_get_text_attributes( $attribute['value'] ); + $assigned_text_attributes = $values; + $values = array(); - foreach ( $text_attributes as $text_attribute ) { - if ( in_array( sanitize_title( $text_attribute ), $assigned_text_attributes ) ) { - $values[] = $text_attribute; + // Pre 2.4 handling where 'slugs' were saved instead of the full text attribute + if ( version_compare( get_post_meta( $this->id, '_product_version', true ), '2.4.0', '<' ) ) { + $assigned_text_attributes = array_map( 'sanitize_title', $assigned_text_attributes ); + + foreach ( $text_attributes as $text_attribute ) { + if ( in_array( sanitize_title( $text_attribute ), $assigned_text_attributes ) ) { + $values[] = $text_attribute; + } } - } - } else { - foreach ( $text_attributes as $text_attribute ) { - if ( in_array( $text_attribute, $assigned_text_attributes ) ) { - $values[] = $text_attribute; + } else { + foreach ( $text_attributes as $text_attribute ) { + if ( in_array( $text_attribute, $assigned_text_attributes ) ) { + $values[] = $text_attribute; + } } } } - } - $variation_attributes[ $attribute['name'] ] = array_unique( $values ); + $variation_attributes[ $attribute['name'] ] = array_unique( $values ); + } } return $variation_attributes; diff --git a/includes/class-wc-shipping-zone.php b/includes/class-wc-shipping-zone.php index f2dd568d246..11e90179696 100644 --- a/includes/class-wc-shipping-zone.php +++ b/includes/class-wc-shipping-zone.php @@ -249,7 +249,7 @@ class WC_Shipping_Zone implements WC_Data { } } - return $methods; + return apply_filters( 'woocommerce_shipping_zone_shipping_methods', $methods, $raw_methods, $allowed_classes, $this ); } /** diff --git a/includes/shortcodes/class-wc-shortcode-checkout.php b/includes/shortcodes/class-wc-shortcode-checkout.php index 1259c8e2d33..ac59a99ee20 100644 --- a/includes/shortcodes/class-wc-shortcode-checkout.php +++ b/includes/shortcodes/class-wc-shortcode-checkout.php @@ -198,8 +198,9 @@ class WC_Shortcode_Checkout { if ( $order_id > 0 ) { $order = wc_get_order( $order_id ); - if ( $order->order_key != $order_key ) - unset( $order ); + if ( $order->order_key != $order_key ) { + $order = false; + } } // Empty awaiting payment session diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 8e2b025d157..6fc38fa0b1e 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -49,6 +49,17 @@ function wc_attribute_taxonomy_name( $attribute_name ) { return 'pa_' . wc_sanitize_taxonomy_name( $attribute_name ); } +/** + * Get the attribute name used when storing values in post meta. + * + * @param string $attribute_name Attribute name. + * @since 2.6.0 + * @return string + */ +function wc_variation_attribute_name( $attribute_name ) { + return 'attribute_' . sanitize_title( $attribute_name ); +} + /** * Get a product attribute name by ID. * diff --git a/tests/unit-tests/util/core-functions.php b/tests/unit-tests/util/core-functions.php index 00d1c424a0f..5567014857f 100644 --- a/tests/unit-tests/util/core-functions.php +++ b/tests/unit-tests/util/core-functions.php @@ -153,5 +153,17 @@ class Core_Functions extends \WC_Unit_Test_Case { $this->assertEquals( '', $default['state'] ); } + /** + * Test wc_format_country_state_string(). + * + * @since 2.6.0 + */ + public function test_wc_format_country_state_string() { + // Test with correct values. + $this->assertEquals( array( 'country' => 'US', 'state' => 'CA' ), wc_format_country_state_string( 'US:CA' ) ); + // Test what happens when we pass an incorrect value. + $this->assertEquals( array( 'country' => 'US-CA', 'state' => '' ), wc_format_country_state_string( 'US-CA' ) ); + } + }