diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php index 6c57d07c775..2d75468bce3 100644 --- a/includes/abstracts/abstract-wc-order.php +++ b/includes/abstracts/abstract-wc-order.php @@ -575,33 +575,29 @@ abstract class WC_Abstract_Order { * @return bool success or fail */ public function calculate_taxes() { - $tax_total = 0; $taxes = array(); $tax_based_on = get_option( 'woocommerce_tax_based_on' ); - if ( 'base' === $tax_based_on ) { + if ( 'billing' === $tax_based_on ) { + $country = $this->billing_country; + $state = $this->billing_state; + $postcode = $this->billing_postcode; + $city = $this->billing_city; + } elseif ( 'shipping' === $tax_based_on ) { + $country = $this->shipping_country; + $state = $this->shipping_state; + $postcode = $this->shipping_postcode; + $city = $this->shipping_city; + } + // Default to base + if ( 'base' === $tax_based_on || empty( $country ) ) { $default = wc_get_base_location(); $country = $default['country']; $state = $default['state']; $postcode = ''; $city = ''; - - } elseif ( 'billing' === $tax_based_on ) { - - $country = $this->billing_country; - $state = $this->billing_state; - $postcode = $this->billing_postcode; - $city = $this->billing_city; - - } else { - - $country = $this->shipping_country; - $state = $this->shipping_state; - $postcode = $this->shipping_postcode; - $city = $this->shipping_city; - } // Get items diff --git a/includes/class-wc-countries.php b/includes/class-wc-countries.php index 2624d07247b..385655d3626 100644 --- a/includes/class-wc-countries.php +++ b/includes/class-wc-countries.php @@ -953,10 +953,11 @@ class WC_Countries { if ( $type == 'billing_' ) { $address_fields['billing_email'] = array( - 'label' => __( 'Email Address', 'woocommerce' ), - 'required' => true, - 'class' => array( 'form-row-first' ), - 'validate' => array( 'email' ), + 'label' => __( 'Email Address', 'woocommerce' ), + 'required' => true, + 'type' => 'email', + 'class' => array( 'form-row-first' ), + 'validate' => array( 'email' ), ); $address_fields['billing_phone'] = array( diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index 067d8167519..01b8b74a3b0 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -121,10 +121,15 @@ class WC_Coupon { $this->code = apply_filters( 'woocommerce_coupon_code', $code ); // Coupon data lets developers create coupons through code - if ( $coupon = apply_filters( 'woocommerce_get_shop_coupon_data', false, $code ) ) { + if ( $coupon = apply_filters( 'woocommerce_get_shop_coupon_data', false, $this->code ) ) { $this->populate( $coupon ); return true; - } elseif ( ( $this->id = $this->get_coupon_id_from_code( $code ) ) && $this->code === apply_filters( 'woocommerce_coupon_code', get_the_title( $this->id ) ) ) { + } + + // Otherwise get ID from the code + $this->id = $this->get_coupon_id_from_code( $this->code ); + + if ( $this->code === apply_filters( 'woocommerce_coupon_code', get_the_title( $this->id ) ) ) { $this->populate(); return true; } @@ -336,7 +341,7 @@ class WC_Coupon { * Ensure coupon amount is valid or throw exception */ private function validate_minimum_amount() { - if ( $this->minimum_amount > 0 && $this->minimum_amount > WC()->cart->subtotal ) { + if ( $this->minimum_amount > 0 && wc_format_decimal( $this->minimum_amount ) > wc_format_decimal( WC()->cart->subtotal ) ) { throw new Exception( self::E_WC_COUPON_MIN_SPEND_LIMIT_NOT_MET ); } } @@ -345,7 +350,7 @@ class WC_Coupon { * Ensure coupon amount is valid or throw exception */ private function validate_maximum_amount() { - if ( $this->maximum_amount > 0 && $this->maximum_amount < WC()->cart->subtotal ) { + if ( $this->maximum_amount > 0 && wc_format_decimal( $this->minimum_amount ) < wc_format_decimal( WC()->cart->subtotal ) ) { throw new Exception( self::E_WC_COUPON_MAX_SPEND_LIMIT_MET ); } } diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index ec188e6fe27..83f79fe8bc7 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -568,6 +568,9 @@ function wc_delete_shop_order_transients( $post_id = 0 ) { delete_transient( $transient ); } + // Increments the transient version to invalidate cache + WC_Cache_Helper::get_transient_version( 'orders', true ); + do_action( 'woocommerce_delete_shop_order_transients', $post_id ); } diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index 85b680f4603..0620c5671ee 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -198,23 +198,35 @@ function wc_get_featured_product_ids() { */ function wc_product_post_type_link( $permalink, $post ) { // Abort if post is not a product - if ( $post->post_type !== 'product' ) + if ( $post->post_type !== 'product' ) { return $permalink; + } // Abort early if the placeholder rewrite tag isn't in the generated URL - if ( false === strpos( $permalink, '%' ) ) + if ( false === strpos( $permalink, '%' ) ) { return $permalink; + } // Get the custom taxonomy terms in use by this post $terms = get_the_terms( $post->ID, 'product_cat' ); - if ( empty( $terms ) ) { + if ( ! empty( $terms ) ) { + usort( $terms, '_usort_terms_by_ID' ); // order by ID + + $category_object = apply_filters( 'wc_product_post_type_link_product_cat', $terms[0], $terms, $post ); + $category_object = get_term( $category_object, 'product_cat' ); + $product_cat = $category_object->slug; + + if ( $parent = $category_object->parent ) { + $ancestors = get_ancestors( $category_object->term_id, 'product_cat' ); + foreach ( $ancestors as $ancestor ) { + $ancestor_object = get_term( $ancestor, 'product_cat' ); + $product_cat = $ancestor_object->slug . '/' . $product_cat; + } + } + } else { // If no terms are assigned to this post, use a string instead (can't leave the placeholder there) $product_cat = _x( 'uncategorized', 'slug', 'woocommerce' ); - } else { - // Replace the placeholder rewrite tag with the first term's slug - $first_term = array_shift( $terms ); - $product_cat = $first_term->slug; } $find = array( @@ -241,8 +253,6 @@ function wc_product_post_type_link( $permalink, $post ) { $product_cat ); - $replace = array_map( 'sanitize_title', $replace ); - $permalink = str_replace( $find, $replace, $permalink ); return $permalink; diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 014f6c2b8ba..4a0861996df 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -1770,6 +1770,23 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) { $field .= '
' . $after; + break; + case 'email' : + + $field = ''; + + if ( $args['label'] ) { + $field .= ''; + } + + $field .= ''; + + if ( $args['description'] ) { + $field .= '' . esc_attr( $args['description'] ) . ''; + } + + $field .= '
' . $after; + break; case 'select' : diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php index 53c2a52e633..63567ab4707 100644 --- a/includes/wc-user-functions.php +++ b/includes/wc-user-functions.php @@ -210,7 +210,6 @@ add_action( 'woocommerce_order_status_completed', 'wc_paying_customer' ); /** * Checks if a user (by email) has bought an item * - * @access public * @param string $customer_email * @param int $user_id * @param int $product_id @@ -219,46 +218,47 @@ add_action( 'woocommerce_order_status_completed', 'wc_paying_customer' ); function wc_customer_bought_product( $customer_email, $user_id, $product_id ) { global $wpdb; - $emails = array(); + $transient_name = 'wc_cbp_' . md5( $customer_email . $user_id . $product_id . WC_Cache_Helper::get_transient_version( 'orders' ) ); - if ( $user_id ) { - $user = get_user_by( 'id', $user_id ); + if ( false === ( $result = get_transient( $transient_name ) ) ) { + $customer_data = array( $user_id ); - if ( isset( $user->user_email ) ) { - $emails[] = $user->user_email; + if ( $user_id ) { + $user = get_user_by( 'id', $user_id ); + + if ( isset( $user->user_email ) ) { + $customer_data[] = $user->user_email; + } } - } - if ( is_email( $customer_email ) ) { - $emails[] = $customer_email; - } + if ( is_email( $customer_email ) ) { + $customer_data[] = $customer_email; + } - if ( sizeof( $emails ) == 0 ) { - return false; - } + $customer_data = array_filter( array_unique( $customer_data ) ); - return $wpdb->get_var( - $wpdb->prepare( " - SELECT COUNT( DISTINCT order_items.order_item_id ) - FROM {$wpdb->prefix}woocommerce_order_items as order_items - LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON order_items.order_item_id = itemmeta.order_item_id - LEFT JOIN {$wpdb->postmeta} AS postmeta ON order_items.order_id = postmeta.post_id - LEFT JOIN {$wpdb->posts} AS posts ON order_items.order_id = posts.ID - WHERE - posts.post_status IN ( 'wc-completed', 'wc-processing' ) AND - itemmeta.meta_value = %s AND - itemmeta.meta_key IN ( '_variation_id', '_product_id' ) AND - postmeta.meta_key IN ( '_billing_email', '_customer_user' ) AND - ( - postmeta.meta_value IN ( '" . implode( "','", array_unique( $emails ) ) . "' ) OR - ( - postmeta.meta_value = %s AND - postmeta.meta_value > 0 - ) - ) - ", $product_id, $user_id - ) - ); + if ( sizeof( $customer_data ) == 0 ) { + return false; + } + + $result = $wpdb->get_var( + $wpdb->prepare( " + SELECT 1 FROM {$wpdb->posts} AS p + INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id + INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON p.ID = i.order_id + INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id + WHERE p.post_status IN ( 'wc-completed', 'wc-processing' ) + AND pm.meta_key IN ( '_billing_email', '_customer_user' ) + AND pm.meta_value IN ( '" . implode( "','", $customer_data ) . "' ) + AND im.meta_key IN ( '_product_id', '_variation_id' ) + AND im.meta_value = %s + ", $product_id + ) + ); + + set_transient( $transient_name, $result ? 1 : 0, DAY_IN_SECONDS * 30 ); + } + return (bool) $result; } /** @@ -406,7 +406,7 @@ function wc_get_customer_available_downloads( $customer_id ) { ", $customer_id, date( 'Y-m-d', current_time( 'timestamp' ) ) ) ); if ( $results ) { - + $looped_downloads = array(); foreach ( $results as $result ) { if ( ! $order || $order->id != $result->order_id ) { @@ -439,7 +439,7 @@ function wc_get_customer_available_downloads( $customer_id ) { } $download_file = $_product->get_file( $result->download_id ); - + // Check if the file has been already added to the downloads list if ( in_array( $download_file, $looped_downloads ) ) { continue; diff --git a/readme.txt b/readme.txt index ac6943599f4..f5cd2202911 100644 --- a/readme.txt +++ b/readme.txt @@ -138,6 +138,7 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woothemes/wooc == Changelog == +* Feature - Show full category hierarchy in permalinks. * Fix - Ensure coupon taxes are reset when calculating totals. * Tweak - Base discounts on the undiscounted price. #5874 @@ -159,7 +160,7 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woothemes/wooc * Fix - Settings API - allow multiselect fields to be emptied. * Fix - Saving an order needs to save the discount amount ex. tax like the cart. * Fix - Order again with custom attributes. -* Fix - Prevent potential XSS within tooltips (discovered by FortiGuard Labs). +* Fix - [CVE-2015-2329] Prevent potential XSS within tooltips (discovered by Fortinet FortiGuard Labs). * Fix - Paypal debug option. * Fix - Removed $q->query['wc_query'] = 'product_query' which broke redirects (#7703). Use $q->get('wc_query') instead. * Fix - Sanitize tax_rate_id when saving taxes in the backend to prevent potential SQL injection (discovered by WordFence). diff --git a/templates/cart/cart-totals.php b/templates/cart/cart-totals.php index 9ffa8884792..cec0da88c8b 100644 --- a/templates/cart/cart-totals.php +++ b/templates/cart/cart-totals.php @@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) { cart->get_coupons() as $code => $coupon ) : ?> -