' . esc_html__( 'You can optionally "tag" your products here. Tags as a method of labeling your products to make them easier for customers to find.', 'woocommerce' ) . '
',
+ '' . esc_html__( 'You can optionally "tag" your products here. Tags are a method of labeling your products to make them easier for customers to find.', 'woocommerce' ) . '
',
'position' => array(
'edge' => 'right',
'align' => 'middle',
diff --git a/includes/admin/class-wc-admin-post-types.php b/includes/admin/class-wc-admin-post-types.php
index 9305ea76fbd..20d3f25498c 100644
--- a/includes/admin/class-wc-admin-post-types.php
+++ b/includes/admin/class-wc-admin-post-types.php
@@ -546,7 +546,12 @@ class WC_Admin_Post_Types {
printf( '
'_reviews_allowed',
+ 'id' => 'comment_status',
'value' => $product_object->get_reviews_allowed( 'edit' ) ? 'open' : 'closed',
'label' => __( 'Enable reviews', 'woocommerce' ),
'cbvalue' => 'open',
diff --git a/includes/admin/reports/class-wc-admin-report.php b/includes/admin/reports/class-wc-admin-report.php
index 9499cc832f4..b634b8c7a0e 100644
--- a/includes/admin/reports/class-wc-admin-report.php
+++ b/includes/admin/reports/class-wc-admin-report.php
@@ -616,8 +616,6 @@ class WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {}
diff --git a/includes/admin/reports/class-wc-report-coupon-usage.php b/includes/admin/reports/class-wc-report-coupon-usage.php
index 2df70e25fb8..7e8a72ec7a0 100644
--- a/includes/admin/reports/class-wc-report-coupon-usage.php
+++ b/includes/admin/reports/class-wc-report-coupon-usage.php
@@ -359,8 +359,6 @@ class WC_Report_Coupon_Usage extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
global $wp_locale;
diff --git a/includes/admin/reports/class-wc-report-sales-by-category.php b/includes/admin/reports/class-wc-report-sales-by-category.php
index 390a6b22210..85f7d876806 100644
--- a/includes/admin/reports/class-wc-report-sales-by-category.php
+++ b/includes/admin/reports/class-wc-report-sales-by-category.php
@@ -272,8 +272,6 @@ class WC_Report_Sales_By_Category extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
global $wp_locale;
diff --git a/includes/admin/reports/class-wc-report-sales-by-date.php b/includes/admin/reports/class-wc-report-sales-by-date.php
index af72e510f12..7921dc61d26 100644
--- a/includes/admin/reports/class-wc-report-sales-by-date.php
+++ b/includes/admin/reports/class-wc-report-sales-by-date.php
@@ -607,8 +607,6 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
global $wp_locale;
diff --git a/includes/admin/reports/class-wc-report-sales-by-product.php b/includes/admin/reports/class-wc-report-sales-by-product.php
index 95858057158..e5edd1a0769 100644
--- a/includes/admin/reports/class-wc-report-sales-by-product.php
+++ b/includes/admin/reports/class-wc-report-sales-by-product.php
@@ -387,8 +387,6 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
global $wp_locale;
diff --git a/includes/admin/reports/class-wc-report-taxes-by-code.php b/includes/admin/reports/class-wc-report-taxes-by-code.php
index 7950e3bb002..8f488ea5c29 100644
--- a/includes/admin/reports/class-wc-report-taxes-by-code.php
+++ b/includes/admin/reports/class-wc-report-taxes-by-code.php
@@ -67,8 +67,6 @@ class WC_Report_Taxes_By_Code extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
global $wpdb;
diff --git a/includes/admin/reports/class-wc-report-taxes-by-date.php b/includes/admin/reports/class-wc-report-taxes-by-date.php
index 2ffa0ea864e..6ac7424d092 100644
--- a/includes/admin/reports/class-wc-report-taxes-by-date.php
+++ b/includes/admin/reports/class-wc-report-taxes-by-date.php
@@ -67,8 +67,6 @@ class WC_Report_Taxes_By_Date extends WC_Admin_Report {
/**
* Get the main chart.
- *
- * @return string
*/
public function get_main_chart() {
$query_data = array(
diff --git a/includes/api/class-wc-rest-authentication.php b/includes/api/class-wc-rest-authentication.php
index 138fbd68bb8..0fbcbc5bf5e 100644
--- a/includes/api/class-wc-rest-authentication.php
+++ b/includes/api/class-wc-rest-authentication.php
@@ -92,7 +92,7 @@ class WC_REST_Authentication {
* @return WP_Error|null|bool
*/
public function check_authentication_error( $error ) {
- // Passthrough other errors.
+ // Pass through other errors.
if ( ! empty( $error ) ) {
return $error;
}
@@ -103,7 +103,7 @@ class WC_REST_Authentication {
/**
* Set authentication error.
*
- * @param WP_Error $error Authication error data.
+ * @param WP_Error $error Authentication error data.
*/
protected function set_error( $error ) {
// Reset user.
@@ -533,6 +533,8 @@ class WC_REST_Authentication {
return new WP_Error( 'woocommerce_rest_authentication_error', __( 'The API key provided does not have write permissions.', 'woocommerce' ), array( 'status' => 401 ) );
}
break;
+ case 'OPTIONS' :
+ return true;
default :
return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Unknown request method.', 'woocommerce' ), array( 'status' => 401 ) );
diff --git a/includes/api/class-wc-rest-coupons-controller.php b/includes/api/class-wc-rest-coupons-controller.php
index 796d0c02536..f76ce441cfa 100644
--- a/includes/api/class-wc-rest-coupons-controller.php
+++ b/includes/api/class-wc-rest-coupons-controller.php
@@ -240,7 +240,7 @@ class WC_REST_Coupons_Controller extends WC_REST_Legacy_Coupons_Controller {
}
/**
- * Only reutrn writeable props from schema.
+ * Only return writable props from schema.
*
* @param array $schema
* @return bool
diff --git a/includes/api/class-wc-rest-orders-controller.php b/includes/api/class-wc-rest-orders-controller.php
index 15f6cdae9b5..32f2e8bd70c 100644
--- a/includes/api/class-wc-rest-orders-controller.php
+++ b/includes/api/class-wc-rest-orders-controller.php
@@ -394,7 +394,7 @@ class WC_REST_Orders_Controller extends WC_REST_Legacy_Orders_Controller {
}
/**
- * Only reutrn writeable props from schema.
+ * Only return writable props from schema.
*
* @param array $schema
* @return bool
diff --git a/includes/api/legacy/class-wc-rest-legacy-orders-controller.php b/includes/api/legacy/class-wc-rest-legacy-orders-controller.php
index 5b4e501c66d..ea454bef983 100644
--- a/includes/api/legacy/class-wc-rest-legacy-orders-controller.php
+++ b/includes/api/legacy/class-wc-rest-legacy-orders-controller.php
@@ -215,7 +215,7 @@ class WC_REST_Legacy_Orders_Controller extends WC_REST_CRUD_Controller {
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for the response.
*
- * @param WC_Order $order The prder object.
+ * @param WC_Order $order The Order object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request );
diff --git a/includes/api/legacy/v1/class-wc-api-authentication.php b/includes/api/legacy/v1/class-wc-api-authentication.php
index ba9372d993a..1d26ae80ff6 100644
--- a/includes/api/legacy/v1/class-wc-api-authentication.php
+++ b/includes/api/legacy/v1/class-wc-api-authentication.php
@@ -19,7 +19,6 @@ class WC_API_Authentication {
* Setup class
*
* @since 2.1
- * @return WC_API_Authentication
*/
public function __construct() {
@@ -293,7 +292,7 @@ class WC_API_Authentication {
*
* @since 2.1
* @see rawurlencode()
- * @param array $parameters un-normalized pararmeters
+ * @param array $parameters un-normalized parameters
* @return array normalized parameters
*/
private function normalize_parameters( $parameters ) {
diff --git a/includes/api/legacy/v1/class-wc-api-customers.php b/includes/api/legacy/v1/class-wc-api-customers.php
index d1b8fac6272..d5edb1503a2 100644
--- a/includes/api/legacy/v1/class-wc-api-customers.php
+++ b/includes/api/legacy/v1/class-wc-api-customers.php
@@ -31,7 +31,6 @@ class WC_API_Customers extends WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Customers
*/
public function __construct( WC_API_Server $server ) {
diff --git a/includes/api/legacy/v1/class-wc-api-resource.php b/includes/api/legacy/v1/class-wc-api-resource.php
index c298e47a078..d419f8346e9 100644
--- a/includes/api/legacy/v1/class-wc-api-resource.php
+++ b/includes/api/legacy/v1/class-wc-api-resource.php
@@ -28,7 +28,6 @@ class WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Resource
*/
public function __construct( WC_API_Server $server ) {
diff --git a/includes/api/legacy/v1/class-wc-api-server.php b/includes/api/legacy/v1/class-wc-api-server.php
index 6c85fcf4ade..530c6324def 100644
--- a/includes/api/legacy/v1/class-wc-api-server.php
+++ b/includes/api/legacy/v1/class-wc-api-server.php
@@ -112,7 +112,6 @@ class WC_API_Server {
*
* @since 2.1
* @param $path
- * @return WC_API_Server
*/
public function __construct( $path ) {
diff --git a/includes/api/legacy/v2/class-wc-api-authentication.php b/includes/api/legacy/v2/class-wc-api-authentication.php
index 8d5f3d4e24f..2c75a75b605 100644
--- a/includes/api/legacy/v2/class-wc-api-authentication.php
+++ b/includes/api/legacy/v2/class-wc-api-authentication.php
@@ -19,7 +19,6 @@ class WC_API_Authentication {
* Setup class
*
* @since 2.1
- * @return WC_API_Authentication
*/
public function __construct() {
@@ -291,7 +290,7 @@ class WC_API_Authentication {
*
* @since 2.1
* @see rawurlencode()
- * @param array $parameters un-normalized pararmeters
+ * @param array $parameters un-normalized parameters
* @return array normalized parameters
*/
private function normalize_parameters( $parameters ) {
diff --git a/includes/api/legacy/v2/class-wc-api-customers.php b/includes/api/legacy/v2/class-wc-api-customers.php
index a38f2450d3b..d88865b1921 100644
--- a/includes/api/legacy/v2/class-wc-api-customers.php
+++ b/includes/api/legacy/v2/class-wc-api-customers.php
@@ -30,7 +30,6 @@ class WC_API_Customers extends WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Customers
*/
public function __construct( WC_API_Server $server ) {
@@ -608,7 +607,7 @@ class WC_API_Customers extends WC_API_Resource {
$query_args['order'] = $args['order'];
}
- // Orderby
+ // Order by
if ( ! empty( $args['orderby'] ) ) {
$query_args['orderby'] = $args['orderby'];
diff --git a/includes/api/legacy/v2/class-wc-api-resource.php b/includes/api/legacy/v2/class-wc-api-resource.php
index 89c3feef0fd..cd2a9ecde6f 100644
--- a/includes/api/legacy/v2/class-wc-api-resource.php
+++ b/includes/api/legacy/v2/class-wc-api-resource.php
@@ -27,7 +27,6 @@ class WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Resource
*/
public function __construct( WC_API_Server $server ) {
diff --git a/includes/api/legacy/v2/class-wc-api-server.php b/includes/api/legacy/v2/class-wc-api-server.php
index e6413dca5ce..e5d43928193 100644
--- a/includes/api/legacy/v2/class-wc-api-server.php
+++ b/includes/api/legacy/v2/class-wc-api-server.php
@@ -111,7 +111,6 @@ class WC_API_Server {
*
* @since 2.1
* @param $path
- * @return WC_API_Server
*/
public function __construct( $path ) {
diff --git a/includes/api/legacy/v3/class-wc-api-authentication.php b/includes/api/legacy/v3/class-wc-api-authentication.php
index 75613c05dea..88f0a711644 100644
--- a/includes/api/legacy/v3/class-wc-api-authentication.php
+++ b/includes/api/legacy/v3/class-wc-api-authentication.php
@@ -19,7 +19,6 @@ class WC_API_Authentication {
* Setup class
*
* @since 2.1
- * @return WC_API_Authentication
*/
public function __construct() {
@@ -290,7 +289,7 @@ class WC_API_Authentication {
*
* @since 2.1
* @see rawurlencode()
- * @param array $parameters un-normalized pararmeters
+ * @param array $parameters un-normalized parameters
* @return array normalized parameters
*/
private function normalize_parameters( $parameters ) {
diff --git a/includes/api/legacy/v3/class-wc-api-customers.php b/includes/api/legacy/v3/class-wc-api-customers.php
index 959faabda4b..3ae5025530b 100644
--- a/includes/api/legacy/v3/class-wc-api-customers.php
+++ b/includes/api/legacy/v3/class-wc-api-customers.php
@@ -30,7 +30,6 @@ class WC_API_Customers extends WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Customers
*/
public function __construct( WC_API_Server $server ) {
@@ -598,7 +597,7 @@ class WC_API_Customers extends WC_API_Resource {
$query_args['order'] = $args['order'];
}
- // Orderby
+ // Order by
if ( ! empty( $args['orderby'] ) ) {
$query_args['orderby'] = $args['orderby'];
diff --git a/includes/api/legacy/v3/class-wc-api-resource.php b/includes/api/legacy/v3/class-wc-api-resource.php
index 4fefab1d0fb..4aface69c0c 100644
--- a/includes/api/legacy/v3/class-wc-api-resource.php
+++ b/includes/api/legacy/v3/class-wc-api-resource.php
@@ -27,7 +27,6 @@ class WC_API_Resource {
*
* @since 2.1
* @param WC_API_Server $server
- * @return WC_API_Resource
*/
public function __construct( WC_API_Server $server ) {
diff --git a/includes/api/legacy/v3/class-wc-api-server.php b/includes/api/legacy/v3/class-wc-api-server.php
index 2e8aaaa6435..76cff49addf 100644
--- a/includes/api/legacy/v3/class-wc-api-server.php
+++ b/includes/api/legacy/v3/class-wc-api-server.php
@@ -111,7 +111,6 @@ class WC_API_Server {
*
* @since 2.1
* @param $path
- * @return WC_API_Server
*/
public function __construct( $path ) {
diff --git a/includes/api/legacy/v3/class-wc-api-taxes.php b/includes/api/legacy/v3/class-wc-api-taxes.php
index 2fbd6184109..5fafe782e44 100644
--- a/includes/api/legacy/v3/class-wc-api-taxes.php
+++ b/includes/api/legacy/v3/class-wc-api-taxes.php
@@ -297,7 +297,7 @@ class WC_API_Taxes extends WC_API_Resource {
continue;
}
- // Fix compund and shipping values
+ // Fix compound and shipping values
if ( in_array( $key, array( 'compound', 'shipping' ) ) ) {
$value = $value ? 1 : 0;
}
diff --git a/includes/api/v1/class-wc-rest-coupons-controller.php b/includes/api/v1/class-wc-rest-coupons-controller.php
index 2f19cb7958d..fcedf22ae1f 100644
--- a/includes/api/v1/class-wc-rest-coupons-controller.php
+++ b/includes/api/v1/class-wc-rest-coupons-controller.php
@@ -219,7 +219,7 @@ class WC_REST_Coupons_V1_Controller extends WC_REST_Posts_Controller {
}
/**
- * Only reutrn writeable props from schema.
+ * Only return writable props from schema.
* @param array $schema
* @return bool
*/
diff --git a/includes/api/v1/class-wc-rest-customers-controller.php b/includes/api/v1/class-wc-rest-customers-controller.php
index 0e46e6df95c..3034377c197 100644
--- a/includes/api/v1/class-wc-rest-customers-controller.php
+++ b/includes/api/v1/class-wc-rest-customers-controller.php
@@ -279,7 +279,7 @@ class WC_REST_Customers_V1_Controller extends WC_REST_Controller {
$response = rest_ensure_response( $users );
- // Store pagation values for headers then unset for count query.
+ // Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
diff --git a/includes/api/v1/class-wc-rest-orders-controller.php b/includes/api/v1/class-wc-rest-orders-controller.php
index 55766ab5d97..11af9597512 100644
--- a/includes/api/v1/class-wc-rest-orders-controller.php
+++ b/includes/api/v1/class-wc-rest-orders-controller.php
@@ -496,7 +496,7 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller {
* The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
* prepared for the response.
*
- * @param WC_Order $order The prder object.
+ * @param WC_Order $order The order object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request );
@@ -513,7 +513,7 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller {
}
/**
- * Only reutrn writeable props from schema.
+ * Only return writable props from schema.
* @param array $schema
* @return bool
*/
diff --git a/includes/api/v1/class-wc-rest-product-attributes-controller.php b/includes/api/v1/class-wc-rest-product-attributes-controller.php
index 32fe92f5189..e4290657e1d 100644
--- a/includes/api/v1/class-wc-rest-product-attributes-controller.php
+++ b/includes/api/v1/class-wc-rest-product-attributes-controller.php
@@ -240,38 +240,20 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
public function create_item( $request ) {
global $wpdb;
- $args = array(
- 'attribute_label' => $request['name'],
- 'attribute_name' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
- 'attribute_type' => ! empty( $request['type'] ) ? $request['type'] : 'select',
- 'attribute_orderby' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order',
- 'attribute_public' => true === $request['has_archives'],
- );
-
- // Set the attribute slug.
- if ( empty( $args['attribute_name'] ) ) {
- $args['attribute_name'] = wc_sanitize_taxonomy_name( stripslashes( $args['attribute_label'] ) );
- } else {
- $args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) );
- }
-
- $valid_slug = $this->validate_attribute_slug( $args['attribute_name'], true );
- if ( is_wp_error( $valid_slug ) ) {
- return $valid_slug;
- }
-
- $insert = $wpdb->insert(
- $wpdb->prefix . 'woocommerce_attribute_taxonomies',
- $args,
- array( '%s', '%s', '%s', '%s', '%d' )
- );
+ $id = wc_create_attribute( array(
+ 'name' => $request['name'],
+ 'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
+ 'type' => ! empty( $request['type'] ) ? $request['type'] : 'select',
+ 'order_by' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order',
+ 'has_archives' => true === $request['has_archives'],
+ ) );
// Checks for errors.
- if ( is_wp_error( $insert ) ) {
- return new WP_Error( 'woocommerce_rest_cannot_create', $insert->get_error_message(), array( 'status' => 400 ) );
+ if ( is_wp_error( $id ) ) {
+ return new WP_Error( 'woocommerce_rest_cannot_create', $id->get_error_message(), array( 'status' => 400 ) );
}
- $attribute = $this->get_attribute( $wpdb->insert_id );
+ $attribute = $this->get_attribute( $id );
if ( is_wp_error( $attribute ) ) {
return $attribute;
@@ -294,10 +276,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
$response->set_status( 201 );
$response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $attribute->attribute_id ) );
- // Clear transients.
- $this->flush_rewrite_rules();
- delete_transient( 'wc_attribute_taxonomies' );
-
return $response;
}
@@ -329,48 +307,17 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
global $wpdb;
$id = (int) $request['id'];
- $format = array( '%s', '%s', '%s', '%s', '%d' );
- $args = array(
- 'attribute_label' => $request['name'],
- 'attribute_name' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
- 'attribute_type' => $request['type'],
- 'attribute_orderby' => $request['order_by'],
- 'attribute_public' => $request['has_archives'],
- );
+ $edited = wc_update_attribute( $id, array(
+ 'name' => $request['name'],
+ 'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ),
+ 'type' => $request['type'],
+ 'order_by' => $request['order_by'],
+ 'has_archives' => $request['has_archives'],
+ ) );
- $i = 0;
- foreach ( $args as $key => $value ) {
- if ( empty( $value ) && ! is_bool( $value ) ) {
- unset( $args[ $key ] );
- unset( $format[ $i ] );
- }
-
- $i++;
- }
-
- // Set the attribute slug.
- if ( ! empty( $args['attribute_name'] ) ) {
- $args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) );
-
- $valid_slug = $this->validate_attribute_slug( $args['attribute_name'], false );
- if ( is_wp_error( $valid_slug ) ) {
- return $valid_slug;
- }
- }
-
- if ( $args ) {
- $update = $wpdb->update(
- $wpdb->prefix . 'woocommerce_attribute_taxonomies',
- $args,
- array( 'attribute_id' => $id ),
- $format,
- array( '%d' )
- );
-
- // Checks for errors.
- if ( false === $update ) {
- return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Could not edit the attribute.', 'woocommerce' ), array( 'status' => 400 ) );
- }
+ // Checks for errors.
+ if ( is_wp_error( $edited ) ) {
+ return new WP_Error( 'woocommerce_rest_cannot_edit', $edited->get_error_message(), array( 'status' => 400 ) );
}
$attribute = $this->get_attribute( $id );
@@ -393,10 +340,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attribute, $request );
- // Clear transients.
- $this->flush_rewrite_rules();
- delete_transient( 'wc_attribute_taxonomies' );
-
return rest_ensure_response( $response );
}
@@ -407,8 +350,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
- global $wpdb;
-
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out.
@@ -425,25 +366,12 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attribute, $request );
- $deleted = $wpdb->delete(
- $wpdb->prefix . 'woocommerce_attribute_taxonomies',
- array( 'attribute_id' => $attribute->attribute_id ),
- array( '%d' )
- );
+ $deleted = wc_delete_attribute( $attribute->attribute_id );
if ( false === $deleted ) {
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
}
- $taxonomy = wc_attribute_taxonomy_name( $attribute->attribute_name );
-
- if ( taxonomy_exists( $taxonomy ) ) {
- $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' );
- foreach ( $terms as $term ) {
- wp_delete_term( $term->term_id, $taxonomy );
- }
- }
-
/**
* Fires after a single attribute is deleted via the REST API.
*
@@ -453,13 +381,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
*/
do_action( 'woocommerce_rest_delete_product_attribute', $attribute, $response, $request );
- // Fires woocommerce_attribute_deleted hook.
- do_action( 'woocommerce_attribute_deleted', $attribute->attribute_id, $attribute->attribute_name, $taxonomy );
-
- // Clear transients.
- $this->flush_rewrite_rules();
- delete_transient( 'wc_attribute_taxonomies' );
-
return $response;
}
@@ -636,6 +557,7 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
/**
* Validate attribute slug.
*
+ * @deprecated 3.2.0
* @param string $slug
* @param bool $new_data
* @return bool|WP_Error
@@ -655,6 +577,7 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller {
/**
* Schedule to flush rewrite rules.
*
+ * @deprecated 3.2.0
* @since 3.0.0
*/
protected function flush_rewrite_rules() {
diff --git a/includes/api/v1/class-wc-rest-taxes-controller.php b/includes/api/v1/class-wc-rest-taxes-controller.php
index 0f924db5674..1b8c9bdd0aa 100644
--- a/includes/api/v1/class-wc-rest-taxes-controller.php
+++ b/includes/api/v1/class-wc-rest-taxes-controller.php
@@ -252,14 +252,14 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller {
$response = rest_ensure_response( $taxes );
- // Store pagation values for headers then unset for count query.
+ // Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
// Query only for ids.
$wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) );
- // Calcule totals.
+ // Calculate totals.
$total_taxes = (int) $wpdb->num_rows;
$response->header( 'X-WP-Total', (int) $total_taxes );
$max_pages = ceil( $total_taxes / $per_page );
diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php
index bc0f0f89953..93489455a89 100644
--- a/includes/class-wc-ajax.php
+++ b/includes/class-wc-ajax.php
@@ -592,7 +592,7 @@ class WC_AJAX {
$variation_object->set_parent_id( $product_id );
$variation_id = $variation_object->save();
$variation = get_post( $variation_id );
- $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat.
+ $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
include( 'admin/meta-boxes/views/html-variation-admin.php' );
wp_die();
}
@@ -1040,7 +1040,7 @@ class WC_AJAX {
// Save order items first
wc_save_order_items( $order_id, $items );
- // Grab the order and recalc taxes
+ // Grab the order and recalculate taxes
$order = wc_get_order( $order_id );
$order->calculate_taxes( $calculate_tax_args );
$order->calculate_totals( false );
@@ -1411,7 +1411,7 @@ class WC_AJAX {
$wpdb->update( $wpdb->posts, array( 'menu_order' => $menu_orders[ $sorting_id ] ), array( 'ID' => $sorting_id ) );
- do_action( 'woocommerce_after_product_ordering' );
+ do_action( 'woocommerce_after_product_ordering', $sorting_id, $menu_orders );
wp_send_json( $menu_orders );
}
@@ -1654,7 +1654,7 @@ class WC_AJAX {
foreach ( $variations as $variation_object ) {
$variation_id = $variation_object->get_id();
$variation = get_post( $variation_id );
- $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat.
+ $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility.
include( 'admin/meta-boxes/views/html-variation-admin.php' );
$loop++;
}
diff --git a/includes/class-wc-auth.php b/includes/class-wc-auth.php
index 0c4c69c0cc4..d1886e76e30 100644
--- a/includes/class-wc-auth.php
+++ b/includes/class-wc-auth.php
@@ -379,7 +379,7 @@ class WC_Auth {
} catch ( Exception $e ) {
$this->maybe_delete_key( $consumer_data );
- /* translators: %s: error messase */
+ /* translators: %s: error message */
wp_die( sprintf( __( 'Error: %s.', 'woocommerce' ), $e->getMessage() ), __( 'Access denied', 'woocommerce' ), array( 'response' => 401 ) );
}
}
diff --git a/includes/class-wc-cache-helper.php b/includes/class-wc-cache-helper.php
index 790b3fdd907..428be13601a 100644
--- a/includes/class-wc-cache-helper.php
+++ b/includes/class-wc-cache-helper.php
@@ -21,6 +21,7 @@ class WC_Cache_Helper {
public static function init() {
add_action( 'template_redirect', array( __CLASS__, 'geolocation_ajax_redirect' ) );
add_action( 'wp', array( __CLASS__, 'prevent_caching' ) );
+ add_filter( 'nocache_headers', array( __CLASS__, 'set_nocache_constants' ) );
add_action( 'admin_notices', array( __CLASS__, 'notices' ) );
add_action( 'delete_version_transients', array( __CLASS__, 'delete_version_transients' ) );
}
@@ -155,19 +156,22 @@ class WC_Cache_Helper {
if ( ! is_blog_installed() ) {
return;
}
- $page_ids = array_filter( array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ), wc_get_page_id( 'myaccount' ) ) );
- $current_page_id = get_queried_object_id();
+ $page_ids = array_filter( array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ), wc_get_page_id( 'myaccount' ) ) );
- if ( isset( $_GET['download_file'] ) || in_array( $current_page_id, $page_ids ) ) {
- self::nocache();
+ if ( isset( $_GET['download_file'] ) || isset( $_GET['add-to-cart'] ) || is_page( $page_ids ) ) {
+ nocache_headers();
}
}
/**
- * Set nocache constants and headers.
- * @access private
+ * Set constants to prevent caching by some plugins.
+ *
+ * Hooked into nocache_headers filter but does not change headers.
+ *
+ * @param array $value
+ * @return array
*/
- private static function nocache() {
+ public static function set_nocache_constants( $value ) {
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
define( "DONOTCACHEPAGE", true );
}
@@ -177,7 +181,7 @@ class WC_Cache_Helper {
if ( ! defined( 'DONOTCACHEDB' ) ) {
define( "DONOTCACHEDB", true );
}
- nocache_headers();
+ return $value;
}
/**
diff --git a/includes/class-wc-cart-totals.php b/includes/class-wc-cart-totals.php
new file mode 100644
index 00000000000..2362e1555b5
--- /dev/null
+++ b/includes/class-wc-cart-totals.php
@@ -0,0 +1,616 @@
+ 0,
+ 'fees_total_tax' => 0,
+ 'items_subtotal' => 0,
+ 'items_subtotal_tax' => 0,
+ 'items_total' => 0,
+ 'items_total_tax' => 0,
+ 'total' => 0,
+ 'taxes' => array(),
+ 'tax_total' => 0,
+ 'shipping_total' => 0,
+ 'shipping_tax_total' => 0,
+ 'discounts_total' => 0,
+ 'discounts_tax_total' => 0,
+ );
+
+ /**
+ * Sets up the items provided, and calculate totals.
+ *
+ * @since 3.2.0
+ * @param object $cart Cart object to calculate totals for.
+ */
+ public function __construct( &$cart = null ) {
+ if ( is_a( $cart, 'WC_Cart' ) ) {
+ $this->object = $cart;
+ $this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
+ $this->calculate();
+ }
+ }
+
+ /**
+ * Run all calculations methods on the given items in sequence.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate() {
+ $this->calculate_item_totals();
+ $this->calculate_fee_totals();
+ $this->calculate_shipping_totals();
+ $this->calculate_totals();
+ }
+
+ /**
+ * Get default blank set of props used per item.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ protected function get_default_item_props() {
+ return (object) array(
+ 'object' => null,
+ 'quantity' => 0,
+ 'product' => false,
+ 'price_includes_tax' => false,
+ 'subtotal' => 0,
+ 'subtotal_tax' => 0,
+ 'total' => 0,
+ 'total_tax' => 0,
+ 'taxes' => array(),
+ );
+ }
+
+ /**
+ * Get default blank set of props used per fee.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ protected function get_default_fee_props() {
+ return (object) array(
+ 'total_tax' => 0,
+ 'taxes' => array(),
+ );
+ }
+
+ /**
+ * Get default blank set of props used per shipping row.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ protected function get_default_shipping_props() {
+ return (object) array(
+ 'total' => 0,
+ 'total_tax' => 0,
+ 'taxes' => array(),
+ );
+ }
+
+ /**
+ * Should we round at subtotal level only?
+ *
+ * @return bool
+ */
+ protected function round_at_subtotal() {
+ return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
+ }
+
+ /**
+ * Handles a cart or order object passed in for calculation. Normalises data
+ * into the same format for use by this class.
+ *
+ * Each item is made up of the following props, in addition to those returned by get_default_item_props() for totals.
+ * - key: An identifier for the item (cart item key or line item ID).
+ * - cart_item: For carts, the cart item from the cart which may include custom data.
+ * - quantity: The qty for this line.
+ * - price: The line price in cents.
+ * - product: The product object this cart item is for.
+ *
+ * @since 3.2.0
+ */
+ protected function set_items() {
+ $this->items = array();
+
+ foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) {
+ $item = $this->get_default_item_props();
+ $item->object = $cart_item;
+ $item->price_includes_tax = wc_prices_include_tax();
+ $item->quantity = $cart_item['quantity'];
+ $item->subtotal = wc_add_number_precision_deep( $cart_item['data']->get_price() ) * $cart_item['quantity'];
+ $item->product = $cart_item['data'];
+ $item->tax_rates = $this->get_item_tax_rates( $item );
+ $this->items[ $cart_item_key ] = $item;
+ }
+ }
+
+ /**
+ * Get fee objects from the cart. Normalises data
+ * into the same format for use by this class.
+ *
+ * @since 3.2.0
+ */
+ protected function set_fees() {
+ $this->fees = array();
+ $this->object->calculate_fees();
+
+ foreach ( $this->object->get_fees() as $fee_key => $fee_object ) {
+ $fee = $this->get_default_fee_props();
+ $fee->object = $fee_object;
+ $fee->total = wc_add_number_precision_deep( $fee->object->amount );
+
+ if ( $this->calculate_tax && $fee->object->taxable ) {
+ $fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->object->get_customer() ), false );
+ $fee->total_tax = array_sum( $fee->taxes );
+
+ if ( ! $this->round_at_subtotal() ) {
+ $fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() );
+ }
+ }
+
+ $this->fees[ $fee_key ] = $fee;
+ }
+ }
+
+ /**
+ * Get shipping methods from the cart and normalise.
+ *
+ * @since 3.2.0
+ */
+ protected function set_shipping() {
+ $this->shipping = array();
+
+ foreach ( $this->object->calculate_shipping() as $key => $shipping_object ) {
+ $shipping_line = $this->get_default_shipping_props();
+ $shipping_line->object = $shipping_object;
+ $shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
+ $shipping_line->taxes = wc_add_number_precision_deep( $shipping_object->taxes );
+ $shipping_line->total_tax = wc_add_number_precision_deep( array_sum( $shipping_object->taxes ) );
+
+ if ( ! $this->round_at_subtotal() ) {
+ $shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, wc_get_rounding_precision() );
+ }
+
+ $this->shipping[ $key ] = $shipping_line;
+ }
+ }
+
+ /**
+ * Return array of coupon objects from the cart. Normalises data
+ * into the same format for use by this class.
+ *
+ * @since 3.2.0
+ */
+ protected function set_coupons() {
+ $this->coupons = $this->object->get_coupons();
+ }
+
+ /**
+ * Only ran if woocommerce_adjust_non_base_location_prices is true.
+ *
+ * If the customer is outside of the base location, this removes the base
+ * taxes. This is off by default unless the filter is used.
+ *
+ * Uses edit context so unfiltered tax class is returned.
+ *
+ * @since 3.2.0
+ * @param object $item Item to adjust the prices of.
+ * @return object
+ */
+ protected function adjust_non_base_location_price( $item ) {
+ $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'edit' ) );
+
+ if ( $item->tax_rates !== $base_tax_rates ) {
+ // Work out a new base price without the shop's base tax.
+ $taxes = WC_Tax::calc_tax( $item->subtotal, $base_tax_rates, true, true );
+
+ // Now we have a new item price (excluding TAX).
+ $item->subtotal = $item->subtotal - array_sum( $taxes );
+ $item->price_includes_tax = false;
+ }
+ return $item;
+ }
+
+ /**
+ * Get discounted price of an item with precision (in cents).
+ *
+ * @since 3.2.0
+ * @param object $item_key Item to get the price of.
+ * @return int
+ */
+ protected function get_discounted_price_in_cents( $item_key ) {
+ $item = $this->items[ $item_key ];
+ $price = $item->subtotal - $this->discount_totals[ $item_key ];
+
+ if ( $item->price_includes_tax ) {
+ $price += $item->subtotal_tax;
+ }
+ return $price;
+ }
+
+ /**
+ * Get tax rates for an item. Caches rates in class to avoid multiple look ups.
+ *
+ * @param object $item Item to get tax rates for.
+ * @return array of taxes
+ */
+ protected function get_item_tax_rates( $item ) {
+ $tax_class = $item->product->get_tax_class();
+ return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class(), $this->object->get_customer() );
+ }
+
+ /**
+ * Get a single total with or without precision (in cents).
+ *
+ * @since 3.2.0
+ * @param string $key Total to get.
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return int|float
+ */
+ public function get_total( $key = 'total', $in_cents = false ) {
+ $totals = $this->get_totals( $in_cents );
+ return isset( $totals[ $key ] ) ? $totals[ $key ] : 0;
+ }
+
+ /**
+ * Set a single total.
+ *
+ * @since 3.2.0
+ * @param string $key Total name you want to set.
+ * @param int $total Total to set.
+ */
+ protected function set_total( $key = 'total', $total ) {
+ $this->totals[ $key ] = $total;
+ }
+
+ /**
+ * Get all totals with or without precision (in cents).
+ *
+ * @since 3.2.0
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return array.
+ */
+ public function get_totals( $in_cents = false ) {
+ return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
+ }
+
+ /**
+ * Get all tax rows from items (including shipping and product line items).
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ protected function get_merged_taxes() {
+ $taxes = array();
+
+ foreach ( array_merge( $this->items, $this->fees, $this->shipping ) as $item ) {
+ foreach ( $item->taxes as $rate_id => $rate ) {
+ $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 );
+ }
+ }
+
+ foreach ( $this->items + $this->fees as $item ) {
+ foreach ( $item->taxes as $rate_id => $rate ) {
+ $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate;
+ }
+ }
+
+ foreach ( $this->shipping as $item ) {
+ foreach ( $item->taxes as $rate_id => $rate ) {
+ $taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate;
+ }
+ }
+ return $taxes;
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Calculation methods.
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Calculate item totals.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate_item_totals() {
+ $this->set_items();
+ $this->calculate_item_subtotals();
+ $this->calculate_discounts();
+
+ foreach ( $this->items as $item_key => $item ) {
+ $item->total = $this->get_discounted_price_in_cents( $item_key );
+ $item->total_tax = 0;
+
+ if ( has_filter( 'woocommerce_get_discounted_price' ) ) {
+ /**
+ * Allow plugins to filter this price like in the legacy cart class.
+ *
+ * This is legacy and should probably be deprecated in the future.
+ * $item->object is the cart item object.
+ * $this->object is the cart object.
+ */
+ $item->total = wc_add_number_precision(
+ apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->object )
+ );
+ }
+
+ if ( $this->calculate_tax && $item->product->is_taxable() ) {
+ $item->taxes = WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax );
+ $item->total_tax = array_sum( $item->taxes );
+
+ if ( ! $this->round_at_subtotal() ) {
+ $item->total_tax = wc_round_tax_total( $item->total_tax, wc_get_rounding_precision() );
+ }
+
+ if ( $item->price_includes_tax ) {
+ $item->total = $item->total - $item->total_tax;
+ } else {
+ $item->total = $item->total;
+ }
+ }
+
+ $this->object->cart_contents[ $item_key ]['line_total'] = wc_remove_number_precision( $item->total );
+ $this->object->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
+ }
+
+ $this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
+ $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
+ }
+
+ /**
+ * Subtotals are costs before discounts.
+ *
+ * To prevent rounding issues we need to work with the inclusive price where possible.
+ * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
+ * be 8.325 leading to totals being 1p off.
+ *
+ * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
+ * afterwards.
+ *
+ * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate_item_subtotals() {
+ foreach ( $this->items as $item_key => $item ) {
+ if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
+ $item = $this->adjust_non_base_location_price( $item );
+ }
+
+ if ( $this->calculate_tax && $item->product->is_taxable() ) {
+ $subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
+ $item->subtotal_tax = array_sum( $subtotal_taxes );
+
+ if ( ! $this->round_at_subtotal() ) {
+ $item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, wc_get_rounding_precision() );
+ }
+
+ if ( $item->price_includes_tax ) {
+ $item->subtotal = $item->subtotal - $item->subtotal_tax;
+ }
+ }
+
+ $this->object->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
+ $this->object->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
+ }
+ $this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) );
+ $this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) );
+
+ $this->object->subtotal = $this->get_total( 'items_subtotal' ) + $this->get_total( 'items_subtotal_tax' );
+ $this->object->subtotal_ex_tax = $this->get_total( 'items_subtotal' );
+ }
+
+ /**
+ * Calculate all discount and coupon amounts.
+ *
+ * @since 3.2.0
+ * @uses WC_Discounts class.
+ */
+ protected function calculate_discounts() {
+ $this->set_coupons();
+
+ $discounts = new WC_Discounts( $this->object );
+
+ foreach ( $this->coupons as $coupon ) {
+ $discounts->apply_discount( $coupon );
+ }
+
+ $coupon_discount_amounts = $discounts->get_discounts_by_coupon( true );
+ $coupon_discount_tax_amounts = array();
+
+ // See how much tax was 'discounted' per item and per coupon.
+ if ( $this->calculate_tax ) {
+ foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) {
+ $coupon_discount_tax_amounts[ $coupon_code ] = 0;
+
+ foreach ( $coupon_discounts as $item_key => $item_discount ) {
+ $item = $this->items[ $item_key ];
+
+ if ( $item->product->is_taxable() ) {
+ $item_tax = array_sum( WC_Tax::calc_tax( $item_discount, $item->tax_rates, $item->price_includes_tax ) );
+ $coupon_discount_tax_amounts[ $coupon_code ] += $item_tax;
+ }
+ }
+
+ $coupon_discount_amounts[ $coupon_code ] -= $coupon_discount_tax_amounts[ $coupon_code ];
+ }
+ }
+
+ $this->discount_totals = $discounts->get_discounts_by_item( true );
+ $this->object->coupon_discount_amounts = wc_remove_number_precision_deep( $coupon_discount_amounts );
+ $this->object->coupon_discount_tax_amounts = wc_remove_number_precision_deep( $coupon_discount_tax_amounts );
+
+ $this->set_total( 'discounts_total', ! empty( $this->discount_totals ) ? array_sum( $this->discount_totals ) : 0 );
+ $this->set_total( 'discounts_tax_total', array_sum( $coupon_discount_tax_amounts ) );
+ }
+
+ /**
+ * Return discounted tax amount for an item.
+ *
+ * @param object $item
+ * @param int $discount_amount
+ * @return int
+ */
+ protected function get_item_discount_tax( $item, $discount_amount ) {
+ if ( $item->product->is_taxable() ) {
+ $taxes = WC_Tax::calc_tax( $discount_amount, $item->tax_rates, false );
+ return array_sum( $taxes );
+ }
+ return 0;
+ }
+
+ /**
+ * Triggers the cart fees API, grabs the list of fees, and calculates taxes.
+ *
+ * Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate_fee_totals() {
+ $this->set_fees();
+ $this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
+ $this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
+
+ foreach ( $this->fees as $fee_key => $fee ) {
+ $this->object->fees[ $fee_key ]->tax = wc_remove_number_precision_deep( $fee->total_tax );
+ $this->object->fees[ $fee_key ]->tax_data = wc_remove_number_precision_deep( $fee->taxes );
+ }
+ $this->object->fee_total = wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
+ }
+
+ /**
+ * Calculate any shipping taxes.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate_shipping_totals() {
+ $this->set_shipping();
+ $this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) );
+ $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) );
+
+ $this->object->shipping_total = $this->get_total( 'shipping_total' );
+ $this->object->shipping_tax_total = $this->get_total( 'shipping_tax_total' );
+ }
+
+ /**
+ * Main cart totals.
+ *
+ * @since 3.2.0
+ */
+ protected function calculate_totals() {
+ $this->set_total( 'taxes', $this->get_merged_taxes() );
+ $this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) );
+ $this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) );
+
+ // Add totals to cart object.
+ $this->object->taxes = wp_list_pluck( $this->get_total( 'taxes' ), 'shipping_tax_total' );
+ $this->object->shipping_taxes = wp_list_pluck( $this->get_total( 'taxes' ), 'tax_total' );
+ $this->object->cart_contents_total = $this->get_total( 'items_total' );
+ $this->object->tax_total = $this->get_total( 'tax_total' );
+ $this->object->total = $this->get_total( 'total' );
+ $this->object->discount_cart = $this->get_total( 'discounts_total' ) - $this->get_total( 'discounts_tax_total' );
+ $this->object->discount_cart_tax = $this->get_total( 'discounts_tax_total' );
+
+ // Allow plugins to hook and alter totals before final total is calculated.
+ if ( has_action( 'woocommerce_calculate_totals' ) ) {
+ do_action( 'woocommerce_calculate_totals', $this->object );
+ }
+
+ // Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
+ $totals_to_sum = wc_add_number_precision_deep( array( $this->object->cart_contents_total, $this->object->tax_total, $this->object->shipping_tax_total, $this->object->shipping_total, $this->object->fee_total ) );
+ $this->object->total = max( 0, apply_filters( 'woocommerce_calculated_total', wc_remove_number_precision( round( array_sum( $totals_to_sum ) ) ), $this->object ) );
+ }
+}
diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php
index 96f1a75723c..43dcbcac291 100644
--- a/includes/class-wc-cart.php
+++ b/includes/class-wc-cart.php
@@ -1,81 +1,158 @@
0,
'total' => 0,
@@ -106,8 +183,8 @@ class WC_Cart {
*/
public function __construct() {
add_action( 'wp_loaded', array( $this, 'init' ) ); // Get cart after WP and plugins are loaded.
- add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies
- add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing
+ add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies.
+ add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing.
add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
}
@@ -115,7 +192,7 @@ class WC_Cart {
/**
* Auto-load in-accessible properties on demand.
*
- * @param mixed $key
+ * @param mixed $key Key to get.
* @return mixed
*/
public function __get( $key ) {
@@ -145,10 +222,12 @@ class WC_Cart {
case 'tax' :
wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' );
$this->tax = new WC_Tax();
- return $this->tax;
+ return $this->tax;
case 'discount_total':
wc_deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' );
- return 0;
+ return 0;
+ case 'coupons' :
+ return $this->get_coupons();
}
}
@@ -180,12 +259,12 @@ class WC_Cart {
* Set cart hash cookie and items in cart.
*
* @access private
- * @param bool $set (default: true)
+ * @param bool $set Should cookies be set (true) or unset.
*/
private function set_cart_cookies( $set = true ) {
if ( $set ) {
wc_setcookie( 'woocommerce_items_in_cart', 1 );
- wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $this->get_cart_for_session() ) ) );
+ wc_setcookie( 'woocommerce_cart_hash', md5( wp_json_encode( $this->get_cart_for_session() ) ) );
} elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) {
wc_setcookie( 'woocommerce_items_in_cart', 0, time() - HOUR_IN_SECONDS );
wc_setcookie( 'woocommerce_cart_hash', '', time() - HOUR_IN_SECONDS );
@@ -193,15 +272,10 @@ class WC_Cart {
do_action( 'woocommerce_set_cart_cookies', $set );
}
- /*
- /* Cart Session Handling
- */
-
/**
* Get the cart data from the PHP session and store it in class variables.
*/
public function get_cart_from_session() {
- // Load cart session data from session
foreach ( $this->cart_session_data as $key => $default ) {
$this->$key = WC()->session->get( $key, $default );
}
@@ -223,7 +297,7 @@ class WC_Cart {
}
if ( is_array( $cart ) ) {
- // Prime meta cache to reduce future queries
+ // Prime meta cache to reduce future queries.
update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) );
update_object_term_cache( wp_list_pluck( $cart, 'product_id' ), 'product' );
@@ -233,32 +307,28 @@ class WC_Cart {
if ( ! empty( $product ) && $product->exists() && $values['quantity'] > 0 ) {
if ( ! $product->is_purchasable() ) {
-
- // Flag to indicate the stored cart should be update
- $update_cart_session = true;
+ $update_cart_session = true; // Flag to indicate the stored cart should be updated.
/* translators: %s: product name */
wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $product->get_name() ), 'error' );
do_action( 'woocommerce_remove_cart_item_from_session', $key, $values );
} else {
- // Put session data into array. Run through filter so other plugins can load their own session data
+ // Put session data into array. Run through filter so other plugins can load their own session data.
$session_data = array_merge( $values, array( 'data' => $product ) );
$this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
-
}
}
}
}
- // Trigger action
do_action( 'woocommerce_cart_loaded_from_session', $this );
if ( $update_cart_session ) {
WC()->session->cart = $this->get_cart_for_session();
}
- // Queue re-calc if subtotal is not set
+ // Queue re-calc if subtotal is not set.
if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) {
$this->calculate_totals();
}
@@ -268,7 +338,6 @@ class WC_Cart {
* Sets the php session data for the cart and coupons.
*/
public function set_session() {
- // Set cart and coupon session data
$cart_session = $this->get_cart_for_session();
WC()->session->set( 'cart', $cart_session );
@@ -291,10 +360,11 @@ class WC_Cart {
/**
* Empties the cart and optionally the persistent cart too.
*
- * @param bool $clear_persistent_cart (default: true)
+ * @param bool $clear_persistent_cart Should the persistant cart be cleared too. Defaults to true.
*/
public function empty_cart( $clear_persistent_cart = true ) {
$this->cart_contents = array();
+ $this->shipping_methods = null;
$this->reset( true );
unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart );
@@ -306,10 +376,6 @@ class WC_Cart {
do_action( 'woocommerce_cart_emptied' );
}
- /*
- * Persistent cart handling
- */
-
/**
* Save the persistent cart when the cart is updated.
*/
@@ -326,23 +392,9 @@ class WC_Cart {
delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id() );
}
- /*
- * Cart Data Functions.
- */
-
- /**
- * Coupons enabled function. Filterable.
- *
- * @deprecated 2.5.0 in favor to wc_coupons_enabled()
- *
- * @return bool
- */
- public function coupons_enabled() {
- return wc_coupons_enabled();
- }
-
/**
* Get number of items in the cart.
+ *
* @return int
*/
public function get_cart_contents_count() {
@@ -351,6 +403,7 @@ class WC_Cart {
/**
* Get weight of items in the cart.
+ *
* @since 2.5.0
* @return int
*/
@@ -370,18 +423,14 @@ class WC_Cart {
* @return bool
*/
public function is_empty() {
- return 0 === sizeof( $this->get_cart() );
+ return 0 === count( $this->get_cart() );
}
/**
* Check all cart items for errors.
*/
public function check_cart_items() {
-
- // Result
$return = true;
-
- // Check cart item validity
$result = $this->check_cart_item_validity();
if ( is_wp_error( $result ) ) {
@@ -389,7 +438,6 @@ class WC_Cart {
$return = false;
}
- // Check item stock
$result = $this->check_cart_item_stock();
if ( is_wp_error( $result ) ) {
@@ -409,13 +457,10 @@ class WC_Cart {
$coupon = new WC_Coupon( $code );
if ( ! $coupon->is_valid() ) {
- // Error message
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
-
- // Remove the coupon
$this->remove_coupon( $code );
- // Flag totals for refresh
+ // Flag totals for refresh.
WC()->session->set( 'refresh_totals', true );
}
}
@@ -468,7 +513,6 @@ class WC_Cart {
$error = new WP_Error();
$product_qty_in_cart = $this->get_cart_item_quantities();
- // First stock check loop
foreach ( $this->get_cart() as $cart_item_key => $values ) {
$product = $values['data'];
@@ -531,8 +575,8 @@ class WC_Cart {
/**
* Gets and formats a list of cart item data + variations for display on the frontend.
*
- * @param array $cart_item
- * @param bool $flat (default: false)
+ * @param array $cart_item Cart item object.
+ * @param bool $flat Should the data be returned flat or in a list.
* @return string
*/
public function get_item_data( $cart_item, $flat = false ) {
@@ -544,16 +588,15 @@ class WC_Cart {
foreach ( $cart_item['variation'] as $name => $value ) {
$taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) );
- // If this is a term slug, get the term's nice name
if ( taxonomy_exists( $taxonomy ) ) {
+ // If this is a term slug, get the term's nice name.
$term = get_term_by( 'slug', $value, $taxonomy );
if ( ! is_wp_error( $term ) && $term && $term->name ) {
$value = $term->name;
}
$label = wc_attribute_label( $taxonomy );
-
- // If this is a custom option slug, get the options name.
} else {
+ // If this is a custom option slug, get the options name.
$value = apply_filters( 'woocommerce_variation_option_name', $value );
$label = wc_attribute_label( str_replace( 'attribute_', '', $name ), $cart_item['data'] );
}
@@ -570,10 +613,10 @@ class WC_Cart {
}
}
- // Filter item data to allow 3rd parties to add more to the array
+ // Filter item data to allow 3rd parties to add more to the array.
$item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item );
- // Format item data ready to display
+ // Format item data ready to display.
foreach ( $item_data as $key => $data ) {
// Set hidden to true to not display meta on cart.
if ( ! empty( $data['hidden'] ) ) {
@@ -584,8 +627,8 @@ class WC_Cart {
$item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value'];
}
- // Output flat or in list format
- if ( sizeof( $item_data ) > 0 ) {
+ // Output flat or in list format.
+ if ( count( $item_data ) > 0 ) {
ob_start();
if ( $flat ) {
@@ -622,32 +665,10 @@ class WC_Cart {
return apply_filters( 'woocommerce_cart_crosssell_ids', wp_parse_id_list( $cross_sells ), $this );
}
- /**
- * Gets the url to the cart page.
- *
- * @deprecated 2.5.0 in favor to wc_get_cart_url()
- *
- * @return string url to page
- */
- public function get_cart_url() {
- return wc_get_cart_url();
- }
-
- /**
- * Gets the url to the checkout page.
- *
- * @deprecated 2.5.0 in favor to wc_get_checkout_url()
- *
- * @return string url to page
- */
- public function get_checkout_url() {
- return wc_get_checkout_url();
- }
-
/**
* Gets the url to remove an item from the cart.
*
- * @param string $cart_item_key contains the id of the cart item
+ * @param string $cart_item_key contains the id of the cart item.
* @return string url to page
*/
public function get_remove_url( $cart_item_key ) {
@@ -658,7 +679,7 @@ class WC_Cart {
/**
* Gets the url to re-add an item into the cart.
*
- * @param string $cart_item_key
+ * @param string $cart_item_key Cart item key to undo.
* @return string url to page
*/
public function get_undo_url( $cart_item_key ) {
@@ -697,7 +718,7 @@ class WC_Cart {
if ( $this->get_cart() ) {
foreach ( $this->get_cart() as $key => $values ) {
$cart_session[ $key ] = $values;
- unset( $cart_session[ $key ]['data'] ); // Unset product object
+ unset( $cart_session[ $key ]['data'] ); // Unset product object.
}
}
@@ -726,7 +747,6 @@ class WC_Cart {
public function get_taxes() {
$taxes = array();
- // Merge
foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) {
$taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
}
@@ -769,13 +789,16 @@ class WC_Cart {
/**
* Get all tax classes for items in the cart.
+ *
* @return array
*/
public function get_cart_item_tax_classes() {
$found_tax_classes = array();
foreach ( WC()->cart->get_cart() as $item ) {
- $found_tax_classes[] = $item['data']->get_tax_class();
+ if ( $item['data'] && ( $item['data']->is_taxable() || $item['data']->is_shipping_taxable() ) ) {
+ $found_tax_classes[] = $item['data']->get_tax_class();
+ }
}
return array_unique( $found_tax_classes );
@@ -803,16 +826,12 @@ class WC_Cart {
}
}
- /*
- * Add to cart handling.
- */
-
/**
* Check if product is in the cart and return cart item key.
*
* Cart item key will be unique based on the item and its properties, such as variations.
*
- * @param mixed id of product to find in the cart
+ * @param mixed $cart_id id of product to find in the cart.
* @return string cart item key
*/
public function find_product_in_cart( $cart_id = false ) {
@@ -827,16 +846,16 @@ class WC_Cart {
/**
* Generate a unique ID for the cart item being added.
*
- * @param int $product_id - id of the product the key is being generated for
- * @param int $variation_id of the product the key is being generated for
- * @param array $variation data for the cart item
- * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart
+ * @param int $product_id - id of the product the key is being generated for.
+ * @param int $variation_id of the product the key is being generated for.
+ * @param array $variation data for the cart item.
+ * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart.
* @return string cart item key
*/
public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
$id_parts = array( $product_id );
- if ( $variation_id && 0 != $variation_id ) {
+ if ( $variation_id && 0 !== $variation_id ) {
$id_parts[] = $variation_id;
}
@@ -866,46 +885,43 @@ class WC_Cart {
/**
* Add a product to the cart.
*
- * @param int $product_id contains the id of the product to add to the cart
- * @param int $quantity contains the quantity of the item to add
- * @param int $variation_id
- * @param array $variation attribute values
- * @param array $cart_item_data extra cart item data we want to pass into the item
+ * @throws Exception To prevent adding to cart.
+ * @param int $product_id contains the id of the product to add to the cart.
+ * @param int $quantity contains the quantity of the item to add.
+ * @param int $variation_id ID of the variation being added to the cart.
+ * @param array $variation attribute values.
+ * @param array $cart_item_data extra cart item data we want to pass into the item.
* @return string|bool $cart_item_key
*/
public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
- // Wrap in try catch so plugins can throw an exception to prevent adding to cart
+ // Wrap in try catch so plugins can throw an exception to prevent adding to cart.
try {
$product_id = absint( $product_id );
$variation_id = absint( $variation_id );
- // Ensure we don't add a variation to the cart directly by variation ID
+ // Ensure we don't add a variation to the cart directly by variation ID.
if ( 'product_variation' === get_post_type( $product_id ) ) {
$variation_id = $product_id;
$product_id = wp_get_post_parent_id( $variation_id );
}
- // Get the product
$product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
-
- // Filter quantity being added to the cart before stock checks
$quantity = apply_filters( 'woocommerce_add_to_cart_quantity', $quantity, $product_id );
- // Sanity check
if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->get_status() ) {
return false;
}
- // Load cart item data - may be added by other plugins
+ // Load cart item data - may be added by other plugins.
$cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id );
- // Generate a ID based on product ID, variation ID, variation data, and other cart item data
+ // Generate a ID based on product ID, variation ID, variation data, and other cart item data.
$cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
- // Find the cart item key in the existing cart
+ // Find the cart item key in the existing cart.
$cart_item_key = $this->find_product_in_cart( $cart_id );
- // Force quantity to 1 if sold individually and check for existing item in cart
+ // Force quantity to 1 if sold individually and check for existing item in cart.
if ( $product_data->is_sold_individually() ) {
$quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
$found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $this->cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id );
@@ -916,12 +932,11 @@ class WC_Cart {
}
}
- // Check product is_purchasable
if ( ! $product_data->is_purchasable() ) {
throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) );
}
- // Stock check - only check if we're managing stock and backorders are not allowed
+ // Stock check - only check if we're managing stock and backorders are not allowed.
if ( ! $product_data->is_in_stock() ) {
throw new Exception( sprintf( __( 'You cannot add "%s" to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ) );
}
@@ -931,7 +946,7 @@ class WC_Cart {
throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s" to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ) ) );
}
- // Stock check - this time accounting for whats already in-cart
+ // Stock check - this time accounting for whats already in-cart.
if ( $product_data->managing_stock() ) {
$products_qty_in_cart = $this->get_cart_item_quantities();
@@ -945,20 +960,21 @@ class WC_Cart {
}
}
- // If cart_item_key is set, the item is already in the cart
+ // If cart_item_key is set, the item is already in the cart.
if ( $cart_item_key ) {
$new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity'];
$this->set_quantity( $cart_item_key, $new_quantity, false );
} else {
$cart_item_key = $cart_id;
- // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item
+ // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item.
$this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array(
- 'product_id' => $product_id,
- 'variation_id' => $variation_id,
- 'variation' => $variation,
- 'quantity' => $quantity,
- 'data' => $product_data,
+ 'key' => $cart_item_key,
+ 'product_id' => $product_id,
+ 'variation_id' => $variation_id,
+ 'variation' => $variation,
+ 'quantity' => $quantity,
+ 'data' => $product_data,
) ), $cart_item_key );
}
@@ -982,7 +998,7 @@ class WC_Cart {
* Remove a cart item.
*
* @since 2.3.0
- * @param string $cart_item_key
+ * @param string $cart_item_key Cart item key to remove from the cart.
* @return bool
*/
public function remove_cart_item( $cart_item_key ) {
@@ -1007,7 +1023,7 @@ class WC_Cart {
/**
* Restore a cart item.
*
- * @param string $cart_item_key
+ * @param string $cart_item_key Cart item key to restore to the cart.
* @return bool
*/
public function restore_cart_item( $cart_item_key ) {
@@ -1032,14 +1048,13 @@ class WC_Cart {
/**
* Set the quantity for an item in the cart.
*
- * @param string $cart_item_key contains the id of the cart item
- * @param int $quantity contains the quantity of the item
- * @param bool $refresh_totals whether or not to calculate totals after setting the new qty
- *
+ * @param string $cart_item_key contains the id of the cart item.
+ * @param int $quantity contains the quantity of the item.
+ * @param bool $refresh_totals whether or not to calculate totals after setting the new qty.
* @return bool
*/
public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
- if ( 0 == $quantity || $quantity < 0 ) {
+ if ( 0 === $quantity || $quantity < 0 ) {
do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
unset( $this->cart_contents[ $cart_item_key ] );
} else {
@@ -1055,14 +1070,10 @@ class WC_Cart {
return true;
}
- /*
- * Cart Calculation Functions.
- */
-
/**
* Reset cart totals to the defaults. Useful before running calculations.
*
- * @param bool $unset_session If true, the session data will be forced unset.
+ * @param bool $unset_session If true, the session data will be forced unset.
* @access private
*/
private function reset( $unset_session = false ) {
@@ -1076,26 +1087,22 @@ class WC_Cart {
}
/**
- * Sort by subtotal.
- * @param array $a
- * @param array $b
- * @return int
+ * Get cart's owner.
+ *
+ * @since 3.2.0
+ * @return WC_Customer
*/
- private function sort_by_subtotal( $a, $b ) {
- $first_item_subtotal = isset( $a['line_subtotal'] ) ? $a['line_subtotal'] : 0;
- $second_item_subtotal = isset( $b['line_subtotal'] ) ? $b['line_subtotal'] : 0;
- if ( $first_item_subtotal === $second_item_subtotal ) {
- return 0;
- }
- return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1;
+ public function get_customer() {
+ return WC()->customer;
}
/**
* Calculate totals for the items in the cart.
+ *
+ * @uses WC_Cart_Totals
*/
public function calculate_totals() {
$this->reset();
- $this->coupons = $this->get_coupons();
do_action( 'woocommerce_before_calculate_totals', $this );
@@ -1104,310 +1111,7 @@ class WC_Cart {
return;
}
- $tax_rates = array();
- $shop_tax_rates = array();
- $cart = $this->get_cart();
-
- /**
- * Calculate subtotals for items. This is done first so that discount logic can use the values.
- */
- foreach ( $cart as $cart_item_key => $values ) {
- $product = $values['data'];
- $line_price = $product->get_price() * $values['quantity'];
- $line_subtotal = 0;
- $line_subtotal_tax = 0;
-
- /**
- * No tax to calculate.
- */
- if ( ! $product->is_taxable() ) {
-
- // Subtotal is the undiscounted price
- $this->subtotal += $line_price;
- $this->subtotal_ex_tax += $line_price;
-
- /**
- * Prices include tax.
- *
- * To prevent rounding issues we need to work with the inclusive price where possible.
- * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
- * be 8.325 leading to totals being 1p off.
- *
- * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
- * afterwards.
- *
- * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
- */
- } elseif ( $this->prices_include_tax ) {
-
- // Get base tax rates
- if ( empty( $shop_tax_rates[ $product->get_tax_class( 'unfiltered' ) ] ) ) {
- $shop_tax_rates[ $product->get_tax_class( 'unfiltered' ) ] = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) );
- }
-
- // Get item tax rates
- if ( empty( $tax_rates[ $product->get_tax_class() ] ) ) {
- $tax_rates[ $product->get_tax_class() ] = WC_Tax::get_rates( $product->get_tax_class() );
- }
-
- $base_tax_rates = $shop_tax_rates[ $product->get_tax_class( 'unfiltered' ) ];
- $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
-
- /**
- * ADJUST TAX - Calculations when base tax is not equal to the item tax.
- *
- * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
- * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
- * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
- */
- if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
-
- // Work out a new base price without the shop's base tax
- $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true );
-
- // Now we have a new item price (excluding TAX)
- $line_subtotal = $line_price - array_sum( $taxes );
-
- // Now add modified taxes
- $tax_result = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates );
- $line_subtotal_tax = array_sum( $tax_result );
-
- /**
- * Regular tax calculation (customer inside base and the tax class is unmodified.
- */
- } else {
-
- // Calc tax normally
- $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true );
- $line_subtotal_tax = array_sum( $taxes );
- $line_subtotal = $line_price - array_sum( $taxes );
- }
-
- /**
- * Prices exclude tax.
- *
- * This calculation is simpler - work with the base, untaxed price.
- */
- } else {
-
- // Get item tax rates
- if ( empty( $tax_rates[ $product->get_tax_class() ] ) ) {
- $tax_rates[ $product->get_tax_class() ] = WC_Tax::get_rates( $product->get_tax_class() );
- }
-
- $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
-
- // Base tax for line before discount - we will store this in the order data
- $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates );
- $line_subtotal_tax = array_sum( $taxes );
-
- $line_subtotal = $line_price;
- }
-
- // Add to main subtotal
- $this->subtotal += $line_subtotal + $line_subtotal_tax;
- $this->subtotal_ex_tax += $line_subtotal;
- }
-
- // Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart.
- uasort( $cart, apply_filters( 'woocommerce_sort_by_subtotal_callback', array( $this, 'sort_by_subtotal' ) ) );
-
- /**
- * Calculate totals for items.
- */
- foreach ( $cart as $cart_item_key => $values ) {
-
- $product = $values['data'];
-
- // Prices
- $base_price = $product->get_price();
- $line_price = $product->get_price() * $values['quantity'];
-
- // Tax data
- $taxes = array();
- $discounted_taxes = array();
-
- /**
- * No tax to calculate.
- */
- if ( ! $product->is_taxable() ) {
-
- // Discounted Price (price with any pre-tax discounts applied)
- $discounted_price = $this->get_discounted_price( $values, $base_price, true );
- $line_subtotal_tax = 0;
- $line_subtotal = $line_price;
- $line_tax = 0;
- $line_total = round( $discounted_price * $values['quantity'], wc_get_rounding_precision() );
-
- /**
- * Prices include tax.
- */
- } elseif ( $this->prices_include_tax ) {
-
- $base_tax_rates = $shop_tax_rates[ $product->get_tax_class( 'unfiltered' ) ];
- $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
-
- /**
- * ADJUST TAX - Calculations when base tax is not equal to the item tax.
- *
- * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
- * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
- * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
- */
- if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
-
- // Work out a new base price without the shop's base tax
- $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true );
-
- // Now we have a new item price (excluding TAX)
- $line_subtotal = round( $line_price - array_sum( $taxes ), wc_get_rounding_precision() );
- $taxes = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates );
- $line_subtotal_tax = array_sum( $taxes );
-
- // Adjusted price (this is the price including the new tax rate)
- $adjusted_price = ( $line_subtotal + $line_subtotal_tax ) / $values['quantity'];
-
- // Apply discounts and get the discounted price FOR A SINGLE ITEM
- $discounted_price = $this->get_discounted_price( $values, $adjusted_price, true );
-
- // Convert back to line price
- $discounted_line_price = $discounted_price * $values['quantity'];
-
- // Now use rounded line price to get taxes.
- $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true );
- $line_tax = array_sum( $discounted_taxes );
- $line_total = $discounted_line_price - $line_tax;
-
- /**
- * Regular tax calculation (customer inside base and the tax class is unmodified.
- */
- } else {
-
- // Work out a new base price without the item tax
- $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true );
-
- // Now we have a new item price (excluding TAX)
- $line_subtotal = $line_price - array_sum( $taxes );
- $line_subtotal_tax = array_sum( $taxes );
-
- // Calc prices and tax (discounted)
- $discounted_price = $this->get_discounted_price( $values, $base_price, true );
-
- // Convert back to line price
- $discounted_line_price = $discounted_price * $values['quantity'];
-
- // Now use rounded line price to get taxes.
- $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true );
- $line_tax = array_sum( $discounted_taxes );
- $line_total = $discounted_line_price - $line_tax;
- }
-
- // Tax rows - merge the totals we just got
- foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
- $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
- }
-
- /**
- * Prices exclude tax.
- */
- } else {
-
- $item_tax_rates = $tax_rates[ $product->get_tax_class() ];
-
- // Work out a new base price without the shop's base tax
- $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates );
-
- // Now we have the item price (excluding TAX)
- $line_subtotal = $line_price;
- $line_subtotal_tax = array_sum( $taxes );
-
- // Now calc product rates
- $discounted_price = $this->get_discounted_price( $values, $base_price, true );
- $discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates );
- $discounted_tax_amount = array_sum( $discounted_taxes );
- $line_tax = $discounted_tax_amount;
- $line_total = $discounted_price * $values['quantity'];
-
- // Tax rows - merge the totals we just got
- foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
- $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
- }
- }
-
- // Cart contents total is based on discounted prices and is used for the final total calculation
- $this->cart_contents_total += $line_total;
-
- /**
- * Store costs + taxes for lines. For tax inclusive prices, we do some extra rounding logic so the stored
- * values "add up" when viewing the order in admin. This does have the disadvatage of not being able to
- * recalculate the tax total/subtotal accurately in the future, but it does ensure the data looks correct.
- *
- * Tax exclusive prices are not affected.
- */
- if ( ! $product->is_taxable() || $this->prices_include_tax ) {
- $this->cart_contents[ $cart_item_key ]['line_total'] = round( $line_total + $line_tax - wc_round_tax_total( $line_tax ), $this->dp );
- $this->cart_contents[ $cart_item_key ]['line_subtotal'] = round( $line_subtotal + $line_subtotal_tax - wc_round_tax_total( $line_subtotal_tax ), $this->dp );
- $this->cart_contents[ $cart_item_key ]['line_tax'] = wc_round_tax_total( $line_tax );
- $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = wc_round_tax_total( $line_subtotal_tax );
- $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => array_map( 'wc_round_tax_total', $discounted_taxes ), 'subtotal' => array_map( 'wc_round_tax_total', $taxes ) );
- } else {
- $this->cart_contents[ $cart_item_key ]['line_total'] = $line_total;
- $this->cart_contents[ $cart_item_key ]['line_subtotal'] = $line_subtotal;
- $this->cart_contents[ $cart_item_key ]['line_tax'] = $line_tax;
- $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax;
- $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => $discounted_taxes, 'subtotal' => $taxes );
- }
- }
-
- // Only calculate the grand total + shipping if on the cart/checkout
- if ( is_checkout() || is_cart() || defined( 'WOOCOMMERCE_CHECKOUT' ) || defined( 'WOOCOMMERCE_CART' ) ) {
-
- // Calculate the Shipping.
- $local_pickup_methods = apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) );
- $had_local_pickup = 0 < count( array_intersect( wc_get_chosen_shipping_method_ids(), $local_pickup_methods ) );
- $this->calculate_shipping();
- $has_local_pickup = 0 < count( array_intersect( wc_get_chosen_shipping_method_ids(), $local_pickup_methods ) );
-
- // If methods changed and local pickup is selected, we need to do a recalculation of taxes.
- if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && $had_local_pickup !== $has_local_pickup ) {
- return $this->calculate_totals();
- }
-
- // Trigger the fees API where developers can add fees to the cart
- $this->calculate_fees();
-
- // Total up/round taxes and shipping taxes
- if ( $this->round_at_subtotal ) {
- $this->tax_total = WC_Tax::get_tax_total( $this->taxes );
- $this->shipping_tax_total = WC_Tax::get_tax_total( $this->shipping_taxes );
- $this->taxes = array_map( array( 'WC_Tax', 'round' ), $this->taxes );
- $this->shipping_taxes = array_map( array( 'WC_Tax', 'round' ), $this->shipping_taxes );
- } else {
- $this->tax_total = array_sum( $this->taxes );
- $this->shipping_tax_total = array_sum( $this->shipping_taxes );
- }
-
- // VAT exemption done at this point - so all totals are correct before exemption
- if ( WC()->customer->get_is_vat_exempt() ) {
- $this->remove_taxes();
- }
-
- // Allow plugins to hook and alter totals before final total is calculated
- do_action( 'woocommerce_calculate_totals', $this );
-
- // Grand Total - Discounted product prices, discounted tax, shipping cost + tax
- $this->total = max( 0, apply_filters( 'woocommerce_calculated_total', round( $this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp ), $this ) );
-
- } else {
-
- // Set tax total to sum of all tax rows
- $this->tax_total = WC_Tax::get_tax_total( $this->taxes );
-
- // VAT exemption done at this point - so all totals are correct before exemption
- if ( WC()->customer->get_is_vat_exempt() ) {
- $this->remove_taxes();
- }
- }
+ new WC_Cart_Totals( $this );
do_action( 'woocommerce_after_calculate_totals', $this );
@@ -1451,22 +1155,39 @@ class WC_Cart {
* Uses the shipping class to calculate shipping then gets the totals when its finished.
*/
public function calculate_shipping() {
- if ( $this->needs_shipping() && $this->show_shipping() ) {
- WC()->shipping->calculate_shipping( $this->get_shipping_packages() );
- } else {
- WC()->shipping->reset_shipping();
- }
+ $this->shipping_methods = $this->needs_shipping() ? $this->get_chosen_shipping_methods( WC()->shipping->calculate_shipping( $this->get_shipping_packages() ) ) : array();
- // Get totals for the chosen shipping method
- $this->shipping_total = WC()->shipping->shipping_total; // Shipping Total
- $this->shipping_taxes = WC()->shipping->shipping_taxes; // Shipping Taxes
+ // Set legacy totals for backwards compatibility with versions prior to 3.2.
+ $this->shipping_total = WC()->shipping->shipping_total = array_sum( wp_list_pluck( $this->shipping_methods, 'cost' ) );
+ $this->shipping_taxes = WC()->shipping->shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' );
+
+ return $this->shipping_methods;
+ }
+
+ /**
+ * Given a set of packages with rates, get the chosen ones only.
+ *
+ * @since 3.2.0
+ * @param array $calculated_shipping_packages Array of packages.
+ * @return array
+ */
+ protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) {
+ $chosen_methods = array();
+ // Get chosen methods for each package to get our totals.
+ foreach ( $calculated_shipping_packages as $key => $package ) {
+ $chosen_method = wc_get_chosen_shipping_method_for_package( $key, $package );
+ if ( $chosen_method ) {
+ $chosen_methods[ $key ] = $package['rates'][ $chosen_method ];
+ }
+ }
+ return $chosen_methods;
}
/**
* Filter items needing shipping callback.
*
* @since 3.0.0
- * @param array $item
+ * @param array $item Item to check for shipping.
* @return bool
*/
protected function filter_items_needing_shipping( $item ) {
@@ -1508,12 +1229,12 @@ class WC_Cart {
'ID' => get_current_user_id(),
),
'destination' => array(
- 'country' => WC()->customer->get_shipping_country(),
- 'state' => WC()->customer->get_shipping_state(),
- 'postcode' => WC()->customer->get_shipping_postcode(),
- 'city' => WC()->customer->get_shipping_city(),
- 'address' => WC()->customer->get_shipping_address(),
- 'address_2' => WC()->customer->get_shipping_address_2(),
+ 'country' => $this->get_customer()->get_shipping_country(),
+ 'state' => $this->get_customer()->get_shipping_state(),
+ 'postcode' => $this->get_customer()->get_shipping_postcode(),
+ 'city' => $this->get_customer()->get_shipping_city(),
+ 'address' => $this->get_customer()->get_shipping_address(),
+ 'address_2' => $this->get_customer()->get_shipping_address_2(),
),
'cart_subtotal' => $this->get_displayed_subtotal(),
),
@@ -1550,14 +1271,7 @@ class WC_Cart {
* @return bool
*/
public function needs_shipping_address() {
-
- $needs_shipping_address = false;
-
- if ( $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ) {
- $needs_shipping_address = true;
- }
-
- return apply_filters( 'woocommerce_cart_needs_shipping_address', $needs_shipping_address );
+ return apply_filters( 'woocommerce_cart_needs_shipping_address', $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() );
}
/**
@@ -1571,8 +1285,8 @@ class WC_Cart {
}
if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
- if ( ! WC()->customer->has_calculated_shipping() ) {
- if ( ! WC()->customer->get_shipping_country() || ( ! WC()->customer->get_shipping_state() && ! WC()->customer->get_shipping_postcode() ) ) {
+ if ( ! $this->get_customer()->has_calculated_shipping() ) {
+ if ( ! $this->get_customer()->get_shipping_country() || ( ! $this->get_customer()->get_shipping_state() && ! $this->get_customer()->get_shipping_postcode() ) ) {
return false;
}
}
@@ -1581,17 +1295,6 @@ class WC_Cart {
return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true );
}
- /**
- * Sees if we need a shipping address.
- *
- * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only()
- *
- * @return bool
- */
- public function ship_to_billing_address_only() {
- return wc_ship_to_billing_address_only();
- }
-
/**
* Gets the shipping total (after calculation).
*
@@ -1601,9 +1304,7 @@ class WC_Cart {
if ( isset( $this->shipping_total ) ) {
if ( $this->shipping_total > 0 ) {
- // Display varies depending on settings
if ( 'excl' === $this->tax_display_cart ) {
-
$return = wc_price( $this->shipping_total );
if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) {
@@ -1611,9 +1312,7 @@ class WC_Cart {
}
return $return;
-
} else {
-
$return = wc_price( $this->shipping_total + $this->shipping_tax_total );
if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) {
@@ -1621,20 +1320,14 @@ class WC_Cart {
}
return $return;
-
}
} else {
return __( 'Free!', 'woocommerce' );
}
}
-
return '';
}
- /*
- * Coupons/Discount related functions.
- */
-
/**
* Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
*
@@ -1642,7 +1335,7 @@ class WC_Cart {
* 1. Where a list of customer emails are set (limits coupon usage to those defined).
* 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email).
*
- * @param array $posted
+ * @param array $posted Post data.
*/
public function check_customer_coupons( $posted ) {
if ( ! empty( $this->applied_coupons ) ) {
@@ -1651,8 +1344,8 @@ class WC_Cart {
if ( $coupon->is_valid() ) {
- // Limit to defined email addresses
- if ( is_array( $coupon->get_email_restrictions() ) && sizeof( $coupon->get_email_restrictions() ) > 0 ) {
+ // Limit to defined email addresses.
+ if ( is_array( $coupon->get_email_restrictions() ) && count( $coupon->get_email_restrictions() ) > 0 ) {
$check_emails = array();
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
@@ -1661,18 +1354,16 @@ class WC_Cart {
$check_emails[] = $posted['billing_email'];
$check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
- if ( 0 == sizeof( array_intersect( $check_emails, $coupon->get_email_restrictions() ) ) ) {
+ if ( 0 === count( array_intersect( $check_emails, $coupon->get_email_restrictions() ) ) ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
-
- // Remove the coupon
$this->remove_coupon( $code );
- // Flag totals for refresh
+ // Flag totals for refresh.
WC()->session->set( 'refresh_totals', true );
}
}
- // Usage limits per user - check against billing and user email and user ID
+ // Usage limits per user - check against billing and user email and user ID.
if ( $coupon->get_usage_limit_per_user() > 0 ) {
$check_emails = array();
$used_by = $coupon->get_used_by();
@@ -1680,28 +1371,26 @@ class WC_Cart {
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
$check_emails[] = sanitize_email( $current_user->user_email );
- $usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) );
+ $usage_count = count( array_keys( $used_by, get_current_user_id(), true ) );
} else {
$check_emails[] = sanitize_email( $posted['billing_email'] );
$user = get_user_by( 'email', $posted['billing_email'] );
if ( $user ) {
- $usage_count = sizeof( array_keys( $used_by, $user->ID ) );
+ $usage_count = count( array_keys( $used_by, $user->ID, true ) );
} else {
$usage_count = 0;
}
}
foreach ( $check_emails as $check_email ) {
- $usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) );
+ $usage_count = $usage_count + count( array_keys( $used_by, $check_email, true ) );
}
if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
-
- // Remove the coupon
$this->remove_coupon( $code );
- // Flag totals for refresh
+ // Flag totals for refresh.
WC()->session->set( 'refresh_totals', true );
}
}
@@ -1712,18 +1401,19 @@ class WC_Cart {
/**
* Returns whether or not a discount has been applied.
- * @param string $coupon_code
+ *
+ * @param string $coupon_code Coupon code to check.
* @return bool
*/
public function has_discount( $coupon_code = '' ) {
- return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons ) : sizeof( $this->applied_coupons ) > 0;
+ return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons, true ) : count( $this->applied_coupons ) > 0;
}
/**
* Applies a coupon code passed to the method.
*
- * @param string $coupon_code - The code to apply
- * @return bool True if the coupon is applied, false if it does not exist or cannot be applied
+ * @param string $coupon_code - The code to apply.
+ * @return bool True if the coupon is applied, false if it does not exist or cannot be applied.
*/
public function add_discount( $coupon_code ) {
// Coupons are globally disabled.
@@ -1761,7 +1451,7 @@ class WC_Cart {
$coupons_to_keep = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons );
foreach ( $this->applied_coupons as $applied_coupon ) {
- $keep_key = array_search( $applied_coupon, $coupons_to_keep );
+ $keep_key = array_search( $applied_coupon, $coupons_to_keep, true );
if ( false === $keep_key ) {
$this->remove_coupon( $applied_coupon );
} else {
@@ -1813,8 +1503,7 @@ class WC_Cart {
/**
* Get array of applied coupon objects and codes.
*
- * @param null $deprecated
- *
+ * @param null $deprecated No longer used.
* @return array of applied coupons
*/
public function get_coupons( $deprecated = null ) {
@@ -1843,8 +1532,9 @@ class WC_Cart {
/**
* Get the discount amount for a used coupon.
- * @param string $code coupon code
- * @param bool $ex_tax inc or ex tax
+ *
+ * @param string $code coupon code.
+ * @param bool $ex_tax inc or ex tax.
* @return float discount amount
*/
public function get_coupon_discount_amount( $code, $ex_tax = true ) {
@@ -1859,8 +1549,8 @@ class WC_Cart {
/**
* Get the discount tax amount for a used coupon (for tax inclusive prices).
- * @param string $code coupon code
- * @param bool inc or ex tax
+ *
+ * @param string $code coupon code.
* @return float discount amount
*/
public function get_coupon_discount_tax_amount( $code ) {
@@ -1870,7 +1560,7 @@ class WC_Cart {
/**
* Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
*
- * @param null $deprecated
+ * @param null $deprecated No longer used.
*/
public function remove_coupons( $deprecated = null ) {
$this->applied_coupons = $this->coupon_discount_amounts = $this->coupon_discount_tax_amounts = $this->coupon_applied_count = array();
@@ -1881,18 +1571,18 @@ class WC_Cart {
/**
* Remove a single coupon by code.
- * @param string $coupon_code Code of the coupon to remove
+ *
+ * @param string $coupon_code Code of the coupon to remove.
* @return bool
*/
public function remove_coupon( $coupon_code ) {
- // Coupons are globally disabled
+ // Coupons are globally disabled.
if ( ! wc_coupons_enabled() ) {
return false;
}
- // Get the coupon
$coupon_code = wc_format_coupon_code( $coupon_code );
- $position = array_search( $coupon_code, $this->applied_coupons );
+ $position = array_search( $coupon_code, $this->applied_coupons, true );
if ( false !== $position ) {
unset( $this->applied_coupons[ $position ] );
@@ -1905,90 +1595,6 @@ class WC_Cart {
return true;
}
- /**
- * Function to apply discounts to a product and get the discounted price (before tax is applied).
- *
- * @param mixed $values
- * @param mixed $price
- * @param bool $add_totals (default: false)
- * @return float price
- */
- public function get_discounted_price( $values, $price, $add_totals = false ) {
- if ( ! $price ) {
- return $price;
- }
-
- $undiscounted_price = $price;
-
- if ( ! empty( $this->coupons ) ) {
- $product = $values['data'];
-
- foreach ( $this->coupons as $code => $coupon ) {
- if ( $coupon->is_valid() && ( $coupon->is_valid_for_product( $product, $values ) || $coupon->is_valid_for_cart() ) ) {
- $discount_amount = $coupon->get_discount_amount( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ? $price : $undiscounted_price, $values, true );
- $discount_amount = min( $price, $discount_amount );
- $price = max( $price - $discount_amount, 0 );
-
- // Store the totals for DISPLAY in the cart.
- if ( $add_totals ) {
- $total_discount = $discount_amount * $values['quantity'];
- $total_discount_tax = 0;
-
- if ( wc_tax_enabled() && $product->is_taxable() ) {
- $tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
- $taxes = WC_Tax::calc_tax( $discount_amount, $tax_rates, $this->prices_include_tax );
- $total_discount_tax = WC_Tax::get_tax_total( $taxes ) * $values['quantity'];
- $total_discount = $this->prices_include_tax ? $total_discount - $total_discount_tax : $total_discount;
- $this->discount_cart_tax += $total_discount_tax;
- }
-
- $this->discount_cart += $total_discount;
- $this->increase_coupon_discount_amount( $code, $total_discount, $total_discount_tax );
- $this->increase_coupon_applied_count( $code, $values['quantity'] );
- }
- }
-
- // If the price is 0, we can stop going through coupons because there is nothing more to discount for this product.
- if ( 0 >= $price ) {
- break;
- }
- }
- }
-
- return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this );
- }
-
- /**
- * Store how much discount each coupon grants.
- *
- * @access private
- * @param string $code
- * @param double $amount
- * @param double $tax
- */
- private function increase_coupon_discount_amount( $code, $amount, $tax ) {
- $this->coupon_discount_amounts[ $code ] = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] + $amount : $amount;
- $this->coupon_discount_tax_amounts[ $code ] = isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] + $tax : $tax;
- }
-
- /**
- * Store how many times each coupon is applied to cart/items.
- *
- * @access private
- * @param string $code
- * @param int $count
- */
- private function increase_coupon_applied_count( $code, $count = 1 ) {
- if ( empty( $this->coupon_applied_count[ $code ] ) ) {
- $this->coupon_applied_count[ $code ] = 0;
- }
- $this->coupon_applied_count[ $code ] += $count;
- }
-
- /*
- * Fees API to add additional costs to orders.
- */
-
/**
* Add additional fee to the cart.
*
@@ -2005,12 +1611,11 @@ class WC_Cart {
* @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: '').
*/
public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
-
$new_fee_id = sanitize_title( $name );
// Only add each fee once.
foreach ( $this->fees as $fee ) {
- if ( $fee->id == $new_fee_id ) {
+ if ( $fee->id === $new_fee_id ) {
return;
}
}
@@ -2039,31 +1644,27 @@ class WC_Cart {
* Calculate fees.
*/
public function calculate_fees() {
- // Reset fees before calculation
+ // Reset fees before calculation.
$this->fee_total = 0;
$this->fees = array();
- // Fire an action where developers can add their fees
+ // Fire an action where developers can add their fees.
do_action( 'woocommerce_cart_calculate_fees', $this );
- // If fees were added, total them and calculate tax
+ // If fees were added, total them and calculate tax.
if ( ! empty( $this->fees ) ) {
foreach ( $this->fees as $fee_key => $fee ) {
$this->fee_total += $fee->amount;
if ( $fee->taxable ) {
- // Get tax rates
$tax_rates = WC_Tax::get_rates( $fee->tax_class );
$fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false );
if ( ! empty( $fee_taxes ) ) {
- // Set the tax total for this fee
$this->fees[ $fee_key ]->tax = array_sum( $fee_taxes );
-
- // Set tax data - Since 2.2
$this->fees[ $fee_key ]->tax_data = $fee_taxes;
- // Tax rows - merge the totals we just got
+ // Tax rows - merge the totals we just got.
foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) {
$this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
}
@@ -2073,10 +1674,6 @@ class WC_Cart {
}
}
- /*
- * Get Formatted Totals.
- */
-
/**
* Gets the order total (after calculation).
*
@@ -2102,7 +1699,6 @@ class WC_Cart {
/**
* Gets the cart contents total (after calculation).
*
- * @todo deprecate? It's unused.
* @return string formatted price
*/
public function get_cart_total() {
@@ -2118,35 +1714,28 @@ class WC_Cart {
/**
* Gets the sub total (after calculation).
*
- * @param bool $compound whether to include compound taxes
+ * @param bool $compound whether to include compound taxes.
* @return string formatted price
*/
public function get_cart_subtotal( $compound = false ) {
- // If the cart has compound tax, we want to show the subtotal as
- // cart + shipping + non-compound taxes (after discount)
+ /**
+ * If the cart has compound tax, we want to show the subtotal as cart + shipping + non-compound taxes (after discount).
+ */
if ( $compound ) {
-
$cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) );
- // Otherwise we show cart items totals only (before discount)
+ } elseif ( 'excl' === $this->tax_display_cart ) {
+ $cart_subtotal = wc_price( $this->subtotal_ex_tax );
+
+ if ( $this->tax_total > 0 && $this->prices_include_tax ) {
+ $cart_subtotal .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
+ }
} else {
+ $cart_subtotal = wc_price( $this->subtotal );
- // Display varies depending on settings
- if ( 'excl' === $this->tax_display_cart ) {
-
- $cart_subtotal = wc_price( $this->subtotal_ex_tax );
-
- if ( $this->tax_total > 0 && $this->prices_include_tax ) {
- $cart_subtotal .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
- }
- } else {
-
- $cart_subtotal = wc_price( $this->subtotal );
-
- if ( $this->tax_total > 0 && ! $this->prices_include_tax ) {
- $cart_subtotal .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
- }
+ if ( $this->tax_total > 0 && ! $this->prices_include_tax ) {
+ $cart_subtotal .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
}
}
@@ -2156,7 +1745,7 @@ class WC_Cart {
/**
* Get the product row price per item.
*
- * @param WC_Product $product
+ * @param WC_Product $product Product object.
* @return string formatted price
*/
public function get_product_price( $product ) {
@@ -2175,16 +1764,14 @@ class WC_Cart {
*
* When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
*
- * @param WC_Product $product
- * @param int $quantity
+ * @param WC_Product $product Product object.
+ * @param int $quantity Quantity being purchased.
* @return string formatted price
*/
public function get_product_subtotal( $product, $quantity ) {
- $price = $product->get_price();
- $taxable = $product->is_taxable();
+ $price = $product->get_price();
- // Taxable
- if ( $taxable ) {
+ if ( $product->is_taxable() ) {
if ( 'excl' === $this->tax_display_cart ) {
@@ -2203,13 +1790,9 @@ class WC_Cart {
$product_subtotal .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
}
}
-
- // Non-taxable
} else {
-
$row_price = $price * $quantity;
$product_subtotal = wc_price( $row_price );
-
}
return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this );
@@ -2228,7 +1811,8 @@ class WC_Cart {
/**
* Get a tax amount.
- * @param string $tax_rate_id
+ *
+ * @param string $tax_rate_id ID of the tax rate to get taxes for.
* @return float amount
*/
public function get_tax_amount( $tax_rate_id ) {
@@ -2237,7 +1821,8 @@ class WC_Cart {
/**
* Get a tax amount.
- * @param string $tax_rate_id
+ *
+ * @param string $tax_rate_id ID of the tax rate to get taxes for.
* @return float amount
*/
public function get_shipping_tax_amount( $tax_rate_id ) {
@@ -2247,8 +1832,8 @@ class WC_Cart {
/**
* Get tax row amounts with or without compound taxes includes.
*
- * @param bool $compound True if getting compound taxes
- * @param bool $display True if getting total to display
+ * @param bool $compound True if getting compound taxes.
+ * @param bool $display True if getting total to display.
* @return float price
*/
public function get_taxes_total( $compound = true, $display = true ) {
@@ -2295,68 +1880,6 @@ class WC_Cart {
* @return mixed formatted price or false if there are none
*/
public function get_total_discount() {
- if ( $this->get_cart_discount_total() ) {
- $total_discount = wc_price( $this->get_cart_discount_total() );
- } else {
- $total_discount = false;
- }
- return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this );
- }
-
- /**
- * Gets the total (product) discount amount - these are applied before tax.
- *
- * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required.
- * @return mixed formatted price or false if there are none
- */
- public function get_discounts_before_tax() {
- wc_deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' );
- if ( $this->get_cart_discount_total() ) {
- $discounts_before_tax = wc_price( $this->get_cart_discount_total() );
- } else {
- $discounts_before_tax = false;
- }
- return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this );
- }
-
- /**
- * Get the total of all order discounts (after tax discounts).
- *
- * @deprecated Order discounts (after tax) removed in 2.3
- * @return int
- */
- public function get_order_discount_total() {
- wc_deprecated_function( 'get_order_discount_total', '2.3' );
- return 0;
- }
-
- /**
- * Function to apply cart discounts after tax.
- * @deprecated Coupons can not be applied after tax
- *
- * @param $values
- * @param $price
- */
- public function apply_cart_discounts_after_tax( $values, $price ) {
- wc_deprecated_function( 'apply_cart_discounts_after_tax', '2.3' );
- }
-
- /**
- * Function to apply product discounts after tax.
- * @deprecated Coupons can not be applied after tax
- *
- * @param $values
- * @param $price
- */
- public function apply_product_discounts_after_tax( $values, $price ) {
- wc_deprecated_function( 'apply_product_discounts_after_tax', '2.3' );
- }
-
- /**
- * Gets the order discount amount - these are applied after tax.
- * @deprecated Coupons can not be applied after tax
- */
- public function get_discounts_after_tax() {
- wc_deprecated_function( 'get_discounts_after_tax', '2.3' );
+ return apply_filters( 'woocommerce_cart_total_discount', $this->get_cart_discount_total() ? wc_price( $this->get_cart_discount_total() ) : false, $this );
}
}
diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php
index d176435df38..0984e30c510 100644
--- a/includes/class-wc-checkout.php
+++ b/includes/class-wc-checkout.php
@@ -562,7 +562,7 @@ class WC_Checkout {
switch ( $type ) {
case 'checkbox' :
- $value = (int) isset( $_POST[ $key ] );
+ $value = isset( $_POST[ $key ] ) ? 1 : '';
break;
case 'multiselect' :
$value = isset( $_POST[ $key ] ) ? implode( ', ', wc_clean( $_POST[ $key ] ) ) : '';
diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php
index 227634f5ed7..0fda8c44e2e 100644
--- a/includes/class-wc-coupon.php
+++ b/includes/class-wc-coupon.php
@@ -316,7 +316,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
}
/**
- * Get minium spend amount.
+ * Get minimum spend amount.
* @since 3.0.0
* @param string $context
* @return float
@@ -390,29 +390,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
$discount = $single ? $discount : $discount * $cart_item_qty;
}
- $discount = (float) min( $discount, $discounting_amount );
-
- // Handle the limit_usage_to_x_items option
- if ( ! $this->is_type( array( 'fixed_cart' ) ) ) {
- if ( $discounting_amount ) {
- if ( null === $this->get_limit_usage_to_x_items() ) {
- $limit_usage_qty = $cart_item_qty;
- } else {
- $limit_usage_qty = min( $this->get_limit_usage_to_x_items(), $cart_item_qty );
-
- $this->set_limit_usage_to_x_items( max( 0, ( $this->get_limit_usage_to_x_items() - $limit_usage_qty ) ) );
- }
- if ( $single ) {
- $discount = ( $discount * $limit_usage_qty ) / $cart_item_qty;
- } else {
- $discount = ( $discount / $cart_item_qty ) * $limit_usage_qty;
- }
- }
- }
-
- $discount = round( $discount, wc_get_rounding_precision() );
-
- return apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $discounting_amount, $cart_item, $single, $this );
+ return apply_filters( 'woocommerce_coupon_get_discount_amount', round( min( $discount, $discounting_amount ), wc_get_rounding_precision() ), $discounting_amount, $cart_item, $single, $this );
}
/*
@@ -665,7 +643,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
*/
/**
- * Developers can programically return coupons. This function will read those values into our WC_Coupon class.
+ * Developers can programmatically return coupons. This function will read those values into our WC_Coupon class.
* @since 3.0.0
* @param string $code Coupon code
* @param array $coupon Array of coupon properties
@@ -761,260 +739,22 @@ class WC_Coupon extends WC_Legacy_Coupon {
}
/**
- * Ensure coupon exists or throw exception.
+ * Check if a coupon is valid for the cart.
*
+ * @deprecated 3.2.0 In favor of WC_Discounts->is_coupon_valid.
* @throws Exception
- */
- private function validate_exists() {
- if ( ! $this->get_id() ) {
- throw new Exception( self::E_WC_COUPON_NOT_EXIST );
- }
- }
-
- /**
- * Ensure coupon usage limit is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_usage_limit() {
- if ( $this->get_usage_limit() > 0 && $this->get_usage_count() >= $this->get_usage_limit() ) {
- throw new Exception( self::E_WC_COUPON_USAGE_LIMIT_REACHED );
- }
- }
-
- /**
- * Ensure coupon user usage limit is valid or throw exception.
- *
- * Per user usage limit - check here if user is logged in (against user IDs).
- * Checked again for emails later on in WC_Cart::check_customer_coupons().
- *
- * @param int $user_id
- * @throws Exception
- */
- private function validate_user_usage_limit( $user_id = 0 ) {
- if ( empty( $user_id ) ) {
- $user_id = get_current_user_id();
- }
- if ( $this->get_usage_limit_per_user() > 0 && is_user_logged_in() && $this->get_id() && $this->data_store ) {
- $usage_count = $this->data_store->get_usage_by_user_id( $this, $user_id );
- if ( $usage_count >= $this->get_usage_limit_per_user() ) {
- throw new Exception( self::E_WC_COUPON_USAGE_LIMIT_REACHED );
- }
- }
- }
-
- /**
- * Ensure coupon date is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_expiry_date() {
- if ( $this->get_date_expires() && current_time( 'timestamp', true ) > $this->get_date_expires()->getTimestamp() ) {
- throw new Exception( $error_code = self::E_WC_COUPON_EXPIRED );
- }
- }
-
- /**
- * Ensure coupon amount is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_minimum_amount() {
- if ( $this->get_minimum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_minimum_amount', $this->get_minimum_amount() > WC()->cart->get_displayed_subtotal(), $this ) ) {
- throw new Exception( self::E_WC_COUPON_MIN_SPEND_LIMIT_NOT_MET );
- }
- }
-
- /**
- * Ensure coupon amount is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_maximum_amount() {
- if ( $this->get_maximum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_maximum_amount', $this->get_maximum_amount() < WC()->cart->get_displayed_subtotal(), $this ) ) {
- throw new Exception( self::E_WC_COUPON_MAX_SPEND_LIMIT_MET );
- }
- }
-
- /**
- * Ensure coupon is valid for products in the cart is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_product_ids() {
- if ( sizeof( $this->get_product_ids() ) > 0 ) {
- $valid_for_cart = false;
- if ( ! WC()->cart->is_empty() ) {
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- if ( in_array( $cart_item['product_id'], $this->get_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_product_ids() ) ) {
- $valid_for_cart = true;
- }
- }
- }
- if ( ! $valid_for_cart ) {
- throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
- }
- }
- }
-
- /**
- * Ensure coupon is valid for product categories in the cart is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_product_categories() {
- if ( sizeof( $this->get_product_categories() ) > 0 ) {
- $valid_for_cart = false;
- if ( ! WC()->cart->is_empty() ) {
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- if ( $this->get_exclude_sale_items() && $cart_item['data'] && $cart_item['data']->is_on_sale() ) {
- continue;
- }
- $product_cats = wc_get_product_cat_ids( $cart_item['product_id'] );
-
- // If we find an item with a cat in our allowed cat list, the coupon is valid
- if ( sizeof( array_intersect( $product_cats, $this->get_product_categories() ) ) > 0 ) {
- $valid_for_cart = true;
- }
- }
- }
- if ( ! $valid_for_cart ) {
- throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
- }
- }
- }
-
- /**
- * Ensure coupon is valid for sale items in the cart is valid or throw exception.
- *
- * @throws Exception
- */
- private function validate_sale_items() {
- if ( $this->get_exclude_sale_items() ) {
- $valid_for_cart = false;
-
- if ( ! WC()->cart->is_empty() ) {
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- $product = $cart_item['data'];
-
- if ( ! $product->is_on_sale() ) {
- $valid_for_cart = true;
- }
- }
- }
- if ( ! $valid_for_cart ) {
- throw new Exception( self::E_WC_COUPON_NOT_VALID_SALE_ITEMS );
- }
- }
- }
-
- /**
- * All exclusion rules must pass at the same time for a product coupon to be valid.
- */
- private function validate_excluded_items() {
- if ( ! WC()->cart->is_empty() && $this->is_type( wc_get_product_coupon_types() ) ) {
- $valid = false;
-
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- if ( $this->is_valid_for_product( $cart_item['data'], $cart_item ) ) {
- $valid = true;
- break;
- }
- }
-
- if ( ! $valid ) {
- throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE );
- }
- }
- }
-
- /**
- * Cart discounts cannot be added if non-eligble product is found in cart.
- */
- private function validate_cart_excluded_items() {
- if ( ! $this->is_type( wc_get_product_coupon_types() ) ) {
- $this->validate_cart_excluded_product_ids();
- $this->validate_cart_excluded_product_categories();
- }
- }
-
- /**
- * Exclude products from cart.
- *
- * @throws Exception
- */
- private function validate_cart_excluded_product_ids() {
- // Exclude Products
- if ( sizeof( $this->get_excluded_product_ids() ) > 0 ) {
- $valid_for_cart = true;
- if ( ! WC()->cart->is_empty() ) {
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- if ( in_array( $cart_item['product_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['variation_id'], $this->get_excluded_product_ids() ) || in_array( $cart_item['data']->get_parent_id(), $this->get_excluded_product_ids() ) ) {
- $valid_for_cart = false;
- }
- }
- }
- if ( ! $valid_for_cart ) {
- throw new Exception( self::E_WC_COUPON_EXCLUDED_PRODUCTS );
- }
- }
- }
-
- /**
- * Exclude categories from cart.
- *
- * @throws Exception
- */
- private function validate_cart_excluded_product_categories() {
- if ( sizeof( $this->get_excluded_product_categories() ) > 0 ) {
- $valid_for_cart = true;
- if ( ! WC()->cart->is_empty() ) {
- foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
- if ( $this->get_exclude_sale_items() && $cart_item['data'] && $cart_item['data']->is_on_sale() ) {
- continue;
- }
- $product_cats = wc_get_product_cat_ids( $cart_item['product_id'] );
-
- if ( sizeof( array_intersect( $product_cats, $this->get_excluded_product_categories() ) ) > 0 ) {
- $valid_for_cart = false;
- }
- }
- }
- if ( ! $valid_for_cart ) {
- throw new Exception( self::E_WC_COUPON_EXCLUDED_CATEGORIES );
- }
- }
- }
-
- /**
- * Check if a coupon is valid.
- *
- * @return boolean validity
- * @throws Exception
+ * @return bool Validity.
*/
public function is_valid() {
- try {
- $this->validate_exists();
- $this->validate_usage_limit();
- $this->validate_user_usage_limit();
- $this->validate_expiry_date();
- $this->validate_minimum_amount();
- $this->validate_maximum_amount();
- $this->validate_product_ids();
- $this->validate_product_categories();
- $this->validate_sale_items();
- $this->validate_excluded_items();
- $this->validate_cart_excluded_items();
+ $discounts = new WC_Discounts( WC()->cart );
+ $valid = $discounts->is_coupon_valid( $this );
- if ( ! apply_filters( 'woocommerce_coupon_is_valid', true, $this ) ) {
- throw new Exception( self::E_WC_COUPON_INVALID_FILTERED );
- }
- } catch ( Exception $e ) {
- $this->error_message = $this->get_coupon_error( $e->getMessage() );
+ if ( is_wp_error( $valid ) ) {
+ $this->error_message = $valid->get_error_message();
return false;
}
- return true;
+ return $valid;
}
/**
diff --git a/includes/class-wc-customer.php b/includes/class-wc-customer.php
index e12916886a6..ae3e368cb3a 100644
--- a/includes/class-wc-customer.php
+++ b/includes/class-wc-customer.php
@@ -439,6 +439,17 @@ class WC_Customer extends WC_Legacy_Customer {
return $value;
}
+ /**
+ * Get billing.
+ *
+ * @since 3.2.0
+ * @param string $context
+ * @return array
+ */
+ public function get_billing( $context = 'view' ) {
+ return $this->get_prop( 'billing', $context );
+ }
+
/**
* Get billing_first_name.
*
@@ -559,6 +570,17 @@ class WC_Customer extends WC_Legacy_Customer {
return $this->get_address_prop( 'phone', 'billing', $context );
}
+ /**
+ * Get shipping.
+ *
+ * @since 3.2.0
+ * @param string $context
+ * @return array
+ */
+ public function get_shipping( $context = 'view' ) {
+ return $this->get_prop( 'shipping', $context );
+ }
+
/**
* Get shipping_first_name.
*
diff --git a/includes/class-wc-datetime.php b/includes/class-wc-datetime.php
index fe7873d78f9..985efff2d78 100644
--- a/includes/class-wc-datetime.php
+++ b/includes/class-wc-datetime.php
@@ -5,7 +5,8 @@ if ( ! defined( 'ABSPATH' ) ) {
}
/**
- * WC Wrapper for PHP DateTime.
+ * WC Wrapper for PHP DateTime which adds support for gmt/utc offset when a
+ * timezone is absent.
*
* @class WC_DateTime
* @since 3.0.0
@@ -16,13 +17,15 @@ if ( ! defined( 'ABSPATH' ) ) {
class WC_DateTime extends DateTime {
/**
- * UTC Offset if needed.
+ * UTC Offset, if needed. Only used when a timezone is not set. When
+ * timezones are used this will equal 0.
+ *
* @var integer
*/
protected $utc_offset = 0;
/**
- * Output an ISO 8601 date string in local timezone.
+ * Output an ISO 8601 date string in local (WordPress) timezone.
*
* @since 3.0.0
* @return string
@@ -32,7 +35,7 @@ class WC_DateTime extends DateTime {
}
/**
- * Set UTC offset.
+ * Set UTC offset - this is a fixed offset instead of a timezone.
*
* @param int $offset
*/
@@ -41,7 +44,7 @@ class WC_DateTime extends DateTime {
}
/**
- * getOffset.
+ * Get UTC offset if set, or default to the DateTime object's offset.
*/
public function getOffset() {
if ( $this->utc_offset ) {
@@ -64,7 +67,7 @@ class WC_DateTime extends DateTime {
}
/**
- * Missing in PHP 5.2.
+ * Missing in PHP 5.2 so just here so it can be supported consistently.
*
* @since 3.0.0
* @return int
diff --git a/includes/class-wc-discount.php b/includes/class-wc-discount.php
new file mode 100644
index 00000000000..6e9d12591c7
--- /dev/null
+++ b/includes/class-wc-discount.php
@@ -0,0 +1,82 @@
+ 0, // Discount amount.
+ 'discount_type' => 'fixed', // Fixed, percent, or coupon.
+ 'discount_total' => 0,
+ );
+
+ /**
+ * Get discount amount.
+ *
+ * @return int
+ */
+ public function get_amount() {
+ return $this->data['amount'];
+ }
+
+ /**
+ * Discount amount - either fixed or percentage.
+ *
+ * @param string $raw_amount Amount discount gives.
+ */
+ public function set_amount( $raw_amount ) {
+ $this->data['amount'] = wc_format_decimal( $raw_amount );
+ }
+
+ /**
+ * Get discount type.
+ *
+ * @return string
+ */
+ public function get_discount_type() {
+ return $this->data['discount_type'];
+ }
+
+ /**
+ * Set discount type.
+ *
+ * @param string $discount_type Type of discount.
+ */
+ public function set_discount_type( $discount_type ) {
+ $this->data['discount_type'] = $discount_type;
+ }
+
+ /**
+ * Get discount total.
+ *
+ * @return int
+ */
+ public function get_discount_total() {
+ return $this->data['discount_total'];
+ }
+
+ /**
+ * Discount total.
+ *
+ * @param string $total Total discount applied.
+ */
+ public function set_discount_total( $total ) {
+ $this->data['discount_total'] = wc_format_decimal( $total );
+ }
+}
diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php
new file mode 100644
index 00000000000..5b77db9f751
--- /dev/null
+++ b/includes/class-wc-discounts.php
@@ -0,0 +1,902 @@
+ Item Key => Value
+ */
+ protected $discounts = array();
+
+ /**
+ * An array of applied WC_Discount objects.
+ *
+ * @var array
+ */
+ protected $manual_discounts = array();
+
+ /**
+ * Constructor. @todo accept order objects.
+ *
+ * @param array $object Cart or order object.
+ */
+ public function __construct( $object = array() ) {
+ if ( is_a( $object, 'WC_Cart' ) ) {
+ $this->set_items_from_cart( $object );
+ }
+ }
+
+ /**
+ * Normalise cart/order items which will be discounted.
+ *
+ * @since 3.2.0
+ * @param array $cart Cart object.
+ */
+ public function set_items_from_cart( $cart ) {
+ $this->items = $this->discounts = $this->manual_discounts = array();
+
+ if ( ! is_a( $cart, 'WC_Cart' ) ) {
+ return;
+ }
+
+ foreach ( $cart->get_cart() as $key => $cart_item ) {
+ $item = new stdClass();
+ $item->key = $key;
+ $item->object = $cart_item;
+ $item->product = $cart_item['data'];
+ $item->quantity = $cart_item['quantity'];
+ $item->price = wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
+ $this->items[ $key ] = $item;
+ }
+
+ uasort( $this->items, array( $this, 'sort_by_price' ) );
+ }
+
+ /**
+ * Get items.
+ *
+ * @since 3.2.0
+ * @return object[]
+ */
+ public function get_items() {
+ return $this->items;
+ }
+
+ /**
+ * Get discount by key with or without precision.
+ *
+ * @since 3.2.0
+ * @param string $key name of discount row to return.
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return array
+ */
+ public function get_discount( $key, $in_cents = false ) {
+ $item_discount_totals = $this->get_discounts_by_item( $in_cents );
+ return isset( $item_discount_totals[ $key ] ) ? ( $in_cents ? $item_discount_totals[ $key ] : wc_remove_number_precision( $item_discount_totals[ $key ] ) ) : 0;
+ }
+
+ /**
+ * Get all discount totals.
+ *
+ * @since 3.2.0
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return array
+ */
+ public function get_discounts( $in_cents = false ) {
+ $discounts = $this->discounts;
+
+ foreach ( $this->get_manual_discounts() as $manual_discount_key => $manual_discount ) {
+ $discounts[ $manual_discount_key ] = $manual_discount->get_discount_total();
+ }
+
+ return $in_cents ? $discounts : wc_remove_number_precision_deep( $discounts );
+ }
+
+ /**
+ * Get all discount totals per item.
+ *
+ * @since 3.2.0
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return array
+ */
+ public function get_discounts_by_item( $in_cents = false ) {
+ $discounts = $this->discounts;
+ $item_discount_totals = array_shift( $discounts );
+
+ foreach ( $discounts as $item_discounts ) {
+ foreach ( $item_discounts as $item_key => $item_discount ) {
+ $item_discount_totals[ $item_key ] += $item_discount;
+ }
+ }
+
+ return $in_cents ? $item_discount_totals : wc_remove_number_precision_deep( $item_discount_totals );
+ }
+
+ /**
+ * Get all discount totals per coupon.
+ *
+ * @since 3.2.0
+ * @param bool $in_cents Should the totals be returned in cents, or without precision.
+ * @return array
+ */
+ public function get_discounts_by_coupon( $in_cents = false ) {
+ $coupon_discount_totals = array_map( 'array_sum', $this->discounts );
+
+ return $in_cents ? $coupon_discount_totals : wc_remove_number_precision_deep( $coupon_discount_totals );
+ }
+
+ /**
+ * Get an array of manual discounts which have been applied.
+ *
+ * @since 3.2.0
+ * @return WC_Discount[]
+ */
+ public function get_manual_discounts() {
+ return $this->manual_discounts;
+ }
+
+ /**
+ * Get discounted price of an item without precision.
+ *
+ * @since 3.2.0
+ * @param object $item Get data for this item.
+ * @return float
+ */
+ public function get_discounted_price( $item ) {
+ return wc_remove_number_precision_deep( $this->get_discounted_price_in_cents( $item ) );
+ }
+
+ /**
+ * Get discounted price of an item to precision (in cents).
+ *
+ * @since 3.2.0
+ * @param object $item Get data for this item.
+ * @return int
+ */
+ public function get_discounted_price_in_cents( $item ) {
+ return absint( $item->price - $this->get_discount( $item->key, true ) );
+ }
+
+ /**
+ * Get total remaining after discounts.
+ *
+ * @since 3.2.0
+ * @return int
+ */
+ protected function get_total_after_discounts() {
+ $total_to_discount = 0;
+
+ foreach ( $this->items as $item ) {
+ $total_to_discount += $this->get_discounted_price_in_cents( $item );
+ }
+
+ foreach ( $this->manual_discounts as $key => $value ) {
+ $total_to_discount = $total_to_discount - $value->get_discount_total();
+ }
+
+ return $total_to_discount;
+ }
+
+ /**
+ * Generate a unique ID for a discount.
+ *
+ * @param WC_Discount $discount Discount object.
+ * @return string
+ */
+ protected function generate_discount_id( $discount ) {
+ $discount_id = '';
+ $index = 1;
+ while ( ! $discount_id ) {
+ $discount_id = 'discount-' . $discount->get_amount() . ( 'percent' === $discount->get_discount_type() ? '%' : '' );
+
+ if ( 1 < $index ) {
+ $discount_id .= '-' . $index;
+ }
+
+ if ( isset( $this->manual_discounts[ $discount_id ] ) ) {
+ $index ++;
+ $discount_id = '';
+ }
+ }
+ return $discount_id;
+ }
+
+ /**
+ * Apply a discount to all items.
+ *
+ * @param string|object $raw_discount Accepts a string (fixed or percent discounts), or WC_Coupon object.
+ * @return bool|WP_Error True if applied or WP_Error instance in failure.
+ */
+ public function apply_discount( $raw_discount ) {
+ if ( is_a( $raw_discount, 'WC_Coupon' ) ) {
+ return $this->apply_coupon( $raw_discount );
+ }
+
+ $discount = new WC_Discount;
+
+ if ( strstr( $raw_discount, '%' ) ) {
+ $discount->set_discount_type( 'percent' );
+ $discount->set_amount( trim( $raw_discount, '%' ) );
+ } elseif ( 0 < absint( $raw_discount ) ) {
+ $discount->set_discount_type( 'fixed' );
+ $discount->set_amount( wc_add_number_precision( absint( $raw_discount ) ) );
+ }
+
+ if ( ! $discount->get_amount() ) {
+ return new WP_Error( 'invalid_coupon', __( 'Invalid discount', 'woocommerce' ) );
+ }
+
+ $total_to_discount = $this->get_total_after_discounts();
+
+ if ( 'percent' === $discount->get_discount_type() ) {
+ $discount->set_discount_total( $discount->get_amount() * ( $total_to_discount / 100 ) );
+ } else {
+ $discount->set_discount_total( min( $discount->get_amount(), $total_to_discount ) );
+ }
+
+ $this->manual_discounts[ $this->generate_discount_id( $discount ) ] = $discount;
+
+ return true;
+ }
+
+ /**
+ * Apply a discount to all items using a coupon.
+ *
+ * @since 3.2.0
+ * @param WC_Coupon $coupon Coupon object being applied to the items.
+ * @return bool|WP_Error True if applied or WP_Error instance in failure.
+ */
+ protected function apply_coupon( $coupon ) {
+ $is_coupon_valid = $this->is_coupon_valid( $coupon );
+
+ if ( is_wp_error( $is_coupon_valid ) ) {
+ return $is_coupon_valid;
+ }
+
+ if ( ! isset( $this->discounts[ $coupon->get_code() ] ) ) {
+ $this->discounts[ $coupon->get_code() ] = array_fill_keys( array_keys( $this->items ), 0 );
+ }
+
+ $items_to_apply = $this->get_items_to_apply_coupon( $coupon );
+ $coupon_type = $coupon->get_discount_type();
+
+ // Core discounts are handled here as of 3.2.
+ switch ( $coupon->get_discount_type() ) {
+ case 'percent' :
+ $this->apply_coupon_percent( $coupon, $items_to_apply );
+ break;
+ case 'fixed_product' :
+ $this->apply_coupon_fixed_product( $coupon, $items_to_apply );
+ break;
+ case 'fixed_cart' :
+ $this->apply_coupon_fixed_cart( $coupon, $items_to_apply );
+ break;
+ default :
+ foreach ( $items_to_apply as $item ) {
+ $discounted_price = $this->get_discounted_price_in_cents( $item );
+ $price_to_discount = wc_remove_number_precision( ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price : $discounted_price );
+ $discount = min( $discounted_price, wc_add_number_precision( $coupon->get_discount_amount( $price_to_discount ), $item->object ) );
+
+ // Store code and discount amount per item.
+ $this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sort by price.
+ *
+ * @since 3.2.0
+ * @param array $a First element.
+ * @param array $b Second element.
+ * @return int
+ */
+ protected function sort_by_price( $a, $b ) {
+ $price_1 = $a->price * $a->quantity;
+ $price_2 = $b->price * $b->quantity;
+ if ( $price_1 === $price_2 ) {
+ return 0;
+ }
+ return ( $price_1 < $price_2 ) ? 1 : -1;
+ }
+
+ /**
+ * Filter out all products which have been fully discounted to 0.
+ * Used as array_filter callback.
+ *
+ * @since 3.2.0
+ * @param object $item Get data for this item.
+ * @return bool
+ */
+ protected function filter_products_with_price( $item ) {
+ return $this->get_discounted_price_in_cents( $item ) > 0;
+ }
+
+ /**
+ * Get items which the coupon should be applied to.
+ *
+ * @since 3.2.0
+ * @param object $coupon Coupon object.
+ * @return array
+ */
+ protected function get_items_to_apply_coupon( $coupon ) {
+ $items_to_apply = array();
+ $limit_usage_qty = 0;
+ $applied_count = 0;
+
+ if ( null !== $coupon->get_limit_usage_to_x_items() ) {
+ $limit_usage_qty = $coupon->get_limit_usage_to_x_items();
+ }
+
+ foreach ( $this->items as $item ) {
+ if ( 0 === $this->get_discounted_price_in_cents( $item ) ) {
+ continue;
+ }
+ if ( ! $coupon->is_valid_for_product( $item->product, $item->object ) && ! $coupon->is_valid_for_cart() ) {
+ continue;
+ }
+ if ( $limit_usage_qty && $applied_count > $limit_usage_qty ) {
+ break;
+ }
+ if ( $limit_usage_qty && $item->quantity > ( $limit_usage_qty - $applied_count ) ) {
+ $limit_to_qty = absint( $limit_usage_qty - $applied_count );
+ $item->price = ( $item->price / $item->quantity ) * $limit_to_qty;
+ $item->quantity = $limit_to_qty; // Lower the qty so the discount is applied less.
+ }
+ if ( 0 >= $item->quantity ) {
+ continue;
+ }
+ $items_to_apply[] = $item;
+ $applied_count += $item->quantity;
+ }
+ return $items_to_apply;
+ }
+
+ /**
+ * Apply percent discount to items and return an array of discounts granted.
+ *
+ * @since 3.2.0
+ * @param WC_Coupon $coupon Coupon object. Passed through filters.
+ * @param array $items_to_apply Array of items to apply the coupon to.
+ * @return int Total discounted.
+ */
+ protected function apply_coupon_percent( $coupon, $items_to_apply ) {
+ $total_discount = 0;
+ $cart_total = 0;
+
+ foreach ( $items_to_apply as $item ) {
+ // Find out how much price is available to discount for the item.
+ $discounted_price = $this->get_discounted_price_in_cents( $item );
+
+ // Get the price we actually want to discount, based on settings.
+ $price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
+
+ // Total up.
+ $cart_total += $price_to_discount;
+
+ // Run coupon calculations.
+ $discount = floor( $price_to_discount * ( $coupon->get_amount() / 100 ) );
+ $discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
+ $total_discount += $discount;
+
+ // Store code and discount amount per item.
+ $this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
+ }
+
+ // Work out how much discount would have been given to the cart has a whole and compare to what was discounted on all line items.
+ $cart_total_discount = wc_cart_round_discount( $cart_total * ( $coupon->get_amount() / 100 ), 0 );
+
+ if ( $total_discount < $cart_total_discount ) {
+ $total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $cart_total_discount - $total_discount );
+ }
+
+ return $total_discount;
+ }
+
+ /**
+ * Apply fixed product discount to items.
+ *
+ * @since 3.2.0
+ * @param WC_Coupon $coupon Coupon object. Passed through filters.
+ * @param array $items_to_apply Array of items to apply the coupon to.
+ * @param int $amount Fixed discount amount to apply in cents. Leave blank to pull from coupon.
+ * @return int Total discounted.
+ */
+ protected function apply_coupon_fixed_product( $coupon, $items_to_apply, $amount = null ) {
+ $total_discount = 0;
+ $amount = $amount ? $amount: wc_add_number_precision( $coupon->get_amount() );
+
+ foreach ( $items_to_apply as $item ) {
+ // Find out how much price is available to discount for the item.
+ $discounted_price = $this->get_discounted_price_in_cents( $item );
+
+ // Get the price we actually want to discount, based on settings.
+ $price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
+
+ // Run coupon calculations.
+ $discount = $amount * $item->quantity;
+ $discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) );
+ $total_discount += $discount;
+
+ // Store code and discount amount per item.
+ $this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
+ }
+ return $total_discount;
+ }
+
+ /**
+ * Apply fixed cart discount to items.
+ *
+ * @since 3.2.0
+ * @param WC_Coupon $coupon Coupon object. Passed through filters.
+ * @param array $items_to_apply Array of items to apply the coupon to.
+ * @param int $amount Fixed discount amount to apply in cents. Leave blank to pull from coupon.
+ * @return int Total discounted.
+ */
+ protected function apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount = null ) {
+ $total_discount = 0;
+ $amount = $amount ? $amount : wc_add_number_precision( $coupon->get_amount() );
+ $items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) );
+
+ if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) {
+ return $total_discount;
+ }
+
+ $per_item_discount = absint( $amount / $item_count ); // round it down to the nearest cent.
+
+ if ( $per_item_discount > 0 ) {
+ $total_discount = $this->apply_coupon_fixed_product( $coupon, $items_to_apply, $per_item_discount );
+
+ /**
+ * If there is still discount remaining, repeat the process.
+ */
+ if ( $total_discount > 0 && $total_discount < $amount ) {
+ $total_discount += $this->apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount - $total_discount );
+ }
+ } elseif ( $amount > 0 ) {
+ $total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $amount );
+ }
+ return $total_discount;
+ }
+
+ /**
+ * Deal with remaining fractional discounts by splitting it over items
+ * until the amount is expired, discounting 1 cent at a time.
+ *
+ * @since 3.2.0
+ * @param WC_Coupon $coupon Coupon object if appliable. Passed through filters.
+ * @param array $items_to_apply Array of items to apply the coupon to.
+ * @param int $amount Fixed discount amount to apply.
+ * @return int Total discounted.
+ */
+ protected function apply_coupon_remainder( $coupon, $items_to_apply, $amount ) {
+ $total_discount = 0;
+
+ foreach ( $items_to_apply as $item ) {
+ for ( $i = 0; $i < $item->quantity; $i ++ ) {
+ // Find out how much price is available to discount for the item.
+ $discounted_price = $this->get_discounted_price_in_cents( $item );
+
+ // Get the price we actually want to discount, based on settings.
+ $price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
+
+ // Run coupon calculations.
+ $discount = min( $discounted_price, 1 );
+
+ // Store totals.
+ $total_discount += $discount;
+
+ // Store code and discount amount per item.
+ $this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
+
+ if ( $total_discount >= $amount ) {
+ break 2;
+ }
+ }
+ if ( $total_discount >= $amount ) {
+ break;
+ }
+ }
+ return $total_discount;
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Validation & Error Handling
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Ensure coupon exists or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_exists( $coupon ) {
+ if ( ! $coupon->get_id() ) {
+ /* translators: %s: coupon code */
+ throw new Exception( sprintf( __( 'Coupon "%s" does not exist!', 'woocommerce' ), $coupon->get_code() ), 105 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon usage limit is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_usage_limit( $coupon ) {
+ if ( $coupon->get_usage_limit() > 0 && $coupon->get_usage_count() >= $coupon->get_usage_limit() ) {
+ throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon user usage limit is valid or throw exception.
+ *
+ * Per user usage limit - check here if user is logged in (against user IDs).
+ * Checked again for emails later on in WC_Cart::check_customer_coupons().
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @param int $user_id User ID.
+ * @return bool
+ */
+ protected function validate_coupon_user_usage_limit( $coupon, $user_id = 0 ) {
+ if ( empty( $user_id ) ) {
+ $user_id = get_current_user_id();
+ }
+
+ if ( $coupon->get_usage_limit_per_user() > 0 && is_user_logged_in() && $coupon->get_id() && $coupon->get_data_store() ) {
+ $date_store = $coupon->get_data_store();
+ $usage_count = $date_store->get_usage_by_user_id( $coupon, $user_id );
+ if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
+ throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon date is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_expiry_date( $coupon ) {
+ if ( $coupon->get_date_expires() && current_time( 'timestamp', true ) > $coupon->get_date_expires()->getTimestamp() ) {
+ throw new Exception( __( 'This coupon has expired.', 'woocommerce' ), 107 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon amount is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @param float $subtotal Items subtotal.
+ * @return bool
+ */
+ protected function validate_coupon_minimum_amount( $coupon, $subtotal = 0 ) {
+ if ( $coupon->get_minimum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_minimum_amount', $coupon->get_minimum_amount() > $subtotal, $coupon, $subtotal ) ) {
+ /* translators: %s: coupon minimum amount */
+ throw new Exception( sprintf( __( 'The minimum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_minimum_amount() ) ), 108 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon amount is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @param float $subtotal Items subtotal.
+ * @return bool
+ */
+ protected function validate_coupon_maximum_amount( $coupon, $subtotal = 0 ) {
+ if ( $coupon->get_maximum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_maximum_amount', $coupon->get_maximum_amount() < $subtotal, $coupon ) ) {
+ /* translators: %s: coupon maximum amount */
+ throw new Exception( sprintf( __( 'The maximum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_maximum_amount() ) ), 112 );
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon is valid for products in the list is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_product_ids( $coupon ) {
+ if ( count( $coupon->get_product_ids() ) > 0 ) {
+ $valid = false;
+
+ foreach ( $this->items as $item ) {
+ if ( $item->product && in_array( $item->product->get_id(), $coupon->get_product_ids(), true ) || in_array( $item->product->get_parent_id(), $coupon->get_product_ids(), true ) ) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if ( ! $valid ) {
+ throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon is valid for product categories in the list is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_product_categories( $coupon ) {
+ if ( count( $coupon->get_product_categories() ) > 0 ) {
+ $valid = false;
+
+ foreach ( $this->items as $item ) {
+ if ( $coupon->get_exclude_sale_items() && $item->product && $item->product->is_on_sale() ) {
+ continue;
+ }
+
+ $product_cats = wc_get_product_cat_ids( $item->product->get_id() );
+
+ // If we find an item with a cat in our allowed cat list, the coupon is valid.
+ if ( count( array_intersect( $product_cats, $coupon->get_product_categories() ) ) > 0 ) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if ( ! $valid ) {
+ throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Ensure coupon is valid for sale items in the list is valid or throw exception.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_sale_items( $coupon ) {
+ if ( $coupon->get_exclude_sale_items() ) {
+ $valid = false;
+
+ foreach ( $this->items as $item ) {
+ if ( $item->product && ! $item->product->is_on_sale() ) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if ( ! $valid ) {
+ throw new Exception( __( 'Sorry, this coupon is not valid for sale items.', 'woocommerce' ), 110 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * All exclusion rules must pass at the same time for a product coupon to be valid.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_excluded_items( $coupon ) {
+ if ( ! $this->items && $coupon->is_type( wc_get_product_coupon_types() ) ) {
+ $valid = false;
+
+ foreach ( $this->items as $item ) {
+ if ( $item->product && $coupon->is_valid_for_product( $item->product, $item->object ) ) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if ( ! $valid ) {
+ throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Cart discounts cannot be added if non-eligible product is found.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_eligible_items( $coupon ) {
+ if ( ! $coupon->is_type( wc_get_product_coupon_types() ) ) {
+ $this->validate_coupon_excluded_product_ids( $coupon );
+ $this->validate_coupon_excluded_product_categories( $coupon );
+ }
+
+ return true;
+ }
+
+ /**
+ * Exclude products.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_excluded_product_ids( $coupon ) {
+ // Exclude Products.
+ if ( count( $coupon->get_excluded_product_ids() ) > 0 ) {
+ $products = array();
+
+ foreach ( $this->items as $item ) {
+ if ( $item->product && in_array( $item->product->get_id(), $coupon->get_excluded_product_ids(), true ) || in_array( $item->product->get_parent_id(), $coupon->get_excluded_product_ids(), true ) ) {
+ $products[] = $item->product->get_name();
+ }
+ }
+
+ if ( ! empty( $products ) ) {
+ /* translators: %s: products list */
+ throw new Exception( sprintf( __( 'Sorry, this coupon is not applicable to the products: %s.', 'woocommerce' ), implode( ', ', $products ) ), 113 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Exclude categories from product list.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool
+ */
+ protected function validate_coupon_excluded_product_categories( $coupon ) {
+ if ( count( $coupon->get_excluded_product_categories() ) > 0 ) {
+ $categories = array();
+
+ foreach ( $this->items as $item ) {
+ if ( $coupon->get_exclude_sale_items() && $item->product && $item->product->is_on_sale() ) {
+ continue;
+ }
+
+ $product_cats = wc_get_product_cat_ids( $item->product->get_id() );
+ $cat_id_list = array_intersect( $product_cats, $coupon->get_excluded_product_categories() );
+ if ( count( $cat_id_list ) > 0 ) {
+ foreach ( $cat_id_list as $cat_id ) {
+ $cat = get_term( $cat_id, 'product_cat' );
+ $categories[] = $cat->name;
+ }
+ }
+ }
+
+ if ( ! empty( $categories ) ) {
+ /* translators: %s: categories list */
+ throw new Exception( sprintf( __( 'Sorry, this coupon is not applicable to the categories: %s.', 'woocommerce' ), implode( ', ', array_unique( $categories ) ) ), 114 );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a coupon is valid.
+ *
+ * Error Codes:
+ * - 100: Invalid filtered.
+ * - 101: Invalid removed.
+ * - 102: Not yours removed.
+ * - 103: Already applied.
+ * - 104: Individual use only.
+ * - 105: Not exists.
+ * - 106: Usage limit reached.
+ * - 107: Expired.
+ * - 108: Minimum spend limit not met.
+ * - 109: Not applicable.
+ * - 110: Not valid for sale items.
+ * - 111: Missing coupon code.
+ * - 112: Maximum spend limit met.
+ * - 113: Excluded products.
+ * - 114: Excluded categories.
+ *
+ * @since 3.2.0
+ * @throws Exception Error message.
+ * @param WC_Coupon $coupon Coupon data.
+ * @return bool|WP_Error
+ */
+ public function is_coupon_valid( $coupon ) {
+ try {
+ $this->validate_coupon_exists( $coupon );
+ $this->validate_coupon_usage_limit( $coupon );
+ $this->validate_coupon_user_usage_limit( $coupon );
+ $this->validate_coupon_expiry_date( $coupon );
+ $this->validate_coupon_minimum_amount( $coupon );
+ $this->validate_coupon_maximum_amount( $coupon );
+ $this->validate_coupon_product_ids( $coupon );
+ $this->validate_coupon_product_categories( $coupon );
+ $this->validate_coupon_sale_items( $coupon );
+ $this->validate_coupon_excluded_items( $coupon );
+ $this->validate_coupon_eligible_items( $coupon );
+
+ if ( ! apply_filters( 'woocommerce_coupon_is_valid', true, $coupon, $this ) ) {
+ throw new Exception( __( 'Coupon is not valid.', 'woocommerce' ), 100 );
+ }
+ } catch ( Exception $e ) {
+ /**
+ * Filter the coupon error message.
+ *
+ * @param string $error_message Error message.
+ * @param int $error_code Error code.
+ * @param WC_Coupon $coupon Coupon data.
+ */
+ $message = apply_filters( 'woocommerce_coupon_error', $e->getMessage(), $e->getCode(), $coupon );
+
+ return new WP_Error( 'invalid_coupon', $message, array(
+ 'status' => 400,
+ ) );
+ }
+ return true;
+ }
+}
diff --git a/includes/class-wc-download-handler.php b/includes/class-wc-download-handler.php
index 3f109521d7f..dfd9c8d99c2 100644
--- a/includes/class-wc-download-handler.php
+++ b/includes/class-wc-download-handler.php
@@ -189,13 +189,17 @@ class WC_Download_Handler {
$wp_uploads_dir = $wp_uploads['basedir'];
$wp_uploads_url = $wp_uploads['baseurl'];
- // Replace uploads dir, site url etc with absolute counterparts if we can
+ /**
+ * Replace uploads dir, site url etc with absolute counterparts if we can.
+ * Note the str_replace on site_url is on purpose, so if https is forced
+ * via filters we can still do the string replacement on a HTTP file.
+ */
$replacements = array(
- $wp_uploads_url => $wp_uploads_dir,
- network_site_url( '/', 'https' ) => ABSPATH,
- network_site_url( '/', 'http' ) => ABSPATH,
- site_url( '/', 'https' ) => ABSPATH,
- site_url( '/', 'http' ) => ABSPATH,
+ $wp_uploads_url => $wp_uploads_dir,
+ network_site_url( '/', 'https' ) => ABSPATH,
+ str_replace( 'https:', 'http:', network_site_url( '/', 'http' ) ) => ABSPATH,
+ site_url( '/', 'https' ) => ABSPATH,
+ str_replace( 'https:', 'http:', site_url( '/', 'http' ) ) => ABSPATH,
);
$file_path = str_replace( array_keys( $replacements ), array_values( $replacements ), $file_path );
diff --git a/includes/class-wc-emails.php b/includes/class-wc-emails.php
index b11928cce9e..8edc2d02660 100644
--- a/includes/class-wc-emails.php
+++ b/includes/class-wc-emails.php
@@ -375,9 +375,8 @@ class WC_Emails {
* Add order meta to email templates.
*
* @param mixed $order
- * @param bool $sent_to_admin (default: false)
- * @param bool $plain_text (default: false)
- * @return string
+ * @param bool $sent_to_admin (default: false)
+ * @param bool $plain_text (default: false)
*/
public function order_meta( $order, $sent_to_admin = false, $plain_text = false ) {
$fields = apply_filters( 'woocommerce_email_order_meta_fields', array(), $sent_to_admin, $order );
diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php
index b11c3822f55..fa91885fdc5 100644
--- a/includes/class-wc-form-handler.php
+++ b/includes/class-wc-form-handler.php
@@ -40,7 +40,7 @@ class WC_Form_Handler {
}
/**
- * Remove key and login from querystring, set cookie, and redirect to account page to show the form.
+ * Remove key and login from query string, set cookie, and redirect to account page to show the form.
*/
public static function redirect_reset_password_link() {
if ( is_account_page() && ! empty( $_GET['key'] ) && ! empty( $_GET['login'] ) ) {
@@ -67,6 +67,8 @@ class WC_Form_Handler {
return;
}
+ nocache_headers();
+
$user_id = get_current_user_id();
if ( $user_id <= 0 ) {
@@ -179,6 +181,8 @@ class WC_Form_Handler {
return;
}
+ nocache_headers();
+
$errors = new WP_Error();
$user = new stdClass();
@@ -274,6 +278,7 @@ class WC_Form_Handler {
*/
public static function checkout_action() {
if ( isset( $_POST['woocommerce_checkout_place_order'] ) || isset( $_POST['woocommerce_checkout_update_totals'] ) ) {
+ nocache_headers();
if ( WC()->cart->is_empty() ) {
wp_redirect( wc_get_page_permalink( 'cart' ) );
@@ -295,7 +300,7 @@ class WC_Form_Handler {
global $wp;
if ( isset( $_POST['woocommerce_pay'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-pay' ) ) {
-
+ nocache_headers();
ob_start();
// Pay for existing order
@@ -374,27 +379,42 @@ class WC_Form_Handler {
*/
public static function add_payment_method_action() {
if ( isset( $_POST['woocommerce_add_payment_method'], $_POST['payment_method'], $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-add-payment-method' ) ) {
-
+ nocache_headers();
ob_start();
- $payment_method = wc_clean( $_POST['payment_method'] );
-
+ $payment_method_id = wc_clean( wp_unslash( $_POST['payment_method'] ) );
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
- // Validate
- $available_gateways[ $payment_method ]->validate_fields();
- // Process
- if ( wc_notice_count( 'wc_errors' ) == 0 ) {
- $result = $available_gateways[ $payment_method ]->add_payment_method();
- // Redirect to success/confirmation/payment page
+ if ( isset( $available_gateways[ $payment_method_id ] ) ) {
+ $gateway = $available_gateways[ $payment_method_id ];
+
+ if ( ! $gateway->supports( 'add_payment_method' ) && ! $gateway->supports( 'tokenization' ) ) {
+ wc_add_notice( __( 'Invalid payment gateway.', 'woocommerce' ), 'error' );
+ return;
+ }
+
+ $gateway->validate_fields();
+
+ if ( wc_notice_count( 'error' ) > 0 ) {
+ return;
+ }
+
+ $result = $gateway->add_payment_method();
+
if ( 'success' === $result['result'] ) {
- wc_add_notice( __( 'Payment method added.', 'woocommerce' ) );
+ wc_add_notice( __( 'Payment method successfully added.', 'woocommerce' ) );
+ }
+
+ if ( 'failure' === $result['result'] ) {
+ wc_add_notice( __( 'Unable to add payment method to your account.', 'woocommerce' ), 'error' );
+ }
+
+ if ( ! empty( $result['redirect'] ) ) {
wp_redirect( $result['redirect'] );
exit();
}
}
}
-
}
/**
@@ -404,6 +424,7 @@ class WC_Form_Handler {
global $wp;
if ( isset( $wp->query_vars['delete-payment-method'] ) ) {
+ nocache_headers();
$token_id = absint( $wp->query_vars['delete-payment-method'] );
$token = WC_Payment_Tokens::get( $token_id );
@@ -428,6 +449,7 @@ class WC_Form_Handler {
global $wp;
if ( isset( $wp->query_vars['set-default-payment-method'] ) ) {
+ nocache_headers();
$token_id = absint( $wp->query_vars['set-default-payment-method'] );
$token = WC_Payment_Tokens::get( $token_id );
@@ -449,20 +471,19 @@ class WC_Form_Handler {
* Remove from cart/update.
*/
public static function update_cart_action() {
+ if ( ! ( isset( $_REQUEST['apply_coupon'] ) || isset( $_REQUEST['remove_coupon'] ) || isset( $_REQUEST['remove_item'] ) || isset( $_REQUEST['undo_item'] ) || isset( $_REQUEST['update_cart'] ) || isset( $_REQUEST['proceed'] ) ) ) {
+ return;
+ }
+
+ nocache_headers();
if ( ! empty( $_POST['apply_coupon'] ) && ! empty( $_POST['coupon_code'] ) ) {
-
- // Add Discount
WC()->cart->add_discount( sanitize_text_field( $_POST['coupon_code'] ) );
} elseif ( isset( $_GET['remove_coupon'] ) ) {
-
- // Remove Coupon Codes
WC()->cart->remove_coupon( wc_clean( $_GET['remove_coupon'] ) );
- } elseif ( ! empty( $_GET['remove_item'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'woocommerce-cart' ) ) {
-
- // Remove from cart
+ } elseif ( ! empty( $_GET['remove_item'] ) && wp_verify_nonce( wc_get_var( $_REQUEST['_wpnonce'] ), 'woocommerce-cart' ) ) {
$cart_item_key = sanitize_text_field( $_GET['remove_item'] );
if ( $cart_item = WC()->cart->get_cart_item( $cart_item_key ) ) {
@@ -501,7 +522,7 @@ class WC_Form_Handler {
}
// Update Cart - checks apply_coupon too because they are in the same form
- if ( ( ! empty( $_POST['apply_coupon'] ) || ! empty( $_POST['update_cart'] ) || ! empty( $_POST['proceed'] ) ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-cart' ) ) {
+ if ( ( ! empty( $_POST['apply_coupon'] ) || ! empty( $_POST['update_cart'] ) || ! empty( $_POST['proceed'] ) ) && wp_verify_nonce( wc_get_var( $_POST['_wpnonce'] ), 'woocommerce-cart' ) ) {
$cart_updated = false;
$cart_totals = isset( $_POST['cart'] ) ? $_POST['cart'] : '';
@@ -563,12 +584,13 @@ class WC_Form_Handler {
* Place a previous order again.
*/
public static function order_again() {
-
// Nothing to do
if ( ! isset( $_GET['order_again'] ) || ! is_user_logged_in() || ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'woocommerce-order_again' ) ) {
return;
}
+ nocache_headers();
+
if ( apply_filters( 'woocommerce_empty_cart_when_order_again', true ) ) {
WC()->cart->empty_cart();
}
@@ -648,6 +670,7 @@ class WC_Form_Handler {
*/
public static function cancel_order() {
if ( isset( $_GET['cancel_order'] ) && isset( $_GET['order'] ) && isset( $_GET['order_id'] ) ) {
+ nocache_headers();
$order_key = $_GET['order'];
$order_id = absint( $_GET['order_id'] );
@@ -694,6 +717,8 @@ class WC_Form_Handler {
return;
}
+ nocache_headers();
+
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_REQUEST['add-to-cart'] ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
@@ -773,16 +798,22 @@ class WC_Form_Handler {
// Add to cart validation
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $item, $quantity );
+ // Suppress total recalculation until finished.
+ remove_action( 'woocommerce_add_to_cart', array( WC()->cart, 'calculate_totals' ), 20, 0 );
+
if ( $passed_validation && WC()->cart->add_to_cart( $item, $quantity ) !== false ) {
$was_added_to_cart = true;
$added_to_cart[ $item ] = $quantity;
}
+
+ add_action( 'woocommerce_add_to_cart', array( WC()->cart, 'calculate_totals' ), 20, 0 );
}
if ( ! $was_added_to_cart && ! $quantity_set ) {
wc_add_notice( __( 'Please choose the quantity of items you wish to add to your cart…', 'woocommerce' ), 'error' );
} elseif ( $was_added_to_cart ) {
wc_add_to_cart_message( $added_to_cart );
+ WC()->cart->calculate_totals();
return true;
}
} elseif ( $product_id ) {
diff --git a/includes/class-wc-geolocation.php b/includes/class-wc-geolocation.php
index 3ee4fa27cbe..66617a3bf99 100644
--- a/includes/class-wc-geolocation.php
+++ b/includes/class-wc-geolocation.php
@@ -98,8 +98,8 @@ class WC_Geolocation {
* @return string
*/
public static function get_ip_address() {
- if ( isset( $_SERVER['X-Real-IP'] ) ) {
- return $_SERVER['X-Real-IP'];
+ if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) {
+ return $_SERVER['HTTP_X_REAL_IP'];
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
// Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2
// Make sure we always only send through the first IP in the list which should always be the client IP.
diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php
new file mode 100644
index 00000000000..b1830b5468b
--- /dev/null
+++ b/includes/class-wc-meta-data.php
@@ -0,0 +1,104 @@
+current_data = $meta;
+ $this->apply_changes();
+ }
+
+ /**
+ * Merge changes with data and clear.
+ */
+ public function apply_changes() {
+ $this->data = $this->current_data;
+ }
+
+ /**
+ * Creates or updates a property in the metadata object.
+ *
+ * @param string $key Key to set.
+ * @param mixed $value Value to set.
+ */
+ public function __set( $key, $value ) {
+ $this->current_data[ $key ] = $value;
+ }
+
+ /**
+ * Checks if a given key exists in our data. This is called internally
+ * by `empty` and `isset`.
+ *
+ * @param string $key Key to check if set.
+ */
+ public function __isset( $key ) {
+ return array_key_exists( $key, $this->current_data );
+ }
+
+ /**
+ * Returns the value of any property.
+ *
+ * @param string $key Key to get.
+ * @return mixed Property value or NULL if it does not exists
+ */
+ public function __get( $key ) {
+ if ( array_key_exists( $key, $this->current_data ) ) {
+ return $this->current_data[ $key ];
+ }
+ return null;
+ }
+
+ /**
+ * Return data changes only.
+ *
+ * @return array
+ */
+ public function get_changes() {
+ $changes = array();
+ foreach ( $this->current_data as $id => $value ) {
+ if ( ! array_key_exists( $id, $this->data ) || $value !== $this->data[ $id ] ) {
+ $changes[ $id ] = $value;
+ }
+ }
+ return $changes;
+ }
+
+}
diff --git a/includes/class-wc-order-item-coupon.php b/includes/class-wc-order-item-coupon.php
index b0b3ad04474..8300743fd22 100644
--- a/includes/class-wc-order-item-coupon.php
+++ b/includes/class-wc-order-item-coupon.php
@@ -131,7 +131,7 @@ class WC_Order_Item_Coupon extends WC_Order_Item {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order-item-fee.php b/includes/class-wc-order-item-fee.php
index f811684a8f1..8974ba392f8 100644
--- a/includes/class-wc-order-item-fee.php
+++ b/includes/class-wc-order-item-fee.php
@@ -188,7 +188,7 @@ class WC_Order_Item_Fee extends WC_Order_Item {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order-item-meta.php b/includes/class-wc-order-item-meta.php
index 72f5b693f5a..b7186975b86 100644
--- a/includes/class-wc-order-item-meta.php
+++ b/includes/class-wc-order-item-meta.php
@@ -37,7 +37,7 @@ class WC_Order_Item_Meta {
public function __construct( $item = array(), $product = null ) {
wc_deprecated_function( 'WC_Order_Item_Meta::__construct', '3.1', 'WC_Order_Item_Product' );
- // Backwards (pre 2.4) compat
+ // Backwards (pre 2.4) compatibility
if ( ! isset( $item['item_meta'] ) ) {
$this->legacy = true;
$this->meta = array_filter( (array) $item );
diff --git a/includes/class-wc-order-item-product.php b/includes/class-wc-order-item-product.php
index 1aafdcd45d7..73d7c6d5648 100644
--- a/includes/class-wc-order-item-product.php
+++ b/includes/class-wc-order-item-product.php
@@ -165,9 +165,11 @@ class WC_Order_Item_Product extends WC_Order_Item {
*
* @param array $data Key/Value pairs
*/
- public function set_variation( $data ) {
- foreach ( $data as $key => $value ) {
- $this->add_meta_data( str_replace( 'attribute_', '', $key ), $value, true );
+ public function set_variation( $data = array() ) {
+ if ( is_array( $data ) ) {
+ foreach ( $data as $key => $value ) {
+ $this->add_meta_data( str_replace( 'attribute_', '', $key ), $value, true );
+ }
}
}
@@ -198,7 +200,7 @@ class WC_Order_Item_Product extends WC_Order_Item {
public function set_backorder_meta() {
$product = $this->get_product();
if ( $product && $product->backorders_require_notification() && $product->is_on_backorder( $this->get_quantity() ) ) {
- $this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $this->get_quantity() - max( 0, $product->get_stock_quantity() ), true );
+ $this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ), $this ), $this->get_quantity() - max( 0, $product->get_stock_quantity() ), true );
}
}
@@ -397,7 +399,7 @@ class WC_Order_Item_Product extends WC_Order_Item {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order-item-shipping.php b/includes/class-wc-order-item-shipping.php
index df20fae8400..b77ef122b50 100644
--- a/includes/class-wc-order-item-shipping.php
+++ b/includes/class-wc-order-item-shipping.php
@@ -28,6 +28,27 @@ class WC_Order_Item_Shipping extends WC_Order_Item {
),
);
+ /**
+ * Calculate item taxes.
+ *
+ * @since 3.2.0
+ * @param array $calculate_tax_for Location data to get taxes for. Required.
+ * @return bool True if taxes were calculated.
+ */
+ public function calculate_taxes( $calculate_tax_for = array() ) {
+ if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'], $calculate_tax_for['tax_class'] ) ) {
+ return false;
+ }
+ if ( wc_tax_enabled() ) {
+ $tax_rates = WC_Tax::find_shipping_rates( $calculate_tax_for );
+ $taxes = WC_Tax::calc_tax( $this->get_total(), $tax_rates, false );
+ $this->set_taxes( array( 'total' => $taxes ) );
+ } else {
+ $this->set_taxes( false );
+ }
+ return true;
+ }
+
/*
|--------------------------------------------------------------------------
| Setters
@@ -205,7 +226,7 @@ class WC_Order_Item_Shipping extends WC_Order_Item {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order-item-tax.php b/includes/class-wc-order-item-tax.php
index 890a3cfea13..63f431701e6 100644
--- a/includes/class-wc-order-item-tax.php
+++ b/includes/class-wc-order-item-tax.php
@@ -215,7 +215,7 @@ class WC_Order_Item_Tax extends WC_Order_Item {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order-item.php b/includes/class-wc-order-item.php
index 873a4f04cd0..3ab1235dff9 100644
--- a/includes/class-wc-order-item.php
+++ b/includes/class-wc-order-item.php
@@ -152,7 +152,8 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
*/
/**
- * Type checking
+ * Type checking.
+ *
* @param string|array $type
* @return boolean
*/
@@ -160,6 +161,34 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type();
}
+ /**
+ * Calculate item taxes.
+ *
+ * @since 3.2.0
+ * @param array $calculate_tax_for Location data to get taxes for. Required.
+ * @return bool True if taxes were calculated.
+ */
+ public function calculate_taxes( $calculate_tax_for = array() ) {
+ if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'] ) ) {
+ return false;
+ }
+ if ( '0' !== $this->get_tax_class() && 'taxable' === $this->get_tax_status() && wc_tax_enabled() ) {
+ $calculate_tax_for['tax_class'] = $this->get_tax_class();
+ $tax_rates = WC_Tax::find_rates( $calculate_tax_for );
+ $taxes = WC_Tax::calc_tax( $this->get_total(), $tax_rates, false );
+
+ if ( method_exists( $this, 'get_subtotal' ) ) {
+ $subtotal_taxes = WC_Tax::calc_tax( $this->get_subtotal(), $tax_rates, false );
+ $this->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
+ } else {
+ $this->set_taxes( array( 'total' => $taxes ) );
+ }
+ } else {
+ $this->set_taxes( false );
+ }
+ return true;
+ }
+
/*
|--------------------------------------------------------------------------
| Meta Data Handling
@@ -219,7 +248,7 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
| Array Access Methods
|--------------------------------------------------------------------------
|
- | For backwards compat with legacy arrays.
+ | For backwards compatibility with legacy arrays.
|
*/
diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php
index d4db07492fd..0d665a6517e 100644
--- a/includes/class-wc-order.php
+++ b/includes/class-wc-order.php
@@ -303,25 +303,27 @@ class WC_Order extends WC_Abstract_Order {
* Handle the status transition.
*/
protected function status_transition() {
- if ( $this->status_transition ) {
- do_action( 'woocommerce_order_status_' . $this->status_transition['to'], $this->get_id(), $this );
+ $status_transition = $this->status_transition;
- if ( ! empty( $this->status_transition['from'] ) ) {
+ // Reset status transition variable
+ $this->status_transition = false;
+
+ if ( $status_transition ) {
+ do_action( 'woocommerce_order_status_' . $status_transition['to'], $this->get_id(), $this );
+
+ if ( ! empty( $status_transition['from'] ) ) {
/* translators: 1: old order status 2: new order status */
- $transition_note = sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $this->status_transition['from'] ), wc_get_order_status_name( $this->status_transition['to'] ) );
+ $transition_note = sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $status_transition['from'] ), wc_get_order_status_name( $status_transition['to'] ) );
- do_action( 'woocommerce_order_status_' . $this->status_transition['from'] . '_to_' . $this->status_transition['to'], $this->get_id(), $this );
- do_action( 'woocommerce_order_status_changed', $this->get_id(), $this->status_transition['from'], $this->status_transition['to'], $this );
+ do_action( 'woocommerce_order_status_' . $status_transition['from'] . '_to_' . $status_transition['to'], $this->get_id(), $this );
+ do_action( 'woocommerce_order_status_changed', $this->get_id(), $status_transition['from'], $status_transition['to'], $this );
} else {
/* translators: %s: new order status */
- $transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $this->status_transition['to'] ) );
+ $transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $status_transition['to'] ) );
}
// Note the transition occurred
- $this->add_order_note( trim( $this->status_transition['note'] . ' ' . $transition_note ), 0, $this->status_transition['manual'] );
-
- // This has ran, so reset status transition variable
- $this->status_transition = false;
+ $this->add_order_note( trim( $status_transition['note'] . ' ' . $transition_note ), 0, $status_transition['manual'] );
}
}
diff --git a/includes/class-wc-payment-gateways.php b/includes/class-wc-payment-gateways.php
index 46fc76063a7..b04e72ad318 100644
--- a/includes/class-wc-payment-gateways.php
+++ b/includes/class-wc-payment-gateways.php
@@ -154,9 +154,7 @@ class WC_Payment_Gateways {
if ( $gateway->is_available() ) {
if ( ! is_add_payment_method_page() ) {
$_available_gateways[ $gateway->id ] = $gateway;
- } elseif ( $gateway->supports( 'add_payment_method' ) ) {
- $_available_gateways[ $gateway->id ] = $gateway;
- } elseif ( $gateway->supports( 'tokenization' ) ) {
+ } elseif ( $gateway->supports( 'add_payment_method' ) || $gateway->supports( 'tokenization' ) ) {
$_available_gateways[ $gateway->id ] = $gateway;
}
}
diff --git a/includes/class-wc-post-data.php b/includes/class-wc-post-data.php
index eeb3cd68321..07263500541 100644
--- a/includes/class-wc-post-data.php
+++ b/includes/class-wc-post-data.php
@@ -400,7 +400,9 @@ class WC_Post_Data {
if ( in_array( get_post_type( $order_id ), wc_get_order_types() ) ) {
// Clean up user.
$order = wc_get_order( $order_id );
- $customer_id = $order->get_customer_id();
+
+ // Check for `get_customer_id`, since this may be e.g. a refund order (which doesn't implement it).
+ $customer_id = is_callable( array( $order, 'get_customer_id' ) ) ? $order->get_customer_id() : 0;
if ( $customer_id > 0 && 'shop_order' === $order->get_type() ) {
$customer = new WC_Customer( $customer_id );
diff --git a/includes/class-wc-product-external.php b/includes/class-wc-product-external.php
index 0508a4f461a..d9b7e72964a 100644
--- a/includes/class-wc-product-external.php
+++ b/includes/class-wc-product-external.php
@@ -122,7 +122,7 @@ class WC_Product_External extends WC_Product {
}
/**
- * xternal products cannot be backordered.
+ * External products cannot be backordered.
*
* @since 3.0.0
* @param string $backorders Options: 'yes', 'no' or 'notify'.
diff --git a/includes/class-wc-product-grouped.php b/includes/class-wc-product-grouped.php
index dadf9fe2caa..c10e4d28949 100644
--- a/includes/class-wc-product-grouped.php
+++ b/includes/class-wc-product-grouped.php
@@ -162,7 +162,7 @@ class WC_Product_Grouped extends WC_Product {
* upwards (from child to parent) when the variation is saved.
*
* @param WC_Product|int $product Product object or ID for which you wish to sync.
- * @param bool $save If true, the prouduct object will be saved to the DB before returning it.
+ * @param bool $save If true, the product object will be saved to the DB before returning it.
* @return WC_Product Synced product object.
*/
public static function sync( $product, $save = true ) {
diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php
new file mode 100644
index 00000000000..045a917cb30
--- /dev/null
+++ b/includes/class-wc-product-query.php
@@ -0,0 +1,74 @@
+ array( 'draft', 'pending', 'private', 'publish' ),
+ 'type' => array_merge( array_keys( wc_get_product_types() ) ),
+ 'limit' => get_option( 'posts_per_page' ),
+ 'include' => array(),
+ 'date_created' => '',
+ 'date_modified' => '',
+ 'featured' => '',
+ 'visibility' => '',
+ 'sku' => '',
+ 'price' => '',
+ 'regular_price' => '',
+ 'sale_price' => '',
+ 'date_on_sale_from' => '',
+ 'date_on_sale_to' => '',
+ 'total_sales' => '',
+ 'tax_status' => '',
+ 'tax_class' => '',
+ 'manage_stock' => '',
+ 'stock_quantity' => '',
+ 'stock_status' => '',
+ 'backorders' => '',
+ 'sold_individually' => '',
+ 'weight' => '',
+ 'length' => '',
+ 'width' => '',
+ 'height' => '',
+ 'reviews_allowed' => '',
+ 'virtual' => '',
+ 'downloadable' => '',
+ 'category' => array(),
+ 'tag' => array(),
+ 'shipping_class' => array(),
+ 'download_limit' => '',
+ 'download_expiry' => '',
+ 'average_rating' => '',
+ 'review_count' => '',
+ )
+ );
+ }
+
+ /**
+ * Get products matching the current query vars.
+ * @return array of WC_Product objects
+ */
+ public function get_products() {
+ $args = apply_filters( 'woocommerce_product_query_args', $this->get_query_vars() );
+ $results = WC_Data_Store::load( 'product' )->query( $args );
+ return apply_filters( 'woocommerce_product_query', $results, $args );
+ }
+}
diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php
index 6afe8461a57..c64ecfc0c6b 100644
--- a/includes/class-wc-product-variable.php
+++ b/includes/class-wc-product-variable.php
@@ -538,7 +538,7 @@ class WC_Product_Variable extends WC_Product {
* Sync parent stock status with the status of all children and save.
*
* @param WC_Product|int $product Product object or ID for which you wish to sync.
- * @param bool $save If true, the prouduct object will be saved to the DB before returning it.
+ * @param bool $save If true, the product object will be saved to the DB before returning it.
* @return WC_Product Synced product object.
*/
public static function sync_stock_status( $product, $save = true ) {
diff --git a/includes/class-wc-product-variation.php b/includes/class-wc-product-variation.php
index f0c58de6f0a..f0eba835ab1 100644
--- a/includes/class-wc-product-variation.php
+++ b/includes/class-wc-product-variation.php
@@ -20,7 +20,7 @@ class WC_Product_Variation extends WC_Product_Simple {
* Post type.
* @var string
*/
- public $post_type = 'product_variation';
+ protected $post_type = 'product_variation';
/**
* Parent data.
@@ -426,16 +426,6 @@ class WC_Product_Variation extends WC_Product_Simple {
$this->set_prop( 'attributes', $attributes );
}
- /**
- * Returns array of attribute name value pairs. Keys are prefixed with attribute_, as stored.
- *
- * @param string $context
- * @return array
- */
- public function get_attributes( $context = 'view' ) {
- return $this->get_prop( 'attributes', $context );
- }
-
/**
* Returns whether or not the product has any visible attributes.
*
diff --git a/includes/class-wc-query.php b/includes/class-wc-query.php
index a6f11531fc4..43fb49c8b16 100644
--- a/includes/class-wc-query.php
+++ b/includes/class-wc-query.php
@@ -516,7 +516,7 @@ class WC_Query {
}
/**
- * WP Core doens't let us change the sort direction for invidual orderby params - https://core.trac.wordpress.org/ticket/17065.
+ * WP Core doens't let us change the sort direction for individual orderby params - https://core.trac.wordpress.org/ticket/17065.
*
* This lets us sort by meta value desc, and have a second orderby param.
*
diff --git a/includes/class-wc-shipping-zones.php b/includes/class-wc-shipping-zones.php
index 2fe22367b27..bcbfae2e892 100644
--- a/includes/class-wc-shipping-zones.php
+++ b/includes/class-wc-shipping-zones.php
@@ -82,7 +82,7 @@ class WC_Shipping_Zones {
*
* @param $instance_id
*
- * @return bool|WC_Shipping_Meethod
+ * @return bool|WC_Shipping_Method
*/
public static function get_shipping_method( $instance_id ) {
$data_store = WC_Data_Store::load( 'shipping-zone' );
diff --git a/includes/class-wc-shipping.php b/includes/class-wc-shipping.php
index 0a1e931f8a2..ece466afd5e 100644
--- a/includes/class-wc-shipping.php
+++ b/includes/class-wc-shipping.php
@@ -26,12 +26,6 @@ class WC_Shipping {
/** @var array|null Stores methods loaded into woocommerce. */
public $shipping_methods = null;
- /** @var float Stores the cost of shipping */
- public $shipping_total = 0;
-
- /** @var array Stores an array of shipping taxes. */
- public $shipping_taxes = array();
-
/** @var array Stores the shipping classes. */
public $shipping_classes = array();
@@ -237,18 +231,17 @@ class WC_Shipping {
/**
* Calculate shipping for (multiple) packages of cart items.
*
- * @param array $packages multi-dimensional array of cart items to calc shipping for
+ * @param array $packages multi-dimensional array of cart items to calc shipping for.
+ * @return array Array of calculated packages.
*/
public function calculate_shipping( $packages = array() ) {
- $this->shipping_total = 0;
- $this->shipping_taxes = array();
- $this->packages = array();
+ $this->packages = array();
if ( ! $this->enabled || empty( $packages ) ) {
- return;
+ return array();
}
- // Calculate costs for passed packages
+ // Calculate costs for passed packages.
foreach ( $packages as $package_key => $package ) {
$this->packages[ $package_key ] = $this->calculate_shipping_for_package( $package, $package_key );
}
@@ -264,58 +257,9 @@ class WC_Shipping {
*
* @param array $packages The array of packages after shipping costs are calculated.
*/
- $this->packages = apply_filters( 'woocommerce_shipping_packages', $this->packages );
+ $this->packages = array_filter( (array) apply_filters( 'woocommerce_shipping_packages', $this->packages ) );
- if ( ! is_array( $this->packages ) || empty( $this->packages ) ) {
- return;
- }
-
- // Get all chosen methods
- $chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
- $method_counts = WC()->session->get( 'shipping_method_counts' );
-
- // Get chosen methods for each package
- foreach ( $this->packages as $i => $package ) {
- $chosen_method = false;
- $method_count = false;
-
- if ( ! empty( $chosen_methods[ $i ] ) ) {
- $chosen_method = $chosen_methods[ $i ];
- }
-
- if ( ! empty( $method_counts[ $i ] ) ) {
- $method_count = absint( $method_counts[ $i ] );
- }
-
- if ( sizeof( $package['rates'] ) > 0 ) {
-
- // If not set, not available, or available methods have changed, set to the DEFAULT option
- if ( empty( $chosen_method ) || ! isset( $package['rates'][ $chosen_method ] ) || sizeof( $package['rates'] ) !== $method_count ) {
- $chosen_method = apply_filters( 'woocommerce_shipping_chosen_method', $this->get_default_method( $package['rates'], false ), $package['rates'], $chosen_method );
- $chosen_methods[ $i ] = $chosen_method;
- $method_counts[ $i ] = sizeof( $package['rates'] );
- do_action( 'woocommerce_shipping_method_chosen', $chosen_method );
- }
-
- // Store total costs
- if ( $chosen_method && isset( $package['rates'][ $chosen_method ] ) ) {
- $rate = $package['rates'][ $chosen_method ];
-
- // Merge cost and taxes - label and ID will be the same
- $this->shipping_total += $rate->cost;
-
- if ( ! empty( $rate->taxes ) && is_array( $rate->taxes ) ) {
- foreach ( array_keys( $this->shipping_taxes + $rate->taxes ) as $key ) {
- $this->shipping_taxes[ $key ] = ( isset( $rate->taxes[ $key ] ) ? $rate->taxes[ $key ] : 0 ) + ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 );
- }
- }
- }
- }
- }
-
- // Save all chosen methods (array)
- WC()->session->set( 'chosen_shipping_methods', $chosen_methods );
- WC()->session->set( 'shipping_method_counts', $method_counts );
+ return $this->packages;
}
/**
@@ -396,8 +340,6 @@ class WC_Shipping {
*/
public function reset_shipping() {
unset( WC()->session->chosen_shipping_methods );
- $this->shipping_total = 0;
- $this->shipping_taxes = array();
$this->packages = array();
}
diff --git a/includes/class-wc-structured-data.php b/includes/class-wc-structured-data.php
index d689bef9429..9c381a8228e 100644
--- a/includes/class-wc-structured-data.php
+++ b/includes/class-wc-structured-data.php
@@ -91,11 +91,8 @@ class WC_Structured_Data {
// Wrap the multiple values of each type inside a graph... Then add context to each type.
foreach ( $data as $type => $value ) {
- if ( 1 < count( $value ) ) {
- $data[ $type ] = apply_filters( 'woocommerce_structured_data_context', array( '@context' => 'https://schema.org/' ), $data, $type, $value ) + array( '@graph' => $value );
- } else {
- $data[ $type ] = $value[0];
- }
+ $data[ $type ] = count( $value ) > 1 ? array( '@graph' => $value ) : $value[0];
+ $data[ $type ] = apply_filters( 'woocommerce_structured_data_context', array( '@context' => 'https://schema.org/' ), $data, $type, $value ) + $data[ $type ];
}
// If requested types, pick them up... Finally change the associative array to an indexed one.
@@ -193,54 +190,63 @@ class WC_Structured_Data {
return;
}
- $shop_name = get_bloginfo( 'name' );
- $shop_url = home_url();
- $currency = get_woocommerce_currency();
- $markup = array();
- $markup['@type'] = 'Product';
- $markup['@id'] = get_permalink( $product->get_id() );
- $markup['url'] = $markup['@id'];
- $markup['name'] = $product->get_name();
+ $shop_name = get_bloginfo( 'name' );
+ $shop_url = home_url();
+ $currency = get_woocommerce_currency();
+
+ $markup = array(
+ '@type' => 'Product',
+ '@id' => get_permalink( $product->get_id() ),
+ 'name' => $product->get_name(),
+ );
if ( apply_filters( 'woocommerce_structured_data_product_limit', is_product_taxonomy() || is_shop() ) ) {
+ $markup['url'] = $markup['@id'];
+
$this->set_data( apply_filters( 'woocommerce_structured_data_product_limited', $markup, $product ) );
return;
}
- if ( '' !== $product->get_price() ) {
- $markup_offer = array(
- '@type' => 'Offer',
- 'priceCurrency' => $currency,
- 'availability' => 'https://schema.org/' . $stock = ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ),
- 'sku' => $product->get_sku(),
- 'image' => wp_get_attachment_url( $product->get_image_id() ),
- 'description' => $product->get_description(),
- 'seller' => array(
- '@type' => 'Organization',
- 'name' => $shop_name,
- 'url' => $shop_url,
- ),
- );
+ $markup['image'] = wp_get_attachment_url( $product->get_image_id() );
+ $markup['description'] = wpautop( do_shortcode( $product->get_short_description() ? $product->get_short_description() : $product->get_description() ) );
+ $markup['sku'] = $product->get_sku();
+ if ( '' !== $product->get_price() ) {
if ( $product->is_type( 'variable' ) ) {
$prices = $product->get_variation_prices();
$lowest = reset( $prices['price'] );
$highest = end( $prices['price'] );
if ( $lowest === $highest ) {
- $markup_offer['price'] = wc_format_decimal( $product->get_price(), wc_get_price_decimals() );
+ $markup_offer = array(
+ '@type' => 'Offer',
+ 'price' => wc_format_decimal( $lowest, wc_get_price_decimals() ),
+ );
} else {
- $markup_offer['priceSpecification'] = array(
- 'price' => wc_format_decimal( $product->get_price(), wc_get_price_decimals() ),
- 'minPrice' => wc_format_decimal( $lowest, wc_get_price_decimals() ),
- 'maxPrice' => wc_format_decimal( $highest, wc_get_price_decimals() ),
- 'priceCurrency' => $currency,
+ $markup_offer = array(
+ '@type' => 'AggregateOffer',
+ 'lowPrice' => wc_format_decimal( $lowest, wc_get_price_decimals() ),
+ 'highPrice' => wc_format_decimal( $highest, wc_get_price_decimals() ),
);
}
} else {
- $markup_offer['price'] = wc_format_decimal( $product->get_price(), wc_get_price_decimals() );
+ $markup_offer = array(
+ '@type' => 'Offer',
+ 'price' => wc_format_decimal( $product->get_price(), wc_get_price_decimals() ),
+ );
}
+ $markup_offer += array(
+ 'priceCurrency' => $currency,
+ 'availability' => 'https://schema.org/' . ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ),
+ 'url' => $markup['@id'],
+ 'seller' => array(
+ '@type' => 'Organization',
+ 'name' => $shop_name,
+ 'url' => $shop_url,
+ ),
+ );
+
$markup['offers'] = array( apply_filters( 'woocommerce_structured_data_product_offer', $markup_offer, $product ) );
}
diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php
index 2325f0e9cc9..53556c76d61 100644
--- a/includes/class-wc-tax.php
+++ b/includes/class-wc-tax.php
@@ -452,13 +452,18 @@ class WC_Tax {
* Used by get_rates(), get_shipping_rates().
*
* @param $tax_class string Optional, passed to the filter for advanced tax setups.
+ * @param object $customer Override the customer object to get their location.
* @return array
*/
- public static function get_tax_location( $tax_class = '' ) {
+ public static function get_tax_location( $tax_class = '', $customer = null ) {
$location = array();
- if ( ! empty( WC()->customer ) ) {
- $location = WC()->customer->get_taxable_address();
+ if ( is_null( $customer ) && ! empty( WC()->customer ) ) {
+ $customer = WC()->customer;
+ }
+
+ if ( ! empty( $customer ) ) {
+ $location = $customer->get_taxable_address();
} elseif ( wc_prices_include_tax() || 'base' === get_option( 'woocommerce_default_customer_address' ) || 'base' === get_option( 'woocommerce_tax_based_on' ) ) {
$location = array(
WC()->countries->get_base_country(),
@@ -468,17 +473,19 @@ class WC_Tax {
);
}
- return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class );
+ return apply_filters( 'woocommerce_get_tax_location', $location, $tax_class, $customer );
}
/**
* Get's an array of matching rates for a tax class.
- * @param string $tax_class
+ *
+ * @param string $tax_class Tax class to get rates for.
+ * @param object $customer Override the customer object to get their location.
* @return array
*/
- public static function get_rates( $tax_class = '' ) {
+ public static function get_rates( $tax_class = '', $customer = null ) {
$tax_class = sanitize_title( $tax_class );
- $location = self::get_tax_location( $tax_class );
+ $location = self::get_tax_location( $tax_class, $customer );
$matched_tax_rates = array();
if ( sizeof( $location ) === 4 ) {
@@ -526,10 +533,11 @@ class WC_Tax {
/**
* Gets an array of matching shipping tax rates for a given class.
*
- * @param string Tax Class
- * @return mixed
+ * @param string $tax_class Tax class to get rates for.
+ * @param object $customer Override the customer object to get their location.
+ * @return mixed
*/
- public static function get_shipping_tax_rates( $tax_class = null ) {
+ public static function get_shipping_tax_rates( $tax_class = null, $customer = null ) {
// See if we have an explicitly set shipping tax class
$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
@@ -537,7 +545,7 @@ class WC_Tax {
$tax_class = $shipping_tax_class;
}
- $location = self::get_tax_location( $tax_class );
+ $location = self::get_tax_location( $tax_class, $customer );
$matched_tax_rates = array();
if ( sizeof( $location ) === 4 ) {
@@ -553,12 +561,17 @@ class WC_Tax {
'tax_class' => $tax_class,
) );
- } else {
+ } elseif ( WC()->cart->get_cart() ) {
// This will be per order shipping - loop through the order and find the highest tax class rate
$cart_tax_classes = WC()->cart->get_cart_item_tax_classes();
- // If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additonal tax class' section.
+ // No tax classes = no taxable items.
+ if ( empty( $cart_tax_classes ) ) {
+ return array();
+ }
+
+ // If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additional tax class' section.
if ( sizeof( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes ) ) {
$tax_classes = self::get_tax_class_slugs();
@@ -916,7 +929,6 @@ class WC_Tax {
*
* @param int $tax_rate_id
* @param string $postcodes String of postcodes separated by ; characters
- * @return string
*/
public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) {
if ( ! is_array( $postcodes ) ) {
@@ -939,7 +951,6 @@ class WC_Tax {
*
* @param int $tax_rate_id
* @param string $cities
- * @return string
*/
public static function _update_tax_rate_cities( $tax_rate_id, $cities ) {
if ( ! is_array( $cities ) ) {
@@ -961,8 +972,6 @@ class WC_Tax {
* @param int $tax_rate_id
* @param array $values
* @param string $type
- *
- * @return string
*/
private static function _update_tax_rate_locations( $tax_rate_id, $values, $type ) {
global $wpdb;
diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php
new file mode 100644
index 00000000000..2e14dda15f8
--- /dev/null
+++ b/includes/class-woocommerce.php
@@ -0,0 +1,661 @@
+$key();
+ }
+ }
+
+ /**
+ * WooCommerce Constructor.
+ */
+ public function __construct() {
+ $this->define_constants();
+ $this->includes();
+ $this->init_hooks();
+
+ do_action( 'woocommerce_loaded' );
+ }
+
+ /**
+ * Hook into actions and filters.
+ *
+ * @since 2.3
+ */
+ private function init_hooks() {
+ register_activation_hook( WC_PLUGIN_FILE, array( 'WC_Install', 'install' ) );
+ register_shutdown_function( array( $this, 'log_errors' ) );
+ add_action( 'after_setup_theme', array( $this, 'setup_environment' ) );
+ add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 );
+ add_action( 'init', array( $this, 'init' ), 0 );
+ add_action( 'init', array( 'WC_Shortcodes', 'init' ) );
+ add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) );
+ add_action( 'init', array( $this, 'wpdb_table_fix' ), 0 );
+ add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 );
+ }
+
+ /**
+ * Ensures fatal errors are logged so they can be picked up in the status report.
+ *
+ * @since 3.2.0
+ */
+ public function log_errors() {
+ $error = error_get_last();
+ if ( E_ERROR === $error['type'] ) {
+ $logger = wc_get_logger();
+ $logger->critical(
+ $error['message'] . PHP_EOL,
+ array(
+ 'source' => 'fatal-errors',
+ )
+ );
+ }
+ }
+
+ /**
+ * Define WC Constants.
+ */
+ private function define_constants() {
+ $upload_dir = wp_upload_dir();
+
+ $this->define( 'WC_ABSPATH', dirname( WC_PLUGIN_FILE ) . '/' );
+ $this->define( 'WC_PLUGIN_BASENAME', plugin_basename( WC_PLUGIN_FILE ) );
+ $this->define( 'WC_VERSION', $this->version );
+ $this->define( 'WOOCOMMERCE_VERSION', $this->version );
+ $this->define( 'WC_ROUNDING_PRECISION', 4 );
+ $this->define( 'WC_DISCOUNT_ROUNDING_MODE', 2 );
+ $this->define( 'WC_TAX_ROUNDING_MODE', 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1 );
+ $this->define( 'WC_DELIMITER', '|' );
+ $this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' );
+ $this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' );
+ $this->define( 'WC_TEMPLATE_DEBUG_MODE', false );
+ }
+
+ /**
+ * Define constant if not already set.
+ *
+ * @param string $name Constant name.
+ * @param string|bool $value Constant value.
+ */
+ private function define( $name, $value ) {
+ if ( ! defined( $name ) ) {
+ define( $name, $value );
+ }
+ }
+
+ /**
+ * What type of request is this?
+ *
+ * @param string $type admin, ajax, cron or frontend.
+ * @return bool
+ */
+ private function is_request( $type ) {
+ switch ( $type ) {
+ case 'admin' :
+ return is_admin();
+ case 'ajax' :
+ return defined( 'DOING_AJAX' );
+ case 'cron' :
+ return defined( 'DOING_CRON' );
+ case 'frontend' :
+ return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
+ }
+ }
+
+ /**
+ * Check the active theme.
+ *
+ * @since 2.6.9
+ * @param string $theme Theme slug to check.
+ * @return bool
+ */
+ private function is_active_theme( $theme ) {
+ return get_template() === $theme;
+ }
+
+ /**
+ * Include required core files used in admin and on the frontend.
+ */
+ public function includes() {
+ /**
+ * Class autoloader.
+ */
+ include_once( WC_ABSPATH . 'includes/class-wc-autoloader.php' );
+
+ /**
+ * Interfaces.
+ */
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-abstract-order-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-coupon-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-object-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-product-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-type-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-refund-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-payment-token-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-variable-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-shipping-zone-data-store-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-logger-interface.php' );
+ include_once( WC_ABSPATH . 'includes/interfaces/class-wc-log-handler-interface.php' );
+
+ /**
+ * Abstract classes.
+ */
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-object-query.php' ); // WC_Object_Query for CRUD.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations).
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service.
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' );
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-deprecated-hooks.php' );
+ include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-session.php' );
+
+ /**
+ * Core classes.
+ */
+ include_once( WC_ABSPATH . 'includes/wc-core-functions.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-datetime.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types.
+ include_once( WC_ABSPATH . 'includes/class-wc-install.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-geolocation.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-download-handler.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-comments.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-post-data.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-ajax.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-emails.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-query.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-meta-data.php' ); // Meta data internal object
+ include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory.
+ include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query.
+ include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory.
+ include_once( WC_ABSPATH . 'includes/class-wc-product-query.php' ); // Product query
+ include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller.
+ include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' );
+ include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway.
+ include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway.
+ include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states.
+ include_once( WC_ABSPATH . 'includes/class-wc-integrations.php' ); // Loads integrations.
+ include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper.
+ include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper.
+ include_once( WC_ABSPATH . 'includes/class-wc-deprecated-action-hooks.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-discounts.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-cart-totals.php' );
+
+ /**
+ * Data stores - used to store and retrieve CRUD object data from the database.
+ */
+ include_once( WC_ABSPATH . 'includes/class-wc-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-wp.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-coupon-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-grouped-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variable-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variation-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-item-type-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-coupon-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-fee-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-product-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-shipping-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-tax-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' );
+ include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-refund-data-store-cpt.php' );
+
+ /**
+ * REST API.
+ */
+ include_once( WC_ABSPATH . 'includes/class-wc-legacy-api.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class.
+ include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class.
+ include_once( WC_ABSPATH . 'includes/class-wc-register-wp-admin-settings.php' );
+
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ include_once( WC_ABSPATH . 'includes/class-wc-cli.php' );
+ }
+
+ if ( $this->is_request( 'admin' ) ) {
+ include_once( WC_ABSPATH . 'includes/admin/class-wc-admin.php' );
+ }
+
+ if ( $this->is_request( 'frontend' ) ) {
+ $this->frontend_includes();
+ }
+
+ if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) {
+ include_once( WC_ABSPATH . 'includes/class-wc-session-handler.php' );
+ }
+
+ if ( $this->is_request( 'cron' ) && 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ) {
+ include_once( WC_ABSPATH . 'includes/class-wc-tracker.php' );
+ }
+
+ $this->query = new WC_Query();
+ $this->api = new WC_API();
+ }
+
+ /**
+ * Include required frontend files.
+ */
+ public function frontend_includes() {
+ include_once( WC_ABSPATH . 'includes/wc-cart-functions.php' );
+ include_once( WC_ABSPATH . 'includes/wc-notice-functions.php' );
+ include_once( WC_ABSPATH . 'includes/wc-template-hooks.php' );
+ include_once( WC_ABSPATH . 'includes/class-wc-template-loader.php' ); // Template Loader.
+ include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts.
+ include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers.
+ include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class.
+ include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class.
+ include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class.
+ include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class.
+ include_once( WC_ABSPATH . 'includes/class-wc-shortcodes.php' ); // Shortcodes class.
+ include_once( WC_ABSPATH . 'includes/class-wc-embed.php' ); // Embeds.
+ include_once( WC_ABSPATH . 'includes/class-wc-structured-data.php' ); // Structured Data class.
+
+ if ( $this->is_active_theme( 'twentyseventeen' ) ) {
+ include_once( WC_ABSPATH . 'includes/theme-support/class-wc-twenty-seventeen.php' );
+ }
+ }
+
+ /**
+ * Function used to Init WooCommerce Template Functions - This makes them pluggable by plugins and themes.
+ */
+ public function include_template_functions() {
+ include_once( WC_ABSPATH . 'includes/wc-template-functions.php' );
+ }
+
+ /**
+ * Init WooCommerce when WordPress Initialises.
+ */
+ public function init() {
+ // Before init action.
+ do_action( 'before_woocommerce_init' );
+
+ // Set up localisation.
+ $this->load_plugin_textdomain();
+
+ // Load class instances.
+ $this->product_factory = new WC_Product_Factory(); // Product Factory to create new product instances.
+ $this->order_factory = new WC_Order_Factory(); // Order Factory to create new order instances.
+ $this->countries = new WC_Countries(); // Countries class.
+ $this->integrations = new WC_Integrations(); // Integrations class.
+ $this->structured_data = new WC_Structured_Data(); // Structured Data class, generates and handles structured data.
+ $this->deprecated_hook_handlers['actions'] = new WC_Deprecated_Action_Hooks();
+ $this->deprecated_hook_handlers['filters'] = new WC_Deprecated_Filter_Hooks();
+
+ // Session class, handles session data for users - can be overwritten if custom handler is needed.
+ if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) {
+ $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
+ $this->session = new $session_class();
+ }
+
+ // Classes/actions loaded for the frontend and for ajax requests.
+ if ( $this->is_request( 'frontend' ) ) {
+ $this->cart = new WC_Cart(); // Cart class, stores the cart contents.
+ $this->customer = new WC_Customer( get_current_user_id(), true ); // Customer class, handles data such as customer location.
+ add_action( 'shutdown', array( $this->customer, 'save' ), 10 ); // Customer should be saved during shutdown.
+ }
+
+ $this->load_webhooks();
+
+ // Init action.
+ do_action( 'woocommerce_init' );
+ }
+
+ /**
+ * Load Localisation files.
+ *
+ * Note: the first-loaded translation file overrides any following ones if the same translation is present.
+ *
+ * Locales found in:
+ * - WP_LANG_DIR/woocommerce/woocommerce-LOCALE.mo
+ * - WP_LANG_DIR/plugins/woocommerce-LOCALE.mo
+ */
+ public function load_plugin_textdomain() {
+ $locale = is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
+ $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce' );
+
+ unload_textdomain( 'woocommerce' );
+ load_textdomain( 'woocommerce', WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo' );
+ load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( WC_PLUGIN_FILE ) ) . '/i18n/languages' );
+ }
+
+ /**
+ * Ensure theme and server variable compatibility and setup image sizes.
+ */
+ public function setup_environment() {
+ /* @deprecated 2.2 Use WC()->template_path() instead. */
+ $this->define( 'WC_TEMPLATE_PATH', $this->template_path() );
+
+ $this->add_thumbnail_support();
+ $this->add_image_sizes();
+ }
+
+ /**
+ * Ensure post thumbnail support is turned on.
+ */
+ private function add_thumbnail_support() {
+ if ( ! current_theme_supports( 'post-thumbnails' ) ) {
+ add_theme_support( 'post-thumbnails' );
+ }
+ add_post_type_support( 'product', 'thumbnail' );
+ }
+
+ /**
+ * Add WC Image sizes to WP.
+ *
+ * @since 2.3
+ */
+ private function add_image_sizes() {
+ $shop_thumbnail = wc_get_image_size( 'shop_thumbnail' );
+ $shop_catalog = wc_get_image_size( 'shop_catalog' );
+ $shop_single = wc_get_image_size( 'shop_single' );
+
+ add_image_size( 'shop_thumbnail', $shop_thumbnail['width'], $shop_thumbnail['height'], $shop_thumbnail['crop'] );
+ add_image_size( 'shop_catalog', $shop_catalog['width'], $shop_catalog['height'], $shop_catalog['crop'] );
+ add_image_size( 'shop_single', $shop_single['width'], $shop_single['height'], $shop_single['crop'] );
+ }
+
+ /**
+ * Get the plugin url.
+ *
+ * @return string
+ */
+ public function plugin_url() {
+ return untrailingslashit( plugins_url( '/', WC_PLUGIN_FILE ) );
+ }
+
+ /**
+ * Get the plugin path.
+ *
+ * @return string
+ */
+ public function plugin_path() {
+ return untrailingslashit( plugin_dir_path( WC_PLUGIN_FILE ) );
+ }
+
+ /**
+ * Get the template path.
+ *
+ * @return string
+ */
+ public function template_path() {
+ return apply_filters( 'woocommerce_template_path', 'woocommerce/' );
+ }
+
+ /**
+ * Get Ajax URL.
+ *
+ * @return string
+ */
+ public function ajax_url() {
+ return admin_url( 'admin-ajax.php', 'relative' );
+ }
+
+ /**
+ * Return the WC API URL for a given request.
+ *
+ * @param string $request Requested endpoint.
+ * @param bool|null $ssl If should use SSL, null if should auto detect. Default: null.
+ * @return string
+ */
+ public function api_request_url( $request, $ssl = null ) {
+ if ( is_null( $ssl ) ) {
+ $scheme = parse_url( home_url(), PHP_URL_SCHEME );
+ } elseif ( $ssl ) {
+ $scheme = 'https';
+ } else {
+ $scheme = 'http';
+ }
+
+ if ( strstr( get_option( 'permalink_structure' ), '/index.php/' ) ) {
+ $api_request_url = trailingslashit( home_url( '/index.php/wc-api/' . $request, $scheme ) );
+ } elseif ( get_option( 'permalink_structure' ) ) {
+ $api_request_url = trailingslashit( home_url( '/wc-api/' . $request, $scheme ) );
+ } else {
+ $api_request_url = add_query_arg( 'wc-api', $request, trailingslashit( home_url( '', $scheme ) ) );
+ }
+
+ return esc_url_raw( apply_filters( 'woocommerce_api_request_url', $api_request_url, $request, $ssl ) );
+ }
+
+ /**
+ * Load & enqueue active webhooks.
+ *
+ * @since 2.2
+ */
+ private function load_webhooks() {
+
+ if ( ! is_blog_installed() ) {
+ return;
+ }
+
+ if ( false === ( $webhooks = get_transient( 'woocommerce_webhook_ids' ) ) ) {
+ $webhooks = get_posts( array(
+ 'fields' => 'ids',
+ 'post_type' => 'shop_webhook',
+ 'post_status' => 'publish',
+ 'posts_per_page' => -1,
+ ) );
+ set_transient( 'woocommerce_webhook_ids', $webhooks );
+ }
+ foreach ( $webhooks as $webhook_id ) {
+ $webhook = new WC_Webhook( $webhook_id );
+ $webhook->enqueue();
+ }
+ }
+
+ /**
+ * WooCommerce Payment Token Meta API and Term/Order item Meta - set table names.
+ */
+ public function wpdb_table_fix() {
+ global $wpdb;
+ $wpdb->payment_tokenmeta = $wpdb->prefix . 'woocommerce_payment_tokenmeta';
+ $wpdb->order_itemmeta = $wpdb->prefix . 'woocommerce_order_itemmeta';
+ $wpdb->tables[] = 'woocommerce_payment_tokenmeta';
+ $wpdb->tables[] = 'woocommerce_order_itemmeta';
+
+ if ( get_option( 'db_version' ) < 34370 ) {
+ $wpdb->woocommerce_termmeta = $wpdb->prefix . 'woocommerce_termmeta';
+ $wpdb->tables[] = 'woocommerce_termmeta';
+ }
+ }
+
+ /**
+ * Get Checkout Class.
+ *
+ * @return WC_Checkout
+ */
+ public function checkout() {
+ return WC_Checkout::instance();
+ }
+
+ /**
+ * Get gateways class.
+ *
+ * @return WC_Payment_Gateways
+ */
+ public function payment_gateways() {
+ return WC_Payment_Gateways::instance();
+ }
+
+ /**
+ * Get shipping class.
+ *
+ * @return WC_Shipping
+ */
+ public function shipping() {
+ return WC_Shipping::instance();
+ }
+
+ /**
+ * Email Class.
+ *
+ * @return WC_Emails
+ */
+ public function mailer() {
+ return WC_Emails::instance();
+ }
+}
diff --git a/includes/cli/class-wc-cli-rest-command.php b/includes/cli/class-wc-cli-rest-command.php
index daad9e4ed73..363993c1764 100644
--- a/includes/cli/class-wc-cli-rest-command.php
+++ b/includes/cli/class-wc-cli-rest-command.php
@@ -94,7 +94,7 @@ class WC_CLI_REST_Command {
}
/**
- * Peturns an ID of supported ID arguments (things like product_id, order_id, etc) that we should look for in addition to id.
+ * Returns an ID of supported ID arguments (things like product_id, order_id, etc) that we should look for in addition to id.
*
* @return array
*/
diff --git a/includes/data-stores/abstract-wc-order-item-type-data-store.php b/includes/data-stores/abstract-wc-order-item-type-data-store.php
index 61d8e71de46..9bbf98f4a6c 100644
--- a/includes/data-stores/abstract-wc-order-item-type-data-store.php
+++ b/includes/data-stores/abstract-wc-order-item-type-data-store.php
@@ -137,11 +137,12 @@ abstract class Abstract_WC_Order_Item_Type_Data_Store extends WC_Data_Store_WP i
public function save_item_data( &$item ) {}
/**
- * Clear meta cachce.
+ * Clear meta cache.
*
* @param WC_Order_Item $item
*/
public function clear_cache( &$item ) {
wp_cache_delete( 'item-' . $item->get_id(), 'order-items' );
+ wp_cache_delete( 'order-items-' . $item->get_order_id(), 'orders' );
}
}
diff --git a/includes/data-stores/class-wc-customer-data-store.php b/includes/data-stores/class-wc-customer-data-store.php
index d6596487d5c..1a0f800161a 100644
--- a/includes/data-stores/class-wc-customer-data-store.php
+++ b/includes/data-stores/class-wc-customer-data-store.php
@@ -369,8 +369,9 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
/**
* Search customers and return customer IDs.
*
- * @param string $term
- * @oaram int|string $limit @since 3.0.7
+ * @param string $term
+ * @param int|string $limit @since 3.0.7
+ *
* @return array
*/
public function search_customers( $term, $limit = '' ) {
diff --git a/includes/data-stores/class-wc-data-store-wp.php b/includes/data-stores/class-wc-data-store-wp.php
index 821b16a91ab..22d5881e5b3 100644
--- a/includes/data-stores/class-wc-data-store-wp.php
+++ b/includes/data-stores/class-wc-data-store-wp.php
@@ -76,7 +76,8 @@ class WC_Data_Store_WP {
", $object->get_id() ) );
$this->internal_meta_keys = array_merge( array_map( array( $this, 'prefix_key' ), $object->get_data_keys() ), $this->internal_meta_keys );
- return array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
+ $meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
+ return apply_filters( "woocommerce_data_store_wp_{$this->meta_type}_read_meta", $meta_data, $object, $this );
}
/**
@@ -85,7 +86,6 @@ class WC_Data_Store_WP {
* @since 3.0.0
* @param WC_Data
* @param stdClass (containing at least ->id)
- * @return array
*/
public function delete_meta( &$object, $meta ) {
delete_metadata_by_mid( $this->meta_type, $meta->id );
@@ -418,4 +418,14 @@ class WC_Data_Store_WP {
return $wp_query_args;
}
+
+ /**
+ * Return list of internal meta keys.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ public function get_internal_meta_keys() {
+ return $this->internal_meta_keys;
+ }
}
diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php
index 5fa2b9e0d4d..9bffbfacdb2 100644
--- a/includes/data-stores/class-wc-product-data-store-cpt.php
+++ b/includes/data-stores/class-wc-product-data-store-cpt.php
@@ -45,6 +45,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
'_product_attributes',
'_virtual',
'_downloadable',
+ '_download_limit',
+ '_download_expiry',
'_featured',
'_downloadable_files',
'_wc_rating_count',
@@ -1168,124 +1170,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* @return array|object
*/
public function get_products( $args = array() ) {
- /**
- * Generate WP_Query args.
- */
- $wp_query_args = array(
- 'post_status' => $args['status'],
- 'posts_per_page' => $args['limit'],
- 'meta_query' => array(),
- 'orderby' => $args['orderby'],
- 'order' => $args['order'],
- 'tax_query' => array(),
- );
-
- if ( 'variation' === $args['type'] ) {
- $wp_query_args['post_type'] = 'product_variation';
- } elseif ( is_array( $args['type'] ) && in_array( 'variation', $args['type'] ) ) {
- $wp_query_args['post_type'] = array( 'product_variation', 'product' );
- $wp_query_args['tax_query'][] = array(
- 'relation' => 'OR',
- array(
- 'taxonomy' => 'product_type',
- 'field' => 'slug',
- 'terms' => $args['type'],
- ),
- array(
- 'taxonomy' => 'product_type',
- 'field' => 'id',
- 'operator' => 'NOT EXISTS',
- ),
- );
- } else {
- $wp_query_args['post_type'] = 'product';
- $wp_query_args['tax_query'][] = array(
- 'taxonomy' => 'product_type',
- 'field' => 'slug',
- 'terms' => $args['type'],
- );
- }
-
- // Do not load unnecessary post data if the user only wants IDs.
- if ( 'ids' === $args['return'] ) {
- $wp_query_args['fields'] = 'ids';
- }
-
- if ( ! empty( $args['sku'] ) ) {
- $wp_query_args['meta_query'][] = array(
- 'key' => '_sku',
- 'value' => $args['sku'],
- 'compare' => 'LIKE',
- );
- }
-
- if ( ! empty( $args['category'] ) ) {
- $wp_query_args['tax_query'][] = array(
- 'taxonomy' => 'product_cat',
- 'field' => 'slug',
- 'terms' => $args['category'],
- );
- }
-
- if ( ! empty( $args['tag'] ) ) {
- $wp_query_args['tax_query'][] = array(
- 'taxonomy' => 'product_tag',
- 'field' => 'slug',
- 'terms' => $args['tag'],
- );
- }
-
- if ( ! empty( $args['shipping_class'] ) ) {
- $wp_query_args['tax_query'][] = array(
- 'taxonomy' => 'product_shipping_class',
- 'field' => 'slug',
- 'terms' => $args['shipping_class'],
- );
- }
-
- if ( ! is_null( $args['parent'] ) ) {
- $wp_query_args['post_parent'] = absint( $args['parent'] );
- }
-
- if ( ! is_null( $args['offset'] ) ) {
- $wp_query_args['offset'] = absint( $args['offset'] );
- } else {
- $wp_query_args['paged'] = absint( $args['page'] );
- }
-
- if ( ! empty( $args['include'] ) ) {
- $wp_query_args['post__in'] = array_map( 'absint', $args['include'] );
- }
-
- if ( ! empty( $args['exclude'] ) ) {
- $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] );
- }
-
- if ( ! $args['paginate'] ) {
- $wp_query_args['no_found_rows'] = true;
- }
-
- // Get results.
- $products = new WP_Query( $wp_query_args );
-
- if ( 'objects' === $args['return'] ) {
- // Prime caches before grabbing objects.
- update_post_caches( $products->posts, array( 'product', 'product_variation' ) );
-
- $return = array_filter( array_map( 'wc_get_product', $products->posts ) );
- } else {
- $return = $products->posts;
- }
-
- if ( $args['paginate'] ) {
- return (object) array(
- 'products' => $return,
- 'total' => $products->found_posts,
- 'max_num_pages' => $products->max_num_pages,
- );
- } else {
- return $return;
- }
+ $query = new WC_Product_Query( $args );
+ return $query->get_products();
}
/**
@@ -1372,4 +1258,295 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
return false;
}
}
+
+ /**
+ * Add ability to get products by 'reviews_allowed' in WC_Product_Query.
+ *
+ * @since 3.2.0
+ * @param string $where where clause
+ * @param WP_Query $wp_query
+ */
+ public function reviews_allowed_query_where( $where, $wp_query ) {
+ global $wpdb;
+
+ if ( isset( $wp_query->query_vars['reviews_allowed'] ) && is_bool( $wp_query->query_vars['reviews_allowed'] ) ) {
+ if ( $wp_query->query_vars['reviews_allowed'] ) {
+ $where .= " AND $wpdb->posts.comment_status = 'open'";
+ } else {
+ $where .= " AND $wpdb->posts.comment_status = 'closed'";
+ }
+ }
+
+ return $where;
+ }
+
+ /**
+ * Get valid WP_Query args from a WC_Product_Query's query variables.
+ *
+ * @since 3.2.0
+ * @param array $query_vars query vars from a WC_Product_Query
+ * @return array
+ */
+ protected function get_wp_query_args( $query_vars ) {
+
+ // Map query vars to ones that get_wp_query_args or WP_Query recognize.
+ $key_mapping = array(
+ 'status' => 'post_status',
+ 'page' => 'paged',
+ 'include' => 'post__in',
+ 'stock_quantity' => 'stock',
+ 'average_rating' => 'wc_average_rating',
+ 'review_count' => 'wc_review_count',
+ );
+ foreach ( $key_mapping as $query_key => $db_key ) {
+ if ( isset( $query_vars[ $query_key ] ) ) {
+ $query_vars[ $db_key ] = $query_vars[ $query_key ];
+ unset( $query_vars[ $query_key ] );
+ }
+ }
+
+ // Map boolean queries that are stored as 'yes'/'no' in the DB to 'yes' or 'no'.
+ $boolean_queries = array(
+ 'virtual',
+ 'downloadable',
+ 'sold_individually',
+ 'manage_stock',
+ );
+ foreach ( $boolean_queries as $boolean_query ) {
+ if ( isset( $query_vars[ $boolean_query ] ) && '' !== $query_vars[ $boolean_query ] ) {
+ $query_vars[ $boolean_query ] = $query_vars[ $boolean_query ] ? 'yes' : 'no';
+ }
+ }
+
+ // These queries cannot be auto-generated so we have to remove them and build them manually.
+ $manual_queries = array(
+ 'sku' => '',
+ 'featured' => '',
+ 'visibility' => '',
+ );
+ foreach ( $manual_queries as $key => $manual_query ) {
+ if ( isset( $query_vars[ $key ] ) ) {
+ $manual_queries[ $key ] = $query_vars[ $key ];
+ unset( $query_vars[ $key ] );
+ }
+ }
+
+ $wp_query_args = parent::get_wp_query_args( $query_vars );
+
+ if ( ! isset( $wp_query_args['date_query'] ) ) {
+ $wp_query_args['date_query'] = array();
+ }
+ if ( ! isset( $wp_query_args['meta_query'] ) ) {
+ $wp_query_args['meta_query'] = array();
+ }
+
+ // Handle product types.
+ if ( 'variation' === $query_vars['type'] ) {
+ $wp_query_args['post_type'] = 'product_variation';
+ } elseif ( is_array( $query_vars['type'] ) && in_array( 'variation', $query_vars['type'] ) ) {
+ $wp_query_args['post_type'] = array( 'product_variation', 'product' );
+ $wp_query_args['tax_query'][] = array(
+ 'relation' => 'OR',
+ array(
+ 'taxonomy' => 'product_type',
+ 'field' => 'slug',
+ 'terms' => $query_vars['type'],
+ ),
+ array(
+ 'taxonomy' => 'product_type',
+ 'field' => 'id',
+ 'operator' => 'NOT EXISTS',
+ ),
+ );
+ } else {
+ $wp_query_args['post_type'] = 'product';
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_type',
+ 'field' => 'slug',
+ 'terms' => $query_vars['type'],
+ );
+ }
+
+ // Handle product categories.
+ if ( ! empty( $query_vars['category'] ) ) {
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_cat',
+ 'field' => 'slug',
+ 'terms' => $query_vars['category'],
+ );
+ }
+
+ // Handle product tags.
+ if ( ! empty( $query_vars['tag'] ) ) {
+ unset( $wp_query_args['tag'] );
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_tag',
+ 'field' => 'slug',
+ 'terms' => $query_vars['tag'],
+ );
+ }
+
+ // Handle shipping classes.
+ if ( ! empty( $query_vars['shipping_class'] ) ) {
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_shipping_class',
+ 'field' => 'slug',
+ 'terms' => $query_vars['shipping_class'],
+ );
+ }
+
+ // Handle total_sales.
+ // This query doesn't get auto-generated since the meta key doesn't have the underscore prefix.
+ if ( isset( $query_vars['total_sales'] ) && '' !== $query_vars['total_sales'] ) {
+ $wp_query_args['meta_query'][] = array(
+ 'key' => 'total_sales',
+ 'value' => absint( $query_vars['total_sales'] ),
+ 'compare' => '=',
+ );
+ }
+
+ // Handle SKU.
+ if ( $manual_queries['sku'] ) {
+ $wp_query_args['meta_query'][] = array(
+ 'key' => '_sku',
+ 'value' => $manual_queries['sku'],
+ 'compare' => 'LIKE',
+ );
+ }
+
+ // Handle featured.
+ if ( '' !== $manual_queries['featured'] ) {
+ $product_visibility_term_ids = wc_get_product_visibility_term_ids();
+ if ( $manual_queries['featured'] ) {
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'term_taxonomy_id',
+ 'terms' => array( $product_visibility_term_ids['featured'] ),
+ );
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'term_taxonomy_id',
+ 'terms' => array( $product_visibility_term_ids['exclude-from-catalog'] ),
+ 'operator' => 'NOT IN',
+ );
+ } else {
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'term_taxonomy_id',
+ 'terms' => array( $product_visibility_term_ids['featured'] ),
+ 'operator' => 'NOT IN',
+ );
+ }
+ }
+
+ // Handle visibility.
+ if ( $manual_queries['visibility'] ) {
+ switch ( $manual_queries['visibility'] ) {
+ case 'search':
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'slug',
+ 'terms' => array( 'exclude-from-search' ),
+ 'operator' => 'NOT IN',
+ );
+ break;
+ case 'catalog':
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'slug',
+ 'terms' => array( 'exclude-from-catalog' ),
+ 'operator' => 'NOT IN',
+ );
+ break;
+ case 'visible':
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'slug',
+ 'terms' => array( 'exclude-from-catalog', 'exclude-from-search' ),
+ 'operator' => 'NOT IN',
+ );
+ break;
+ case 'hidden':
+ $wp_query_args['tax_query'][] = array(
+ 'taxonomy' => 'product_visibility',
+ 'field' => 'slug',
+ 'terms' => array( 'exclude-from-catalog', 'exclude-from-search' ),
+ 'operator' => 'AND',
+ );
+ break;
+ }
+ }
+
+ // Handle date queries.
+ $date_queries = array(
+ 'date_created' => 'post_date',
+ 'date_modified' => 'post_modified',
+ 'date_on_sale_from' => '_sale_price_dates_from',
+ 'date_on_sale_to' => '_sale_price_dates_to',
+ );
+ foreach ( $date_queries as $query_var_key => $db_key ) {
+ if ( isset( $query_vars[ $query_var_key ] ) && '' !== $query_vars[ $query_var_key ] ) {
+
+ // Remove any existing meta queries for the same keys to prevent conflicts.
+ $existing_queries = wp_list_pluck( $wp_query_args['meta_query'], 'key', true );
+ foreach ( $existing_queries as $query_index => $query_contents ) {
+ unset( $wp_query_args['meta_query'][ $query_index ] );
+ }
+
+ $wp_query_args = $this->parse_date_for_wp_query( $query_vars[ $query_var_key ], $db_key, $wp_query_args );
+ }
+ }
+
+ // Handle paginate.
+ if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) {
+ $wp_query_args['no_found_rows'] = true;
+ }
+
+ // Handle reviews_allowed.
+ if ( isset( $query_vars['reviews_allowed'] ) && is_bool( $query_vars['reviews_allowed'] ) ) {
+ add_filter( 'posts_where', array( $this, 'reviews_allowed_query_where' ), 10, 2 );
+ }
+
+ return apply_filters( 'woocommerce_product_data_store_cpt_get_products_query', $wp_query_args, $query_vars, $this );
+ }
+
+ /**
+ * Query for Products matching specific criteria.
+ *
+ * @since 3.2.0
+ *
+ * @param array $query_vars query vars from a WC_Product_Query
+ *
+ * @return array|object
+ */
+ public function query( $query_vars ) {
+ $args = $this->get_wp_query_args( $query_vars );
+
+ if ( ! empty( $args['errors'] ) ) {
+ $query = (object) array(
+ 'posts' => array(),
+ 'found_posts' => 0,
+ 'max_num_pages' => 0,
+ );
+ } else {
+ $query = new WP_Query( $args );
+ }
+
+ if ( isset( $query_vars['return'] ) && 'objects' === $query_vars['return'] && ! empty( $query->posts ) ) {
+ // Prime caches before grabbing objects.
+ update_post_caches( $products->posts, array( 'product', 'product_variation' ) );
+ }
+
+ $products = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_product', $query->posts ) );
+
+ if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) {
+ return (object) array(
+ 'products' => $products,
+ 'total' => $query->found_posts,
+ 'max_num_pages' => $query->max_num_pages,
+ );
+ }
+
+ return $products;
+ }
}
diff --git a/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index dab9847dbdf..70e1112ae2a 100644
--- a/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -29,6 +29,10 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
protected function read_product_data( &$product ) {
parent::read_product_data( $product );
+ // Make sure data which does not apply to variables is unset.
+ $product->set_regular_price( '' );
+ $product->set_sale_price( '' );
+
// Set directly since individual data needs changed at the WC_Product_Variation level -- these datasets just pull.
$children = $this->read_children( $product );
$product->set_children( $children['all'] );
@@ -227,7 +231,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
/**
* Create unique cache key based on the tax location (affects displayed/cached prices), product version and active price filters.
- * DEVELOPERS should filter this hash if offering conditonal pricing to keep it unique.
+ * DEVELOPERS should filter this hash if offering conditional pricing to keep it unique.
*
* @since 3.0.0
* @param WC_Product
diff --git a/includes/data-stores/class-wc-shipping-zone-data-store.php b/includes/data-stores/class-wc-shipping-zone-data-store.php
index b1cff12151e..f9ff319439f 100644
--- a/includes/data-stores/class-wc-shipping-zone-data-store.php
+++ b/includes/data-stores/class-wc-shipping-zone-data-store.php
@@ -220,7 +220,7 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin
SELECT zones.zone_id FROM {$wpdb->prefix}woocommerce_shipping_zones as zones
LEFT OUTER JOIN {$wpdb->prefix}woocommerce_shipping_zone_locations as locations ON zones.zone_id = locations.zone_id AND location_type != 'postcode'
WHERE " . implode( ' ', $criteria ) . "
- ORDER BY zone_order ASC LIMIT 1
+ ORDER BY zone_order ASC, zone_id ASC LIMIT 1
" );
}
@@ -232,7 +232,7 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin
*/
public function get_zones() {
global $wpdb;
- return $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC;" );
+ return $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC, zone_id ASC;" );
}
diff --git a/includes/emails/class-wc-email-cancelled-order.php b/includes/emails/class-wc-email-cancelled-order.php
index 8dbac9969c3..1791a3fd402 100644
--- a/includes/emails/class-wc-email-cancelled-order.php
+++ b/includes/emails/class-wc-email-cancelled-order.php
@@ -23,11 +23,16 @@ class WC_Email_Cancelled_Order extends WC_Email {
* Constructor.
*/
public function __construct() {
- $this->id = 'cancelled_order';
- $this->title = __( 'Cancelled order', 'woocommerce' );
- $this->description = __( 'Cancelled order emails are sent to chosen recipient(s) when orders have been marked cancelled (if they were previously processing or on-hold).', 'woocommerce' );
- $this->template_html = 'emails/admin-cancelled-order.php';
- $this->template_plain = 'emails/plain/admin-cancelled-order.php';
+ $this->id = 'cancelled_order';
+ $this->title = __( 'Cancelled order', 'woocommerce' );
+ $this->description = __( 'Cancelled order emails are sent to chosen recipient(s) when orders have been marked cancelled (if they were previously processing or on-hold).', 'woocommerce' );
+ $this->template_html = 'emails/admin-cancelled-order.php';
+ $this->template_plain = 'emails/plain/admin-cancelled-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_processing_to_cancelled_notification', array( $this, 'trigger' ), 10, 2 );
@@ -72,11 +77,9 @@ class WC_Email_Cancelled_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-customer-completed-order.php b/includes/emails/class-wc-email-customer-completed-order.php
index 68d499750d9..c986ddbfa99 100644
--- a/includes/emails/class-wc-email-customer-completed-order.php
+++ b/includes/emails/class-wc-email-customer-completed-order.php
@@ -23,20 +23,22 @@ class WC_Email_Customer_Completed_Order extends WC_Email {
* Constructor.
*/
public function __construct() {
-
$this->id = 'customer_completed_order';
$this->customer_email = true;
-
$this->title = __( 'Completed order', 'woocommerce' );
$this->description = __( 'Order complete emails are sent to customers when their orders are marked completed and usually indicate that their orders have been shipped.', 'woocommerce' );
-
$this->template_html = 'emails/customer-completed-order.php';
$this->template_plain = 'emails/plain/customer-completed-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_completed_notification', array( $this, 'trigger' ), 10, 2 );
- // Call parent constuctor
+ // Call parent constructor
parent::__construct();
}
@@ -52,14 +54,10 @@ class WC_Email_Customer_Completed_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->recipient = $this->object->get_billing_email();
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->recipient = $this->object->get_billing_email();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-customer-invoice.php b/includes/emails/class-wc-email-customer-invoice.php
index d585621bde5..9cc37b15d46 100644
--- a/includes/emails/class-wc-email-customer-invoice.php
+++ b/includes/emails/class-wc-email-customer-invoice.php
@@ -37,14 +37,17 @@ class WC_Email_Customer_Invoice extends WC_Email {
* Constructor.
*/
public function __construct() {
-
$this->id = 'customer_invoice';
$this->customer_email = true;
-
$this->title = __( 'Customer invoice', 'woocommerce' );
$this->description = __( 'Customer invoice emails can be sent to customers containing their order information and payment links.', 'woocommerce' );
$this->template_html = 'emails/customer-invoice.php';
$this->template_plain = 'emails/plain/customer-invoice.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Call parent constructor
parent::__construct();
@@ -126,14 +129,10 @@ class WC_Email_Customer_Invoice extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->recipient = $this->object->get_billing_email();
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->recipient = $this->object->get_billing_email();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-customer-new-account.php b/includes/emails/class-wc-email-customer-new-account.php
index c9a0ddc25e6..c161cb0305e 100644
--- a/includes/emails/class-wc-email-customer-new-account.php
+++ b/includes/emails/class-wc-email-customer-new-account.php
@@ -51,13 +51,10 @@ class WC_Email_Customer_New_Account extends WC_Email {
* Constructor.
*/
public function __construct() {
-
$this->id = 'customer_new_account';
$this->customer_email = true;
-
$this->title = __( 'New account', 'woocommerce' );
$this->description = __( 'Customer "new account" emails are sent to the customer when a customer signs up via checkout or account pages.', 'woocommerce' );
-
$this->template_html = 'emails/customer-new-account.php';
$this->template_plain = 'emails/plain/customer-new-account.php';
diff --git a/includes/emails/class-wc-email-customer-note.php b/includes/emails/class-wc-email-customer-note.php
index 2c9bc939eec..7dda8552d21 100644
--- a/includes/emails/class-wc-email-customer-note.php
+++ b/includes/emails/class-wc-email-customer-note.php
@@ -30,15 +30,17 @@ class WC_Email_Customer_Note extends WC_Email {
* Constructor.
*/
public function __construct() {
-
$this->id = 'customer_note';
$this->customer_email = true;
-
$this->title = __( 'Customer note', 'woocommerce' );
$this->description = __( 'Customer note emails are sent when you add a note to an order.', 'woocommerce' );
-
$this->template_html = 'emails/customer-note.php';
$this->template_plain = 'emails/plain/customer-note.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers
add_action( 'woocommerce_new_customer_note_notification', array( $this, 'trigger' ) );
@@ -84,14 +86,10 @@ class WC_Email_Customer_Note extends WC_Email {
extract( $args );
if ( $order_id && ( $this->object = wc_get_order( $order_id ) ) ) {
- $this->recipient = $this->object->get_billing_email();
- $this->customer_note = $customer_note;
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->recipient = $this->object->get_billing_email();
+ $this->customer_note = $customer_note;
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
} else {
return;
}
diff --git a/includes/emails/class-wc-email-customer-on-hold-order.php b/includes/emails/class-wc-email-customer-on-hold-order.php
index 300e8516c00..772b8af7b69 100644
--- a/includes/emails/class-wc-email-customer-on-hold-order.php
+++ b/includes/emails/class-wc-email-customer-on-hold-order.php
@@ -23,13 +23,17 @@ class WC_Email_Customer_On_Hold_Order extends WC_Email {
* Constructor.
*/
public function __construct() {
- $this->id = 'customer_on_hold_order';
- $this->customer_email = true;
-
- $this->title = __( 'Order on-hold', 'woocommerce' );
- $this->description = __( 'This is an order notification sent to customers containing order details after an order is placed on-hold.', 'woocommerce' );
- $this->template_html = 'emails/customer-on-hold-order.php';
- $this->template_plain = 'emails/plain/customer-on-hold-order.php';
+ $this->id = 'customer_on_hold_order';
+ $this->customer_email = true;
+ $this->title = __( 'Order on-hold', 'woocommerce' );
+ $this->description = __( 'This is an order notification sent to customers containing order details after an order is placed on-hold.', 'woocommerce' );
+ $this->template_html = 'emails/customer-on-hold-order.php';
+ $this->template_plain = 'emails/plain/customer-on-hold-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_pending_to_on-hold_notification', array( $this, 'trigger' ), 10, 2 );
@@ -71,14 +75,10 @@ class WC_Email_Customer_On_Hold_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->recipient = $this->object->get_billing_email();
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->recipient = $this->object->get_billing_email();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-customer-processing-order.php b/includes/emails/class-wc-email-customer-processing-order.php
index 85980d9f1f5..295e72d3c14 100644
--- a/includes/emails/class-wc-email-customer-processing-order.php
+++ b/includes/emails/class-wc-email-customer-processing-order.php
@@ -30,6 +30,11 @@ class WC_Email_Customer_Processing_Order extends WC_Email {
$this->description = __( 'This is an order notification sent to customers containing order details after payment.', 'woocommerce' );
$this->template_html = 'emails/customer-processing-order.php';
$this->template_plain = 'emails/plain/customer-processing-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_failed_to_processing_notification', array( $this, 'trigger' ), 10, 2 );
@@ -72,14 +77,10 @@ class WC_Email_Customer_Processing_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->recipient = $this->object->get_billing_email();
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->recipient = $this->object->get_billing_email();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-customer-refunded-order.php b/includes/emails/class-wc-email-customer-refunded-order.php
index f52223c8b10..2e8ca20110d 100644
--- a/includes/emails/class-wc-email-customer-refunded-order.php
+++ b/includes/emails/class-wc-email-customer-refunded-order.php
@@ -43,12 +43,17 @@ class WC_Email_Customer_Refunded_Order extends WC_Email {
$this->description = __( 'Order refunded emails are sent to customers when their orders are refunded.', 'woocommerce' );
$this->template_html = 'emails/customer-refunded-order.php';
$this->template_plain = 'emails/plain/customer-refunded-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_fully_refunded_notification', array( $this, 'trigger_full' ), 10, 2 );
add_action( 'woocommerce_order_partially_refunded_notification', array( $this, 'trigger_partial' ), 10, 2 );
- // Call parent constuctor
+ // Call parent constructor.
parent::__construct();
}
@@ -74,9 +79,9 @@ class WC_Email_Customer_Refunded_Order extends WC_Email {
*/
public function get_default_heading( $partial = false ) {
if ( $partial ) {
- return __( 'Order {order_number} details', 'woocommerce' );
- } else {
return __( 'Your order has been partially refunded', 'woocommerce' );
+ } else {
+ return __( 'Order {order_number} details', 'woocommerce' );
}
}
@@ -90,7 +95,7 @@ class WC_Email_Customer_Refunded_Order extends WC_Email {
if ( $this->partial_refund ) {
$subject = $this->get_option( 'subject_partial', $this->get_default_subject( true ) );
} else {
- $subject = $this->get_option( 'subject_full', $this->get_default_heading() );
+ $subject = $this->get_option( 'subject_full', $this->get_default_subject() );
}
return apply_filters( 'woocommerce_email_subject_customer_refunded_order', $this->format_string( $subject ), $this->object );
}
@@ -148,14 +153,10 @@ class WC_Email_Customer_Refunded_Order extends WC_Email {
$this->id = $this->partial_refund ? 'customer_partially_refunded_order' : 'customer_refunded_order';
if ( $order_id ) {
- $this->object = wc_get_order( $order_id );
- $this->recipient = $this->object->get_billing_email();
-
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
-
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = wc_get_order( $order_id );
+ $this->recipient = $this->object->get_billing_email();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! empty( $refund_id ) ) {
diff --git a/includes/emails/class-wc-email-failed-order.php b/includes/emails/class-wc-email-failed-order.php
index 0b0ff5927e5..586cdfaa24f 100644
--- a/includes/emails/class-wc-email-failed-order.php
+++ b/includes/emails/class-wc-email-failed-order.php
@@ -23,11 +23,16 @@ class WC_Email_Failed_Order extends WC_Email {
* Constructor.
*/
public function __construct() {
- $this->id = 'failed_order';
- $this->title = __( 'Failed order', 'woocommerce' );
- $this->description = __( 'Failed order emails are sent to chosen recipient(s) when orders have been marked failed (if they were previously processing or on-hold).', 'woocommerce' );
- $this->template_html = 'emails/admin-failed-order.php';
- $this->template_plain = 'emails/plain/admin-failed-order.php';
+ $this->id = 'failed_order';
+ $this->title = __( 'Failed order', 'woocommerce' );
+ $this->description = __( 'Failed order emails are sent to chosen recipient(s) when orders have been marked failed (if they were previously processing or on-hold).', 'woocommerce' );
+ $this->template_html = 'emails/admin-failed-order.php';
+ $this->template_plain = 'emails/plain/admin-failed-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_pending_to_failed_notification', array( $this, 'trigger' ), 10, 2 );
@@ -72,11 +77,9 @@ class WC_Email_Failed_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email-new-order.php b/includes/emails/class-wc-email-new-order.php
index 911c3f4e7a6..bfbd165f4a5 100644
--- a/includes/emails/class-wc-email-new-order.php
+++ b/includes/emails/class-wc-email-new-order.php
@@ -23,11 +23,16 @@ class WC_Email_New_Order extends WC_Email {
* Constructor.
*/
public function __construct() {
- $this->id = 'new_order';
- $this->title = __( 'New order', 'woocommerce' );
- $this->description = __( 'New order emails are sent to chosen recipient(s) when a new order is received.', 'woocommerce' );
- $this->template_html = 'emails/admin-new-order.php';
- $this->template_plain = 'emails/plain/admin-new-order.php';
+ $this->id = 'new_order';
+ $this->title = __( 'New order', 'woocommerce' );
+ $this->description = __( 'New order emails are sent to chosen recipient(s) when a new order is received.', 'woocommerce' );
+ $this->template_html = 'emails/admin-new-order.php';
+ $this->template_plain = 'emails/plain/admin-new-order.php';
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_date}' => '',
+ '{order_number}' => '',
+ );
// Triggers for this email
add_action( 'woocommerce_order_status_pending_to_processing_notification', array( $this, 'trigger' ), 10, 2 );
@@ -76,11 +81,9 @@ class WC_Email_New_Order extends WC_Email {
}
if ( is_a( $order, 'WC_Order' ) ) {
- $this->object = $order;
- $this->find['order-date'] = '{order_date}';
- $this->find['order-number'] = '{order_number}';
- $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() );
- $this->replace['order-number'] = $this->object->get_order_number();
+ $this->object = $order;
+ $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
+ $this->placeholders['{order_number}'] = $this->object->get_order_number();
}
if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
diff --git a/includes/emails/class-wc-email.php b/includes/emails/class-wc-email.php
index 9de23a39779..153a00d329c 100644
--- a/includes/emails/class-wc-email.php
+++ b/includes/emails/class-wc-email.php
@@ -49,7 +49,7 @@ class WC_Email extends WC_Settings_API {
* Default heading.
*
* Supported for backwards compatibility but we recommend overloading the
- * get_default_x methods instead so localication can be done when needed.
+ * get_default_x methods instead so localization can be done when needed.
*
* @var string
*/
@@ -59,7 +59,7 @@ class WC_Email extends WC_Settings_API {
* Default subject.
*
* Supported for backwards compatibility but we recommend overloading the
- * get_default_x methods instead so localication can be done when needed.
+ * get_default_x methods instead so localization can be done when needed.
*
* @var string
*/
@@ -95,18 +95,6 @@ class WC_Email extends WC_Settings_API {
*/
public $object;
- /**
- * Strings to find in subjects/headings.
- * @var array
- */
- public $find = array();
-
- /**
- * Strings to replace in subjects/headings.
- * @var array
- */
- public $replace = array();
-
/**
* Mime boundary (for multipart emails).
* @var string
@@ -195,34 +183,54 @@ class WC_Email extends WC_Settings_API {
' ', // Runs of spaces, post-handling
);
+ /**
+ * Strings to find/replace in subjects/headings.
+ *
+ * @var array
+ */
+ protected $placeholders = array();
+
+ /**
+ * Strings to find in subjects/headings.
+ *
+ * @deprecated 3.2.0 in favour of placeholders
+ * @var array
+ */
+ public $find = array();
+
+ /**
+ * Strings to replace in subjects/headings.
+ *
+ * @deprecated 3.2.0 in favour of placeholders
+ * @var array
+ */
+ public $replace = array();
+
/**
* Constructor.
*/
public function __construct() {
+ // Find/replace
+ if ( empty( $this->placeholders ) ) {
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ );
+ }
+
// Init settings
$this->init_form_fields();
$this->init_settings();
- // Save settings hook
- add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) );
-
// Default template base if not declared in child constructor
if ( is_null( $this->template_base ) ) {
$this->template_base = WC()->plugin_path() . '/templates/';
}
- // Settings
$this->email_type = $this->get_option( 'email_type' );
$this->enabled = $this->get_option( 'enabled' );
- // Find/replace
- $this->find['blogname'] = '{blogname}';
- $this->find['site-title'] = '{site_title}';
- $this->replace['blogname'] = $this->get_blogname();
- $this->replace['site-title'] = $this->get_blogname();
-
- // For multipart messages
add_action( 'phpmailer_init', array( $this, 'handle_multipart' ) );
+ add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) );
}
/**
@@ -246,14 +254,16 @@ class WC_Email extends WC_Settings_API {
* @return string
*/
public function format_string( $string ) {
- return str_replace( apply_filters( 'woocommerce_email_format_string_find', $this->find, $this ), apply_filters( 'woocommerce_email_format_string_replace', $this->replace, $this ), $string );
+ // handle legacy find and replace.
+ $string = str_replace( $this->find, $this->replace, $string );
+ return str_replace( apply_filters( 'woocommerce_email_format_string_find', array_keys( $this->placeholders ), $this ), apply_filters( 'woocommerce_email_format_string_replace', array_values( $this->placeholders ), $this ), $string );
}
/**
* Set the locale to the store locale for customer emails to make sure emails are in the store language.
*/
public function setup_locale() {
- if ( $this->is_customer_email() ) {
+ if ( $this->is_customer_email() && apply_filters( 'woocommerce_email_setup_locale', true ) ) {
wc_switch_to_site_locale();
}
}
@@ -262,7 +272,7 @@ class WC_Email extends WC_Settings_API {
* Restore the locale to the default locale. Use after finished with setup_locale.
*/
public function restore_locale() {
- if ( $this->is_customer_email() ) {
+ if ( $this->is_customer_email() && apply_filters( 'woocommerce_email_restore_locale', true ) ) {
wc_restore_locale();
}
}
@@ -538,7 +548,7 @@ class WC_Email extends WC_Settings_API {
'type' => 'text',
'desc_tip' => true,
/* translators: %s: list of placeholders */
- 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '
{site_title}
' ),
+ 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '
' . implode( '
,
', array_keys( $this->placeholders ) ) . '
' ),
'placeholder' => $this->get_default_subject(),
'default' => '',
),
@@ -547,7 +557,7 @@ class WC_Email extends WC_Settings_API {
'type' => 'text',
'desc_tip' => true,
/* translators: %s: list of placeholders */
- 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '
{site_title}
' ),
+ 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '
' . implode( '
,
', array_keys( $this->placeholders ) ) . '
' ),
'placeholder' => $this->get_default_heading(),
'default' => '',
),
diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php
index ab1e4152668..e65a655ddb5 100644
--- a/includes/export/class-wc-product-csv-exporter.php
+++ b/includes/export/class-wc-product-csv-exporter.php
@@ -36,7 +36,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
protected $enable_meta_export = false;
/**
- * Which product types are beign exported.
+ * Which product types are being exported.
* @var array
*/
protected $product_types_to_export = array();
@@ -124,7 +124,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
*/
public function prepare_data_to_export() {
$columns = $this->get_column_names();
- $products = wc_get_products( array(
+ $args = apply_filters( "woocommerce_product_export_{$this->export_type}_query_args", array(
'status' => array( 'private', 'publish' ),
'type' => $this->product_types_to_export,
'limit' => $this->get_limit(),
@@ -135,6 +135,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
'return' => 'objects',
'paginate' => true,
) );
+ $products = wc_get_products( $args );
$this->total_rows = $products->total;
$this->row_data = array();
diff --git a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php
index 396bfd9d530..ea3b58d0bec 100644
--- a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php
+++ b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php
@@ -393,7 +393,6 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce {
* @since 2.4
* @param string $payment_method_id The ID of the payment method to validate
* @param array $payment_meta associative array of meta data required for automatic payments
- * @return array
* @throws Exception
*/
public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Constants.php b/includes/gateways/simplify-commerce/includes/Simplify/Constants.php
index e96ac699cd6..c2174da8984 100644
--- a/includes/gateways/simplify-commerce/includes/Simplify/Constants.php
+++ b/includes/gateways/simplify-commerce/includes/Simplify/Constants.php
@@ -52,7 +52,7 @@ class Simplify_Constants
const API_BASE_SANDBOX_URL = 'https://sandbox.simplify.com/v1/api';
/**
- * @var string OAUTH_BASE_URL URL for the oauth enpoint
+ * @var string OAUTH_BASE_URL URL for the oauth endpoint
*/
const OAUTH_BASE_URL = 'https://www.simplify.com/commerce/oauth';
}
diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Event.php b/includes/gateways/simplify-commerce/includes/Simplify/Event.php
index b632c9dcaad..bdb682edac5 100644
--- a/includes/gateways/simplify-commerce/includes/Simplify/Event.php
+++ b/includes/gateways/simplify-commerce/includes/Simplify/Event.php
@@ -36,7 +36,7 @@ class Simplify_Event extends Simplify_Object {
/**
* Creates an Event object
* @param array $hash A map of parameters; valid keys are:
- *
paylod
The raw JWS payload. required
+ *
payload
The raw JWS payload. required
*
url
The URL for the webhook. If present it must match the URL registered for the webhook.
* @param $authentication Object that contains the API public and private keys. If null the values of the static
* Simplify::$publicKey and Simplify::$privateKey will be used.
@@ -53,7 +53,7 @@ class Simplify_Event extends Simplify_Object {
$jsonObject = $paymentsApi->jwsDecode($hash, $authentication);
if ($jsonObject['event'] == null) {
- throw new InvalidArgumentException("Incorect data in webhook event");
+ throw new InvalidArgumentException("Incorrect data in webhook event");
}
return $paymentsApi->convertFromHashToObject($jsonObject['event'], self::getClazz());
diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Http.php b/includes/gateways/simplify-commerce/includes/Simplify/Http.php
index 603038b6644..e0925a1bfa4 100644
--- a/includes/gateways/simplify-commerce/includes/Simplify/Http.php
+++ b/includes/gateways/simplify-commerce/includes/Simplify/Http.php
@@ -394,7 +394,7 @@ class Simplify_HTTP
break;
case 3: $s = $s . "=";
break;
- default: throw new InvalidArgumentException('incorrecly formatted JWS payload');
+ default: throw new InvalidArgumentException('incorrectly formatted JWS payload');
}
return base64_decode(str_replace(array('-', '_'), array('+', '/'), $s));
}
diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php
index 9d07816c71f..d6404a8a370 100644
--- a/includes/import/abstract-wc-product-importer.php
+++ b/includes/import/abstract-wc-product-importer.php
@@ -293,7 +293,6 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
* @param WC_Product $product Product instance.
* @param array $data Item data.
*
- * @return WC_Product|WP_Error
* @throws Exception
*/
protected function set_product_data( &$product, $data ) {
@@ -471,7 +470,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
// Check if attribute handle variations.
if ( isset( $parent_attributes[ $attribute_name ] ) && ! $parent_attributes[ $attribute_name ]->get_variation() ) {
- // Re-create the attribute to CRUD save and genarate again.
+ // Re-create the attribute to CRUD save and generate again.
$parent_attributes[ $attribute_name ] = clone $parent_attributes[ $attribute_name ];
$parent_attributes[ $attribute_name ]->set_variation( 1 );
@@ -505,8 +504,8 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
$base_url = $upload_dir['baseurl'] . '/';
// Check first if attachment is on WordPress uploads directory.
- if ( false !== strpos( $url, $base_url ) ) {
- // Search for yyyy/mm/slug.extension
+ if ( false === strpos( $url, $base_url ) ) {
+ // Search for yyyy/mm/slug.extension.
$file = str_replace( $base_url, '', $url );
$args = array(
'post_type' => 'attachment',
@@ -516,11 +515,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
array(
'value' => $file,
'compare' => 'LIKE',
- 'key' => '_wp_attachment_metadata',
+ 'key' => '_wp_attached_file',
),
),
);
-
if ( $ids = get_posts( $args ) ) {
$id = current( $ids );
}
@@ -536,14 +534,13 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
),
),
);
-
if ( $ids = get_posts( $args ) ) {
$id = current( $ids );
}
}
// Upload if attachment does not exists.
- if ( ! $id ) {
+ if ( ! $id && stristr( $url, '://' ) ) {
$upload = wc_rest_upload_image_from_url( $url );
if ( is_wp_error( $upload ) ) {
@@ -560,6 +557,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
update_post_meta( $id, '_wc_attachment_source', $url );
}
+ if ( ! $id ) {
+ throw new Exception( sprintf( __( 'Unable to use image "%s".', 'woocommerce' ), $url ), 400 );
+ }
+
return $id;
}
@@ -587,43 +588,39 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
}
// If the attribute does not exist, create it.
- $args = array(
- 'attribute_label' => $raw_name,
- 'attribute_name' => $attribute_name,
- 'attribute_type' => 'select',
- 'attribute_orderby' => 'menu_order',
- 'attribute_public' => 0,
- );
+ $attribute_id = wc_create_attribute( array(
+ 'name' => $raw_name,
+ 'slug' => $attribute_name,
+ 'type' => 'select',
+ 'order_by' => 'menu_order',
+ 'has_archives' => false,
+ ) );
- // Validate attribute.
- if ( strlen( $attribute_name ) >= 28 ) {
- throw new Exception( sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $attribute_name ), 400 );
- } elseif ( wc_check_if_attribute_name_is_reserved( $attribute_name ) ) {
- throw new Exception( sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $attribute_name ), 400 );
- } elseif ( taxonomy_exists( wc_attribute_taxonomy_name( $attribute_name ) ) ) {
- throw new Exception( sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $attribute_name ), 400 );
+ if ( is_wp_error( $attribute_id ) ) {
+ throw new Exception( $attribute_id->get_error_message(), 400 );
}
- $result = $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $args, array( '%s', '%s', '%s', '%s', '%d' ) );
-
- // Pass errors.
- if ( is_wp_error( $result ) ) {
- throw new Exception( $result->get_error_message(), 400 );
- }
-
- $attribute_id = absint( $wpdb->insert_id );
-
- // Delete transient.
- delete_transient( 'wc_attribute_taxonomies' );
-
// Register as taxonomy while importing.
- register_taxonomy( wc_attribute_taxonomy_name( $attribute_name ), array( 'product' ), array( 'labels' => array( 'name' => $raw_name ) ) );
+ $taxonomy_name = wc_attribute_taxonomy_name( $attribute_name );
+ register_taxonomy(
+ $taxonomy_name,
+ apply_filters( 'woocommerce_taxonomy_objects_' . $taxonomy_name, array( 'product' ) ),
+ apply_filters( 'woocommerce_taxonomy_args_' . $taxonomy_name, array(
+ 'labels' => array(
+ 'name' => $raw_name,
+ ),
+ 'hierarchical' => true,
+ 'show_ui' => false,
+ 'query_var' => true,
+ 'rewrite' => false,
+ ) )
+ );
// Set product attributes global.
$wc_product_attributes = array();
- foreach ( wc_get_attribute_taxonomies() as $tax ) {
- $wc_product_attributes[ wc_attribute_taxonomy_name( $attribute_name ) ] = $tax;
+ foreach ( wc_get_attribute_taxonomies() as $taxonomy ) {
+ $wc_product_attributes[ wc_attribute_taxonomy_name( $taxonomy['attribute_name'] ) ] = $taxonomy;
}
return $attribute_id;
diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php
index ece7a0b29be..fbc32877495 100644
--- a/includes/import/class-wc-product-csv-importer.php
+++ b/includes/import/class-wc-product-csv-importer.php
@@ -54,8 +54,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Read file.
- *
- * @return array
*/
protected function read_file() {
if ( false !== ( $handle = fopen( $this->file, 'r' ) ) ) {
@@ -107,8 +105,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Set file mapped keys.
- *
- * @return array
*/
protected function set_mapped_keys() {
$mapping = $this->params['mapping'];
@@ -186,10 +182,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* for rows following this one.
*
* @param stirng $field
- * @param array $raw_data
* @return int
*/
- public function parse_id_field( $field, $raw_data = array() ) {
+ public function parse_id_field( $field ) {
global $wpdb;
$id = absint( $field );
@@ -208,7 +203,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
// Not updating? Make sure we have a new placeholder for this ID.
if ( ! $this->params['update_existing'] ) {
// If row has a SKU, make sure placeholder was not made already.
- if ( isset( $raw_data['sku'] ) && $id = wc_get_product_id_by_sku( $raw_data['sku'] ) ) {
+ if ( isset( $this->raw_data['sku'] ) && $id = wc_get_product_id_by_sku( $this->raw_data['sku'] ) ) {
return $id;
}
@@ -218,8 +213,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$product->add_meta_data( '_original_id', $id, true );
// If row has a SKU, make sure placeholder has it too.
- if ( isset( $raw_data['sku'] ) ) {
- $product->set_sku( $raw_data['sku'] );
+ if ( isset( $this->raw_data['sku'] ) ) {
+ $product->set_sku( $this->raw_data['sku'] );
}
$id = $product->save();
}
@@ -228,7 +223,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
/**
- * Parse reletive comma-delineated field and return product ID.
+ * Parse relative comma-delineated field and return product ID.
*
* @param string $field Field value.
* @return array
@@ -382,7 +377,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
/**
- * Parse images list from a CSV.
+ * Parse images list from a CSV. Images can be filenames or URLs.
*
* @param string $field Field value.
* @return array
@@ -392,7 +387,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
return array();
}
- return array_map( 'esc_url_raw', $this->explode_values( $field ) );
+ $images = array();
+
+ foreach ( $this->explode_values( $field ) as $image ) {
+ if ( stristr( $image, '://' ) ) {
+ $images[] = esc_url_raw( $image );
+ } else {
+ $images[] = sanitize_file_name( $image );
+ }
+ }
+
+ return $images;
}
/**
@@ -669,8 +674,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Map and format raw data to known fields.
- *
- * @return array
*/
protected function set_parsed_data() {
$parse_functions = $this->get_formating_callback();
@@ -705,7 +708,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$value = wp_check_invalid_utf8( $value, true );
}
- $data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value, array_combine( $mapped_keys, $row ) );
+ $data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value );
}
$this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this );
diff --git a/includes/legacy/abstract-wc-legacy-order.php b/includes/legacy/abstract-wc-legacy-order.php
index d606a72808f..30faea7c8af 100644
--- a/includes/legacy/abstract-wc-legacy-order.php
+++ b/includes/legacy/abstract-wc-legacy-order.php
@@ -158,10 +158,10 @@ abstract class WC_Abstract_Legacy_Order extends WC_Data {
}
}
- // Handly qty if set
+ // Handle qty if set.
if ( isset( $args['qty'] ) ) {
if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
- $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true );
+ $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ), $item ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true );
}
$args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) );
$args['total'] = $args['total'] ? $args['total'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) );
diff --git a/includes/legacy/abstract-wc-legacy-product.php b/includes/legacy/abstract-wc-legacy-product.php
index 577989f4d31..15b7ba0f8b5 100644
--- a/includes/legacy/abstract-wc-legacy-product.php
+++ b/includes/legacy/abstract-wc-legacy-product.php
@@ -681,7 +681,7 @@ abstract class WC_Abstract_Legacy_Product extends WC_Data {
}
/**
- * @deprected 3.0.0 Sync is taken care of during save - no need to call this directly.
+ * @deprecated 3.0.0 Sync is taken care of during save - no need to call this directly.
*/
public function grouped_product_sync() {
wc_deprecated_function( 'WC_Product::grouped_product_sync', '3.0' );
diff --git a/includes/legacy/class-wc-legacy-cart.php b/includes/legacy/class-wc-legacy-cart.php
new file mode 100644
index 00000000000..5a806b6b019
--- /dev/null
+++ b/includes/legacy/class-wc-legacy-cart.php
@@ -0,0 +1,150 @@
+cart_contents[ $cart_item_key ];
+
+ return $cart_item->get_line_total();
+ }
+
+ /**
+ * Gets the url to the cart page.
+ *
+ * @deprecated 2.5.0 in favor to wc_get_cart_url()
+ * @return string url to page
+ */
+ public function get_cart_url() {
+ wc_deprecated_function( 'WC_Cart::get_cart_url', '2.5', 'wc_get_cart_url' );
+ return wc_get_cart_url();
+ }
+
+ /**
+ * Gets the url to the checkout page.
+ *
+ * @deprecated 2.5.0 in favor to wc_get_checkout_url()
+ * @return string url to page
+ */
+ public function get_checkout_url() {
+ wc_deprecated_function( 'WC_Cart::get_checkout_url', '2.5', 'wc_get_checkout_url' );
+ return wc_get_checkout_url();
+ }
+
+ /**
+ * Sees if we need a shipping address.
+ *
+ * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only()
+ * @return bool
+ */
+ public function ship_to_billing_address_only() {
+ wc_deprecated_function( 'WC_Cart::ship_to_billing_address_only', '2.5', 'wc_ship_to_billing_address_only' );
+ return wc_ship_to_billing_address_only();
+ }
+
+ /**
+ * Coupons enabled function. Filterable.
+ *
+ * @deprecated 2.5.0 in favor to wc_coupons_enabled()
+ * @return bool
+ */
+ public function coupons_enabled() {
+ return wc_coupons_enabled();
+ }
+
+ /**
+ * Gets the total (product) discount amount - these are applied before tax.
+ *
+ * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required.
+ * @return mixed formatted price or false if there are none.
+ */
+ public function get_discounts_before_tax() {
+ wc_deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' );
+ if ( $this->get_cart_discount_total() ) {
+ $discounts_before_tax = wc_price( $this->get_cart_discount_total() );
+ } else {
+ $discounts_before_tax = false;
+ }
+ return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this );
+ }
+
+ /**
+ * Get the total of all order discounts (after tax discounts).
+ *
+ * @deprecated Order discounts (after tax) removed in 2.3.
+ * @return int
+ */
+ public function get_order_discount_total() {
+ wc_deprecated_function( 'get_order_discount_total', '2.3' );
+ return 0;
+ }
+
+ /**
+ * Function to apply cart discounts after tax.
+ *
+ * @deprecated Coupons can not be applied after tax.
+ * @param $values
+ * @param $price
+ */
+ public function apply_cart_discounts_after_tax( $values, $price ) {
+ wc_deprecated_function( 'apply_cart_discounts_after_tax', '2.3' );
+ }
+
+ /**
+ * Function to apply product discounts after tax.
+ *
+ * @deprecated Coupons can not be applied after tax.
+ *
+ * @param $values
+ * @param $price
+ */
+ public function apply_product_discounts_after_tax( $values, $price ) {
+ wc_deprecated_function( 'apply_product_discounts_after_tax', '2.3' );
+ }
+
+ /**
+ * Gets the order discount amount - these are applied after tax.
+ *
+ * @deprecated Coupons can not be applied after tax.
+ */
+ public function get_discounts_after_tax() {
+ wc_deprecated_function( 'get_discounts_after_tax', '2.3' );
+ }
+}
diff --git a/includes/libraries/class-wc-eval-math.php b/includes/libraries/class-wc-eval-math.php
index 999f5afc39e..eaf174c5dc8 100644
--- a/includes/libraries/class-wc-eval-math.php
+++ b/includes/libraries/class-wc-eval-math.php
@@ -135,7 +135,7 @@ if ( ! class_exists( 'WC_Eval_Math', false ) ) {
return self::trigger( "illegal character '_'" ); // but not in the input expression
// ===============
} elseif ( ( in_array( $op, $ops ) or $ex ) and $expecting_op ) { // are we putting an operator on the stack?
- if ( $ex ) { // are we expecting an operator but have a number/variable/function/opening parethesis?
+ if ( $ex ) { // are we expecting an operator but have a number/variable/function/opening parenthesis?
$op = '*';
$index--; // it's an implicit multiplication
}
diff --git a/includes/log-handlers/class-wc-log-handler-file.php b/includes/log-handlers/class-wc-log-handler-file.php
index c13afb4eca7..3bd5209d135 100644
--- a/includes/log-handlers/class-wc-log-handler-file.php
+++ b/includes/log-handlers/class-wc-log-handler-file.php
@@ -283,7 +283,7 @@ class WC_Log_Handler_File extends WC_Log_Handler {
/**
* Rotate log files.
*
- * Logs are rotatated by prepending '.x' to the '.log' suffix.
+ * Logs are rotated by prepending '.x' to the '.log' suffix.
* The current log plus 10 historical logs are maintained.
* For example:
* base.9.log -> [ REMOVED ]
diff --git a/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php b/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php
index 18838c25358..4adda364d1d 100644
--- a/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php
+++ b/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php
@@ -6,7 +6,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Flat Rate Shipping Method.
*
- * This class is here for backwards commpatility for methods existing before zones existed.
+ * This class is here for backwards compatibility for methods existing before zones existed.
*
* @deprecated 2.6.0
* @version 2.4.0
diff --git a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php
index 9564f385e5e..be23051a1fa 100644
--- a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php
+++ b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php
@@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Free Shipping Method.
*
- * This class is here for backwards commpatility for methods existing before zones existed.
+ * This class is here for backwards compatibility for methods existing before zones existed.
*
* @deprecated 2.6.0
* @version 2.4.0
@@ -216,7 +216,6 @@ class WC_Shipping_Legacy_Free_Shipping extends WC_Shipping_Method {
/**
* calculate_shipping function.
- * @return array
*/
public function calculate_shipping( $package = array() ) {
$args = array(
diff --git a/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php b/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php
index e6a76557c2e..8889195609b 100644
--- a/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php
+++ b/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php
@@ -6,7 +6,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* International Delivery - Based on the Flat Rate Shipping Method.
*
- * This class is here for backwards commpatility for methods existing before zones existed.
+ * This class is here for backwards compatibility for methods existing before zones existed.
*
* @deprecated 2.6.0
* @version 2.4.0
diff --git a/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php b/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php
index 643aa6ac064..065c4667e32 100644
--- a/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php
+++ b/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php
@@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Local Delivery Shipping Method.
*
- * This class is here for backwards commpatility for methods existing before zones existed.
+ * This class is here for backwards compatibility for methods existing before zones existed.
*
* @deprecated 2.6.0
* @version 2.3.0
diff --git a/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php b/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php
index c0f9697b9cf..2d303ae708c 100644
--- a/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php
+++ b/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php
@@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Local Pickup Shipping Method.
*
- * This class is here for backwards commpatility for methods existing before zones existed.
+ * This class is here for backwards compatibility for methods existing before zones existed.
*
* @deprecated 2.6.0
* @version 2.3.0
diff --git a/includes/shortcodes/class-wc-shortcode-checkout.php b/includes/shortcodes/class-wc-shortcode-checkout.php
index 2836803c94e..3d91c8a5885 100644
--- a/includes/shortcodes/class-wc-shortcode-checkout.php
+++ b/includes/shortcodes/class-wc-shortcode-checkout.php
@@ -39,7 +39,7 @@ class WC_Shortcode_Checkout {
return;
}
- // Backwards compat with old pay and thanks link arguments
+ // Backwards compatibility with old pay and thanks link arguments
if ( isset( $_GET['order'] ) && isset( $_GET['key'] ) ) {
wc_deprecated_argument( __CLASS__ . '->' . __FUNCTION__, '2.1', '"order" is no longer used to pass an order ID. Use the order-pay or order-received endpoint instead.' );
diff --git a/includes/shortcodes/class-wc-shortcode-order-tracking.php b/includes/shortcodes/class-wc-shortcode-order-tracking.php
index b12d84743e6..145807f49c9 100644
--- a/includes/shortcodes/class-wc-shortcode-order-tracking.php
+++ b/includes/shortcodes/class-wc-shortcode-order-tracking.php
@@ -32,7 +32,6 @@ class WC_Shortcode_Order_Tracking {
* @param array $atts
*/
public static function output( $atts ) {
-
// Check cart class is loaded or abort
if ( is_null( WC()->cart ) ) {
return;
@@ -40,31 +39,26 @@ class WC_Shortcode_Order_Tracking {
extract( shortcode_atts( array(), $atts, 'woocommerce_order_tracking' ) );
- global $post;
+ if ( isset( $_REQUEST['orderid'], $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-order_tracking' ) ) {
- if ( ! empty( $_REQUEST['orderid'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-order_tracking' ) ) {
-
- $order_id = empty( $_REQUEST['orderid'] ) ? 0 : esc_attr( $_REQUEST['orderid'] );
- $order_email = empty( $_REQUEST['order_email'] ) ? '' : esc_attr( $_REQUEST['order_email'] );
+ $order_id = empty( $_REQUEST['orderid'] ) ? 0 : wc_clean( ltrim( $_REQUEST['orderid'], '#' ) );
+ $order_email = empty( $_REQUEST['order_email'] ) ? '' : sanitize_email( $_REQUEST['order_email'] );
if ( ! $order_id ) {
wc_add_notice( __( 'Please enter a valid order ID', 'woocommerce' ), 'error' );
} elseif ( ! $order_email ) {
- wc_add_notice( __( 'Please enter a valid order email', 'woocommerce' ), 'error' );
+ wc_add_notice( __( 'Please enter a valid email address', 'woocommerce' ), 'error' );
} else {
$order = wc_get_order( apply_filters( 'woocommerce_shortcode_order_tracking_order_id', $order_id ) );
- if ( $order && $order->get_id() && $order_email ) {
- if ( strtolower( $order->get_billing_email() ) == strtolower( $order_email ) ) {
- do_action( 'woocommerce_track_order', $order->get_id() );
- wc_get_template( 'order/tracking.php', array(
- 'order' => $order,
- ) );
-
- return;
- }
+ if ( $order && $order->get_id() && strtolower( $order->get_billing_email() ) === strtolower( $order_email ) ) {
+ do_action( 'woocommerce_track_order', $order->get_id() );
+ wc_get_template( 'order/tracking.php', array(
+ 'order' => $order,
+ ) );
+ return;
} else {
- wc_add_notice( __( 'Sorry, we could not find that order ID in our database.', 'woocommerce' ), 'error' );
+ wc_add_notice( __( 'Sorry, the order could not be found. Please contact us if you are having difficulty finding your order details.', 'woocommerce' ), 'error' );
}
}
}
diff --git a/includes/vendor/abstract-wp-rest-controller.php b/includes/vendor/abstract-wp-rest-controller.php
index 1b170e5d380..779a88bf7f0 100644
--- a/includes/vendor/abstract-wp-rest-controller.php
+++ b/includes/vendor/abstract-wp-rest-controller.php
@@ -27,7 +27,7 @@ abstract class WP_REST_Controller {
* Register the routes for the objects of the controller.
*/
public function register_routes() {
- wc_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden', 'woocommerce' ), 'WPAPI-2.0' );
+ wc_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overridden', 'woocommerce' ), 'WPAPI-2.0' );
}
/**
diff --git a/includes/vendor/wp-rest-functions.php b/includes/vendor/wp-rest-functions.php
index 1e55b8f2049..a3262f58d3a 100644
--- a/includes/vendor/wp-rest-functions.php
+++ b/includes/vendor/wp-rest-functions.php
@@ -139,7 +139,7 @@ if ( ! function_exists( 'register_rest_field' ) ) {
if ( ! function_exists( 'register_api_field' ) ) {
/**
- * Backwards compat shim
+ * Backwards compatibility shim
*
* @param array|string $object_type
* @param string $attributes
diff --git a/includes/wc-account-functions.php b/includes/wc-account-functions.php
index a05f5acc872..ce11e812f2f 100644
--- a/includes/wc-account-functions.php
+++ b/includes/wc-account-functions.php
@@ -240,6 +240,73 @@ function wc_get_account_payment_methods_types() {
) );
}
+/**
+ * Get account orders actions.
+ *
+ * @since 3.2.0
+ * @param int|WC_Order $order
+ * @return array
+ */
+function wc_get_account_orders_actions( $order ) {
+ if ( ! is_object( $order ) ) {
+ $order_id = absint( $order );
+ $order = wc_get_order( $order_id );
+ }
+
+ $actions = array(
+ 'pay' => array(
+ 'url' => $order->get_checkout_payment_url(),
+ 'name' => __( 'Pay', 'woocommerce' ),
+ ),
+ 'view' => array(
+ 'url' => $order->get_view_order_url(),
+ 'name' => __( 'View', 'woocommerce' ),
+ ),
+ 'cancel' => array(
+ 'url' => $order->get_cancel_order_url( wc_get_page_permalink( 'myaccount' ) ),
+ 'name' => __( 'Cancel', 'woocommerce' ),
+ ),
+ );
+
+ if ( ! $order->needs_payment() ) {
+ unset( $actions['pay'] );
+ }
+
+ if ( ! in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_cancel', array( 'pending', 'failed' ), $order ) ) ) {
+ unset( $actions['cancel'] );
+ }
+
+ return apply_filters( 'woocommerce_my_account_my_orders_actions', $actions, $order );
+}
+
+/**
+ * Get account formatted address.
+ *
+ * @since 3.2.0
+ * @param string $address_type Address type.
+ * Accepts: 'billing' or 'shipping'.
+ * Default to 'billing'.
+ * @param int $customer_id Customer ID.
+ * Default to 0.
+ * @return string
+ */
+function wc_get_account_formatted_address( $address_type = 'billing', $customer_id = 0 ) {
+ $getter = "get_{$address_type}";
+
+ if ( 0 === $customer_id ) {
+ $customer_id = get_current_user_id();
+ }
+
+ $customer = new WC_Customer( $customer_id );
+
+ if ( is_callable( array( $customer, $getter ) ) ) {
+ $address = $customer->$getter();
+ unset( $address['email'], $address['tel'] );
+ }
+
+ return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_my_account_my_address_formatted_address', $address, $customer->get_id(), $address_type ) );
+}
+
/**
* Returns an array of a user's saved payments list for output on the account tab.
*
diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php
index d44e6c98619..f64e6400c34 100644
--- a/includes/wc-attribute-functions.php
+++ b/includes/wc-attribute-functions.php
@@ -382,17 +382,21 @@ function wc_get_attribute( $id ) {
/**
* Create attribute.
*
- * Options:
- *
- * id - Unique identifier, used to update an attribute.
- * name - Attribute name.
- * slug - Attribute alphanumeric identifier.
- * type - Type of attribute, core options: 'select' and 'text', default to 'select'.
- * order_by - Sort order, options: 'menu_order', 'name', 'name_num' and 'id'.
- * has_archives - Enable or disable attribute archives.
- *
* @since 3.2.0
- * @param array $args Attribute arguments.
+ * @param array $args Attribute arguments {
+ * Array of attribute parameters.
+ *
+ * @type int $id Unique identifier, used to update an attribute.
+ * @type string $name Attribute name. Always required.
+ * @type string $slug Attribute alphanumeric identifier.
+ * @type string $type Type of attribute.
+ * Core by default accepts: 'select' and 'text'.
+ * Default to 'select'.
+ * @type string $order_by Sort order.
+ * Accepts: 'menu_order', 'name', 'name_num' and 'id'.
+ * Default to 'menu_order'.
+ * @type bool $has_archives Enable or disable attribute archives. False by default.
+ * }
* @return int|WP_Error
*/
function wc_create_attribute( $args ) {
@@ -404,7 +408,7 @@ function wc_create_attribute( $args ) {
// Name is required.
if ( empty( $args['name'] ) ) {
- return new WP_Error( 'missing_attribute_name', __( 'Missing attribute name', 'woocommerce' ), array( 'status' => 400 ) );
+ return new WP_Error( 'missing_attribute_name', __( 'Please, provide an attribute name.', 'woocommerce' ), array( 'status' => 400 ) );
}
// Set the attribute slug.
@@ -419,7 +423,7 @@ function wc_create_attribute( $args ) {
return new WP_Error( 'invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
} elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) {
return new WP_Error( 'invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
- } elseif ( 0 === $id && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) {
+ } elseif ( ( 0 === $id && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) || ( isset( $args['old_slug'] ) && $args['old_slug'] !== $args['slug'] && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) ) {
return new WP_Error( 'invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
}
@@ -438,7 +442,7 @@ function wc_create_attribute( $args ) {
'attribute_name' => $slug,
'attribute_type' => $args['type'],
'attribute_orderby' => $args['order_by'],
- 'attribute_public' => isset( $args['has_archives'] ) ? (bool) $args['has_archives'] : false,
+ 'attribute_public' => isset( $args['has_archives'] ) ? (int) $args['has_archives'] : 0,
);
// Create or update.
@@ -454,6 +458,14 @@ function wc_create_attribute( $args ) {
}
$id = $wpdb->insert_id;
+
+ /**
+ * Attribute added.
+ *
+ * @param int $id Added attribute ID.
+ * @param array $data Attribute data.
+ */
+ do_action( 'woocommerce_attribute_added', $id, $data );
} else {
$results = $wpdb->update(
$wpdb->prefix . 'woocommerce_attribute_taxonomies',
@@ -466,9 +478,55 @@ function wc_create_attribute( $args ) {
if ( false === $results ) {
return new WP_Error( 'cannot_update_attribute', __( 'Could not update the attribute.', 'woocommerce' ), array( 'status' => 400 ) );
}
+
+ // Set old_slug to check for database changes.
+ $args['old_slug'] = ! empty( $args['old_slug'] ) ? $args['old_slug'] : $args['slug'];
+
+ /**
+ * Attribute updated.
+ *
+ * @param int $id Added attribute ID.
+ * @param array $data Attribute data.
+ * @param string $old_slug Attribute old name.
+ */
+ do_action( 'woocommerce_attribute_updated', $id, $data, $args['old_slug'] );
+
+ if ( $args['old_slug'] !== $args['slug'] ) {
+ // Update taxonomies in the wp term taxonomy table.
+ $wpdb->update(
+ $wpdb->term_taxonomy,
+ array( 'taxonomy' => wc_attribute_taxonomy_name( $data['attribute_name'] ) ),
+ array( 'taxonomy' => 'pa_' . $args['old_slug'] )
+ );
+
+ // Update taxonomy ordering term meta.
+ $table_name = get_option( 'db_version' ) < 34370 ? $wpdb->prefix . 'woocommerce_termmeta' : $wpdb->termmeta;
+ $wpdb->update(
+ $table_name,
+ array( 'meta_key' => 'order_pa_' . sanitize_title( $data['attribute_name'] ) ),
+ array( 'meta_key' => 'order_pa_' . sanitize_title( $args['old_slug'] ) )
+ );
+
+ // Update product attributes which use this taxonomy.
+ $old_attribute_name_length = strlen( $args['old_slug'] ) + 3;
+ $attribute_name_length = strlen( $data['attribute_name'] ) + 3;
+
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE( meta_value, %s, %s ) WHERE meta_key = '_product_attributes'",
+ 's:' . $old_attribute_name_length . ':"pa_' . $args['old_slug'] . '"',
+ 's:' . $attribute_name_length . ':"pa_' . $data['attribute_name'] . '"'
+ ) );
+
+ // Update variations which use this taxonomy.
+ $wpdb->update(
+ $wpdb->postmeta,
+ array( 'meta_key' => 'attribute_pa_' . sanitize_title( $data['attribute_name'] ) ),
+ array( 'meta_key' => 'attribute_pa_' . sanitize_title( $args['old_slug'] ) )
+ );
+ }
}
- // Clear transients.
+ // Clear cache and flush rewrite rules.
+ wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' );
delete_transient( 'wc_attribute_taxonomies' );
return $id;
@@ -482,9 +540,11 @@ function wc_create_attribute( $args ) {
* @since 3.2.0
* @param int $id Attribute ID.
* @param array $args Attribute arguments.
- * @return int|WP_Error]
+ * @return int|WP_Error
*/
function wc_update_attribute( $id, $args ) {
+ global $wpdb;
+
$attribute = wc_get_attribute( $id );
$args['id'] = $attribute ? $attribute->id : 0;
@@ -493,6 +553,13 @@ function wc_update_attribute( $id, $args ) {
$args['name'] = $attribute->name;
}
+ $args['old_slug'] = $wpdb->get_var( $wpdb->prepare( "
+ SELECT attribute_name
+ FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
+ WHERE attribute_id = %d
+ ", $args['id']
+ ) );
+
return wc_create_attribute( $args );
}
@@ -506,18 +573,44 @@ function wc_update_attribute( $id, $args ) {
function wc_delete_attribute( $id ) {
global $wpdb;
- $deleted = $wpdb->delete(
- $wpdb->prefix . 'woocommerce_attribute_taxonomies',
- array( 'attribute_id' => $id ),
- array( '%d' )
- );
+ $name = $wpdb->get_var( $wpdb->prepare( "
+ SELECT attribute_name
+ FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
+ WHERE attribute_id = %d
+ ", $id ) );
- if ( in_array( $deleted, array( 0, false ), true ) ) {
- return false;
+ $taxonomy = wc_attribute_taxonomy_name( $name );
+
+ /**
+ * Before deleting an attribute.
+ *
+ * @param int $id Attribute ID.
+ * @param string $name Attribute name.
+ * @param string $taxonomy Attribute taxonomy name.
+ */
+ do_action( 'woocommerce_before_attribute_delete', $id, $name, $taxonomy );
+
+ if ( $name && $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = $id" ) ) {
+ if ( taxonomy_exists( $taxonomy ) ) {
+ $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' );
+ foreach ( $terms as $term ) {
+ wp_delete_term( $term->term_id, $taxonomy );
+ }
+ }
+
+ /**
+ * After deleting an attribute.
+ *
+ * @param int $id Attribute ID.
+ * @param string $name Attribute name.
+ * @param string $taxonomy Attribute taxonomy name.
+ */
+ do_action( 'woocommerce_attribute_deleted', $id, $name, $taxonomy );
+ wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' );
+ delete_transient( 'wc_attribute_taxonomies' );
+
+ return true;
}
- // Clear transients.
- delete_transient( 'wc_attribute_taxonomies' );
-
- return true;
+ return false;
}
diff --git a/includes/wc-cart-functions.php b/includes/wc-cart-functions.php
index 2f69aa152b9..c9b44b75dd0 100644
--- a/includes/wc-cart-functions.php
+++ b/includes/wc-cart-functions.php
@@ -191,7 +191,6 @@ add_action( 'get_header', 'wc_clear_cart_after_payment' );
* Get the subtotal.
*
* @access public
- * @return string
*/
function wc_cart_totals_subtotal_html() {
echo WC()->cart->get_cart_subtotal();
@@ -388,3 +387,69 @@ function wc_get_chosen_shipping_method_ids() {
}
return $method_ids;
}
+
+/**
+ * Get chosen method for package from session.
+ *
+ * @since 3.2.0
+ * @param int $key
+ * @param array $package
+ * @return string|bool
+ */
+function wc_get_chosen_shipping_method_for_package( $key, $package ) {
+ $chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
+ $chosen_method = isset( $chosen_methods[ $key ] ) ? $chosen_methods[ $key ] : false;
+ $changed = wc_shipping_methods_have_changed( $key, $package );
+ // If not set, not available, or available methods have changed, set to the DEFAULT option
+ if ( ! $chosen_method || $changed || ! isset( $package['rates'][ $chosen_method ] ) ) {
+ $chosen_method = wc_get_default_shipping_method_for_package( $key, $package, $chosen_method );
+ $chosen_methods[ $key ] = $chosen_method;
+ WC()->session->set( 'chosen_shipping_methods', $chosen_methods );
+ do_action( 'woocommerce_shipping_method_chosen', $chosen_method );
+ }
+ return $chosen_method;
+}
+/**
+ * Choose the default method for a package.
+ *
+ * @since 3.2.0
+ * @param string $key
+ * @param array $package
+ * @return string
+ */
+function wc_get_default_shipping_method_for_package( $key, $package, $chosen_method ) {
+ $rate_keys = array_keys( $package['rates'] );
+ $default = current( $rate_keys );
+ $coupons = WC()->cart->get_coupons();
+ foreach ( $coupons as $coupon ) {
+ if ( $coupon->get_free_shipping() ) {
+ foreach ( $rate_keys as $rate_key ) {
+ if ( 0 === stripos( $rate_key, 'free_shipping' ) ) {
+ $default = $rate_key;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return apply_filters( 'woocommerce_shipping_chosen_method', $default, $package['rates'], $chosen_method );
+}
+/**
+ * See if the methods have changed since the last request.
+ *
+ * @since 3.2.0
+ * @param int $key
+ * @param array $package
+ * @return bool
+ */
+function wc_shipping_methods_have_changed( $key, $package ) {
+ // Lookup previous methods from session.
+ $previous_shipping_methods = WC()->session->get( 'previous_shipping_methods' );
+ // Get new and old rates.
+ $new_rates = array_keys( $package['rates'] );
+ $prev_rates = isset( $previous_shipping_methods[ $key ] ) ? $previous_shipping_methods[ $key ] : false;
+ // Update session.
+ $previous_shipping_methods[ $key ] = $new_rates;
+ WC()->session->set( 'previous_shipping_methods', $previous_shipping_methods );
+ return $new_rates != $prev_rates;
+}
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 0b2e7793c47..958d6faf7a4 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -1443,6 +1443,66 @@ function wc_get_rounding_precision() {
return $precision;
}
+/**
+ * Add precision to a number and return an int.
+ *
+ * @since 3.2.0
+ * @param float $value Number to add precision to.
+ * @return int
+ */
+function wc_add_number_precision( $value ) {
+ $precision = pow( 10, wc_get_price_decimals() );
+ return $value * $precision;
+}
+
+/**
+ * Remove precision from a number and return a float.
+ *
+ * @since 3.2.0
+ * @param float $value Number to add precision to.
+ * @return float
+ */
+function wc_remove_number_precision( $value ) {
+ $precision = pow( 10, wc_get_price_decimals() );
+ return wc_format_decimal( $value / $precision, wc_get_price_decimals() );
+}
+
+/**
+ * Add precision to an array of number and return an array of int.
+ *
+ * @since 3.2.0
+ * @param array $value Number to add precision to.
+ * @return int
+ */
+function wc_add_number_precision_deep( $value ) {
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $subvalue ) {
+ $value[ $key ] = wc_add_number_precision_deep( $subvalue );
+ }
+ } else {
+ $value = wc_add_number_precision( $value );
+ }
+ return $value;
+}
+
+/**
+ * Remove precision from an array of number and return an array of int.
+ *
+ * @since 3.2.0
+ * @param array $value Number to add precision to.
+ * @return int
+ */
+function wc_remove_number_precision_deep( $value ) {
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $subvalue ) {
+ $value[ $key ] = wc_remove_number_precision_deep( $subvalue );
+ }
+ } else {
+ $value = wc_remove_number_precision( $value );
+ }
+ return $value;
+}
+
/**
* Get a shared logger instance.
*
@@ -1690,7 +1750,19 @@ function wc_make_phone_clickable( $phone ) {
* @return mixed value sanitized by wc_clean
*/
function wc_get_post_data_by_key( $key, $default = '' ) {
- return wc_clean( isset( $_POST[ $key ] ) ? $_POST[ $key ] : $default );
+ return wc_clean( wc_get_var( $_POST[ $key ], $default ) );
+}
+
+/**
+ * Get data if set, otherwise return a default value or null. Prevents notices when data is not set.
+ *
+ * @since 3.2.0
+ * @param mixed $var
+ * @param string $default
+ * @return mixed value sanitized by wc_clean
+ */
+function wc_get_var( &$var, $default = null ) {
+ return isset( $var ) ? $var : $default;
}
/**
diff --git a/includes/wc-formatting-functions.php b/includes/wc-formatting-functions.php
index c4e7bbc98e4..c0d1ba2ff70 100644
--- a/includes/wc-formatting-functions.php
+++ b/includes/wc-formatting-functions.php
@@ -216,11 +216,12 @@ function wc_trim_zeros( $price ) {
/**
* Round a tax amount.
*
- * @param mixed $tax
+ * @param double $tax Amount to round.
+ * @param int $dp DP to round. Defaults to wc_get_price_decimals.
* @return double
*/
-function wc_round_tax_total( $tax ) {
- $dp = wc_get_price_decimals();
+function wc_round_tax_total( $tax, $dp = null ) {
+ $dp = is_null( $dp ) ? wc_get_price_decimals() : absint( $dp );
// @codeCoverageIgnoreStart
if ( version_compare( phpversion(), '5.3', '<' ) ) {
diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php
index b510d625ce2..1f75eda82a8 100644
--- a/includes/wc-order-functions.php
+++ b/includes/wc-order-functions.php
@@ -223,7 +223,7 @@ function wc_get_order_types( $for = '' ) {
/**
* Get an order type by post type name.
* @param string post type name
- * @return bool|array of datails about the order type
+ * @return bool|array of details about the order type
*/
function wc_get_order_type( $type ) {
global $wc_order_types;
diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php
index 3858aeb1200..e95ec07c3d5 100644
--- a/includes/wc-product-functions.php
+++ b/includes/wc-product-functions.php
@@ -15,35 +15,12 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
/**
- * Products wrapper for get_posts.
+ * Standard way of retrieving products based on certain parameters.
*
* This function should be used for product retrieval so that we have a data agnostic
* way to get a list of products.
*
- * Args:
- * status array|string List of statuses to find. Default: any. Options: any, draft, pending, private and publish.
- * type array|string Product type, e.g. Default: all. Options: all, simple, external, variable, variation, grouped.
- * parent int post/product parent
- * sku string Limit result set to products with specific SKU.
- * category array Limit result set to products assigned to specific categories by slug
- * e.g. array('hoodie', 'cap', 't-shirt').
- * tag array Limit result set to products assigned to specific tags (by slug)
- * e.g. array('funky', 'retro', 'designer')
- * shipping_class array Limit results set to products in specific shipping classes (by slug)
- * e.g. array('standard', 'next-day')
- * limit int Maximum of products to retrieve.
- * offset int Offset of products to retrieve.
- * page int Page of products to retrieve. Ignored when using the 'offset' arg.
- * exclude array Product IDs to exclude from the query.
- * orderby string Order by date, title, id, modified, rand etc
- * order string ASC or DESC
- * return string Type of data to return. Allowed values:
- * ids array of Product ids
- * objects array of product objects (default)
- * paginate bool If true, the return value will be an array with values:
- * 'products' => array of data (return value above),
- * 'total' => total number of products matching the query
- * 'max_num_pages' => max number of pages found
+ * Args and usage: https://github.com/woocommerce/woocommerce/wiki/wc_get_products-and-WC_Product_Query
*
* @since 3.0.0
* @param array $args Array of args (above)
@@ -51,25 +28,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* paginate is true, or just an array of values.
*/
function wc_get_products( $args ) {
- $args = wp_parse_args( $args, array(
- 'status' => array( 'draft', 'pending', 'private', 'publish' ),
- 'type' => array_merge( array_keys( wc_get_product_types() ) ),
- 'parent' => null,
- 'sku' => '',
- 'category' => array(),
- 'tag' => array(),
- 'limit' => get_option( 'posts_per_page' ),
- 'offset' => null,
- 'page' => 1,
- 'include' => array(),
- 'exclude' => array(),
- 'orderby' => 'date',
- 'order' => 'DESC',
- 'return' => 'objects',
- 'paginate' => false,
- 'shipping_class' => array(),
- ) );
-
// Handle some BW compatibility arg names where wp_query args differ in naming.
$map_legacy = array(
'numberposts' => 'limit',
@@ -85,7 +43,8 @@ function wc_get_products( $args ) {
}
}
- return WC_Data_Store::load( 'product' )->get_products( $args );
+ $query = new WC_Product_Query( $args );
+ return $query->get_products();
}
/**
@@ -786,7 +745,7 @@ function wc_get_min_max_price_meta_query( $args ) {
'key' => '_price',
'value' => array( $min, $max ),
'compare' => 'BETWEEN',
- 'type' => 'NUMERIC',
+ 'type' => 'DECIMAL(10,' . wc_get_price_decimals() . ')',
);
}
diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php
index 997c811f9fc..3eb519a97b5 100644
--- a/includes/wc-template-functions.php
+++ b/includes/wc-template-functions.php
@@ -1329,7 +1329,7 @@ if ( ! function_exists( 'woocommerce_related_products' ) ) {
$args = wp_parse_args( $args, $defaults );
- // Get visble related products then sort them at random.
+ // Get visible related products then sort them at random.
$args['related_products'] = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $args['posts_per_page'], $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' );
// Handle orderby.
@@ -1371,7 +1371,7 @@ if ( ! function_exists( 'woocommerce_upsell_display' ) ) {
$orderby = apply_filters( 'woocommerce_upsells_orderby', isset( $args['orderby'] ) ? $args['orderby'] : $orderby );
$limit = apply_filters( 'woocommerce_upsells_total', isset( $args['posts_per_page'] ) ? $args['posts_per_page'] : $limit );
- // Get visble upsells then sort them at random, then limit result set.
+ // Get visible upsells then sort them at random, then limit result set.
$upsells = wc_products_array_orderby( array_filter( array_map( 'wc_get_product', $product->get_upsell_ids() ), 'wc_products_array_filter_visible' ), $orderby, $order );
$upsells = $limit > 0 ? array_slice( $upsells, 0, $limit ) : $upsells;
@@ -2102,7 +2102,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
if ( ! empty( $args['options'] ) ) {
foreach ( $args['options'] as $option_key => $option_text ) {
- $field .= '
';
+ $field .= '
';
$field .= '
' . $option_text . ' ';
}
}
@@ -2460,7 +2460,7 @@ if ( ! function_exists( 'wc_display_item_meta' ) ) {
/**
* Display item meta data.
* @since 3.0.0
- * @param WC_Item $item
+ * @param WC_Order_Item $item
* @param array $args
* @return string|void
*/
@@ -2498,7 +2498,7 @@ if ( ! function_exists( 'wc_display_item_downloads' ) ) {
/**
* Display item download links.
* @since 3.0.0
- * @param WC_Item $item
+ * @param WC_Order_Item $item
* @param array $args
* @return string|void
*/
diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php
index 6cdef46a8b3..250c07c4c70 100644
--- a/includes/wc-update-functions.php
+++ b/includes/wc-update-functions.php
@@ -995,8 +995,6 @@ function wc_update_300_webhooks() {
/**
* Add an index to the field comment_type to improve the response time of the query
* used by WC_Comments::wp_count_comments() to get the number of comments by type.
- *
- * @return null
*/
function wc_update_300_comment_type_index() {
global $wpdb;
diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php
index 9d572654eea..407865f05cf 100644
--- a/includes/wc-user-functions.php
+++ b/includes/wc-user-functions.php
@@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Prevent any user who cannot 'edit_posts' (subscribers, customers etc) from seeing the admin bar.
*
- * Note: get_option( 'woocommerce_lock_down_admin', true ) is a deprecated option here for backwards compat. Defaults to true.
+ * Note: get_option( 'woocommerce_lock_down_admin', true ) is a deprecated option here for backwards compatibility. Defaults to true.
*
* @access public
* @param bool $show_admin_bar
@@ -320,7 +320,7 @@ function wc_modify_editable_roles( $roles ) {
add_filter( 'editable_roles', 'wc_modify_editable_roles' );
/**
- * Modify capabiltiies to prevent non-admin users editing admin users.
+ * Modify capabilities to prevent non-admin users editing admin users.
*
* $args[0] will be the user being edited in this case.
*
diff --git a/includes/widgets/class-wc-widget-layered-nav-filters.php b/includes/widgets/class-wc-widget-layered-nav-filters.php
index 1d85c6e7404..8ae2251f99b 100644
--- a/includes/widgets/class-wc-widget-layered-nav-filters.php
+++ b/includes/widgets/class-wc-widget-layered-nav-filters.php
@@ -62,7 +62,7 @@ class WC_Widget_Layered_Nav_Filters extends WC_Widget {
$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link );
}
- // Orderby
+ // Order by
if ( isset( $_GET['orderby'] ) ) {
$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link );
}
diff --git a/includes/widgets/class-wc-widget-layered-nav.php b/includes/widgets/class-wc-widget-layered-nav.php
index e267c81b2e8..6bd3b08d459 100644
--- a/includes/widgets/class-wc-widget-layered-nav.php
+++ b/includes/widgets/class-wc-widget-layered-nav.php
@@ -331,7 +331,7 @@ class WC_Widget_Layered_Nav extends WC_Widget {
$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link );
}
- // Orderby
+ // Order by
if ( isset( $_GET['orderby'] ) ) {
$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link );
}
diff --git a/includes/widgets/class-wc-widget-rating-filter.php b/includes/widgets/class-wc-widget-rating-filter.php
index 3a78a8bd623..da52ea377d3 100644
--- a/includes/widgets/class-wc-widget-rating-filter.php
+++ b/includes/widgets/class-wc-widget-rating-filter.php
@@ -56,7 +56,7 @@ class WC_Widget_Rating_Filter extends WC_Widget {
$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link );
}
- // Orderby
+ // Order by
if ( isset( $_GET['orderby'] ) ) {
$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link );
}
diff --git a/phpunit.xml b/phpunit.xml
index 0a829bc9242..9e915671956 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -18,8 +18,10 @@
.
- ./apigen/
- ./i18n/
+ ./apigen/
+ ./assets/
+ ./dummy-data/
+ ./i18n/
./includes/api/legacy/
./includes/gateways/simplify-commerce-deprecated/
./includes/gateways/simplify-commerce/includes/
@@ -30,14 +32,17 @@
./includes/shipping/legacy-local-delivery/
./includes/shipping/legacy-local-pickup/
./includes/updates/
+ ./includes/vendor/
./includes/widgets/
./templates/
./tests/
+ ./vendor/
+ ./.*/
+ ./tmp/
+ ./tests/
./tmp/
+ ./vendor/
-
-
-
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 0ae53994749..16455121568 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -18,8 +18,10 @@
.
- ./apigen/
- ./i18n/
+ ./apigen/
+ ./assets/
+ ./dummy-data/
+ ./i18n/
./includes/api/legacy/
./includes/gateways/simplify-commerce-deprecated/
./includes/gateways/simplify-commerce/includes/
@@ -33,7 +35,9 @@
./includes/widgets/
./templates/
./tests/
- ./tmp/
+ ./vendor/
+ ./.*/
+ ./tmp/
diff --git a/templates/cart/shipping-calculator.php b/templates/cart/shipping-calculator.php
index 8e225458b9a..0d997231f59 100644
--- a/templates/cart/shipping-calculator.php
+++ b/templates/cart/shipping-calculator.php
@@ -60,7 +60,7 @@ do_action( 'woocommerce_before_shipping_calculator' ); ?>
} elseif ( is_array( $states ) ) {
?>
-
+
$cvalue ) {
diff --git a/templates/global/form-login.php b/templates/global/form-login.php
index 82793796c48..26958ac229f 100644
--- a/templates/global/form-login.php
+++ b/templates/global/form-login.php
@@ -25,7 +25,7 @@ if ( is_user_logged_in() ) {
}
?>
-
-
-