get_items( array( 'coupon' ) );
+ $coupons = $order->get_items( 'coupon' );
if ( $coupons ) {
?>
@@ -109,7 +113,7 @@ if ( wc_tax_enabled() ) {
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' LIMIT 1;", $item->get_code() ) );
$link = $post_id ? add_query_arg( array( 'post' => $post_id, 'action' => 'edit' ), admin_url( 'post.php' ) ) : add_query_arg( array( 's' => $item->get_code(), 'post_status' => 'all', 'post_type' => 'shop_coupon' ), admin_url( 'edit.php' ) );
- echo '
' . esc_html( $item->get_code() ) . ' ';
+ echo '
' . esc_html( $item->get_code() ) . ' ';
}
?>
@@ -117,28 +121,7 @@ if ( wc_tax_enabled() ) {
}
?>
-
-
-
-
- get_total_discount(), array( 'currency' => $order->get_currency() ) ); ?>
-
-
-
get_id() ); ?>
-
-
-
-
- get_total_shipping_refunded() ) > 0 ) {
- echo '' . strip_tags( wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) ) ) . ' ' . wc_price( $order->get_shipping_total() - $refunded, array( 'currency' => $order->get_currency() ) ) . ' ';
- } else {
- echo wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) );
- }
- ?>
-
-
get_id() ); ?>
@@ -186,12 +169,10 @@ if ( wc_tax_enabled() ) {
is_editable() ) : ?>
+
- is_editable() ) : ?>
-
-
get_total() - $order->get_total_refunded() || 0 < absint( $order->get_item_count() - $order->get_item_count_refunded() ) ) : ?>
@@ -208,6 +189,9 @@ if ( wc_tax_enabled() ) {
+
+
+
get_refunded_by() );
get_id(), wc_format_datetime( $refund->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) ) );
-
if ( $who_refunded->exists() ) {
- echo ' ' . esc_attr_x( 'by', 'Ex: Refund - $date >by< $username', 'woocommerce' ) . ' ' . '' . esc_attr( $who_refunded->display_name ) . ' ' ;
+ printf(
+ /* translators: 1: refund id 2: refund date 3: username */
+ __( 'Refund #%1$s - %2$s by %3$s', 'woocommerce' ),
+ $refund->get_id(),
+ wc_format_datetime( $refund->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) ),
+ sprintf(
+ '%2$s ',
+ sprintf( esc_attr__( 'ID: %d', 'woocommerce' ), absint( $who_refunded->ID ) ),
+ esc_html( $who_refunded->display_name )
+ )
+ );
+ } else {
+ printf(
+ /* translators: 1: refund id 2: refund date */
+ __( 'Refund #%1$s - %2$s', 'woocommerce' ),
+ $refund->get_id(),
+ wc_format_datetime( $refund->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) )
+ );
}
?>
get_reason() ) : ?>
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 7921dc61d26..553b472129c 100644
--- a/includes/admin/reports/class-wc-report-sales-by-date.php
+++ b/includes/admin/reports/class-wc-report-sales-by-date.php
@@ -391,7 +391,7 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report {
// Total the refunds and sales amounts. Sales subract refunds. Note - total_sales also includes shipping costs.
$this->report_data->total_sales = wc_format_decimal( array_sum( wp_list_pluck( $this->report_data->orders, 'total_sales' ) ) - $this->report_data->total_refunds, 2 );
- $this->report_data->net_sales = wc_format_decimal( $this->report_data->total_sales - $this->report_data->total_shipping - $this->report_data->total_tax - $this->report_data->total_shipping_tax, 2 );
+ $this->report_data->net_sales = wc_format_decimal( $this->report_data->total_sales - $this->report_data->total_shipping - max( 0, $this->report_data->total_tax ) - max( 0, $this->report_data->total_shipping_tax ), 2 );
// Calculate average based on net
$this->report_data->average_sales = wc_format_decimal( $this->report_data->net_sales / ( $this->chart_interval + 1 ), 2 );
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 e5edd1a0769..974e93594ee 100644
--- a/includes/admin/reports/class-wc-report-sales-by-product.php
+++ b/includes/admin/reports/class-wc-report-sales-by-product.php
@@ -245,7 +245,7 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report {
foreach ( $top_sellers as $product ) {
echo '
' . $product->order_item_qty . '
- ' . get_the_title( $product->product_id ) . '
+ ' . esc_html( get_the_title( $product->product_id ) ) . '
' . $this->sales_sparkline( $product->product_id, 7, 'count' ) . '
';
}
@@ -293,7 +293,7 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report {
foreach ( $top_freebies as $product ) {
echo '
' . $product->order_item_qty . '
- ' . get_the_title( $product->product_id ) . '
+ ' . esc_html( get_the_title( $product->product_id ) ) . '
' . $this->sales_sparkline( $product->product_id, 7, 'count' ) . '
';
}
@@ -333,7 +333,7 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report {
foreach ( $top_earners as $product ) {
echo '
' . wc_price( $product->order_item_total ) . '
- ' . get_the_title( $product->product_id ) . '
+ ' . esc_html( get_the_title( $product->product_id ) ) . '
' . $this->sales_sparkline( $product->product_id, 7, 'sales' ) . '
';
}
diff --git a/includes/admin/reports/class-wc-report-stock.php b/includes/admin/reports/class-wc-report-stock.php
index 46bcc9f7f51..882e8ac29c0 100644
--- a/includes/admin/reports/class-wc-report-stock.php
+++ b/includes/admin/reports/class-wc-report-stock.php
@@ -101,7 +101,7 @@ class WC_Report_Stock extends WP_List_Table {
case 'parent' :
if ( $item->parent ) {
- echo get_the_title( $item->parent );
+ echo esc_html( get_the_title( $item->parent ) );
} else {
echo '-';
}
diff --git a/includes/admin/settings/class-wc-settings-accounts.php b/includes/admin/settings/class-wc-settings-accounts.php
index 1f667ad967d..f92e779c48b 100644
--- a/includes/admin/settings/class-wc-settings-accounts.php
+++ b/includes/admin/settings/class-wc-settings-accounts.php
@@ -23,13 +23,10 @@ class WC_Settings_Accounts extends WC_Settings_Page {
* Constructor.
*/
public function __construct() {
-
$this->id = 'account';
$this->label = __( 'Accounts', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-api.php b/includes/admin/settings/class-wc-settings-api.php
index d5e304c39b1..48bc94d301a 100644
--- a/includes/admin/settings/class-wc-settings-api.php
+++ b/includes/admin/settings/class-wc-settings-api.php
@@ -26,11 +26,8 @@ class WC_Settings_Rest_API extends WC_Settings_Page {
$this->id = 'api';
$this->label = __( 'API', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
add_action( 'woocommerce_settings_form_method_tab_' . $this->id, array( $this, 'form_method' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+ parent::__construct();
$this->notices();
}
diff --git a/includes/admin/settings/class-wc-settings-checkout.php b/includes/admin/settings/class-wc-settings-checkout.php
index 85c5cd9b1c4..4330d08cb4a 100644
--- a/includes/admin/settings/class-wc-settings-checkout.php
+++ b/includes/admin/settings/class-wc-settings-checkout.php
@@ -23,15 +23,11 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page {
* Constructor.
*/
public function __construct() {
-
$this->id = 'checkout';
$this->label = _x( 'Checkout', 'Settings tab label', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
add_action( 'woocommerce_admin_field_payment_gateways', array( $this, 'payment_gateways_setting' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php
index 79ce1838dbb..ad60d0e2640 100644
--- a/includes/admin/settings/class-wc-settings-emails.php
+++ b/includes/admin/settings/class-wc-settings-emails.php
@@ -26,11 +26,8 @@ class WC_Settings_Emails extends WC_Settings_Page {
$this->id = 'email';
$this->label = __( 'Emails', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
add_action( 'woocommerce_admin_field_email_notification', array( $this, 'email_notification_setting' ) );
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-general.php b/includes/admin/settings/class-wc-settings-general.php
index 716945c4aca..595576ed490 100644
--- a/includes/admin/settings/class-wc-settings-general.php
+++ b/includes/admin/settings/class-wc-settings-general.php
@@ -23,13 +23,10 @@ class WC_Settings_General extends WC_Settings_Page {
* Constructor.
*/
public function __construct() {
-
$this->id = 'general';
$this->label = __( 'General', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-integrations.php b/includes/admin/settings/class-wc-settings-integrations.php
index 126844c85f6..e8a2ceabd16 100644
--- a/includes/admin/settings/class-wc-settings-integrations.php
+++ b/includes/admin/settings/class-wc-settings-integrations.php
@@ -23,15 +23,11 @@ class WC_Settings_Integrations extends WC_Settings_Page {
* Constructor.
*/
public function __construct() {
-
$this->id = 'integration';
$this->label = __( 'Integration', 'woocommerce' );
if ( isset( WC()->integrations ) && WC()->integrations->get_integrations() ) {
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+ parent::__construct();
}
}
diff --git a/includes/admin/settings/class-wc-settings-products.php b/includes/admin/settings/class-wc-settings-products.php
index eecdd0defb6..ab639312193 100644
--- a/includes/admin/settings/class-wc-settings-products.php
+++ b/includes/admin/settings/class-wc-settings-products.php
@@ -23,14 +23,10 @@ class WC_Settings_Products extends WC_Settings_Page {
* Constructor.
*/
public function __construct() {
-
$this->id = 'products';
$this->label = __( 'Products', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-shipping.php b/includes/admin/settings/class-wc-settings-shipping.php
index f510bf83411..371079c575a 100644
--- a/includes/admin/settings/class-wc-settings-shipping.php
+++ b/includes/admin/settings/class-wc-settings-shipping.php
@@ -25,10 +25,8 @@ class WC_Settings_Shipping extends WC_Settings_Page {
public function __construct() {
$this->id = 'shipping';
$this->label = __( 'Shipping', 'woocommerce' );
- add_filter( 'woocommerce_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
- add_action( 'woocommerce_sections_' . $this->id, array( $this, 'output_sections' ) );
- add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
- add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
+
+ parent::__construct();
}
/**
diff --git a/includes/admin/settings/class-wc-settings-tax.php b/includes/admin/settings/class-wc-settings-tax.php
index 958390bd60b..fe7308c284f 100644
--- a/includes/admin/settings/class-wc-settings-tax.php
+++ b/includes/admin/settings/class-wc-settings-tax.php
@@ -19,18 +19,13 @@ if ( ! class_exists( 'WC_Settings_Tax', false ) ) :
*/
class WC_Settings_Tax extends WC_Settings_Page {
- /**
- * Setting page id.
- *
- * @var string
- */
- protected $id = 'tax';
-
/**
* Constructor.
*/
public function __construct() {
+ $this->id = 'tax';
$this->label = __( 'Tax', 'woocommerce' );
+
parent::__construct();
}
diff --git a/includes/admin/views/html-admin-page-status-report.php b/includes/admin/views/html-admin-page-status-report.php
index b1e06a17003..33c7529e783 100644
--- a/includes/admin/views/html-admin-page-status-report.php
+++ b/includes/admin/views/html-admin-page-status-report.php
@@ -13,14 +13,15 @@ if ( ! class_exists( 'WC_REST_System_Status_Controller', false ) ) {
wp_die( 'Cannot load the REST API to access WC_REST_System_Status_Controller.' );
}
-$system_status = new WC_REST_System_Status_Controller;
-$environment = $system_status->get_environment_info();
-$database = $system_status->get_database_info();
-$active_plugins = $system_status->get_active_plugins();
-$theme = $system_status->get_theme_info();
-$security = $system_status->get_security_info();
-$settings = $system_status->get_settings();
-$pages = $system_status->get_pages();
+$system_status = new WC_REST_System_Status_Controller;
+$environment = $system_status->get_environment_info();
+$database = $system_status->get_database_info();
+$post_type_counts = $system_status->get_post_type_counts();
+$active_plugins = $system_status->get_active_plugins();
+$theme = $system_status->get_theme_info();
+$security = $system_status->get_security_info();
+$settings = $system_status->get_settings();
+$pages = $system_status->get_pages();
?>
@@ -263,7 +264,7 @@ $pages = $system_status->get_pages();
if ( $environment['remote_post_successful'] ) {
echo '
';
} else {
- echo '
' . __( 'wp_remote_post() failed. Contact your hosting provider.', 'woocommerce' ) . ' ' . esc_html( $environment['remote_post_response'] ) . '';
+ echo '
' . sprintf( __( '%s failed. Contact your hosting provider.', 'woocommerce' ), 'wp_remote_post()' ) . ' ' . esc_html( $environment['remote_post_response'] ) . '';
} ?>
@@ -274,7 +275,7 @@ $pages = $system_status->get_pages();
if ( $environment['remote_get_successful'] ) {
echo '
';
} else {
- echo '
' . __( 'wp_remote_get() failed. Contact your hosting provider.', 'woocommerce' ) . ' ' . esc_html( $environment['remote_get_response'] ) . '';
+ echo '
' . sprintf( __( '%s failed. Contact your hosting provider.', 'woocommerce' ), 'wp_remote_get()' ) . ' ' . esc_html( $environment['remote_get_response'] ) . '';
} ?>
@@ -302,57 +303,106 @@ $pages = $system_status->get_pages();
-
-
-
-
-
-
-
- :
-
-
-
-
-
-
- 20 ) {
- echo ' ' . sprintf( __( '%1$s - We recommend using a prefix with less than 20 characters. See: %2$s', 'woocommerce' ), esc_html( $database['database_prefix'] ), '' . __( 'How to update your database table prefix', 'woocommerce' ) . ' ' ) . ' ';
- } else {
- echo '' . esc_html( $database['database_prefix'] ) . ' ';
- }
- ?>
-
-
- $table_exists ) {
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+ 20 ) {
+ echo ' ' . sprintf( __( '%1$s - We recommend using a prefix with less than 20 characters. See: %2$s', 'woocommerce' ), esc_html( $database['database_prefix'] ), '' . __( 'How to update your database table prefix', 'woocommerce' ) . ' ' ) . ' ';
+ } else {
+ echo '' . esc_html( $database['database_prefix'] ) . ' ';
+ }
?>
-
-
-
- ' . __( 'Table does not exist', 'woocommerce' ) . '' : ' '; ?>
-
-
+
- if ( $settings['geolocation_enabled'] ) {
- ?>
-
- :
-
- ' . esc_html( $database['maxmind_geoip_database'] ) . '
';
- } else {
- printf( ' ' . sprintf( __( 'The MaxMind GeoIP Database does not exist - Geolocation will not function. You can download and install it manually from %1$s to the path: %2$s. Scroll down to "Downloads" and download the "Binary / gzip" file next to "GeoLite Country". Please remember to uncompress GeoIP.dat.gz and upload the GeoIP.dat file only.', 'woocommerce' ), make_clickable( 'http://dev.maxmind.com/geoip/legacy/geolite/' ), '' . $database['maxmind_geoip_database'] . '
' ) . ' ', WC_LOG_DIR );
- }
- ?>
-
-
+
+ :
+
+ ' . esc_html( $database['maxmind_geoip_database'] ) . '
';
+ } else {
+ printf( ' ' . sprintf( __( 'The MaxMind GeoIP Database does not exist - Geolocation will not function. You can download and install it manually from %1$s to the path: %2$s. Scroll down to "Downloads" and download the "Binary / gzip" file next to "GeoLite Country". Please remember to uncompress GeoIP.dat.gz and upload the GeoIP.dat file only.', 'woocommerce' ), make_clickable( 'http://dev.maxmind.com/geoip/legacy/geolite/' ), '' . $database['maxmind_geoip_database'] . '
' ) . ' ', WC_LOG_DIR );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $table_data ) { ?>
+
+
+
+
+ ' . __( 'Table does not exist', 'woocommerce' ) . '';
+ } else {
+ printf( __( 'Data: %.2fMB + Index: %.2fMB', 'woocommerce' ), wc_format_decimal( $table_data['data'], 2 ), wc_format_decimal( $table_data['index'], 2 ) );
+ } ?>
+
+
+
+
+ $table_data ) { ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ type ); ?>
+
+ count ); ?>
+
+
+
diff --git a/includes/admin/views/html-admin-page-status-tools.php b/includes/admin/views/html-admin-page-status-tools.php
index 9c58c3dd957..da52db77540 100644
--- a/includes/admin/views/html-admin-page-status-tools.php
+++ b/includes/admin/views/html-admin-page-status-tools.php
@@ -14,18 +14,15 @@ if ( ! defined( 'ABSPATH' ) ) {
$tool ) : ?>
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
diff --git a/includes/admin/wc-admin-functions.php b/includes/admin/wc-admin-functions.php
index a8f77be4eaf..dd9a8c45325 100644
--- a/includes/admin/wc-admin-functions.php
+++ b/includes/admin/wc-admin-functions.php
@@ -186,12 +186,10 @@ function woocommerce_settings_get_option( $option_name, $default = '' ) {
* @param array $items Order items to save
*/
function wc_save_order_items( $order_id, $items ) {
- // Allow other plugins to check change in order items before they are saved
+ // Allow other plugins to check change in order items before they are saved.
do_action( 'woocommerce_before_save_order_items', $order_id, $items );
- $order = wc_get_order( $order_id );
-
- // Line items and fees
+ // Line items and fees.
if ( isset( $items['order_item_id'] ) ) {
$data_keys = array(
'line_tax' => array(),
@@ -203,7 +201,7 @@ function wc_save_order_items( $order_id, $items ) {
'line_subtotal' => null,
);
foreach ( $items['order_item_id'] as $item_id ) {
- if ( ! $item = $order->get_item( absint( $item_id ) ) ) {
+ if ( ! $item = WC_Order_Factory::get_order_item( absint( $item_id ) ) ) {
continue;
}
@@ -260,7 +258,7 @@ function wc_save_order_items( $order_id, $items ) {
);
foreach ( $items['shipping_method_id'] as $item_id ) {
- if ( ! $item = $order->get_item( absint( $item_id ) ) ) {
+ if ( ! $item = WC_Order_Factory::get_order_item( absint( $item_id ) ) ) {
continue;
}
@@ -299,10 +297,8 @@ function wc_save_order_items( $order_id, $items ) {
}
}
- // Updates tax totals
+ $order = wc_get_order( $order_id );
$order->update_taxes();
-
- // Calc totals - this also triggers save
$order->calculate_totals( false );
// Inform other plugins that the items have been saved
diff --git a/includes/api/class-wc-rest-system-status-controller.php b/includes/api/class-wc-rest-system-status-controller.php
index d3c96c52f09..d1800a08aaa 100644
--- a/includes/api/class-wc-rest-system-status-controller.php
+++ b/includes/api/class-wc-rest-system-status-controller.php
@@ -636,8 +636,18 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
public function get_database_info() {
global $wpdb;
+ $database_table_sizes = $wpdb->get_results( $wpdb->prepare( "
+ SELECT
+ table_name AS 'name',
+ round( ( data_length / 1024 / 1024 ), 2 ) 'data',
+ round( ( index_length / 1024 / 1024 ), 2 ) 'index'
+ FROM information_schema.TABLES
+ WHERE table_schema = %s
+ ORDER BY name ASC;
+ ", DB_NAME ) );
+
// WC Core tables to check existence of
- $tables = apply_filters( 'woocommerce_database_tables', array(
+ $core_tables = apply_filters( 'woocommerce_database_tables', array(
'woocommerce_sessions',
'woocommerce_api_keys',
'woocommerce_attribute_taxonomies',
@@ -651,14 +661,48 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
'woocommerce_shipping_zone_methods',
'woocommerce_payment_tokens',
'woocommerce_payment_tokenmeta',
+ 'woocommerce_log',
) );
if ( get_option( 'db_version' ) < 34370 ) {
- $tables[] = 'woocommerce_termmeta';
+ $core_tables[] = 'woocommerce_termmeta';
}
- $table_exists = array();
- foreach ( $tables as $table ) {
- $table_exists[ $table ] = ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s;", $wpdb->prefix . $table ) ) === $wpdb->prefix . $table );
+
+ /**
+ * Adding the prefix to the tables array, for backwards compatibility.
+ *
+ * If we changed the tables above to include the prefix, then any filters against that table could break.
+ */
+ $core_tables = array_map( function( $table ) {
+ global $wpdb;
+ return $wpdb->prefix . $table;
+ }, $core_tables );
+
+ /**
+ * Organize WooCommerce and non-WooCommerce tables separately for display purposes later.
+ *
+ * To ensure we include all WC tables, even if they do not exist, pre-populate the WC array with all the tables.
+ */
+ $tables = array(
+ 'woocommerce' => array_fill_keys( $core_tables, false ),
+ 'other' => array()
+ );
+
+ $database_size = array(
+ 'data' => 0,
+ 'index' => 0
+ );
+
+ foreach ( $database_table_sizes as $table ) {
+ $table_type = in_array( $table->name, $core_tables ) ? 'woocommerce' : 'other';
+
+ $tables[ $table_type ][ $table->name ] = array(
+ 'data' => $table->data,
+ 'index' => $table->index
+ );
+
+ $database_size[ 'data' ] += $table->data;
+ $database_size[ 'index' ] += $table->index;
}
// Return all database info. Described by JSON Schema.
@@ -666,10 +710,24 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
'wc_database_version' => get_option( 'woocommerce_db_version' ),
'database_prefix' => $wpdb->prefix,
'maxmind_geoip_database' => WC_Geolocation::get_local_database_path(),
- 'database_tables' => $table_exists,
+ 'database_tables' => $tables,
+ 'database_size' => $database_size,
);
}
+ /**
+ * Get array of counts of objects. Orders, products, etc.
+ *
+ * @return array
+ */
+ public function get_post_type_counts() {
+ global $wpdb;
+
+ $post_type_counts = $wpdb->get_results( "SELECT post_type AS 'type', count(1) AS 'count' FROM {$wpdb->posts} GROUP BY post_type;" );
+
+ return is_array( $post_type_counts ) ? $post_type_counts : array();
+ }
+
/**
* Get a list of plugins active on the site.
*
diff --git a/includes/api/class-wc-rest-system-status-tools-controller.php b/includes/api/class-wc-rest-system-status-tools-controller.php
index 07d08c7bcd6..17b0caef5d1 100644
--- a/includes/api/class-wc-rest-system-status-tools-controller.php
+++ b/includes/api/class-wc-rest-system-status-tools-controller.php
@@ -118,13 +118,13 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
public function get_tools() {
$tools = array(
'clear_transients' => array(
- 'name' => __( 'WC transients', 'woocommerce' ),
+ 'name' => __( 'WooCommerce transients', 'woocommerce' ),
'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear the product/shop transients cache.', 'woocommerce' ),
),
'clear_expired_transients' => array(
'name' => __( 'Expired transients', 'woocommerce' ),
- 'button' => __( 'Clear expired transients', 'woocommerce' ),
+ 'button' => __( 'Clear transients', 'woocommerce' ),
'desc' => __( 'This tool will clear ALL expired transients from WordPress.', 'woocommerce' ),
),
'delete_orphaned_variations' => array(
@@ -134,7 +134,7 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
),
'add_order_indexes' => array(
'name' => __( 'Order address indexes', 'woocommerce' ),
- 'button' => __( 'Add order address indexes', 'woocommerce' ),
+ 'button' => __( 'Index orders', 'woocommerce' ),
'desc' => __( 'This tool will add address indexes to orders that do not have them yet. This improves order search results.', 'woocommerce' ),
),
'recount_terms' => array(
@@ -148,8 +148,8 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
'desc' => __( 'This tool will reset the admin, customer and shop_manager roles to default. Use this if your users cannot access all of the WooCommerce admin pages.', 'woocommerce' ),
),
'clear_sessions' => array(
- 'name' => __( 'Customer sessions', 'woocommerce' ),
- 'button' => __( 'Clear all sessions', 'woocommerce' ),
+ 'name' => __( 'Clear customer sessions', 'woocommerce' ),
+ 'button' => __( 'Clear', 'woocommerce' ),
'desc' => sprintf(
'
%1$s %2$s',
__( 'Note:', 'woocommerce' ),
@@ -157,8 +157,8 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
),
),
'install_pages' => array(
- 'name' => __( 'Install WooCommerce pages', 'woocommerce' ),
- 'button' => __( 'Install pages', 'woocommerce' ),
+ 'name' => __( 'Create default WooCommerce pages', 'woocommerce' ),
+ 'button' => __( 'Create pages', 'woocommerce' ),
'desc' => sprintf(
'
%1$s %2$s',
__( 'Note:', 'woocommerce' ),
@@ -166,17 +166,26 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
),
),
'delete_taxes' => array(
- 'name' => __( 'Delete all WooCommerce tax rates', 'woocommerce' ),
- 'button' => __( 'Delete ALL tax rates', 'woocommerce' ),
+ 'name' => __( 'Delete WooCommerce tax rates', 'woocommerce' ),
+ 'button' => __( 'Delete tax rates', 'woocommerce' ),
'desc' => sprintf(
'
%1$s %2$s',
__( 'Note:', 'woocommerce' ),
- __( 'This option will delete ALL of your tax rates, use with caution.', 'woocommerce' )
+ __( 'This option will delete ALL of your tax rates, use with caution. This action cannot be reversed.', 'woocommerce' )
+ ),
+ ),
+ 'delete_webhook_logs' => array(
+ 'name' => __( 'Delete WebHook logs', 'woocommerce' ),
+ 'button' => __( 'Delete logs', 'woocommerce' ),
+ 'desc' => sprintf(
+ '
%1$s %2$s',
+ __( 'Note:', 'woocommerce' ),
+ __( 'This tool removes WebHook logs. This will not delete the WebHooks themselves.', 'woocommerce' )
),
),
'reset_tracking' => array(
- 'name' => __( 'Reset usage tracking settings', 'woocommerce' ),
- 'button' => __( 'Reset usage tracking settings', 'woocommerce' ),
+ 'name' => __( 'Reset usage tracking', 'woocommerce' ),
+ 'button' => __( 'Reset', 'woocommerce' ),
'desc' => __( 'This will reset your usage tracking settings, causing it to show the opt-in banner again and not sending any data.', 'woocommerce' ),
),
);
@@ -218,11 +227,11 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
}
$tool = $tools[ $request['id'] ];
return rest_ensure_response( $this->prepare_item_for_response( array(
- 'id' => $request['id'],
- 'name' => $tool['name'],
- 'action' => $tool['button'],
- 'description' => $tool['desc'],
- ), $request ) );
+ 'id' => $request['id'],
+ 'name' => $tool['name'],
+ 'action' => $tool['button'],
+ 'description' => $tool['desc'],
+ ), $request ) );
}
/**
@@ -459,6 +468,12 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
WC_Cache_Helper::incr_cache_prefix( 'taxes' );
$message = __( 'Tax rates successfully deleted', 'woocommerce' );
break;
+ case 'delete_webhook_logs' :
+
+ $result = absint( $wpdb->query( "DELETE FROM {$wpdb->comments} WHERE comment_type='webhook_delivery';" ) );
+ $wpdb->query( "DELETE commentmeta FROM {$wpdb->commentmeta} commentmeta LEFT JOIN {$wpdb->comments} comments ON comments.comment_ID = commentmeta.comment_id WHERE comments.comment_ID IS NULL;" );
+ $message = sprintf( __( '%d logs deleted', 'woocommerce' ), $result );
+ break;
case 'reset_tracking' :
delete_option( 'woocommerce_allow_tracking' );
WC_Admin_Notices::add_notice( 'tracking' );
diff --git a/includes/api/legacy/v2/class-wc-api-coupons.php b/includes/api/legacy/v2/class-wc-api-coupons.php
index f9f7dd82c28..ca57fb4670e 100644
--- a/includes/api/legacy/v2/class-wc-api-coupons.php
+++ b/includes/api/legacy/v2/class-wc-api-coupons.php
@@ -297,6 +297,7 @@ class WC_API_Coupons extends WC_API_Resource {
update_post_meta( $id, 'customer_email', array_filter( array_map( 'sanitize_email', $coupon_data['customer_emails'] ) ) );
do_action( 'woocommerce_api_create_coupon', $id, $data );
+ do_action( 'woocommerce_new_coupon', $id );
$this->server->send_status( 201 );
@@ -432,6 +433,7 @@ class WC_API_Coupons extends WC_API_Resource {
}
do_action( 'woocommerce_api_edit_coupon', $id, $data );
+ do_action( 'woocommerce_update_coupon', $id );
return $this->get_coupon( $id );
} catch ( WC_API_Exception $e ) {
diff --git a/includes/api/legacy/v2/class-wc-api-orders.php b/includes/api/legacy/v2/class-wc-api-orders.php
index 4c9461c9ec1..1ad7dca9450 100644
--- a/includes/api/legacy/v2/class-wc-api-orders.php
+++ b/includes/api/legacy/v2/class-wc-api-orders.php
@@ -464,6 +464,7 @@ class WC_API_Orders extends WC_API_Resource {
wc_delete_shop_order_transients( $order );
do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this );
+ do_action( 'woocommerce_new_order', $order->get_id() );
wc_transaction_query( 'commit' );
@@ -632,6 +633,7 @@ class WC_API_Orders extends WC_API_Resource {
wc_delete_shop_order_transients( $order );
do_action( 'woocommerce_api_edit_order', $order->get_id(), $data, $this );
+ do_action( 'woocommerce_update_order', $order->get_id() );
return $this->get_order( $id );
diff --git a/includes/api/legacy/v3/class-wc-api-coupons.php b/includes/api/legacy/v3/class-wc-api-coupons.php
index 1e2c9b8651c..43c71cb817c 100644
--- a/includes/api/legacy/v3/class-wc-api-coupons.php
+++ b/includes/api/legacy/v3/class-wc-api-coupons.php
@@ -295,6 +295,7 @@ class WC_API_Coupons extends WC_API_Resource {
update_post_meta( $id, 'customer_email', array_filter( array_map( 'sanitize_email', $coupon_data['customer_emails'] ) ) );
do_action( 'woocommerce_api_create_coupon', $id, $data );
+ do_action( 'woocommerce_new_coupon', $id );
$this->server->send_status( 201 );
@@ -430,6 +431,7 @@ class WC_API_Coupons extends WC_API_Resource {
}
do_action( 'woocommerce_api_edit_coupon', $id, $data );
+ do_action( 'woocommerce_update_coupon', $id );
return $this->get_coupon( $id );
} catch ( WC_API_Exception $e ) {
diff --git a/includes/api/legacy/v3/class-wc-api-orders.php b/includes/api/legacy/v3/class-wc-api-orders.php
index 262d8fcdd59..ec76b6c76bf 100644
--- a/includes/api/legacy/v3/class-wc-api-orders.php
+++ b/includes/api/legacy/v3/class-wc-api-orders.php
@@ -506,6 +506,7 @@ class WC_API_Orders extends WC_API_Resource {
wc_delete_shop_order_transients( $order );
do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this );
+ do_action( 'woocommerce_new_order', $order->get_id() );
wc_transaction_query( 'commit' );
@@ -671,6 +672,7 @@ class WC_API_Orders extends WC_API_Resource {
wc_delete_shop_order_transients( $order );
do_action( 'woocommerce_api_edit_order', $order->get_id(), $data, $this );
+ do_action( 'woocommerce_update_order', $order->get_id() );
return $this->get_order( $id );
} catch ( WC_Data_Exception $e ) {
diff --git a/includes/api/v1/class-wc-rest-customers-controller.php b/includes/api/v1/class-wc-rest-customers-controller.php
index 3034377c197..551b6e89dba 100644
--- a/includes/api/v1/class-wc-rest-customers-controller.php
+++ b/includes/api/v1/class-wc-rest-customers-controller.php
@@ -338,15 +338,13 @@ class WC_REST_Customers_V1_Controller extends WC_REST_Controller {
$customer->set_username( $request['username'] );
$customer->set_password( $request['password'] );
$customer->set_email( $request['email'] );
+ $this->update_customer_meta_fields( $customer, $request );
$customer->save();
if ( ! $customer->get_id() ) {
throw new WC_REST_Exception( 'woocommerce_rest_cannot_create', __( 'This resource cannot be created.', 'woocommerce' ), 400 );
}
- $this->update_customer_meta_fields( $customer, $request );
- $customer->save();
-
$user_data = get_userdata( $customer->get_id() );
$this->update_additional_fields_for_object( $user_data, $request );
diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php
index 93489455a89..6356b134e9b 100644
--- a/includes/class-wc-ajax.php
+++ b/includes/class-wc-ajax.php
@@ -113,6 +113,8 @@ class WC_AJAX {
'add_order_fee' => false,
'add_order_shipping' => false,
'add_order_tax' => false,
+ 'add_coupon_discount' => false,
+ 'remove_order_coupon' => false,
'remove_order_item' => false,
'remove_order_tax' => false,
'reduce_order_item_stock' => false,
@@ -365,7 +367,7 @@ class WC_AJAX {
do_action( 'woocommerce_ajax_added_to_cart', $product_id );
- if ( get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) {
+ if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
@@ -891,6 +893,60 @@ class WC_AJAX {
}
}
+ /**
+ * Add order discount via ajax.
+ */
+ public static function add_coupon_discount() {
+ check_ajax_referer( 'order-item', 'security' );
+
+ if ( ! current_user_can( 'edit_shop_orders' ) ) {
+ wp_die( -1 );
+ }
+
+ try {
+ $order_id = absint( $_POST['order_id'] );
+ $order = wc_get_order( $order_id );
+
+ $order->apply_coupon( wc_clean( $_POST['coupon'] ) );
+
+ ob_start();
+ include( 'admin/meta-boxes/views/html-order-items.php' );
+
+ wp_send_json_success( array(
+ 'html' => ob_get_clean(),
+ ) );
+ } catch ( Exception $e ) {
+ wp_send_json_error( array( 'error' => $e->getMessage() ) );
+ }
+ }
+
+ /**
+ * Remove coupon from an order via ajax.
+ */
+ public static function remove_order_coupon() {
+ check_ajax_referer( 'order-item', 'security' );
+
+ if ( ! current_user_can( 'edit_shop_orders' ) ) {
+ wp_die( -1 );
+ }
+
+ try {
+ $order_id = absint( $_POST['order_id'] );
+ $order = wc_get_order( $order_id );
+
+ $order->remove_coupon( wc_clean( $_POST['coupon'] ) );
+
+ ob_start();
+ include( 'admin/meta-boxes/views/html-order-items.php' );
+
+ wp_send_json_success( array(
+ 'html' => ob_get_clean(),
+ ) );
+ } catch ( Exception $e ) {
+ wp_send_json_error( array( 'error' => $e->getMessage() ) );
+ }
+ }
+
/**
* Remove an order item.
*/
@@ -901,18 +957,32 @@ class WC_AJAX {
wp_die( -1 );
}
- $order_item_ids = $_POST['order_item_ids'];
+ try {
+ $order_id = absint( $_POST['order_id'] );
+ $order_item_ids = $_POST['order_item_ids'];
- if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
- $order_item_ids = array( $order_item_ids );
- }
-
- if ( sizeof( $order_item_ids ) > 0 ) {
- foreach ( $order_item_ids as $id ) {
- wc_delete_order_item( absint( $id ) );
+ if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
+ $order_item_ids = array( $order_item_ids );
}
+
+ if ( sizeof( $order_item_ids ) > 0 ) {
+ foreach ( $order_item_ids as $id ) {
+ wc_delete_order_item( absint( $id ) );
+ }
+ }
+
+ $order = wc_get_order( $order_id );
+ $order->calculate_totals( true );
+
+ ob_start();
+ include( 'admin/meta-boxes/views/html-order-items.php' );
+
+ wp_send_json_success( array(
+ 'html' => ob_get_clean(),
+ ) );
+ } catch ( Exception $e ) {
+ wp_send_json_error( array( 'error' => $e->getMessage() ) );
}
- wp_die();
}
/**
@@ -925,15 +995,24 @@ class WC_AJAX {
wp_die( -1 );
}
- $order_id = absint( $_POST['order_id'] );
- $rate_id = absint( $_POST['rate_id'] );
+ try {
+ $order_id = absint( $_POST['order_id'] );
+ $rate_id = absint( $_POST['rate_id'] );
- wc_delete_order_item( $rate_id );
+ wc_delete_order_item( $rate_id );
- // Return HTML items
- $order = wc_get_order( $order_id );
- include( 'admin/meta-boxes/views/html-order-items.php' );
- wp_die();
+ $order = wc_get_order( $order_id );
+ $order->calculate_totals( false );
+
+ ob_start();
+ include( 'admin/meta-boxes/views/html-order-items.php' );
+
+ wp_send_json_success( array(
+ 'html' => ob_get_clean(),
+ ) );
+ } catch ( Exception $e ) {
+ wp_send_json_error( array( 'error' => $e->getMessage() ) );
+ }
}
/**
diff --git a/includes/class-wc-auth.php b/includes/class-wc-auth.php
index d1886e76e30..a85a9fdcdb8 100644
--- a/includes/class-wc-auth.php
+++ b/includes/class-wc-auth.php
@@ -374,7 +374,7 @@ class WC_Auth {
exit;
}
} else {
- throw new Exception( __( 'You do not have permissions to access this page!', 'woocommerce' ) );
+ throw new Exception( __( 'You do not have permission to access this page', 'woocommerce' ) );
}
} catch ( Exception $e ) {
$this->maybe_delete_key( $consumer_data );
diff --git a/includes/class-wc-autoloader.php b/includes/class-wc-autoloader.php
index b6c7416a5ea..a44efdd07b0 100644
--- a/includes/class-wc-autoloader.php
+++ b/includes/class-wc-autoloader.php
@@ -73,21 +73,21 @@ class WC_Autoloader {
$file = $this->get_file_name_from_class( $class );
$path = '';
- if ( strpos( $class, 'wc_addons_gateway_' ) === 0 ) {
+ if ( 0 === strpos( $class, 'wc_addons_gateway_' ) ) {
$path = $this->include_path . 'gateways/' . substr( str_replace( '_', '-', $class ), 18 ) . '/';
- } elseif ( strpos( $class, 'wc_gateway_' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_gateway_' ) ) {
$path = $this->include_path . 'gateways/' . substr( str_replace( '_', '-', $class ), 11 ) . '/';
- } elseif ( strpos( $class, 'wc_shipping_' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_shipping_' ) ) {
$path = $this->include_path . 'shipping/' . substr( str_replace( '_', '-', $class ), 12 ) . '/';
- } elseif ( strpos( $class, 'wc_shortcode_' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_shortcode_' ) ) {
$path = $this->include_path . 'shortcodes/';
- } elseif ( strpos( $class, 'wc_meta_box' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_meta_box' ) ) {
$path = $this->include_path . 'admin/meta-boxes/';
- } elseif ( strpos( $class, 'wc_admin' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_admin' ) ) {
$path = $this->include_path . 'admin/';
- } elseif ( strpos( $class, 'wc_payment_token_' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_payment_token_' ) ) {
$path = $this->include_path . 'payment-tokens/';
- } elseif ( strpos( $class, 'wc_log_handler_' ) === 0 ) {
+ } elseif ( 0 === strpos( $class, 'wc_log_handler_' ) ) {
$path = $this->include_path . 'log-handlers/';
}
diff --git a/includes/class-wc-breadcrumb.php b/includes/class-wc-breadcrumb.php
index 2950012fd83..768c9ef0d42 100644
--- a/includes/class-wc-breadcrumb.php
+++ b/includes/class-wc-breadcrumb.php
@@ -155,7 +155,7 @@ class WC_Breadcrumb {
} else {
$cat = current( get_the_category( $post ) );
if ( $cat ) {
- $this->term_ancestors( $cat->term_id, 'post_category' );
+ $this->term_ancestors( $cat->term_id, 'category' );
$this->add_crumb( $cat->name, get_term_link( $cat ) );
}
}
@@ -247,8 +247,7 @@ class WC_Breadcrumb {
$this_category = get_category( $GLOBALS['wp_query']->get_queried_object() );
if ( 0 != $this_category->parent ) {
- $this->term_ancestors( $this_category->parent, 'post_category' );
- $this->add_crumb( $this_category->name, get_category_link( $this_category->term_id ) );
+ $this->term_ancestors( $this_category->term_id, 'category' );
}
$this->add_crumb( single_cat_title( '', false ), get_category_link( $this_category->term_id ) );
diff --git a/includes/class-wc-cart-session.php b/includes/class-wc-cart-session.php
new file mode 100644
index 00000000000..7673a44d162
--- /dev/null
+++ b/includes/class-wc-cart-session.php
@@ -0,0 +1,201 @@
+cart = $cart;
+
+ add_action( 'wp_loaded', array( $this, 'get_cart_from_session' ) );
+ add_action( 'woocommerce_cart_emptied', array( $this, 'destroy_cart_session' ) );
+ add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 );
+ add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 );
+ add_action( 'woocommerce_add_to_cart', array( $this, 'maybe_set_cart_cookies' ) );
+ add_action( 'woocommerce_after_calculate_totals', array( $this, 'set_session' ) );
+ add_action( 'woocommerce_cart_loaded_from_session', array( $this, 'set_session' ) );
+ add_action( 'woocommerce_removed_coupon', array( $this, 'set_session' ) );
+ add_action( 'woocommerce_cart_updated', array( $this, 'persistent_cart_update' ) );
+ }
+
+ /**
+ * Get the cart data from the PHP session and store it in class variables.
+ *
+ * @since 3.2.0
+ */
+ public function get_cart_from_session() {
+ $update_cart_session = false;
+ $totals = WC()->session->get( 'cart_totals', null );
+ $cart = WC()->session->get( 'cart', null );
+
+ $this->cart->set_totals( $totals );
+ $this->cart->set_applied_coupons( WC()->session->get( 'applied_coupons', array() ) );
+ $this->cart->set_coupon_discount_totals( WC()->session->get( 'coupon_discount_totals', array() ) );
+ $this->cart->set_coupon_discount_tax_totals( WC()->session->get( 'coupon_discount_tax_totals', array() ) );
+ $this->cart->set_removed_cart_contents( WC()->session->get( 'removed_cart_contents', array() ) );
+
+ if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id(), true ) ) ) {
+ $cart = $saved_cart['cart'];
+ $update_cart_session = true;
+ } elseif ( is_null( $cart ) ) {
+ $cart = array();
+ }
+
+ if ( is_array( $cart ) ) {
+ $cart_contents = array();
+
+ update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) ); // Prime meta cache to reduce future queries.
+ update_object_term_cache( wp_list_pluck( $cart, 'product_id' ), 'product' );
+
+ foreach ( $cart as $key => $values ) {
+ $product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
+
+ if ( ! empty( $product ) && $product->exists() && $values['quantity'] > 0 ) {
+
+ if ( ! $product->is_purchasable() ) {
+ $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.
+ $session_data = array_merge( $values, array( 'data' => $product ) );
+ $cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
+ }
+ }
+ }
+
+ $this->cart->set_cart_contents( $cart_contents );
+ }
+
+ do_action( 'woocommerce_cart_loaded_from_session', $this->cart );
+
+ if ( $update_cart_session || is_null( $totals ) ) {
+ WC()->session->set( 'cart', $this->get_cart_for_session() );
+ $this->cart->calculate_totals();
+ }
+ }
+
+ /**
+ * Destroy cart session data.
+ *
+ * @since 3.2.0
+ */
+ public function destroy_cart_session() {
+ WC()->session->set( 'cart', null );
+ WC()->session->set( 'cart_totals', null );
+ WC()->session->set( 'applied_coupons', null );
+ WC()->session->set( 'coupon_discount_totals', null );
+ WC()->session->set( 'coupon_discount_tax_totals', null );
+ WC()->session->set( 'removed_cart_contents', null );
+ WC()->session->set( 'order_awaiting_payment', null );
+ }
+
+ /**
+ * Will set cart cookies if needed and when possible.
+ *
+ * @since 3.2.0
+ */
+ public function maybe_set_cart_cookies() {
+ if ( ! headers_sent() && did_action( 'wp_loaded' ) ) {
+ if ( ! $this->cart->is_empty() ) {
+ $this->set_cart_cookies( true );
+ } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) {
+ $this->set_cart_cookies( false );
+ }
+ }
+ }
+
+ /**
+ * Sets the php session data for the cart and coupons.
+ */
+ public function set_session() {
+ WC()->session->set( 'cart', $this->get_cart_for_session() );
+ WC()->session->set( 'cart_totals', $this->cart->get_totals() );
+ WC()->session->set( 'applied_coupons', $this->cart->get_applied_coupons() );
+ WC()->session->set( 'coupon_discount_totals', $this->cart->get_coupon_discount_totals() );
+ WC()->session->set( 'coupon_discount_tax_totals', $this->cart->get_coupon_discount_tax_totals() );
+ WC()->session->set( 'removed_cart_contents', $this->cart->get_removed_cart_contents() );
+
+ do_action( 'woocommerce_cart_updated' );
+ }
+
+ /**
+ * Returns the contents of the cart in an array without the 'data' element.
+ *
+ * @return array contents of the cart
+ */
+ public function get_cart_for_session() {
+ $cart_session = array();
+
+ foreach ( $this->cart->get_cart() as $key => $values ) {
+ $cart_session[ $key ] = $values;
+ unset( $cart_session[ $key ]['data'] ); // Unset product object.
+ }
+
+ return $cart_session;
+ }
+
+ /**
+ * Save the persistent cart when the cart is updated.
+ */
+ public function persistent_cart_update() {
+ if ( get_current_user_id() ) {
+ update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id(), array( 'cart' => $this->get_cart_for_session() ) );
+ }
+ }
+
+ /**
+ * Delete the persistent cart permanently.
+ */
+ public function persistent_cart_destroy() {
+ if ( get_current_user_id() ) {
+ delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id() );
+ }
+ }
+
+ /**
+ * Set cart hash cookie and items in cart.
+ *
+ * @access private
+ * @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( 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 );
+ }
+ do_action( 'woocommerce_set_cart_cookies', $set );
+ }
+}
diff --git a/includes/class-wc-cart-totals.php b/includes/class-wc-cart-totals.php
index 2362e1555b5..59ae13a5f42 100644
--- a/includes/class-wc-cart-totals.php
+++ b/includes/class-wc-cart-totals.php
@@ -26,7 +26,7 @@ final class WC_Cart_Totals {
* @since 3.2.0
* @var array
*/
- protected $object;
+ protected $cart;
/**
* Reference to customer object.
@@ -69,12 +69,20 @@ final class WC_Cart_Totals {
protected $coupons = array();
/**
- * Discount amounts in cents after calculation for the cart.
+ * Item/coupon discount totals.
*
* @since 3.2.0
* @var array
*/
- protected $discount_totals = array();
+ protected $coupon_discount_totals = array();
+
+ /**
+ * Item/coupon discount tax totals.
+ *
+ * @since 3.2.0
+ * @var array
+ */
+ protected $coupon_discount_tax_totals = array();
/**
* Should taxes be calculated?
@@ -97,12 +105,9 @@ final class WC_Cart_Totals {
'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,
);
/**
@@ -113,7 +118,7 @@ final class WC_Cart_Totals {
*/
public function __construct( &$cart = null ) {
if ( is_a( $cart, 'WC_Cart' ) ) {
- $this->object = $cart;
+ $this->cart = $cart;
$this->calculate_tax = wc_tax_enabled() && ! $cart->get_customer()->get_is_vat_exempt();
$this->calculate();
}
@@ -126,8 +131,8 @@ final class WC_Cart_Totals {
*/
protected function calculate() {
$this->calculate_item_totals();
- $this->calculate_fee_totals();
$this->calculate_shipping_totals();
+ $this->calculate_fee_totals();
$this->calculate_totals();
}
@@ -140,6 +145,8 @@ final class WC_Cart_Totals {
protected function get_default_item_props() {
return (object) array(
'object' => null,
+ 'tax_class' => '',
+ 'taxable' => false,
'quantity' => 0,
'product' => false,
'price_includes_tax' => false,
@@ -159,6 +166,9 @@ final class WC_Cart_Totals {
*/
protected function get_default_fee_props() {
return (object) array(
+ 'object' => null,
+ 'tax_class' => '',
+ 'taxable' => false,
'total_tax' => 0,
'taxes' => array(),
);
@@ -172,6 +182,9 @@ final class WC_Cart_Totals {
*/
protected function get_default_shipping_props() {
return (object) array(
+ 'object' => null,
+ 'tax_class' => '',
+ 'taxable' => false,
'total' => 0,
'total_tax' => 0,
'taxes' => array(),
@@ -200,12 +213,14 @@ final class WC_Cart_Totals {
*
* @since 3.2.0
*/
- protected function set_items() {
+ protected function get_items_from_cart() {
$this->items = array();
- foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) {
+ foreach ( $this->cart->get_cart() as $cart_item_key => $cart_item ) {
$item = $this->get_default_item_props();
$item->object = $cart_item;
+ $item->tax_class = $cart_item['data']->get_tax_class();
+ $item->taxable = 'taxable' === $cart_item['data']->get_tax_status();
$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'];
@@ -221,17 +236,19 @@ final class WC_Cart_Totals {
*
* @since 3.2.0
*/
- protected function set_fees() {
+ protected function get_fees_from_cart() {
$this->fees = array();
- $this->object->calculate_fees();
+ $this->cart->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 );
+ foreach ( $this->cart->get_fees() as $fee_key => $fee_object ) {
+ $fee = $this->get_default_fee_props();
+ $fee->object = $fee_object;
+ $fee->tax_class = $fee->object->tax_class;
+ $fee->taxable = $fee->object->taxable;
+ $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->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->cart->get_customer() ), false );
$fee->total_tax = array_sum( $fee->taxes );
if ( ! $this->round_at_subtotal() ) {
@@ -248,12 +265,14 @@ final class WC_Cart_Totals {
*
* @since 3.2.0
*/
- protected function set_shipping() {
+ protected function get_shipping_from_cart() {
$this->shipping = array();
- foreach ( $this->object->calculate_shipping() as $key => $shipping_object ) {
+ foreach ( $this->cart->calculate_shipping() as $key => $shipping_object ) {
$shipping_line = $this->get_default_shipping_props();
$shipping_line->object = $shipping_object;
+ $shipping_line->tax_class = get_option( 'woocommerce_shipping_tax_class' );
+ $shipping_line->taxable = true;
$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 ) );
@@ -272,8 +291,41 @@ final class WC_Cart_Totals {
*
* @since 3.2.0
*/
- protected function set_coupons() {
- $this->coupons = $this->object->get_coupons();
+ protected function get_coupons_from_cart() {
+ $this->coupons = $this->cart->get_coupons();
+
+ foreach ( $this->coupons as $coupon ) {
+ switch ( $coupon->get_discount_type() ) {
+ case 'fixed_product' :
+ $coupon->sort = 1;
+ break;
+ case 'percent' :
+ $coupon->sort = 2;
+ break;
+ case 'fixed_cart' :
+ $coupon->sort = 3;
+ break;
+ default:
+ $coupon->sort = 0;
+ break;
+ }
+ }
+
+ uasort( $this->coupons, array( $this, 'sort_coupons_callback' ) );
+ }
+
+ /**
+ * Sort coupons so discounts apply consistently.
+ *
+ * @param WC_Coupon $a Coupon object.
+ * @param WC_Coupon $b Coupon object.
+ * @return int
+ */
+ protected function sort_coupons_callback( $a, $b ) {
+ if ( $a->sort === $b->sort ) {
+ return $a->get_id() - $b->get_id();
+ }
+ return ( $a->sort < $b->sort ) ? -1 : 1;
}
/**
@@ -311,7 +363,7 @@ final class WC_Cart_Totals {
*/
protected function get_discounted_price_in_cents( $item_key ) {
$item = $this->items[ $item_key ];
- $price = $item->subtotal - $this->discount_totals[ $item_key ];
+ $price = isset( $this->coupon_discount_totals[ $item_key ] ) ? $item->subtotal - $this->coupon_discount_totals[ $item_key ] : $item->subtotal;
if ( $item->price_includes_tax ) {
$price += $item->subtotal_tax;
@@ -327,7 +379,33 @@ final class WC_Cart_Totals {
*/
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() );
+ 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->cart->get_customer() );
+ }
+
+ /**
+ * Get item costs grouped by tax class.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ protected function get_item_costs_by_tax_class() {
+ $tax_classes = array(
+ 'non-taxable' => 0,
+ );
+
+ foreach ( $this->items + $this->fees + $this->shipping as $item ) {
+ if ( ! isset( $tax_classes[ $item->tax_class ] ) ) {
+ $tax_classes[ $item->tax_class ] = 0;
+ }
+
+ if ( $item->taxable ) {
+ $tax_classes[ $item->tax_class ] += $item->total;
+ } else {
+ $tax_classes['non-taxable'] += $item->total;
+ }
+ }
+
+ return $tax_classes;
}
/**
@@ -366,32 +444,56 @@ final class WC_Cart_Totals {
}
/**
- * Get all tax rows from items (including shipping and product line items).
+ * Get taxes merged by type.
*
- * @since 3.2.0
+ * @since 3.2.0
+ * @param array|string $types Types to merge and return. Defaults to all.
* @return array
*/
- protected function get_merged_taxes() {
+ protected function get_merged_taxes( $in_cents = false, $types = array( 'items', 'fees', 'shipping' ) ) {
+ $items = array();
$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 );
+ if ( is_string( $types ) ) {
+ $types = array( $types );
+ }
+
+ foreach ( $types as $type ) {
+ if ( isset( $this->$type ) ) {
+ $items = array_merge( $items, $this->$type );
}
}
- foreach ( $this->items + $this->fees as $item ) {
+ foreach ( $items as $item ) {
foreach ( $item->taxes as $rate_id => $rate ) {
- $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate;
+ if ( ! isset( $taxes[ $rate_id ] ) ) {
+ $taxes[ $rate_id ] = 0;
+ }
+ $taxes[ $rate_id ] += $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 $in_cents ? $taxes : wc_remove_number_precision_deep( $taxes );
+ }
+
+ /**
+ * Combine item taxes into a single array, preserving keys.
+ *
+ * @since 3.2.0
+ * @param array $taxes Taxes to combine.
+ * @return array
+ */
+ protected function combine_item_taxes( $item_taxes ) {
+ $merged_taxes = array();
+ foreach ( $item_taxes as $taxes ) {
+ foreach ( $taxes as $tax_id => $tax_amount ) {
+ if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
+ $merged_taxes[ $tax_id ] = 0;
+ }
+ $merged_taxes[ $tax_id ] += $tax_amount;
}
}
- return $taxes;
+ return $merged_taxes;
}
/*
@@ -406,7 +508,7 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_item_totals() {
- $this->set_items();
+ $this->get_items_from_cart();
$this->calculate_item_subtotals();
$this->calculate_discounts();
@@ -420,10 +522,10 @@ final class WC_Cart_Totals {
*
* This is legacy and should probably be deprecated in the future.
* $item->object is the cart item object.
- * $this->object is the cart object.
+ * $this->cart 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 )
+ apply_filters( 'woocommerce_get_discounted_price', wc_remove_number_precision( $item->total ), $item->object, $this->cart )
);
}
@@ -442,8 +544,9 @@ final class WC_Cart_Totals {
}
}
- $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->cart->cart_contents[ $item_key ]['line_tax_data']['total'] = wc_remove_number_precision_deep( $item->taxes );
+ $this->cart->cart_contents[ $item_key ]['line_total'] = wc_remove_number_precision( $item->total );
+ $this->cart->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' ) ) ) );
@@ -470,6 +573,8 @@ final class WC_Cart_Totals {
$item = $this->adjust_non_base_location_price( $item );
}
+ $subtotal_taxes = array();
+
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 );
@@ -483,29 +588,30 @@ final class WC_Cart_Totals {
}
}
- $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->cart->cart_contents[ $item_key ]['line_tax_data'] = array( 'subtotal' => wc_remove_number_precision_deep( $subtotal_taxes ) );
+ $this->cart->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
+ $this->cart->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' );
+ $this->cart->set_subtotal( $this->get_total( 'items_subtotal' ) );
+ $this->cart->set_subtotal_tax( $this->get_total( 'items_subtotal_tax' ) );
}
/**
- * Calculate all discount and coupon amounts.
+ * Calculate COUPON based discounts which change item prices.
*
* @since 3.2.0
* @uses WC_Discounts class.
*/
protected function calculate_discounts() {
- $this->set_coupons();
+ $this->get_coupons_from_cart();
- $discounts = new WC_Discounts( $this->object );
+ $discounts = new WC_Discounts( $this->cart );
foreach ( $this->coupons as $coupon ) {
- $discounts->apply_discount( $coupon );
+ $discounts->apply_coupon( $coupon );
}
$coupon_discount_amounts = $discounts->get_discounts_by_coupon( true );
@@ -516,40 +622,26 @@ final class WC_Cart_Totals {
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 ) {
+ foreach ( $coupon_discounts as $item_key => $coupon_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 ) );
+ $item_tax = array_sum( WC_Tax::calc_tax( $coupon_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 ];
+ if ( wc_prices_include_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->coupon_discount_totals = (array) $discounts->get_discounts_by_item( true );
+ $this->coupon_discount_tax_totals = $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;
+ $this->cart->set_coupon_discount_totals( wc_remove_number_precision_deep( $coupon_discount_amounts ) );
+ $this->cart->set_coupon_discount_tax_totals( wc_remove_number_precision_deep( $coupon_discount_tax_amounts ) );
}
/**
@@ -560,15 +652,18 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_fee_totals() {
- $this->set_fees();
+ $this->get_fees_from_cart();
$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->cart->fees[ $fee_key ]->tax = wc_remove_number_precision_deep( $fee->total_tax );
+ $this->cart->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' ) ) );
+
+ $this->cart->set_fee_total( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ) );
+ $this->cart->set_fee_tax( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
+ $this->cart->set_fee_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->fees, 'taxes' ) ) ) );
}
/**
@@ -577,12 +672,13 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_shipping_totals() {
- $this->set_shipping();
+ $this->get_shipping_from_cart();
$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' );
+ $this->cart->set_shipping_total( $this->get_total( 'shipping_total' ) );
+ $this->cart->set_shipping_tax( $this->get_total( 'shipping_tax_total' ) );
+ $this->cart->set_shipping_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->shipping, 'taxes' ) ) ) );
}
/**
@@ -591,26 +687,24 @@ final class WC_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 ) ) );
+ $this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
+ $this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
+ $this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( 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' );
+ $this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
+ $this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
+ $this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
+ $this->cart->set_discount_total( $this->get_total( 'discounts_total' ) );
+ $this->cart->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
+ $this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );
// 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 );
+ do_action( 'woocommerce_calculate_totals', $this->cart );
}
// 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 ) );
+ $this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
}
}
diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php
index 43dcbcac291..2c4038cbe06 100644
--- a/includes/class-wc-cart.php
+++ b/includes/class-wc-cart.php
@@ -15,20 +15,14 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-include_once( 'legacy/class-wc-legacy-cart.php' );
+include_once( WC_ABSPATH . 'includes/legacy/class-wc-legacy-cart.php' );
+include_once( WC_ABSPATH . 'includes/class-wc-cart-session.php' );
/**
* WC_Cart class.
*/
class WC_Cart extends WC_Legacy_Cart {
- /**
- * This stores the chosen shipping methods for the cart item packages.
- *
- * @var array
- */
- protected $shipping_methods;
-
/**
* Contains an array of cart items.
*
@@ -50,127 +44,6 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public $applied_coupons = array();
- /**
- * Contains an array of coupon code discounts after they have been applied.
- *
- * @var array
- */
- public $coupon_discount_amounts = array();
-
- /**
- * Contains an array of coupon code discount taxes. Used for tax incl pricing.
- *
- * @var array
- */
- public $coupon_discount_tax_amounts = array();
-
- /**
- * The total cost of the cart items.
- *
- * @var float
- */
- public $cart_contents_total;
-
- /**
- * Cart grand total.
- *
- * @var float
- */
- public $total;
-
- /**
- * Cart subtotal.
- *
- * @var float
- */
- public $subtotal;
-
- /**
- * Cart subtotal without tax.
- *
- * @var float
- */
- public $subtotal_ex_tax;
-
- /**
- * Total cart tax.
- *
- * @var float
- */
- public $tax_total;
-
- /**
- * An array of taxes/tax rates for the cart.
- *
- * @var array
- */
- public $taxes;
-
- /**
- * An array of taxes/tax rates for the shipping.
- *
- * @var array
- */
- public $shipping_taxes;
-
- /**
- * Discount amount before tax.
- *
- * @var float
- */
- public $discount_cart;
-
- /**
- * Discounted tax amount. Used predominantly for displaying tax inclusive prices correctly.
- *
- * @var float
- */
- public $discount_cart_tax;
-
- /**
- * Total for additional fees.
- *
- * @var float
- */
- public $fee_total;
-
- /**
- * Shipping cost.
- *
- * @var float
- */
- public $shipping_total;
-
- /**
- * Shipping tax.
- *
- * @var float
- */
- public $shipping_tax_total;
-
- /**
- * Array of data the cart calculates and stores in the session with defaults
- *
- * @var array cart_session_data.
- */
- public $cart_session_data = array(
- 'cart_contents_total' => 0,
- 'total' => 0,
- 'subtotal' => 0,
- 'subtotal_ex_tax' => 0,
- 'tax_total' => 0,
- 'taxes' => array(),
- 'shipping_taxes' => array(),
- 'discount_cart' => 0,
- 'discount_cart_tax' => 0,
- 'shipping_total' => 0,
- 'shipping_tax_total' => 0,
- 'coupon_discount_amounts' => array(),
- 'coupon_discount_tax_amounts' => array(),
- 'fee_total' => 0,
- 'fees' => array(),
- );
-
/**
* An array of fees.
*
@@ -178,183 +51,550 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public $fees = array();
+ /**
+ * Are prices in the cart displayed inc or excl tax.
+ *
+ * @var string
+ */
+ public $tax_display_cart;
+
+ /**
+ * This stores the chosen shipping methods for the cart item packages.
+ *
+ * @var array
+ */
+ protected $shipping_methods;
+
+ /**
+ * Total defaults used to reset.
+ *
+ * @var array
+ */
+ protected $default_totals = array(
+ 'subtotal' => 0,
+ 'subtotal_tax' => 0,
+ 'shipping_total' => 0,
+ 'shipping_tax' => 0,
+ 'shipping_taxes' => array(),
+ 'discount_total' => 0,
+ 'discount_tax' => 0,
+ 'cart_contents_total' => 0,
+ 'cart_contents_tax' => 0,
+ 'cart_contents_taxes' => array(),
+ 'fee_total' => 0,
+ 'fee_tax' => 0,
+ 'fee_taxes' => array(),
+ 'total' => 0,
+ 'total_tax' => 0,
+ );
+ /**
+ * Store calculated totals.
+ *
+ * @var array
+ */
+ protected $totals = array();
+
+ /**
+ * Reference to the cart session handling class.
+ *
+ * @var WC_Cart_Session
+ */
+ protected $session;
+
/**
* Constructor for the cart class. Loads options and hooks in the init method.
*/
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.
+ $this->session = new WC_Cart_Session( $this );
+ $this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' );
+
add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
- }
-
- /**
- * Auto-load in-accessible properties on demand.
- *
- * @param mixed $key Key to get.
- * @return mixed
- */
- public function __get( $key ) {
- switch ( $key ) {
- case 'prices_include_tax' :
- return wc_prices_include_tax();
- break;
- case 'round_at_subtotal' :
- return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
- break;
- case 'tax_display_cart' :
- return get_option( 'woocommerce_tax_display_cart' );
- break;
- case 'dp' :
- return wc_get_price_decimals();
- break;
- case 'display_totals_ex_tax' :
- case 'display_cart_ex_tax' :
- return 'excl' === $this->tax_display_cart;
- break;
- case 'cart_contents_weight' :
- return $this->get_cart_contents_weight();
- break;
- case 'cart_contents_count' :
- return $this->get_cart_contents_count();
- break;
- case 'tax' :
- wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' );
- $this->tax = new WC_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;
- case 'coupons' :
- return $this->get_coupons();
- }
- }
-
- /**
- * Loads the cart data from the PHP session during WordPress init and hooks in other methods.
- */
- public function init() {
- $this->get_cart_from_session();
-
+ add_action( 'woocommerce_cart_item_removed', array( $this, 'calculate_totals' ), 20, 0 );
+ add_action( 'woocommerce_cart_item_restored', array( $this, 'calculate_totals' ), 20, 0 );
add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 );
}
- /**
- * Will set cart cookies if needed, once, during WP hook.
- */
- public function maybe_set_cart_cookies() {
- if ( ! headers_sent() && did_action( 'wp_loaded' ) ) {
- if ( ! $this->is_empty() ) {
- $this->set_cart_cookies( true );
- } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) {
- $this->set_cart_cookies( false );
- }
- }
- }
+ /*
+ |--------------------------------------------------------------------------
+ | Getters.
+ |--------------------------------------------------------------------------
+ |
+ | Methods to retrieve class properties and avoid direct access.
+ */
/**
- * Set cart hash cookie and items in cart.
+ * Gets cart contents.
*
- * @access private
- * @param bool $set Should cookies be set (true) or unset.
+ * @since 3.2.0
+ * @return array of applied coupons
*/
- private function set_cart_cookies( $set = true ) {
- if ( $set ) {
- wc_setcookie( 'woocommerce_items_in_cart', 1 );
- 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 );
- }
- do_action( 'woocommerce_set_cart_cookies', $set );
+ public function get_cart_contents() {
+ return (array) $this->cart_contents;
}
/**
- * Get the cart data from the PHP session and store it in class variables.
+ * Return items removed from the cart.
+ *
+ * @since 3.2.0
+ * @return array
*/
- public function get_cart_from_session() {
- foreach ( $this->cart_session_data as $key => $default ) {
- $this->$key = WC()->session->get( $key, $default );
- }
-
- $update_cart_session = false;
- $this->removed_cart_contents = array_filter( WC()->session->get( 'removed_cart_contents', array() ) );
- $this->applied_coupons = array_filter( WC()->session->get( 'applied_coupons', array() ) );
-
- /**
- * Load the cart object. This defaults to the persistent cart if null.
- */
- $cart = WC()->session->get( 'cart', null );
-
- if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id(), true ) ) ) {
- $cart = $saved_cart['cart'];
- $update_cart_session = true;
- } elseif ( is_null( $cart ) ) {
- $cart = array();
- }
-
- if ( is_array( $cart ) ) {
- // 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' );
-
- foreach ( $cart as $key => $values ) {
- $product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
-
- if ( ! empty( $product ) && $product->exists() && $values['quantity'] > 0 ) {
-
- if ( ! $product->is_purchasable() ) {
- $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.
- $session_data = array_merge( $values, array( 'data' => $product ) );
- $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
- }
- }
- }
- }
-
- 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.
- if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) {
- $this->calculate_totals();
- }
+ public function get_removed_cart_contents() {
+ return (array) $this->removed_cart_contents;
}
/**
- * Sets the php session data for the cart and coupons.
+ * Gets the array of applied coupon codes.
+ *
+ * @return array of applied coupons
*/
- public function set_session() {
- $cart_session = $this->get_cart_for_session();
+ public function get_applied_coupons() {
+ return (array) $this->applied_coupons;
+ }
- WC()->session->set( 'cart', $cart_session );
- WC()->session->set( 'applied_coupons', $this->applied_coupons );
- WC()->session->set( 'coupon_discount_amounts', $this->coupon_discount_amounts );
- WC()->session->set( 'coupon_discount_tax_amounts', $this->coupon_discount_tax_amounts );
- WC()->session->set( 'removed_cart_contents', $this->removed_cart_contents );
+ /**
+ * Return all calculated coupon totals.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ public function get_coupon_discount_totals() {
+ return (array) $this->coupon_discount_totals;
+ }
+ /**
+ * Return all calculated coupon tax totals.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ public function get_coupon_discount_tax_totals() {
+ return (array) $this->coupon_discount_tax_totals;
+ }
- foreach ( $this->cart_session_data as $key => $default ) {
- WC()->session->set( $key, $this->$key );
+ /**
+ * Return all calculated totals.
+ *
+ * @since 3.2.0
+ * @return array
+ */
+ public function get_totals() {
+ return empty( $this->totals ) ? $this->default_totals : $this->totals;
+ }
+
+ /**
+ * Get subtotal.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_subtotal() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['subtotal'] );
+ }
+
+ /**
+ * Get subtotal.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_subtotal_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['subtotal_tax'] );
+ }
+
+ /**
+ * Get discount_total.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_discount_total() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['discount_total'] );
+ }
+
+ /**
+ * Get discount_tax.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_discount_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['discount_tax'] );
+ }
+
+ /**
+ * Get shipping_total.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_shipping_total() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_total'] );
+ }
+
+ /**
+ * Get shipping_tax.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_shipping_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_tax'] );
+ }
+
+ /**
+ * Gets cart total. This is the total of items in the cart, but after discounts. Subtotal is before discounts.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_cart_contents_total() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_total'] );
+ }
+
+ /**
+ * Gets cart tax amount.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_cart_contents_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_tax'] );
+ }
+
+ /**
+ * Gets cart total after calculation.
+ *
+ * @since 3.2.0
+ * @param string $context If the context is view, the value will be formatted for display. This keeps it compatible with pre-3.2 versions.
+ * @return float
+ */
+ public function get_total( $context = 'view' ) {
+ $total = apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['total'] );
+ return 'view' === $context ? apply_filters( 'woocommerce_cart_total', wc_price( $total ) ) : $total;
+ }
+
+ /**
+ * Get total tax amount.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_total_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['total_tax'] );
+ }
+
+ /**
+ * Get total fee amount.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_fee_total() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_total'] );
+ }
+
+ /**
+ * Get total fee tax amount.
+ *
+ * @since 3.2.0
+ * @return float
+ */
+ public function get_fee_tax() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_tax'] );
+ }
+
+ /**
+ * Get taxes.
+ *
+ * @since 3.2.0
+ */
+ public function get_shipping_taxes() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_taxes'] );
+ }
+
+ /**
+ * Get taxes.
+ *
+ * @since 3.2.0
+ */
+ public function get_cart_contents_taxes() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_taxes'] );
+
+ }
+
+ /**
+ * Get taxes.
+ *
+ * @since 3.2.0
+ */
+ public function get_fee_taxes() {
+ return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_taxes'] );
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Setters.
+ |--------------------------------------------------------------------------
+ |
+ | Methods to set class properties and avoid direct access.
+ */
+
+ /**
+ * Sets the contents of the cart.
+ *
+ * @param array $value Cart array.
+ */
+ public function set_cart_contents( $value ) {
+ $this->cart_contents = (array) $value;
+ }
+
+ /**
+ * Set items removed from the cart.
+ *
+ * @since 3.2.0
+ * @param array $value Item array.
+ */
+ public function set_removed_cart_contents( $value = array() ) {
+ $this->removed_cart_contents = (array) $value;
+ }
+
+ /**
+ * Sets the array of applied coupon codes.
+ *
+ * @param array $value List of applied coupon codes.
+ */
+ public function set_applied_coupons( $value = array() ) {
+ $this->applied_coupons = (array) $value;
+ }
+
+ /**
+ * Return all calculated coupon totals.
+ *
+ * @since 3.2.0
+ * @param array $value Value to set.
+ */
+ public function set_coupon_discount_totals( $value = array() ) {
+ $this->coupon_discount_totals = (array) $value;
+ }
+ /**
+ * Return all calculated coupon tax totals.
+ *
+ * @since 3.2.0
+ * @param array $value Value to set.
+ */
+ public function set_coupon_discount_tax_totals( $value = array() ) {
+ $this->coupon_discount_tax_totals = (array) $value;
+ }
+
+ /**
+ * Set all calculated totals.
+ *
+ * @since 3.2.0
+ * @param array $value Value to set.
+ */
+ public function set_totals( $value = array() ) {
+ $this->totals = wp_parse_args( $value, $this->default_totals );
+ }
+
+ /**
+ * Set subtotal.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_subtotal( $value ) {
+ $this->totals['subtotal'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set subtotal.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_subtotal_tax( $value ) {
+ $this->totals['subtotal_tax'] = wc_round_tax_total( $value );
+ }
+
+ /**
+ * Set discount_total.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_discount_total( $value ) {
+ $this->totals['discount_total'] = wc_cart_round_discount( $value, wc_get_price_decimals() );
+ }
+
+ /**
+ * Set discount_tax.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_discount_tax( $value ) {
+ $this->totals['discount_tax'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set shipping_total.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_shipping_total( $value ) {
+ $this->totals['shipping_total'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set shipping_tax.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_shipping_tax( $value ) {
+ $this->totals['shipping_tax'] = wc_round_tax_total( $value );
+ }
+
+ /**
+ * Set cart_contents_total.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_cart_contents_total( $value ) {
+ $this->totals['cart_contents_total'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set cart tax amount.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_cart_contents_tax( $value ) {
+ $this->totals['cart_contents_tax'] = wc_round_tax_total( $value );
+ }
+
+ /**
+ * Set cart total.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_total( $value ) {
+ $this->totals['total'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set total tax amount.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_total_tax( $value ) {
+ $this->totals['total_tax'] = wc_round_tax_total( $value );
+ }
+
+ /**
+ * Set fee amount.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_fee_total( $value ) {
+ $this->totals['fee_total'] = wc_format_decimal( $value );
+ }
+
+ /**
+ * Set fee tax.
+ *
+ * @since 3.2.0
+ * @param string $value Value to set.
+ */
+ public function set_fee_tax( $value ) {
+ $this->totals['fee_tax'] = wc_round_tax_total( $value );
+ }
+
+ /**
+ * Set taxes.
+ *
+ * @since 3.2.0
+ * @param array $value Tax values.
+ */
+ public function set_shipping_taxes( $value ) {
+ $this->totals['shipping_taxes'] = (array) $value;
+ }
+
+ /**
+ * Set taxes.
+ *
+ * @since 3.2.0
+ * @param array $value Tax values.
+ */
+ public function set_cart_contents_taxes( $value ) {
+ $this->totals['cart_contents_taxes'] = (array) $value;
+ }
+
+ /**
+ * Set taxes.
+ *
+ * @since 3.2.0
+ * @param array $value Tax values.
+ */
+ public function set_fee_taxes( $value ) {
+ $this->totals['fee_taxes'] = (array) $value;
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Helper methods.
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Returns the cart and shipping taxes, merged.
+ *
+ * @return array merged taxes
+ */
+ public function get_taxes() {
+ return apply_filters( 'woocommerce_cart_get_taxes', wc_array_merge_recursive_numeric( $this->get_shipping_taxes(), $this->get_cart_contents_taxes(), $this->get_fee_taxes() ), $this );
+ }
+
+ /**
+ * Returns the contents of the cart in an array.
+ *
+ * @return array contents of the cart
+ */
+ public function get_cart() {
+ if ( ! did_action( 'wp_loaded' ) ) {
+ wc_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
}
-
- if ( get_current_user_id() ) {
- $this->persistent_cart_update();
+ if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) {
+ $this->session->get_cart_from_session();
}
+ return array_filter( $this->get_cart_contents() );
+ }
- do_action( 'woocommerce_cart_updated' );
+ /**
+ * Returns a specific item in the cart.
+ *
+ * @param string $item_key Cart item key.
+ * @return array Item data
+ */
+ public function get_cart_item( $item_key ) {
+ return isset( $this->cart_contents[ $item_key ] ) ? $this->cart_contents[ $item_key ] : array();
+ }
+
+ /**
+ * Checks if the cart is empty.
+ *
+ * @return bool
+ */
+ public function is_empty() {
+ return 0 === count( $this->get_cart() );
}
/**
@@ -363,35 +603,22 @@ class WC_Cart extends WC_Legacy_Cart {
* @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 );
+ $this->cart_contents = array();
+ $this->removed_cart_contents = array();
+ $this->shipping_methods = array();
+ $this->coupon_discount_totals = array();
+ $this->coupon_discount_tax_totals = array();
+ $this->applied_coupons = array();
+ $this->fees = array();
+ $this->totals = $this->default_totals;
- unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart );
-
- if ( $clear_persistent_cart && get_current_user_id() ) {
- $this->persistent_cart_destroy();
+ if ( $clear_persistent_cart ) {
+ $this->session->persistent_cart_destroy();
}
do_action( 'woocommerce_cart_emptied' );
}
- /**
- * Save the persistent cart when the cart is updated.
- */
- public function persistent_cart_update() {
- update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id(), array(
- 'cart' => WC()->session->get( 'cart' ),
- ) );
- }
-
- /**
- * Delete the persistent cart permanently.
- */
- public function persistent_cart_destroy() {
- delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id() );
- }
-
/**
* Get number of items in the cart.
*
@@ -418,12 +645,19 @@ class WC_Cart extends WC_Legacy_Cart {
}
/**
- * Checks if the cart is empty.
+ * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
*
- * @return bool
+ * @return array
*/
- public function is_empty() {
- return 0 === count( $this->get_cart() );
+ public function get_cart_item_quantities() {
+ $quantities = array();
+
+ foreach ( $this->get_cart() as $cart_item_key => $values ) {
+ $product = $values['data'];
+ $quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $values['quantity'] : $values['quantity'];
+ }
+
+ return $quantities;
}
/**
@@ -453,35 +687,16 @@ class WC_Cart extends WC_Legacy_Cart {
* Check cart coupons for errors.
*/
public function check_cart_coupons() {
- foreach ( $this->applied_coupons as $code ) {
+ foreach ( $this->get_applied_coupons() as $code ) {
$coupon = new WC_Coupon( $code );
if ( ! $coupon->is_valid() ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
$this->remove_coupon( $code );
-
- // Flag totals for refresh.
- WC()->session->set( 'refresh_totals', true );
}
}
}
- /**
- * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
- *
- * @return array
- */
- public function get_cart_item_quantities() {
- $quantities = array();
-
- foreach ( $this->get_cart() as $cart_item_key => $values ) {
- $product = $values['data'];
- $quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $values['quantity'] : $values['quantity'];
- }
-
- return $quantities;
- }
-
/**
* Looks through cart items and checks the posts are not trashed or deleted.
*
@@ -692,68 +907,6 @@ class WC_Cart extends WC_Legacy_Cart {
return apply_filters( 'woocommerce_get_undo_url', $cart_page_url ? wp_nonce_url( add_query_arg( $query_args, $cart_page_url ), 'woocommerce-cart' ) : '', $cart_item_key );
}
- /**
- * Returns the contents of the cart in an array.
- *
- * @return array contents of the cart
- */
- public function get_cart() {
- if ( ! did_action( 'wp_loaded' ) ) {
- wc_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
- }
- if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) {
- $this->get_cart_from_session();
- }
- return array_filter( (array) $this->cart_contents );
- }
-
- /**
- * Returns the contents of the cart in an array without the 'data' element.
- *
- * @return array contents of the cart
- */
- public function get_cart_for_session() {
- $cart_session = array();
-
- if ( $this->get_cart() ) {
- foreach ( $this->get_cart() as $key => $values ) {
- $cart_session[ $key ] = $values;
- unset( $cart_session[ $key ]['data'] ); // Unset product object.
- }
- }
-
- return $cart_session;
- }
-
- /**
- * Returns a specific item in the cart.
- *
- * @param string $item_key Cart item key.
- * @return array Item data
- */
- public function get_cart_item( $item_key ) {
- if ( isset( $this->cart_contents[ $item_key ] ) ) {
- return $this->cart_contents[ $item_key ];
- }
-
- return array();
- }
-
- /**
- * Returns the cart and shipping taxes, merged.
- *
- * @return array merged taxes
- */
- public function get_taxes() {
- $taxes = array();
-
- 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 );
- }
-
- return apply_filters( 'woocommerce_cart_get_taxes', $taxes, $this );
- }
-
/**
* Get taxes, merged by code, formatted ready for output.
*
@@ -819,11 +972,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return string
*/
public function get_displayed_subtotal() {
- if ( 'incl' === $this->tax_display_cart ) {
- return wc_format_decimal( $this->subtotal );
- } elseif ( 'excl' === $this->tax_display_cart ) {
- return wc_format_decimal( $this->subtotal_ex_tax );
- }
+ return 'incl' === $this->tax_display_cart ? $this->get_subtotal() + $this->get_subtotal_tax() : $this->get_subtotal();
}
/**
@@ -885,7 +1034,7 @@ class WC_Cart extends WC_Legacy_Cart {
/**
* Add a product to the cart.
*
- * @throws Exception To prevent adding to cart.
+ * @throws Exception Plugins can throw an 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.
@@ -894,7 +1043,6 @@ class WC_Cart extends WC_Legacy_Cart {
* @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.
try {
$product_id = absint( $product_id );
$variation_id = absint( $variation_id );
@@ -978,10 +1126,6 @@ class WC_Cart extends WC_Legacy_Cart {
) ), $cart_item_key );
}
- if ( did_action( 'wp' ) ) {
- $this->set_cart_cookies( ! $this->is_empty() );
- }
-
do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
return $cart_item_key;
@@ -1004,6 +1148,7 @@ class WC_Cart extends WC_Legacy_Cart {
public function remove_cart_item( $cart_item_key ) {
if ( isset( $this->cart_contents[ $cart_item_key ] ) ) {
$this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ];
+
unset( $this->removed_cart_contents[ $cart_item_key ]['data'] );
do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this );
@@ -1012,11 +1157,8 @@ class WC_Cart extends WC_Legacy_Cart {
do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this );
- $this->calculate_totals();
-
return true;
}
-
return false;
}
@@ -1028,8 +1170,9 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public function restore_cart_item( $cart_item_key ) {
if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) {
- $this->cart_contents[ $cart_item_key ] = $this->removed_cart_contents[ $cart_item_key ];
- $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $this->cart_contents[ $cart_item_key ]['variation_id'] ? $this->cart_contents[ $cart_item_key ]['variation_id'] : $this->cart_contents[ $cart_item_key ]['product_id'] );
+ $restore_item = $this->removed_cart_contents[ $cart_item_key ];
+ $this->cart_contents[ $cart_item_key ] = $restore_item;
+ $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $restore_item['variation_id'] ? $restore_item['variation_id'] : $restore_item['product_id'] );
do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this );
@@ -1037,11 +1180,8 @@ class WC_Cart extends WC_Legacy_Cart {
do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this );
- $this->calculate_totals();
-
return true;
}
-
return false;
}
@@ -1070,22 +1210,6 @@ class WC_Cart extends WC_Legacy_Cart {
return true;
}
- /**
- * Reset cart totals to the defaults. Useful before running calculations.
- *
- * @param bool $unset_session If true, the session data will be forced unset.
- * @access private
- */
- private function reset( $unset_session = false ) {
- foreach ( $this->cart_session_data as $key => $default ) {
- $this->$key = $default;
- if ( $unset_session ) {
- unset( WC()->session->$key );
- }
- }
- do_action( 'woocommerce_cart_reset', $this, $unset_session );
- }
-
/**
* Get cart's owner.
*
@@ -1102,40 +1226,18 @@ class WC_Cart extends WC_Legacy_Cart {
* @uses WC_Cart_Totals
*/
public function calculate_totals() {
- $this->reset();
-
- do_action( 'woocommerce_before_calculate_totals', $this );
+ $this->reset_totals();
if ( $this->is_empty() ) {
- $this->set_session();
+ $this->session->set_session();
return;
}
+ do_action( 'woocommerce_before_calculate_totals', $this );
+
new WC_Cart_Totals( $this );
do_action( 'woocommerce_after_calculate_totals', $this );
-
- $this->set_session();
- }
-
- /**
- * Remove taxes.
- */
- public function remove_taxes() {
- $this->shipping_tax_total = $this->tax_total = 0;
- $this->subtotal = $this->subtotal_ex_tax;
-
- foreach ( $this->cart_contents as $cart_item_key => $item ) {
- $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $this->cart_contents[ $cart_item_key ]['line_tax'] = 0;
- $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => array(), 'subtotal' => array() );
- }
-
- // If true, zero rate is applied so '0' tax is displayed on the frontend rather than nothing.
- if ( apply_filters( 'woocommerce_cart_remove_taxes_apply_zero_rate', true ) ) {
- $this->taxes = $this->shipping_taxes = array( apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) => 0 );
- } else {
- $this->taxes = $this->shipping_taxes = array();
- }
}
/**
@@ -1144,7 +1246,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return bool
*/
public function needs_payment() {
- return apply_filters( 'woocommerce_cart_needs_payment', $this->total > 0, $this );
+ return apply_filters( 'woocommerce_cart_needs_payment', 0 < $this->get_total( 'edit' ), $this );
}
/*
@@ -1157,9 +1259,20 @@ class WC_Cart extends WC_Legacy_Cart {
public function calculate_shipping() {
$this->shipping_methods = $this->needs_shipping() ? $this->get_chosen_shipping_methods( WC()->shipping->calculate_shipping( $this->get_shipping_packages() ) ) : array();
- // 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' );
+ $shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' );
+ $merged_taxes = array();
+ foreach ( $shipping_taxes as $taxes ) {
+ foreach ( $taxes as $tax_id => $tax_amount ) {
+ if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
+ $merged_taxes[ $tax_id ] = 0;
+ }
+ $merged_taxes[ $tax_id ] += $tax_amount;
+ }
+ }
+
+ $this->set_shipping_total( array_sum( wp_list_pluck( $this->shipping_methods, 'cost' ) ) );
+ $this->set_shipping_tax( array_sum( $shipping_taxes ) );
+ $this->set_shipping_taxes( $merged_taxes );
return $this->shipping_methods;
}
@@ -1224,7 +1337,7 @@ class WC_Cart extends WC_Legacy_Cart {
array(
'contents' => $this->get_items_needing_shipping(),
'contents_cost' => array_sum( wp_list_pluck( $this->get_items_needing_shipping(), 'line_total' ) ),
- 'applied_coupons' => $this->applied_coupons,
+ 'applied_coupons' => $this->get_applied_coupons(),
'user' => array(
'ID' => get_current_user_id(),
),
@@ -1253,12 +1366,10 @@ class WC_Cart extends WC_Legacy_Cart {
}
$needs_shipping = false;
- if ( ! empty( $this->cart_contents ) ) {
- foreach ( $this->cart_contents as $cart_item_key => $values ) {
- if ( $values['data']->needs_shipping() ) {
- $needs_shipping = true;
- break;
- }
+ foreach ( $this->get_cart_contents() as $cart_item_key => $values ) {
+ if ( $values['data']->needs_shipping() ) {
+ $needs_shipping = true;
+ break;
}
}
@@ -1280,7 +1391,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return bool
*/
public function show_shipping() {
- if ( ! wc_shipping_enabled() || ! is_array( $this->cart_contents ) ) {
+ if ( ! wc_shipping_enabled() || ! $this->get_cart_contents() ) {
return false;
}
@@ -1301,31 +1412,28 @@ class WC_Cart extends WC_Legacy_Cart {
* @return string price or string for the shipping total
*/
public function get_cart_shipping_total() {
- if ( isset( $this->shipping_total ) ) {
- if ( $this->shipping_total > 0 ) {
+ if ( 0 < $this->get_shipping_total() ) {
- if ( 'excl' === $this->tax_display_cart ) {
- $return = wc_price( $this->shipping_total );
+ if ( 'excl' === $this->tax_display_cart ) {
+ $return = wc_price( $this->shipping_total );
- if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) {
- $return .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
- }
-
- return $return;
- } else {
- $return = wc_price( $this->shipping_total + $this->shipping_tax_total );
-
- if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) {
- $return .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
- }
-
- return $return;
+ if ( $this->shipping_tax_total > 0 && wc_prices_include_tax() ) {
+ $return .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
}
+
+ return $return;
} else {
- return __( 'Free!', 'woocommerce' );
+ $return = wc_price( $this->shipping_total + $this->shipping_tax_total );
+
+ if ( $this->shipping_tax_total > 0 && ! wc_prices_include_tax() ) {
+ $return .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
+ }
+
+ return $return;
}
+ } else {
+ return __( 'Free!', 'woocommerce' );
}
- return '';
}
/**
@@ -1338,61 +1446,60 @@ class WC_Cart extends WC_Legacy_Cart {
* @param array $posted Post data.
*/
public function check_customer_coupons( $posted ) {
- if ( ! empty( $this->applied_coupons ) ) {
- foreach ( $this->applied_coupons as $code ) {
- $coupon = new WC_Coupon( $code );
+ foreach ( $this->get_applied_coupons() as $code ) {
+ $coupon = new WC_Coupon( $code );
- if ( $coupon->is_valid() ) {
+ if ( $coupon->is_valid() ) {
- // 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();
- $check_emails[] = $current_user->user_email;
- }
- $check_emails[] = $posted['billing_email'];
- $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
+ // Get user and posted emails to compare.
+ $current_user = wp_get_current_user();
+ $check_emails = array_unique( array_filter( array_map( 'strtolower', array_map( 'sanitize_email', array(
+ $posted['billing_email'],
+ $current_user->user_email,
+ ) ) ) ) );
- if ( 0 === count( array_intersect( $check_emails, $coupon->get_email_restrictions() ) ) ) {
- $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
- $this->remove_coupon( $code );
+ // Limit to defined email addresses.
+ $restrictions = $coupon->get_email_restrictions();
- // Flag totals for refresh.
- WC()->session->set( 'refresh_totals', true );
- }
+ if ( is_array( $restrictions ) && 0 < count( $restrictions ) && 0 === count( array_intersect( $check_emails, $restrictions ) ) ) {
+ $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
+ $this->remove_coupon( $code );
+ }
+
+ // Usage limits per user - check against billing and user email and user ID.
+ $limit_per_user = $coupon->get_usage_limit_per_user();
+
+ if ( 0 < $limit_per_user ) {
+ $used_by = $coupon->get_used_by();
+ $usage_count = 0;
+ $user_id_matches = array( get_current_user_id() );
+
+ // Check usage against emails.
+ foreach ( $check_emails as $check_email ) {
+ $usage_count += count( array_keys( $used_by, $check_email, true ) );
+ $user = get_user_by( 'email', $check_email );
+ $user_id_matches[] = $user ? $user->ID : 0;
}
- // 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();
+ // Check against billing emails of existing users.
+ $users_query = new WP_User_Query( array(
+ 'fields' => 'ID',
+ 'meta_query' => array(
+ 'key' => '_billing_email',
+ 'value' => $check_emails,
+ 'compare' => 'IN',
+ ),
+ ) );
- if ( is_user_logged_in() ) {
- $current_user = wp_get_current_user();
- $check_emails[] = sanitize_email( $current_user->user_email );
- $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 = count( array_keys( $used_by, $user->ID, true ) );
- } else {
- $usage_count = 0;
- }
- }
+ $user_id_matches = array_unique( array_filter( array_merge( $user_id_matches, $users_query->get_results() ) ) );
- foreach ( $check_emails as $check_email ) {
- $usage_count = $usage_count + count( array_keys( $used_by, $check_email, true ) );
- }
+ foreach ( $user_id_matches as $user_id ) {
+ $usage_count += count( array_keys( $used_by, $user_id ) );
+ }
- if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
- $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
- $this->remove_coupon( $code );
-
- // Flag totals for refresh.
- WC()->session->set( 'refresh_totals', true );
- }
+ if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
+ $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
+ $this->remove_coupon( $code );
}
}
}
@@ -1415,7 +1522,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @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 ) {
+ public function apply_coupon( $coupon_code ) {
// Coupons are globally disabled.
if ( ! wc_coupons_enabled() ) {
return false;
@@ -1521,15 +1628,6 @@ class WC_Cart extends WC_Legacy_Cart {
return $coupons;
}
- /**
- * Gets the array of applied coupon codes.
- *
- * @return array of applied coupons
- */
- public function get_applied_coupons() {
- return $this->applied_coupons;
- }
-
/**
* Get the discount amount for a used coupon.
*
@@ -1538,13 +1636,14 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float discount amount
*/
public function get_coupon_discount_amount( $code, $ex_tax = true ) {
- $discount_amount = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] : 0;
+ $totals = $this->get_coupon_discount_totals();
+ $discount_amount = isset( $totals[ $code ] ) ? $totals[ $code ]: 0;
if ( ! $ex_tax ) {
$discount_amount += $this->get_coupon_discount_tax_amount( $code );
}
- return wc_cart_round_discount( $discount_amount, $this->dp );
+ return wc_cart_round_discount( $discount_amount, wc_get_price_decimals() );
}
/**
@@ -1554,7 +1653,8 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float discount amount
*/
public function get_coupon_discount_tax_amount( $code ) {
- return wc_cart_round_discount( isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] : 0, $this->dp );
+ $totals = $this->get_coupon_discount_tax_totals();
+ return wc_cart_round_discount( isset( $totals[ $code ] ) ? $totals[ $code ] : 0, wc_get_price_decimals() );
}
/**
@@ -1563,10 +1663,10 @@ class WC_Cart extends WC_Legacy_Cart {
* @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();
- WC()->session->set( 'applied_coupons', array() );
- WC()->session->set( 'coupon_discount_amounts', array() );
- WC()->session->set( 'coupon_discount_tax_amounts', array() );
+ $this->set_coupon_discount_totals( array() );
+ $this->set_coupon_discount_tax_totals( array() );
+ $this->set_applied_coupons( array() );
+ $this->session->set_session();
}
/**
@@ -1576,19 +1676,18 @@ class WC_Cart extends WC_Legacy_Cart {
* @return bool
*/
public function remove_coupon( $coupon_code ) {
- // Coupons are globally disabled.
if ( ! wc_coupons_enabled() ) {
return false;
}
$coupon_code = wc_format_coupon_code( $coupon_code );
- $position = array_search( $coupon_code, $this->applied_coupons, true );
+ $position = array_search( $coupon_code, $this->get_applied_coupons(), true );
if ( false !== $position ) {
unset( $this->applied_coupons[ $position ] );
}
- WC()->session->set( 'applied_coupons', $this->applied_coupons );
+ WC()->session->set( 'refresh_totals', true );
do_action( 'woocommerce_removed_coupon', $coupon_code );
@@ -1644,43 +1743,13 @@ class WC_Cart extends WC_Legacy_Cart {
* Calculate fees.
*/
public function calculate_fees() {
- // Reset fees before calculation.
- $this->fee_total = 0;
- $this->fees = array();
+ $this->fees = array();
+ $this->set_fee_total( 0 );
+ $this->set_fee_tax( 0 );
+ $this->set_fee_taxes( array() );
// 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 ( ! empty( $this->fees ) ) {
- foreach ( $this->fees as $fee_key => $fee ) {
- $this->fee_total += $fee->amount;
-
- if ( $fee->taxable ) {
- $tax_rates = WC_Tax::get_rates( $fee->tax_class );
- $fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false );
-
- if ( ! empty( $fee_taxes ) ) {
- $this->fees[ $fee_key ]->tax = array_sum( $fee_taxes );
- $this->fees[ $fee_key ]->tax_data = $fee_taxes;
-
- // 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 );
- }
- }
- }
- }
- }
- }
-
- /**
- * Gets the order total (after calculation).
- *
- * @return string formatted price
- */
- public function get_total() {
- return apply_filters( 'woocommerce_cart_total', wc_price( $this->total ) );
}
/**
@@ -1689,11 +1758,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return string formatted price
*/
public function get_total_ex_tax() {
- $total = $this->total - $this->tax_total - $this->shipping_tax_total;
- if ( $total < 0 ) {
- $total = 0;
- }
- return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( $total ) );
+ return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( max( 0, $this->get_total( 'edit' ) - $this->get_cart_contents_tax() - $this->get_shipping_tax() ) ) );
}
/**
@@ -1702,13 +1767,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return string formatted price
*/
public function get_cart_total() {
- if ( ! $this->prices_include_tax ) {
- $cart_contents_total = wc_price( $this->cart_contents_total );
- } else {
- $cart_contents_total = wc_price( $this->cart_contents_total + $this->tax_total );
- }
-
- return apply_filters( 'woocommerce_cart_contents_total', $cart_contents_total );
+ return apply_filters( 'woocommerce_cart_contents_total', wc_price( wc_prices_include_tax() ? $this->get_cart_contents_total() + $this->get_cart_contents_tax() : $this->get_cart_contents_total() ) );
}
/**
@@ -1718,23 +1777,22 @@ class WC_Cart extends WC_Legacy_Cart {
* @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 ( $compound ) {
- $cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) );
+ $cart_subtotal = wc_price( $this->get_cart_contents_total() + $this->get_shipping_total() + $this->get_taxes_total( false, false ) );
} elseif ( 'excl' === $this->tax_display_cart ) {
- $cart_subtotal = wc_price( $this->subtotal_ex_tax );
+ $cart_subtotal = wc_price( $this->get_subtotal() );
- if ( $this->tax_total > 0 && $this->prices_include_tax ) {
+ if ( $this->get_subtotal_tax() > 0 && wc_prices_include_tax() ) {
$cart_subtotal .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
}
} else {
- $cart_subtotal = wc_price( $this->subtotal );
+ $cart_subtotal = wc_price( $this->get_subtotal() + $this->get_subtotal_tax() );
- if ( $this->tax_total > 0 && ! $this->prices_include_tax ) {
+ if ( $this->get_subtotal_tax() > 0 && ! wc_prices_include_tax() ) {
$cart_subtotal .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
}
}
@@ -1778,7 +1836,7 @@ class WC_Cart extends WC_Legacy_Cart {
$row_price = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
$product_subtotal = wc_price( $row_price );
- if ( $this->prices_include_tax && $this->tax_total > 0 ) {
+ if ( wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
$product_subtotal .= '
' . WC()->countries->ex_tax_or_vat() . ' ';
}
} else {
@@ -1786,7 +1844,7 @@ class WC_Cart extends WC_Legacy_Cart {
$row_price = wc_get_price_including_tax( $product, array( 'qty' => $quantity ) );
$product_subtotal = wc_price( $row_price );
- if ( ! $this->prices_include_tax && $this->tax_total > 0 ) {
+ if ( ! wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
$product_subtotal .= '
' . WC()->countries->inc_tax_or_vat() . ' ';
}
}
@@ -1804,7 +1862,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return string formatted price
*/
public function get_cart_tax() {
- $cart_total_tax = wc_round_tax_total( $this->tax_total + $this->shipping_tax_total );
+ $cart_total_tax = wc_round_tax_total( $this->get_cart_contents_tax() + $this->get_shipping_tax() + $this->get_fee_tax() );
return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' );
}
@@ -1816,7 +1874,8 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float amount
*/
public function get_tax_amount( $tax_rate_id ) {
- return isset( $this->taxes[ $tax_rate_id ] ) ? $this->taxes[ $tax_rate_id ] : 0;
+ $taxes = wc_array_merge_recursive_numeric( $this->get_cart_contents_taxes(), $this->get_fee_taxes() );
+ return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
}
/**
@@ -1826,7 +1885,8 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float amount
*/
public function get_shipping_tax_amount( $tax_rate_id ) {
- return isset( $this->shipping_taxes[ $tax_rate_id ] ) ? $this->shipping_taxes[ $tax_rate_id ] : 0;
+ $taxes = $this->get_shipping_taxes();
+ return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
}
/**
@@ -1838,13 +1898,8 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public function get_taxes_total( $compound = true, $display = true ) {
$total = 0;
- foreach ( $this->taxes as $key => $tax ) {
- if ( ! $compound && WC_Tax::is_compound( $key ) ) {
- continue;
- }
- $total += $tax;
- }
- foreach ( $this->shipping_taxes as $key => $tax ) {
+ $taxes = $this->get_taxes();
+ foreach ( $taxes as $key => $tax ) {
if ( ! $compound && WC_Tax::is_compound( $key ) ) {
continue;
}
@@ -1857,29 +1912,21 @@ class WC_Cart extends WC_Legacy_Cart {
}
/**
- * Get the total of all cart discounts.
- *
- * @return float
- */
- public function get_cart_discount_total() {
- return wc_cart_round_discount( $this->discount_cart, $this->dp );
- }
-
- /**
- * Get the total of all cart tax discounts (used for discounts on tax inclusive prices).
- *
- * @return float
- */
- public function get_cart_discount_tax_total() {
- return wc_cart_round_discount( $this->discount_cart_tax, $this->dp );
- }
-
- /**
- * Gets the total discount amount - both kinds.
+ * Gets the total discount amount.
*
* @return mixed formatted price or false if there are none
*/
public function get_total_discount() {
- return apply_filters( 'woocommerce_cart_total_discount', $this->get_cart_discount_total() ? wc_price( $this->get_cart_discount_total() ) : false, $this );
+ return apply_filters( 'woocommerce_cart_total_discount', $this->get_discount_total() ? wc_price( $this->get_discount_total() ) : false, $this );
+ }
+
+ /**
+ * Reset cart totals to the defaults. Useful before running calculations.
+ *
+ * @access private
+ */
+ private function reset_totals() {
+ $this->totals = $this->default_totals;
+ do_action( 'woocommerce_cart_reset', $this, false );
}
}
diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php
index 0984e30c510..0f89ebe1475 100644
--- a/includes/class-wc-checkout.php
+++ b/includes/class-wc-checkout.php
@@ -216,7 +216,7 @@ class WC_Checkout {
if ( 'no' === get_option( 'woocommerce_registration_generate_password' ) ) {
$this->fields['account']['account_password'] = array(
'type' => 'password',
- 'label' => __( 'Account password', 'woocommerce' ),
+ 'label' => __( 'Create account password', 'woocommerce' ),
'required' => true,
'placeholder' => esc_attr__( 'Password', 'woocommerce' ),
);
@@ -313,12 +313,12 @@ class WC_Checkout {
$order->set_customer_user_agent( wc_get_user_agent() );
$order->set_customer_note( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
$order->set_payment_method( isset( $available_gateways[ $data['payment_method'] ] ) ? $available_gateways[ $data['payment_method'] ] : $data['payment_method'] );
- $order->set_shipping_total( WC()->cart->shipping_total );
- $order->set_discount_total( WC()->cart->get_cart_discount_total() );
- $order->set_discount_tax( WC()->cart->get_cart_discount_tax_total() );
- $order->set_cart_tax( WC()->cart->tax_total );
- $order->set_shipping_tax( WC()->cart->shipping_tax_total );
- $order->set_total( WC()->cart->total );
+ $order->set_shipping_total( WC()->cart->get_shipping_total() );
+ $order->set_discount_total( WC()->cart->get_discount_total() );
+ $order->set_discount_tax( WC()->cart->get_discount_tax() );
+ $order->set_cart_tax( WC()->cart->get_cart_contents_tax() + WC()->cart->get_fee_tax() );
+ $order->set_shipping_tax( WC()->cart->get_shipping_tax() );
+ $order->set_total( WC()->cart->get_total( 'edit' ) );
$this->create_order_line_items( $order, WC()->cart );
$this->create_order_fee_lines( $order, WC()->cart );
$this->create_order_shipping_lines( $order, WC()->session->get( 'chosen_shipping_methods' ), WC()->shipping->get_packages() );
diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php
index 0fda8c44e2e..a1998d6d89f 100644
--- a/includes/class-wc-coupon.php
+++ b/includes/class-wc-coupon.php
@@ -447,6 +447,12 @@ class WC_Coupon extends WC_Legacy_Coupon {
* @throws WC_Data_Exception
*/
public function set_amount( $amount ) {
+ if ( $amount < 0 ) {
+ $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
+ }
+ if ( 'percent' === $this->get_discount_type() && $amount > 100 ) {
+ $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
+ }
$this->set_prop( 'amount', wc_format_decimal( $amount ) );
}
diff --git a/includes/class-wc-discount.php b/includes/class-wc-discount.php
deleted file mode 100644
index 6e9d12591c7..00000000000
--- a/includes/class-wc-discount.php
+++ /dev/null
@@ -1,82 +0,0 @@
- 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
index 5b77db9f751..954dbf3db45 100644
--- a/includes/class-wc-discounts.php
+++ b/includes/class-wc-discounts.php
@@ -32,31 +32,26 @@ class WC_Discounts {
protected $discounts = array();
/**
- * An array of applied WC_Discount objects.
- *
- * @var array
- */
- protected $manual_discounts = array();
-
- /**
- * Constructor. @todo accept order objects.
+ * Constructor.
*
* @param array $object Cart or order object.
*/
public function __construct( $object = array() ) {
if ( is_a( $object, 'WC_Cart' ) ) {
$this->set_items_from_cart( $object );
+ } elseif ( is_a( $object, 'WC_Order' ) ) {
+ $this->set_items_from_order( $object );
}
}
/**
- * Normalise cart/order items which will be discounted.
+ * Normalise cart 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();
+ $this->items = $this->discounts = array();
if ( ! is_a( $cart, 'WC_Cart' ) ) {
return;
@@ -75,6 +70,32 @@ class WC_Discounts {
uasort( $this->items, array( $this, 'sort_by_price' ) );
}
+ /**
+ * Normalise order items which will be discounted.
+ *
+ * @since 3.2.0
+ * @param array $order Cart object.
+ */
+ public function set_items_from_order( $order ) {
+ $this->items = $this->discounts = array();
+
+ if ( ! is_a( $order, 'WC_Order' ) ) {
+ return;
+ }
+
+ foreach ( $order->get_items() as $order_item ) {
+ $item = new stdClass();
+ $item->key = $order_item->get_id();
+ $item->object = $order_item;
+ $item->product = $order_item->get_product();
+ $item->quantity = $order_item->get_quantity();
+ $item->price = wc_add_number_precision_deep( $order_item->get_total() );
+ $this->items[ $order_item->get_id() ] = $item;
+ }
+
+ uasort( $this->items, array( $this, 'sort_by_price' ) );
+ }
+
/**
* Get items.
*
@@ -107,11 +128,6 @@ class WC_Discounts {
*/
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 );
}
@@ -124,7 +140,7 @@ class WC_Discounts {
*/
public function get_discounts_by_item( $in_cents = false ) {
$discounts = $this->discounts;
- $item_discount_totals = array_shift( $discounts );
+ $item_discount_totals = (array) array_shift( $discounts );
foreach ( $discounts as $item_discounts ) {
foreach ( $item_discounts as $item_key => $item_discount ) {
@@ -148,16 +164,6 @@ class WC_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.
*
@@ -180,88 +186,6 @@ class WC_Discounts {
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.
*
@@ -269,7 +193,11 @@ class WC_Discounts {
* @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 ) {
+ public function apply_coupon( $coupon ) {
+ if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
+ return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
+ }
+
$is_coupon_valid = $this->is_coupon_valid( $coupon );
if ( is_wp_error( $is_coupon_valid ) ) {
@@ -298,7 +226,8 @@ class WC_Discounts {
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 ) );
+ $discount = wc_add_number_precision( $coupon->get_discount_amount( $price_to_discount, $item->object ) ) * $item->quantity;
+ $discount = min( $discounted_price, $discount );
// Store code and discount amount per item.
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
@@ -401,8 +330,14 @@ class WC_Discounts {
$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 ) );
+ $discount = floor( $price_to_discount * ( $coupon->get_amount() / 100 ) );
+
+ if ( has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
+ // Send through the legacy filter, but not as cents.
+ $discount = wc_add_number_precision( apply_filters( 'woocommerce_coupon_get_discount_amount', wc_remove_number_precision( $discount ), wc_remove_number_precision( $price_to_discount ), $item->object, false, $coupon ) );
+ }
+
+ $discount = min( $discounted_price, $discount );
$total_discount += $discount;
// Store code and discount amount per item.
@@ -430,7 +365,7 @@ class WC_Discounts {
*/
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() );
+ $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.
@@ -440,8 +375,14 @@ class WC_Discounts {
$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 ) );
+ $discount = $amount * $item->quantity;
+
+ if ( has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
+ // Send through the legacy filter, but not as cents.
+ $discount = wc_add_number_precision( apply_filters( 'woocommerce_coupon_get_discount_amount', wc_remove_number_precision( $discount ), wc_remove_number_precision( $price_to_discount ), $item->object, false, $coupon ) );
+ }
+
+ $discount = min( $discounted_price, $discount );
$total_discount += $discount;
// Store code and discount amount per item.
@@ -468,19 +409,24 @@ class WC_Discounts {
return $total_discount;
}
- $per_item_discount = absint( $amount / $item_count ); // round it down to the nearest cent.
+ if ( ! $amount ) {
+ // If there is no amount we still send it through so filters are fired.
+ $total_discount = $this->apply_coupon_fixed_product( $coupon, $items_to_apply, 0 );
+ } else {
+ $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 ( $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 );
+ /**
+ * 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 );
}
- } elseif ( $amount > 0 ) {
- $total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $amount );
}
return $total_discount;
}
@@ -582,7 +528,7 @@ class WC_Discounts {
$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() ) {
+ if ( $coupon && $user_id && $coupon->get_usage_limit_per_user() > 0 && $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() ) {
@@ -742,7 +688,7 @@ class WC_Discounts {
* @return bool
*/
protected function validate_coupon_excluded_items( $coupon ) {
- if ( ! $this->items && $coupon->is_type( wc_get_product_coupon_types() ) ) {
+ if ( ! empty( $this->items ) && $coupon->is_type( wc_get_product_coupon_types() ) ) {
$valid = false;
foreach ( $this->items as $item ) {
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 0ee7fcb15a4..fb96050851a 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -84,6 +84,10 @@ class WC_Install {
'wc_update_310_old_comments',
'wc_update_310_db_version',
),
+ '3.1.2' => array(
+ 'wc_update_312_shop_manager_capabilities',
+ 'wc_update_312_db_version',
+ ),
'3.2.0' => array(
'wc_update_320_mexican_states',
'wc_update_320_db_version',
@@ -702,7 +706,6 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
'manage_categories' => true,
'manage_links' => true,
'moderate_comments' => true,
- 'unfiltered_html' => true,
'upload_files' => true,
'export' => true,
'import' => true,
diff --git a/includes/class-wc-logger.php b/includes/class-wc-logger.php
index f93bacafb6f..53285b4601f 100644
--- a/includes/class-wc-logger.php
+++ b/includes/class-wc-logger.php
@@ -54,8 +54,10 @@ class WC_Logger implements WC_Logger_Interface {
wc_doing_it_wrong(
__METHOD__,
sprintf(
- __( 'The provided handler
%s
does not implement WC_Log_Handler_Interface.', 'woocommerce' ),
- esc_html( is_object( $handler ) ? get_class( $handler ) : $handler )
+ /* translators: 1: class name 2: WC_Log_Handler_Interface */
+ __( 'The provided handler %1$s does not implement %2$s.', 'woocommerce' ),
+ '
' . esc_html( is_object( $handler ) ? get_class( $handler ) : $handler ) . '
',
+ '
WC_Log_Handler_Interface
'
),
'3.0'
);
@@ -124,7 +126,8 @@ class WC_Logger implements WC_Logger_Interface {
*/
public function log( $level, $message, $context = array() ) {
if ( ! WC_Log_Levels::is_valid_level( $level ) ) {
- wc_doing_it_wrong( __METHOD__, sprintf( __( 'WC_Logger::log was called with an invalid level "%s".', 'woocommerce' ), $level ), '3.0' );
+ /* translators: 1: WC_Logger::log 2: level */
+ wc_doing_it_wrong( __METHOD__, sprintf( __( '%1$s was called with an invalid level "%2$s".', 'woocommerce' ), '
WC_Logger::log
', $level ), '3.0' );
}
if ( $this->should_handle( $level ) ) {
diff --git a/includes/class-wc-order-item-shipping.php b/includes/class-wc-order-item-shipping.php
index b77ef122b50..943ac8e0359 100644
--- a/includes/class-wc-order-item-shipping.php
+++ b/includes/class-wc-order-item-shipping.php
@@ -221,6 +221,16 @@ class WC_Order_Item_Shipping extends WC_Order_Item {
return $this->get_prop( 'taxes', $context );
}
+ /**
+ * Get tax class.
+ *
+ * @param string $context
+ * @return string
+ */
+ public function get_tax_class( $context = 'view' ) {
+ return get_option( 'woocommerce_shipping_tax_class' );
+ }
+
/*
|--------------------------------------------------------------------------
| Array Access Methods
diff --git a/includes/class-wc-order-item.php b/includes/class-wc-order-item.php
index 3ab1235dff9..8187c93ba1f 100644
--- a/includes/class-wc-order-item.php
+++ b/includes/class-wc-order-item.php
@@ -68,6 +68,25 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
}
}
+ /**
+ * Merge changes with data and clear.
+ * Overrides WC_Data::apply_changes.
+ * array_replace_recursive does not work well for order items because it merges taxes instead
+ * of replacing them.
+ *
+ * @since 3.2.0
+ */
+ public function apply_changes() {
+ if ( function_exists( 'array_replace' ) ) {
+ $this->data = array_replace( $this->data, $this->changes );
+ } else { // PHP 5.2 compatibility.
+ foreach ( $this->changes as $key => $change ) {
+ $this->data[ $key ] = $change;
+ }
+ }
+ $this->changes = array();
+ }
+
/*
|--------------------------------------------------------------------------
| Getters
@@ -111,6 +130,24 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
return 1;
}
+ /**
+ * Get tax status.
+ * @return string
+ */
+ public function get_tax_status() {
+ return 'taxable';
+ }
+
+ /**
+ * Get tax class.
+ *
+ * @param string $context
+ * @return string
+ */
+ public function get_tax_class() {
+ return '';
+ }
+
/**
* Get parent order object.
* @return WC_Order
diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php
index d4db07492fd..b870f42956a 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'] );
}
}
@@ -1836,7 +1838,6 @@ class WC_Order extends WC_Abstract_Order {
$total_rows = array();
$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
- $this->add_order_item_totals_discount_row( $total_rows, $tax_display );
$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
diff --git a/includes/class-wc-post-data.php b/includes/class-wc-post-data.php
index 07263500541..eeb5d2c67c9 100644
--- a/includes/class-wc-post-data.php
+++ b/includes/class-wc-post-data.php
@@ -66,7 +66,7 @@ class WC_Post_Data {
* @return string
*/
public static function variation_post_link( $permalink, $post ) {
- if ( isset( $post->ID, $post->post_type ) && 'product_variation' === $post->post_type && ( $variation = wc_get_product( $post->ID ) ) ) {
+ if ( isset( $post->ID, $post->post_type ) && 'product_variation' === $post->post_type && ( $variation = wc_get_product( $post->ID ) ) && $variation->get_parent_id() ) {
return $variation->get_permalink();
}
return $permalink;
diff --git a/includes/class-wc-shipping.php b/includes/class-wc-shipping.php
index ece466afd5e..5aa4159c3fa 100644
--- a/includes/class-wc-shipping.php
+++ b/includes/class-wc-shipping.php
@@ -72,6 +72,22 @@ class WC_Shipping {
wc_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce' ), '2.1' );
}
+ /**
+ * Magic getter.
+ *
+ * @param string $name Property name.
+ * @return mixed
+ */
+ public function __get( $name ) {
+ // Grab from cart for backwards compatibility with versions prior to 3.2.
+ if ( 'shipping_total' === $name ){
+ return wc()->cart->get_shipping_total();
+ }
+ if ( 'shipping_taxes' === $name ){
+ return wc()->cart->get_shipping_taxes();
+ }
+ }
+
/**
* Initialize shipping.
*/
@@ -131,7 +147,7 @@ class WC_Shipping {
$this->shipping_methods = $shipping_zone->get_shipping_methods( true );
// Debug output
- if ( $debug_mode && ! defined( 'WOOCOMMERCE_CHECKOUT' ) && ! wc_has_notice( 'Customer matched zone "' . $shipping_zone->get_zone_name() . '"' ) ) {
+ if ( $debug_mode && ! defined( 'WOOCOMMERCE_CHECKOUT' ) && ! defined( 'WC_DOING_AJAX' ) && ! wc_has_notice( 'Customer matched zone "' . $shipping_zone->get_zone_name() . '"' ) ) {
wc_add_notice( 'Customer matched zone "' . $shipping_zone->get_zone_name() . '"' );
}
} else {
diff --git a/includes/class-wc-webhook.php b/includes/class-wc-webhook.php
index 7722874cb0f..3ad693ac1dc 100644
--- a/includes/class-wc-webhook.php
+++ b/includes/class-wc-webhook.php
@@ -610,11 +610,11 @@ class WC_Webhook {
$topic_hooks = array(
'coupon.created' => array(
'woocommerce_process_shop_coupon_meta',
- 'woocommerce_api_create_coupon',
+ 'woocommerce_new_coupon',
),
'coupon.updated' => array(
'woocommerce_process_shop_coupon_meta',
- 'woocommerce_api_edit_coupon',
+ 'woocommerce_update_coupon',
),
'coupon.deleted' => array(
'wp_trash_post',
@@ -625,26 +625,22 @@ class WC_Webhook {
'customer.created' => array(
'user_register',
'woocommerce_created_customer',
- 'woocommerce_api_create_customer',
+ 'woocommerce_new_customer',
),
'customer.updated' => array(
'profile_update',
- 'woocommerce_api_edit_customer',
- 'woocommerce_customer_save_address',
+ 'woocommerce_update_customer',
),
'customer.deleted' => array(
'delete_user',
),
'order.created' => array(
- 'woocommerce_checkout_order_processed',
'woocommerce_process_shop_order_meta',
- 'woocommerce_api_create_order',
+ 'woocommerce_new_order',
),
'order.updated' => array(
'woocommerce_process_shop_order_meta',
- 'woocommerce_api_edit_order',
- 'woocommerce_order_edit_status',
- 'woocommerce_order_status_changed',
+ 'woocommerce_update_order',
),
'order.deleted' => array(
'wp_trash_post',
@@ -654,13 +650,13 @@ class WC_Webhook {
),
'product.created' => array(
'woocommerce_process_product_meta',
- 'woocommerce_api_create_product',
+ 'woocommerce_new_product',
+ 'woocommerce_new_product_variation',
),
'product.updated' => array(
'woocommerce_process_product_meta',
- 'woocommerce_api_edit_product',
- 'woocommerce_product_quick_edit_save',
- 'woocommerce_product_bulk_edit_save',
+ 'woocommerce_update_product',
+ 'woocommerce_update_product_variation',
),
'product.deleted' => array(
'wp_trash_post',
diff --git a/includes/cli/class-wc-cli-tool-command.php b/includes/cli/class-wc-cli-tool-command.php
index f2859cd4b12..5c91585d42a 100644
--- a/includes/cli/class-wc-cli-tool-command.php
+++ b/includes/cli/class-wc-cli-tool-command.php
@@ -23,7 +23,7 @@ class WC_CLI_Tool_Command {
public static function register_commands() {
global $wp_rest_server;
- $request = new WP_REST_Request( 'OPTIONS', '/wc/v1/system_status/tools' );
+ $request = new WP_REST_Request( 'OPTIONS', '/wc/v2/system_status/tools' );
$response = $wp_rest_server->dispatch( $request );
$response_data = $response->get_data();
if ( empty( $response_data ) ) {
@@ -42,7 +42,7 @@ class WC_CLI_Tool_Command {
'optional' => false,
);
$method = 'update_item';
- $route = '/wc/v1/system_status/tools/(?P
[\w-]+)';
+ $route = '/wc/v2/system_status/tools/(?P[\w-]+)';
} elseif ( 'list' === $command ) {
$synopsis[] = array(
'name' => 'fields',
@@ -75,7 +75,7 @@ class WC_CLI_Tool_Command {
),
);
$method = 'list_items';
- $route = '/wc/v1/system_status/tools';
+ $route = '/wc/v2/system_status/tools';
}
$before_invoke = null;
diff --git a/includes/data-stores/abstract-wc-order-data-store-cpt.php b/includes/data-stores/abstract-wc-order-data-store-cpt.php
index 4ba1553ed87..5cc063748e4 100644
--- a/includes/data-stores/abstract-wc-order-data-store-cpt.php
+++ b/includes/data-stores/abstract-wc-order-data-store-cpt.php
@@ -210,8 +210,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
/**
* Read order data. Can be overridden by child classes to load other props.
*
- * @param WC_Order
- * @param object $post_object
+ * @param WC_Order $order
+ * @param object $post_object
* @since 3.0.0
*/
protected function read_order_data( &$order, $post_object ) {
@@ -278,7 +278,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
/**
* Clear any caches.
*
- * @param WC_Order
+ * @param WC_Order $order
* @since 3.0.0
*/
protected function clear_caches( &$order ) {
@@ -323,8 +323,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
/**
* Remove all line items (products, coupons, shipping, taxes) from the order.
*
- * @param WC_Order
- * @param string $type Order item type. Default null.
+ * @param WC_Order $order
+ * @param string $type Order item type. Default null.
*/
public function delete_items( $order, $type = null ) {
global $wpdb;
@@ -341,7 +341,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
/**
* Get token ids for an order.
*
- * @param WC_Order
+ * @param WC_Order $order
* @return array
*/
public function get_payment_token_ids( $order ) {
@@ -352,8 +352,8 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
/**
* Update token ids for an order.
*
- * @param WC_Order
- * @param array $token_ids
+ * @param WC_Order $order
+ * @param array $token_ids
*/
public function update_payment_token_ids( $order, $token_ids ) {
update_post_meta( $order->get_id(), '_payment_tokens', $token_ids );
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 9bbf98f4a6c..6ec980dfd08 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
@@ -144,5 +144,6 @@ abstract class Abstract_WC_Order_Item_Type_Data_Store extends WC_Data_Store_WP i
public function clear_cache( &$item ) {
wp_cache_delete( 'item-' . $item->get_id(), 'order-items' );
wp_cache_delete( 'order-items-' . $item->get_order_id(), 'orders' );
+ wp_cache_delete( $item->get_id(), $this->meta_type . '_meta' );
}
}
diff --git a/includes/data-stores/class-wc-order-data-store-cpt.php b/includes/data-stores/class-wc-order-data-store-cpt.php
index 31483b17ff1..150dfeeaa46 100644
--- a/includes/data-stores/class-wc-order-data-store-cpt.php
+++ b/includes/data-stores/class-wc-order-data-store-cpt.php
@@ -82,8 +82,8 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
/**
* Read order data. Can be overridden by child classes to load other props.
*
- * @param WC_Order
- * @param object $post_object
+ * @param WC_Order $order
+ * @param object $post_object
* @since 3.0.0
*/
protected function read_order_data( &$order, $post_object ) {
@@ -155,7 +155,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
/**
* Helper method that updates all the post meta for an order based on it's settings in the WC_Order class.
*
- * @param WC_Order
+ * @param WC_Order $order
* @since 3.0.0
*/
protected function update_post_meta( &$order ) {
@@ -270,7 +270,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
/**
* Get amount already refunded.
*
- * @param WC_Order
+ * @param WC_Order $order
* @return string
*/
public function get_total_refunded( $order ) {
@@ -290,7 +290,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
/**
* Get the total tax refunded.
*
- * @param WC_Order
+ * @param WC_Order $order
* @return float
*/
public function get_total_tax_refunded( $order ) {
@@ -311,7 +311,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
/**
* Get the total shipping refunded.
*
- * @param WC_Order
+ * @param WC_Order $order
* @return float
*/
public function get_total_shipping_refunded( $order ) {
@@ -704,4 +704,17 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
return $orders;
}
+
+ /**
+ * Return the order type of a given item which belongs to WC_Order.
+ *
+ * @since 3.2.0
+ * @param WC_Order $order Order Object.
+ * @param int $order_item_id Order item id.
+ * @return string Order Item type
+ */
+ public function get_order_item_type( $order, $order_item_id ) {
+ global $wpdb;
+ return $wpdb->get_var( $wpdb->prepare( "SELECT DISTINCT order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d and order_item_id = %d;", $order->get_id(), $order_item_id ) );
+ }
}
diff --git a/includes/data-stores/class-wc-product-variation-data-store-cpt.php b/includes/data-stores/class-wc-product-variation-data-store-cpt.php
index 5e3ca5b44b9..ed37a86581a 100644
--- a/includes/data-stores/class-wc-product-variation-data-store-cpt.php
+++ b/includes/data-stores/class-wc-product-variation-data-store-cpt.php
@@ -255,6 +255,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
* @since 3.0.0
*/
protected function update_version_and_type( &$product ) {
+ wp_set_object_terms( $product->get_id(), '', 'product_type' );
update_post_meta( $product->get_id(), '_product_version', WC_VERSION );
}
diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php
index d6404a8a370..e1ebb00969f 100644
--- a/includes/import/abstract-wc-product-importer.php
+++ b/includes/import/abstract-wc-product-importer.php
@@ -620,7 +620,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
$wc_product_attributes = array();
foreach ( wc_get_attribute_taxonomies() as $taxonomy ) {
- $wc_product_attributes[ wc_attribute_taxonomy_name( $taxonomy['attribute_name'] ) ] = $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 fbc32877495..d7cd383ecc8 100644
--- a/includes/import/class-wc-product-csv-importer.php
+++ b/includes/import/class-wc-product-csv-importer.php
@@ -125,17 +125,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* If mapping to a SKU and the product ID does not exist, a temporary object
* will be created so it can be updated later.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return int|string
*/
- public function parse_relative_field( $field ) {
+ public function parse_relative_field( $value ) {
global $wpdb;
- if ( empty( $field ) ) {
+ if ( empty( $value ) ) {
return '';
}
- if ( preg_match( '/^id:(\d+)$/', $field, $matches ) ) {
+ if ( preg_match( '/^id:(\d+)$/', $value, $matches ) ) {
$id = intval( $matches[1] );
$original_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_original_id' AND meta_value = %s;", $id ) );
@@ -154,15 +154,15 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
return $id;
}
- if ( $id = wc_get_product_id_by_sku( $field ) ) {
+ if ( $id = wc_get_product_id_by_sku( $value ) ) {
return $id;
}
try {
$product = new WC_Product_Simple();
- $product->set_name( 'Import placeholder for ' . $field );
+ $product->set_name( 'Import placeholder for ' . $value );
$product->set_status( 'importing' );
- $product->set_sku( $field );
+ $product->set_sku( $value );
$id = $product->save();
if ( $id && ! is_wp_error( $id ) ) {
@@ -181,13 +181,13 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* If we're not doing an update, create a placeholder product so mapping works
* for rows following this one.
*
- * @param stirng $field
+ * @param string $value Field value.
* @return int
*/
- public function parse_id_field( $field ) {
+ public function parse_id_field( $value ) {
global $wpdb;
- $id = absint( $field );
+ $id = absint( $value );
if ( ! $id ) {
return 0;
@@ -225,77 +225,91 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse relative comma-delineated field and return product ID.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return array
*/
- public function parse_relative_comma_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_relative_comma_field( $value ) {
+ if ( empty( $value ) ) {
return array();
}
- return array_filter( array_map( array( $this, 'parse_relative_field' ), $this->explode_values( $field ) ) );
+ return array_filter( array_map( array( $this, 'parse_relative_field' ), $this->explode_values( $value ) ) );
}
/**
* Parse a comma-delineated field from a CSV.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return array
*/
- public function parse_comma_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_comma_field( $value ) {
+ if ( empty( $value ) ) {
return array();
}
- return array_map( 'wc_clean', $this->explode_values( $field ) );
+ return array_map( 'wc_clean', $this->explode_values( $value ) );
}
/**
* Parse a field that is generally '1' or '0' but can be something else.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return bool|string
*/
- public function parse_bool_field( $field ) {
- if ( '0' === $field ) {
+ public function parse_bool_field( $value ) {
+ if ( '0' === $value ) {
return false;
}
- if ( '1' === $field ) {
+ if ( '1' === $value ) {
return true;
}
// Don't return explicit true or false for empty fields or values like 'notify'.
- return wc_clean( $field );
+ return wc_clean( $value );
}
/**
* Parse a float value field.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return float|string
*/
- public function parse_float_field( $field ) {
- if ( '' === $field ) {
- return $field;
+ public function parse_float_field( $value ) {
+ if ( '' === $value ) {
+ return $value;
}
- return floatval( $field );
+ return floatval( $value );
+ }
+
+ /**
+ * Parse the stock qty field.
+ *
+ * @param string $value Field value.
+ * @return float|string
+ */
+ public function parse_stock_quantity_field( $value ) {
+ if ( '' === $value ) {
+ return $value;
+ }
+
+ return wc_stock_amount( $value );
}
/**
* Parse a category field from a CSV.
* Categories are separated by commas and subcategories are "parent > subcategory".
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return array of arrays with "parent" and "name" keys.
*/
- public function parse_categories_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_categories_field( $value ) {
+ if ( empty( $value ) ) {
return array();
}
- $row_terms = $this->explode_values( $field );
+ $row_terms = $this->explode_values( $value );
$categories = array();
foreach ( $row_terms as $row_term ) {
@@ -313,6 +327,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$term_id = $term['term_id'];
} else {
$term = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
+
+ if ( is_wp_error( $term ) ) {
+ break; // We cannot continue if the term cannot be inserted.
+ }
+
$term_id = $term['term_id'];
}
@@ -332,15 +351,15 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse a tag field from a CSV.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return array
*/
- public function parse_tags_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_tags_field( $value ) {
+ if ( empty( $value ) ) {
return array();
}
- $names = $this->explode_values( $field );
+ $names = $this->explode_values( $value );
$tags = array();
foreach ( $names as $name ) {
@@ -350,7 +369,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$term = (object) wp_insert_term( $name, 'product_tag' );
}
- $tags[] = $term->term_id;
+ if ( ! is_wp_error( $term ) ) {
+ $tags[] = $term->term_id;
+ }
}
return $tags;
@@ -359,18 +380,22 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse a shipping class field from a CSV.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return int
*/
- public function parse_shipping_class_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_shipping_class_field( $value ) {
+ if ( empty( $value ) ) {
return 0;
}
- $term = get_term_by( 'name', $field, 'product_shipping_class' );
+ $term = get_term_by( 'name', $value, 'product_shipping_class' );
if ( ! $term || is_wp_error( $term ) ) {
- $term = (object) wp_insert_term( $field, 'product_shipping_class' );
+ $term = (object) wp_insert_term( $value, 'product_shipping_class' );
+ }
+
+ if ( is_wp_error( $term ) ) {
+ return 0;
}
return $term->term_id;
@@ -379,17 +404,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse images list from a CSV. Images can be filenames or URLs.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return array
*/
- public function parse_images_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_images_field( $value ) {
+ if ( empty( $value ) ) {
return array();
}
$images = array();
- foreach ( $this->explode_values( $field ) as $image ) {
+ foreach ( $this->explode_values( $value ) as $image ) {
if ( stristr( $image, '://' ) ) {
$images[] = esc_url_raw( $image );
} else {
@@ -404,17 +429,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
* Parse dates from a CSV.
* Dates requires the format YYYY-MM-DD and time is optional.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return string|null
*/
- public function parse_date_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_date_field( $value ) {
+ if ( empty( $value ) ) {
return null;
}
- if ( preg_match( '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])([ 01-9:]*)$/', $field ) ) {
+ if ( preg_match( '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])([ 01-9:]*)$/', $value ) ) {
// Don't include the time if the field had time in it.
- return current( explode( ' ', $field ) );
+ return current( explode( ' ', $value ) );
}
return null;
@@ -423,25 +448,38 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse backorders from a CSV.
*
- * @param string $field Field value.
+ * @param string $value Field value.
* @return string
*/
- public function parse_backorders_field( $field ) {
- if ( empty( $field ) ) {
+ public function parse_backorders_field( $value ) {
+ if ( empty( $value ) ) {
return '';
}
- $field = $this->parse_bool_field( $field );
+ $value = $this->parse_bool_field( $value );
- if ( 'notify' === $field ) {
+ if ( 'notify' === $value ) {
return 'notify';
- } elseif ( is_bool( $field ) ) {
- return $field ? 'yes' : 'no';
+ } elseif ( is_bool( $value ) ) {
+ return $value ? 'yes' : 'no';
}
return '';
}
+ /**
+ * Just skip current field.
+ *
+ * By default is applied wc_clean() to all not listed fields
+ * in self::get_formating_callback(), use this method to skip any formating.
+ *
+ * @param string $value Field value.
+ * @return string
+ */
+ public function parse_skip_field( $value ) {
+ return $value;
+ }
+
/**
* Get formatting callback.
*
@@ -460,9 +498,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
'featured' => array( $this, 'parse_bool_field' ),
'date_on_sale_from' => array( $this, 'parse_date_field' ),
'date_on_sale_to' => array( $this, 'parse_date_field' ),
- 'name' => 'wp_filter_post_kses',
- 'short_description' => 'wp_filter_post_kses',
- 'description' => 'wp_filter_post_kses',
+ 'name' => array( $this, 'parse_skip_field' ),
+ 'short_description' => array( $this, 'parse_skip_field' ),
+ 'description' => array( $this, 'parse_skip_field' ),
'manage_stock' => array( $this, 'parse_bool_field' ),
'backorders' => array( $this, 'parse_backorders_field' ),
'stock_status' => array( $this, 'parse_bool_field' ),
@@ -475,7 +513,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
'purchase_note' => 'wp_filter_post_kses',
'price' => 'wc_format_decimal',
'regular_price' => 'wc_format_decimal',
- 'stock_quantity' => 'wc_stock_amount',
+ 'stock_quantity' => array( $this, 'parse_stock_quantity_field' ),
'category_ids' => array( $this, 'parse_categories_field' ),
'tag_ids' => array( $this, 'parse_tags_field' ),
'shipping_class_id' => array( $this, 'parse_shipping_class_field' ),
@@ -571,7 +609,13 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
if ( isset( $data['stock_quantity'] ) ) {
- $data['manage_stock'] = 0 < $data['stock_quantity'];
+ if ( '' === $data['stock_quantity'] ) {
+ $data['manage_stock'] = false;
+ $data['stock_status'] = isset( $data['stock_status'] ) ? $data['stock_status'] : true;
+ } else {
+ $data['manage_stock'] = true;
+ $data['stock_status'] = 0 < $data['stock_quantity'];
+ }
}
// Stock is bool.
diff --git a/includes/legacy/class-wc-legacy-cart.php b/includes/legacy/class-wc-legacy-cart.php
index 5a806b6b019..07b58001105 100644
--- a/includes/legacy/class-wc-legacy-cart.php
+++ b/includes/legacy/class-wc-legacy-cart.php
@@ -20,6 +20,29 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
abstract class WC_Legacy_Cart {
+ /**
+ * Array of defaults. Not used since 3.2.
+ *
+ * @deprecated 3.2.0
+ */
+ public $cart_session_data = array(
+ 'cart_contents_total' => 0,
+ 'total' => 0,
+ 'subtotal' => 0,
+ 'subtotal_ex_tax' => 0,
+ 'tax_total' => 0,
+ 'taxes' => array(),
+ 'shipping_taxes' => array(),
+ 'discount_cart' => 0,
+ 'discount_cart_tax' => 0,
+ 'shipping_total' => 0,
+ 'shipping_tax_total' => 0,
+ 'coupon_discount_amounts' => array(),
+ 'coupon_discount_tax_amounts' => array(),
+ 'fee_total' => 0,
+ 'fees' => array(),
+ );
+
/**
* Contains an array of coupon usage counts after they have been applied.
*
@@ -28,6 +51,179 @@ abstract class WC_Legacy_Cart {
*/
public $coupon_applied_count = array();
+ /**
+ * Magic getters.
+ *
+ * @param string $name Property name.
+ * @return mixed
+ */
+ public function __get( $name ) {
+ switch ( $name ) {
+ case 'dp' :
+ return wc_get_price_decimals();
+ case 'prices_include_tax' :
+ return wc_prices_include_tax();
+ case 'round_at_subtotal' :
+ return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
+ // map old public props to methods.
+ case 'cart_contents_total' :
+ return $this->get_cart_contents_total();
+ case 'total' :
+ return $this->get_total( 'edit' );
+ case 'subtotal' :
+ return $this->get_subtotal() + $this->get_subtotal_tax();
+ case 'subtotal_ex_tax' :
+ return $this->get_subtotal();
+ case 'tax_total' :
+ return $this->get_fee_tax() + $this->get_cart_contents_tax();
+ case 'taxes' :
+ return $this->get_taxes();
+ case 'shipping_taxes' :
+ return $this->get_shipping_taxes();
+ case 'fee_total' :
+ return $this->get_fee_total();
+ case 'discount_cart' :
+ return $this->get_discount_total();
+ case 'discount_cart_tax' :
+ return $this->get_discount_tax();
+ case 'shipping_total' :
+ return $this->get_shipping_total();
+ case 'shipping_tax_total' :
+ return $this->get_shipping_tax();
+ case 'coupon_discount_amounts' :
+ return $this->get_coupon_discount_totals();
+ case 'coupon_discount_tax_amounts' :
+ return $this->get_coupon_discount_tax_totals();
+ case 'display_totals_ex_tax' :
+ case 'display_cart_ex_tax' :
+ return 'excl' === $this->tax_display_cart;
+ case 'cart_contents_weight' :
+ return $this->get_cart_contents_weight();
+ case 'cart_contents_count' :
+ return $this->get_cart_contents_count();
+ case 'tax' :
+ wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' );
+ $this->tax = new WC_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;
+ case 'coupons' :
+ return $this->get_coupons();
+ }
+ }
+ /**
+ * Map legacy variables to setters.
+ *
+ * @param string $name Property name.
+ * @param mixed $value Value to set.
+ */
+ public function __set( $name, $value ) {
+ switch ( $name ) {
+ case 'cart_contents_total' :
+ $this->set_cart_contents_total( $value );
+ break;
+ case 'total' :
+ $this->set_total( $value );
+ break;
+ case 'subtotal' :
+ $this->set_subtotal( $value );
+ break;
+ case 'subtotal_ex_tax' :
+ $this->set_subtotal( $value );
+ break;
+ case 'tax_total' :
+ $this->set_cart_contents_tax( $value );
+ $this->set_fee_tax( 0 );
+ break;
+ case 'taxes' :
+ $this->set_cart_contents_taxes( $value );
+ break;
+ case 'shipping_taxes' :
+ $this->set_shipping_taxes( $value );
+ break;
+ case 'fee_total' :
+ $this->set_fee_total( $value );
+ break;
+ case 'discount_cart' :
+ $this->set_discount_total( $value );
+ break;
+ case 'discount_cart_tax' :
+ $this->set_discount_tax( $value );
+ break;
+ case 'shipping_total' :
+ $this->set_shipping_total( $value );
+ break;
+ case 'shipping_tax_total' :
+ $this->set_shipping_tax( $value );
+ break;
+ case 'coupon_discount_amounts' :
+ $this->set_coupon_discount_totals( $value );
+ break;
+ case 'coupon_discount_tax_amounts' :
+ $this->set_coupon_discount_tax_totals( $value );
+ break;
+ default :
+ $this->$name = $value;
+ break;
+ }
+ }
+
+ /**
+ * Methods moved to session class in 3.2.0.
+ */
+ public function get_cart_from_session() { $this->session->get_cart_from_session(); }
+ public function maybe_set_cart_cookies() { $this->session->maybe_set_cart_cookies(); }
+ public function set_session() { $this->session->set_session(); }
+ public function get_cart_for_session() { $this->session->get_cart_for_session(); }
+ public function persistent_cart_update() { $this->session->persistent_cart_update(); }
+ public function persistent_cart_destroy() { $this->session->persistent_cart_destroy(); }
+
+ /**
+ * Get the total of all cart discounts.
+ *
+ * @return float
+ */
+ public function get_cart_discount_total() {
+ return $this->get_discount_total();
+ }
+
+ /**
+ * Get the total of all cart tax discounts (used for discounts on tax inclusive prices).
+ *
+ * @return float
+ */
+ public function get_cart_discount_tax_total() {
+ return $this->get_discount_tax();
+ }
+
+ /**
+ * Renamed for consistency.
+ *
+ * @param string $coupon_code
+ * @return bool True if the coupon is applied, false if it does not exist or cannot be applied.
+ */
+ public function add_discount( $coupon_code ) {
+ return $this->apply_coupon( $coupon_code );
+ }
+ /**
+ * Remove taxes.
+ *
+ * @deprecated 3.2.0 Taxes are never calculated if customer is tax except making this function unused.
+ */
+ public function remove_taxes() {
+ wc_deprecated_function( 'WC_Cart::remove_taxes', '3.2', '' );
+ }
+ /**
+ * Init.
+ *
+ * @deprecated 3.2.0 Session is loaded via hooks rather than directly.
+ */
+ public function init() {
+ wc_deprecated_function( 'WC_Cart::init', '3.2', '' );
+ $this->get_cart_from_session();
+ }
+
/**
* Function to apply discounts to a product and get the discounted price (before tax is applied).
*
diff --git a/includes/shipping/free-shipping/class-wc-shipping-free-shipping.php b/includes/shipping/free-shipping/class-wc-shipping-free-shipping.php
index bc8741f0e1d..db665efd637 100644
--- a/includes/shipping/free-shipping/class-wc-shipping-free-shipping.php
+++ b/includes/shipping/free-shipping/class-wc-shipping-free-shipping.php
@@ -163,9 +163,9 @@ class WC_Shipping_Free_Shipping extends WC_Shipping_Method {
$total = WC()->cart->get_displayed_subtotal();
if ( 'incl' === WC()->cart->tax_display_cart ) {
- $total = round( $total - ( WC()->cart->get_cart_discount_total() + WC()->cart->get_cart_discount_tax_total() ), wc_get_price_decimals() );
+ $total = round( $total - ( WC()->cart->get_discount_total() + WC()->cart->get_discount_tax() ), wc_get_price_decimals() );
} else {
- $total = round( $total - WC()->cart->get_cart_discount_total(), wc_get_price_decimals() );
+ $total = round( $total - WC()->cart->get_discount_total(), wc_get_price_decimals() );
}
if ( $total >= $this->min_amount ) {
@@ -191,7 +191,7 @@ class WC_Shipping_Free_Shipping extends WC_Shipping_Method {
break;
}
- return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package );
+ return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package, $this );
}
/**
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 be23051a1fa..18658f84965 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
@@ -174,7 +174,7 @@ class WC_Shipping_Legacy_Free_Shipping extends WC_Shipping_Method {
}
if ( in_array( $this->requires, array( 'min_amount', 'either', 'both' ) ) && isset( WC()->cart->cart_contents_total ) ) {
- if ( WC()->cart->prices_include_tax ) {
+ if ( wc_prices_include_tax() ) {
$total = WC()->cart->cart_contents_total + array_sum( WC()->cart->taxes );
} else {
$total = WC()->cart->cart_contents_total;
@@ -211,7 +211,7 @@ class WC_Shipping_Legacy_Free_Shipping extends WC_Shipping_Method {
break;
}
- return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package );
+ return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package, $this );
}
/**
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 8889195609b..464012e9542 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
@@ -73,6 +73,6 @@ class WC_Shipping_Legacy_International_Delivery extends WC_Shipping_Legacy_Flat_
return false;
}
}
- return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', true, $package );
+ return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', true, $package, $this );
}
}
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 2d303ae708c..bb7672ec6f3 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
@@ -206,7 +206,7 @@ class WC_Shipping_Legacy_Local_Pickup extends WC_Shipping_Method {
}
}
- return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package );
+ return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package, $this );
}
/**
diff --git a/includes/shortcodes/class-wc-shortcode-checkout.php b/includes/shortcodes/class-wc-shortcode-checkout.php
index 3d91c8a5885..6d04350b2f4 100644
--- a/includes/shortcodes/class-wc-shortcode-checkout.php
+++ b/includes/shortcodes/class-wc-shortcode-checkout.php
@@ -157,35 +157,8 @@ class WC_Shortcode_Checkout {
if ( $order->needs_payment() ) {
- ?>
-
-
-
- get_order_number(); ?>
-
-
-
- get_date_created() ); ?>
-
-
-
- get_formatted_order_total(); ?>
-
- get_payment_method_title() ) : ?>
-
-
- get_payment_method_title() );
- ?>
-
-
-
-
- get_payment_method(), $order_id ); ?>
-
-
- $order ) );
+
} else {
wc_add_notice( sprintf( __( 'This order’s status is “%s”—it cannot be paid for. Please contact us if you need assistance.', 'woocommerce' ), wc_get_order_status_name( $order->get_status() ) ), 'error' );
}
diff --git a/includes/vendor/abstract-wp-rest-controller.php b/includes/vendor/abstract-wp-rest-controller.php
index 779a88bf7f0..fa483b20812 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 overridden', 'woocommerce' ), 'WPAPI-2.0' );
+ wc_doing_it_wrong( 'WP_REST_Controller::register_routes', sprintf( __( "Method '%s' must be overridden.", 'woocommerce' ), 'register_routes()' ), 'WPAPI-2.0' );
}
/**
diff --git a/includes/wc-cart-functions.php b/includes/wc-cart-functions.php
index c9b44b75dd0..b540ff5b9f4 100644
--- a/includes/wc-cart-functions.php
+++ b/includes/wc-cart-functions.php
@@ -344,12 +344,12 @@ function wc_cart_totals_shipping_method_label( $method ) {
if ( $method->cost > 0 ) {
if ( WC()->cart->tax_display_cart == 'excl' ) {
$label .= ': ' . wc_price( $method->cost );
- if ( $method->get_shipping_tax() > 0 && WC()->cart->prices_include_tax ) {
+ if ( $method->get_shipping_tax() > 0 && wc_prices_include_tax() ) {
$label .= ' ' . WC()->countries->ex_tax_or_vat() . ' ';
}
} else {
$label .= ': ' . wc_price( $method->cost + $method->get_shipping_tax() );
- if ( $method->get_shipping_tax() > 0 && ! WC()->cart->prices_include_tax ) {
+ if ( $method->get_shipping_tax() > 0 && ! wc_prices_include_tax() ) {
$label .= ' ' . WC()->countries->inc_tax_or_vat() . ' ';
}
}
@@ -369,7 +369,19 @@ function wc_cart_round_discount( $value, $precision ) {
if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
return round( $value, $precision, WC_DISCOUNT_ROUNDING_MODE );
} else {
- return round( $value, $precision );
+ // Fake it in PHP 5.2.
+ if ( 2 === WC_DISCOUNT_ROUNDING_MODE && strstr( $value, '.' ) ) {
+ $value = (string) $value;
+ $value = explode( '.', $value );
+ $value[1] = substr( $value[1], 0, $precision + 1 );
+ $value = implode( '.', $value );
+
+ if ( substr( $value, -1 ) === '5' ) {
+ $value = substr( $value, 0, -1 ) . '4';
+ }
+ $value = floatval( $value );
+ }
+ return round( $value, $precision );
}
}
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index e0d8a59a714..1bd6e458456 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -37,7 +37,7 @@ include( WC_ABSPATH . 'includes/wc-webhook-functions.php' );
*/
add_filter( 'woocommerce_coupon_code', 'html_entity_decode' );
add_filter( 'woocommerce_coupon_code', 'sanitize_text_field' );
-add_filter( 'woocommerce_coupon_code', 'strtolower' ); // Coupons case-insensitive by default
+add_filter( 'woocommerce_coupon_code', 'wc_strtolower' );
add_filter( 'woocommerce_stock_amount', 'intval' ); // Stock amounts are integers by default
add_filter( 'woocommerce_shipping_rate_label', 'sanitize_text_field' ); // Shipping rate label
@@ -1530,8 +1530,11 @@ function wc_get_logger() {
wc_doing_it_wrong(
__FUNCTION__,
sprintf(
- __( 'The class %s
provided by woocommerce_logging_class filter must implement WC_Logger_Interface
.', 'woocommerce' ),
- esc_html( is_object( $class ) ? get_class( $class ) : $class )
+ /* translators: 1: class name 2: woocommerce_logging_class 3: WC_Logger_Interface */
+ __( 'The class %1$s provided by %2$s filter must implement %3$s.', 'woocommerce' ),
+ '' . esc_html( is_object( $class ) ? get_class( $class ) : $class ) . '
',
+ 'woocommerce_logging_class
',
+ 'WC_Logger_Interface
'
),
'3.0'
);
@@ -1650,7 +1653,10 @@ function wc_list_pluck( $list, $callback_or_field, $index_key = null ) {
*/
$newlist = array();
foreach ( $list as $value ) {
- if ( isset( $value->$index_key ) ) {
+ // Get index. @since 3.2.0 this supports a callback.
+ if ( is_callable( array( $value, $index_key ) ) ) {
+ $newlist[ $value->{$index_key}() ] = $value->{$callback_or_field}();
+ } elseif ( isset( $value->$index_key ) ) {
$newlist[ $value->$index_key ] = $value->{$callback_or_field}();
} else {
$newlist[] = $value->{$callback_or_field}();
diff --git a/includes/wc-formatting-functions.php b/includes/wc-formatting-functions.php
index c0d1ba2ff70..ed6931ffb82 100644
--- a/includes/wc-formatting-functions.php
+++ b/includes/wc-formatting-functions.php
@@ -216,21 +216,32 @@ function wc_trim_zeros( $price ) {
/**
* Round a tax amount.
*
- * @param double $tax Amount to round.
- * @param int $dp DP to round. Defaults to wc_get_price_decimals.
+ * @param double $value Amount to round.
+ * @param int $precision DP to round. Defaults to wc_get_price_decimals.
* @return double
*/
-function wc_round_tax_total( $tax, $dp = null ) {
- $dp = is_null( $dp ) ? wc_get_price_decimals() : absint( $dp );
+function wc_round_tax_total( $value, $precision = null ) {
+ $precision = is_null( $precision ) ? wc_get_price_decimals() : absint( $precision );
- // @codeCoverageIgnoreStart
- if ( version_compare( phpversion(), '5.3', '<' ) ) {
- $rounded_tax = round( $tax, $dp );
+ if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
+ $rounded_tax = round( $value, $precision, WC_TAX_ROUNDING_MODE );
} else {
- // @codeCoverageIgnoreEnd
- $rounded_tax = round( $tax, $dp, WC_TAX_ROUNDING_MODE );
+ // Fake it in PHP 5.2.
+ if ( 2 === WC_TAX_ROUNDING_MODE && strstr( $value, '.' ) ) {
+ $value = (string) $value;
+ $value = explode( '.', $value );
+ $value[1] = substr( $value[1], 0, $precision + 1 );
+ $value = implode( '.', $value );
+
+ if ( substr( $value, -1 ) === '5' ) {
+ $value = substr( $value, 0, -1 ) . '4';
+ }
+ $value = floatval( $value );
+ }
+ $rounded_tax = round( $value, $precision );
}
- return apply_filters( 'wc_round_tax_total', $rounded_tax, $tax, $dp, WC_TAX_ROUNDING_MODE );
+
+ return apply_filters( 'wc_round_tax_total', $rounded_tax, $value, $precision, WC_TAX_ROUNDING_MODE );
}
/**
@@ -1154,3 +1165,62 @@ function wc_do_oembeds( $content ) {
function wc_get_string_before_colon( $string ) {
return trim( current( explode( ':', (string) $string ) ) );
}
+
+/**
+ * Array merge and sum function.
+ *
+ * Source: https://gist.github.com/Nickology/f700e319cbafab5eaedc
+ *
+ * @since 3.2.0
+ * @return array
+ */
+function wc_array_merge_recursive_numeric() {
+ $arrays = func_get_args();
+
+ // If there's only one array, it's already merged.
+ if ( 1 === count( $arrays ) ) {
+ return $arrays[0];
+ }
+
+ // Remove any items in $arrays that are NOT arrays.
+ foreach ( $arrays as $key => $array ) {
+ if ( ! is_array( $array ) ) {
+ unset( $arrays[ $key ] );
+ }
+ }
+
+ // We start by setting the first array as our final array.
+ // We will merge all other arrays with this one.
+ $final = array_shift( $arrays );
+
+ foreach ( $arrays as $b ) {
+ foreach ( $final as $key => $value ) {
+ // If $key does not exist in $b, then it is unique and can be safely merged.
+ if ( ! isset( $b[ $key ] ) ) {
+ $final[ $key ] = $value;
+ } else {
+ // If $key is present in $b, then we need to merge and sum numeric values in both.
+ if ( is_numeric( $value ) && is_numeric( $b[ $key ] ) ) {
+ // If both values for these keys are numeric, we sum them.
+ $final[ $key ] = $value + $b[ $key ];
+ } elseif ( is_array( $value ) && is_array( $b[ $key ] ) ) {
+ // If both values are arrays, we recursively call ourself.
+ $final[ $key ] = wc_array_merge_recursive_numeric( $value, $b[ $key ] );
+ } else {
+ // If both keys exist but differ in type, then we cannot merge them.
+ // In this scenario, we will $b's value for $key is used.
+ $final[ $key ] = $b[ $key ];
+ }
+ }
+ }
+
+ // Finally, we need to merge any keys that exist only in $b.
+ foreach ( $b as $key => $value ) {
+ if ( ! isset( $final[ $key ] ) ) {
+ $final[ $key ] = $value;
+ }
+ }
+ }
+
+ return $final;
+}
diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php
index e95ec07c3d5..8e124b2de33 100644
--- a/includes/wc-product-functions.php
+++ b/includes/wc-product-functions.php
@@ -58,7 +58,8 @@ function wc_get_products( $args ) {
*/
function wc_get_product( $the_product = false, $deprecated = array() ) {
if ( ! did_action( 'woocommerce_init' ) ) {
- wc_doing_it_wrong( __FUNCTION__, __( 'wc_get_product should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' );
+ /* translators: 1: wc_get_product 2: woocommerce_init */
+ wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), 'wc_get_product', 'woocommerce_init' ), '2.5' );
return false;
}
if ( ! empty( $deprecated ) ) {
diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php
index 3eb519a97b5..a4c3fac264d 100644
--- a/includes/wc-template-functions.php
+++ b/includes/wc-template-functions.php
@@ -1900,7 +1900,7 @@ if ( ! function_exists( 'woocommerce_order_again_button' ) ) {
* @subpackage Orders
*/
function woocommerce_order_again_button( $order ) {
- if ( ! $order || ! $order->has_status( 'completed' ) || ! is_user_logged_in() ) {
+ if ( ! $order || ! $order->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_order_again', array( 'completed' ) ) ) || ! is_user_logged_in() ) {
return;
}
diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php
index 250c07c4c70..f2e1b2fb902 100644
--- a/includes/wc-update-functions.php
+++ b/includes/wc-update-functions.php
@@ -1126,6 +1126,21 @@ function wc_update_310_db_version() {
WC_Install::update_db_version( '3.1.0' );
}
+/**
+ * Update shop_manager capabilities.
+ */
+function wc_update_312_shop_manager_capabilities() {
+ $role = get_role( 'shop_manager' );
+ $role->remove_cap( 'unfiltered_html' );
+}
+
+/**
+ * Update DB Version.
+ */
+function wc_update_312_db_version() {
+ WC_Install::update_db_version( '3.1.2' );
+}
+
/**
* Update state codes for Mexico.
*/
diff --git a/readme.txt b/readme.txt
index b5078d8cabd..817cc0efce9 100644
--- a/readme.txt
+++ b/readme.txt
@@ -162,6 +162,43 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woocommerce/wo
== Changelog ==
= 3.2.0 - 2017-XX-XX =
+* Feature - Simplified the ability to resend order details to customers with a single "Resend Order Details" action.
+* Feature - Added store street address, city and postal code to settings for use by plugins.
+* Feature - wrapping values in quotes now let's you use commas in the product CSV importer.
+* Feature - If a fatal error occurs, WooCommerce will catch and log it to be viewed in WC > Status > Logs.
+* Feature - Drag and drop sorting on the grouped product field to control display order.
+* Feature - Integrated selectWoo; more accessible Select2 (enhanced select boxes) in admin and on the frontend.
+* Feature - Enhanced select boxes in the shipping calculator.
+* Feature - Enhanced select boxes in layered nav "or" widget.
+* Feature - Ajaxified the product category filter on the products screen.
+* Tweak - Made the buyer phone number clickable in the in the order backend.
+* Tweak - Clean up user is_paying_customer after deleting an order.
+* Tweak - If stock changes between page load and editing, reject stock changes to avoid incorrect stock changes.
+* Tweak - Disable search engines indexing core, dynamic, cart/checkout pages.
+* Tweak - Added shortcodes to description output in structured data, and improved variable product data.
+* Tweak - Use ajax when restoring an item in the cart, and removing an item from the mini-cart.
+* Tweak - Onboarding: added "next" button to pointers and allowed them to be dismissed.
+* Tweak - Display post states for WC pages e.g. shop, checkout etc.
+* Tweak - Improved order tracking page display and validation.
+* Dev - Product CRUD search helpers.
+* Dev - Refactor shipping rate to include instance IDs.
+* Dev - New attribute helper functions.
+* Dev - Order note helper functions.
+* Dev - Added the "Terms and conditions" page to the api system status report.
+* Dev - Made date inputs reusable.
+* Dev - Added option for merging when using 'Order Again' via filter woocommerce_empty_cart_when_order_again.
+* Dev - Added tool for repopulating order address search indexes.
+* Dev - Added woocommerce_get_asset_url filter.
+* Dev - Show notice when internal meta props are accessed directly.
+* Dev - Improve meta data updates so data is only updated when changed.
+* Dev - Improved get_filtered_term_product_counts performance.
+* Dev - Introduced wc_get_account_orders_actions function.
+* Theming - Display downloads in their own table, universally, using a new template file.
+* Theming - Checkout: Order pay template
+* Localization - Added cantons of Switzerland.
+* Localization - Add rtl support for activation.css.
+* Localization - Updated Mexican states to use correct 2 letter codes.
+* Many smaller fixes and improvements - see Github!
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/CHANGELOG.txt).
diff --git a/templates/checkout/order-receipt.php b/templates/checkout/order-receipt.php
new file mode 100644
index 00000000000..5a064c681b3
--- /dev/null
+++ b/templates/checkout/order-receipt.php
@@ -0,0 +1,47 @@
+
+
+
+
+
+ get_order_number() ); ?>
+
+
+
+ get_date_created() ) ); ?>
+
+
+
+ get_formatted_order_total() ); ?>
+
+ get_payment_method_title() ) : ?>
+
+
+ get_payment_method_title() ); ?>
+
+
+
+
+get_payment_method(), $order_id ); ?>
+
+
diff --git a/tests/framework/helpers/class-wc-helper-order.php b/tests/framework/helpers/class-wc-helper-order.php
index b0ab1d97b5b..ed6a053b3b4 100644
--- a/tests/framework/helpers/class-wc-helper-order.php
+++ b/tests/framework/helpers/class-wc-helper-order.php
@@ -62,7 +62,7 @@ class WC_Helper_Order {
'product' => $product,
'quantity' => 4,
'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => 4 ) ),
- 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => 4 ) ),
+ 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => 4 ) ),
) );
$item->save();
$order->add_item( $item );
@@ -105,7 +105,7 @@ class WC_Helper_Order {
$order->set_discount_tax( 0 );
$order->set_cart_tax( 0 );
$order->set_shipping_tax( 0 );
- $order->set_total( 40 ); // 4 x $10 simple helper product
+ $order->set_total( 50 ); // 4 x $10 simple helper product
$order->save();
return $order;
diff --git a/tests/unit-tests/api/system-status.php b/tests/unit-tests/api/system-status.php
index 3769a42c3de..92a8397429f 100644
--- a/tests/unit-tests/api/system-status.php
+++ b/tests/unit-tests/api/system-status.php
@@ -223,11 +223,10 @@ class WC_Tests_REST_System_Status extends WC_REST_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $raw_tools ), count( $data ) );
-
$this->assertContains( array(
'id' => 'reset_tracking',
- 'name' => 'Reset usage tracking settings',
- 'action' => 'Reset usage tracking settings',
+ 'name' => 'Reset usage tracking',
+ 'action' => 'Reset',
'description' => 'This will reset your usage tracking settings, causing it to show the opt-in banner again and not sending any data.',
'_links' => array(
'item' => array(
diff --git a/tests/unit-tests/cart/cart.php b/tests/unit-tests/cart/cart.php
index 5b82efb4c41..af9d5eff115 100644
--- a/tests/unit-tests/cart/cart.php
+++ b/tests/unit-tests/cart/cart.php
@@ -429,7 +429,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$cart_id = WC()->cart->generate_cart_id( $product->get_id() );
// Set quantity of product in cart to 2
- $this->assertTrue( WC()->cart->set_quantity( $cart_id, 2 ) );
+ $this->assertTrue( WC()->cart->set_quantity( $cart_id, 2 ), $cart_id );
// Check if there are 2 items in cart now
$this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
diff --git a/tests/unit-tests/customer/crud.php b/tests/unit-tests/customer/crud.php
index bd859a5de00..476edded680 100644
--- a/tests/unit-tests/customer/crud.php
+++ b/tests/unit-tests/customer/crud.php
@@ -267,7 +267,7 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case {
$this->assertEquals( 0, $customer->get_total_spent() );
$order->update_status( 'wc-completed' );
$customer = new WC_Customer( $customer_id );
- $this->assertEquals( 40, $customer->get_total_spent() );
+ $this->assertEquals( 50, $customer->get_total_spent() );
$order->delete();
}
diff --git a/tests/unit-tests/discounts/discount.php b/tests/unit-tests/discounts/discount.php
deleted file mode 100644
index b1a900cc7fa..00000000000
--- a/tests/unit-tests/discounts/discount.php
+++ /dev/null
@@ -1,36 +0,0 @@
-set_amount( '10' );
- $this->assertEquals( '10', $discount->get_amount() );
- }
-
- public function test_get_set_type() {
- $discount = new WC_Discount;
-
- $discount->set_discount_type( 'fixed' );
- $this->assertEquals( 'fixed', $discount->get_discount_type() );
-
- $discount->set_discount_type( 'percent' );
- $this->assertEquals( 'percent', $discount->get_discount_type() );
- }
-
- /**
- * Test get and set discount total.
- */
- public function test_get_set_discount_total() {
- $discount = new WC_Discount;
- $discount->set_discount_total( 1000 );
- $this->assertEquals( 1000, $discount->get_discount_total() );
- }
-}
diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php
index 01bb91e14e2..507b90365a5 100644
--- a/tests/unit-tests/discounts/discounts.php
+++ b/tests/unit-tests/discounts/discounts.php
@@ -66,19 +66,19 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
// Apply a percent discount.
$coupon->set_discount_type( 'percent' );
$discounts->set_items_from_cart( WC()->cart );
- $discounts->apply_discount( $coupon );
+ $discounts->apply_coupon( $coupon );
$this->assertEquals( 9, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
// Apply a fixed cart coupon.
$coupon->set_discount_type( 'fixed_cart' );
$discounts->set_items_from_cart( WC()->cart );
- $discounts->apply_discount( $coupon );
+ $discounts->apply_coupon( $coupon );
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
// Apply a fixed product coupon.
$coupon->set_discount_type( 'fixed_product' );
$discounts->set_items_from_cart( WC()->cart );
- $discounts->apply_discount( $coupon );
+ $discounts->apply_coupon( $coupon );
$this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ), print_r( $discounts->get_discounts(), true ) );
// Cleanup.
@@ -354,7 +354,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
foreach ( $test['coupons'] as $coupon_props ) {
$coupon->set_props( $coupon_props );
- $discounts->apply_discount( $coupon );
+ $discounts->apply_coupon( $coupon );
}
$all_discounts = $discounts->get_discounts();
@@ -372,88 +372,4 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
update_option( 'woocommerce_calc_taxes', 'no' );
$coupon->delete( true );
}
-
- /**
- * Test apply_discount method.
- */
- public function test_apply_discount() {
- $tax_rate = array(
- 'tax_rate_country' => '',
- 'tax_rate_state' => '',
- 'tax_rate' => '20.0000',
- 'tax_rate_name' => 'VAT',
- 'tax_rate_priority' => '1',
- 'tax_rate_compound' => '0',
- 'tax_rate_shipping' => '1',
- 'tax_rate_order' => '1',
- 'tax_rate_class' => '',
- );
- $tax_rate2 = array(
- 'tax_rate_country' => '',
- 'tax_rate_state' => '',
- 'tax_rate' => '20.0000',
- 'tax_rate_name' => 'VAT',
- 'tax_rate_priority' => '1',
- 'tax_rate_compound' => '0',
- 'tax_rate_shipping' => '1',
- 'tax_rate_order' => '1',
- 'tax_rate_class' => 'reduced-rate',
- );
- $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
- $tax_rate_id2 = WC_Tax::_insert_tax_rate( $tax_rate2 );
- update_option( 'woocommerce_calc_taxes', 'yes' );
-
- $product = WC_Helper_Product::create_simple_product();
- $product2 = WC_Helper_Product::create_simple_product();
-
- $product->set_tax_class( '' );
- $product2->set_tax_class( 'reduced-rate' );
-
- $product->save();
- $product2->save();
-
- // Add product to the cart.
- WC()->cart->empty_cart();
- WC()->cart->add_to_cart( $product->get_id(), 1 );
- WC()->cart->add_to_cart( $product2->get_id(), 1 );
-
- $discounts = new WC_Discounts();
- $discounts->set_items_from_cart( WC()->cart );
-
- $discounts->apply_discount( '50%' );
- $all_discounts = $discounts->get_discounts();
- $this->assertEquals( 10, $all_discounts['discount-50%'], print_r( $all_discounts, true ) );
-
- $discounts->apply_discount( '50%' );
- $all_discounts = $discounts->get_discounts();
- $this->assertEquals( 10, $all_discounts['discount-50%'], print_r( $all_discounts, true ) );
- $this->assertEquals( 5, $all_discounts['discount-50%-2'], print_r( $all_discounts, true ) );
-
- // Test fixed discounts.
- $discounts = new WC_Discounts();
- $discounts->set_items_from_cart( WC()->cart );
-
- $discounts->apply_discount( '5' );
- $all_discounts = $discounts->get_discounts();
- $this->assertEquals( 5, $all_discounts['discount-500'] );
-
- $discounts->apply_discount( '5' );
- $all_discounts = $discounts->get_discounts();
- $this->assertEquals( 5, $all_discounts['discount-500'], print_r( $all_discounts, true ) );
- $this->assertEquals( 5, $all_discounts['discount-500-2'], print_r( $all_discounts, true ) );
-
- $discounts->apply_discount( '15' );
- $all_discounts = $discounts->get_discounts();
- $this->assertEquals( 5, $all_discounts['discount-500'], print_r( $all_discounts, true ) );
- $this->assertEquals( 5, $all_discounts['discount-500-2'], print_r( $all_discounts, true ) );
- $this->assertEquals( 10, $all_discounts['discount-1500'], print_r( $all_discounts, true ) );
-
- // Cleanup.
- WC()->cart->empty_cart();
- $product->delete( true );
- $product2->delete( true );
- WC_Tax::_delete_tax_rate( $tax_rate_id );
- WC_Tax::_delete_tax_rate( $tax_rate_id2 );
- update_option( 'woocommerce_calc_taxes', 'no' );
- }
}
diff --git a/tests/unit-tests/importer/product.php b/tests/unit-tests/importer/product.php
index db2e4d7c9b3..45148650325 100644
--- a/tests/unit-tests/importer/product.php
+++ b/tests/unit-tests/importer/product.php
@@ -301,7 +301,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
- 'stock_quantity' => 0,
+ 'stock_quantity' => '',
'backorders' => '',
'sold_individually' => '',
'weight' => '',
@@ -352,7 +352,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
- 'stock_quantity' => 0,
+ 'stock_quantity' => '',
'backorders' => '',
'sold_individually' => '',
'weight' => '',
@@ -387,7 +387,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'tax_status' => '',
'tax_class' => '',
'stock_status' => 'outofstock',
- 'stock_quantity' => 0,
+ 'stock_quantity' => '',
'backorders' => '',
'sold_individually' => '',
'weight' => '',
@@ -526,7 +526,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'tax_status' => '',
'tax_class' => '',
'stock_status' => 'instock',
- 'stock_quantity' => 0,
+ 'stock_quantity' => '',
'backorders' => '',
'sold_individually' => '',
'weight' => '',
diff --git a/tests/unit-tests/order/crud.php b/tests/unit-tests/order/crud.php
index 204824240a2..822c57ba4f2 100644
--- a/tests/unit-tests/order/crud.php
+++ b/tests/unit-tests/order/crud.php
@@ -1564,4 +1564,50 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object = WC_Helper_Order::create_order();
$this->assertEquals( 4, $object->get_remaining_refund_items() );
}
+
+ /**
+ * Test apply_coupon and remove_coupon with a fixed discount coupon.
+ * @since 3.2.0
+ */
+ function test_add_remove_coupon_fixed() {
+ $order = WC_Helper_Order::create_order();
+
+ $coupon = new WC_Coupon;
+ $coupon->set_code( 'test' );
+ $coupon->set_discount_type( 'fixed_cart' );
+ $coupon->set_amount( 10 );
+ $coupon->save();
+
+ $order->apply_coupon( 'test' );
+ $this->assertEquals( 40, $order->get_total() );
+
+ $order->remove_coupon( 'test' );
+ $this->assertEquals( 50, $order->get_total() );
+
+ $coupon->delete( true );
+ $order->delete( true );
+ }
+
+ /**
+ * Test apply_coupon and remove_coupon with a percent discount coupon.
+ * @since 3.2.0
+ */
+ function test_add_remove_coupon_percent() {
+ $order = WC_Helper_Order::create_order();
+
+ $coupon = new WC_Coupon;
+ $coupon->set_code( 'test' );
+ $coupon->set_discount_type( 'percent' );
+ $coupon->set_amount( 50 );
+ $coupon->save();
+
+ $order->apply_coupon( 'test' );
+ $this->assertEquals( 30, $order->get_total() );
+
+ $order->remove_coupon( 'test' );
+ $this->assertEquals( 50, $order->get_total() );
+
+ $coupon->delete( true );
+ $order->delete( true );
+ }
}
diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php
index ab43f98d7f6..c38eef16d81 100644
--- a/tests/unit-tests/totals/totals.php
+++ b/tests/unit-tests/totals/totals.php
@@ -124,13 +124,6 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
'items_total' => 27.00,
'items_total_tax' => 5.40,
'total' => 90.40,
- 'taxes' => array(
- $this->ids['tax_rate_ids'][0] => array(
- 'tax_total' => 11.40,
- 'shipping_tax_total' => 2.00,
- ),
- ),
- 'tax_total' => 11.40,
'shipping_total' => 10,
'shipping_tax_total' => 2,
'discounts_total' => 3.00,
@@ -144,13 +137,26 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
public function test_cart_totals() {
$cart = WC()->cart;
- $this->assertEquals( 40.00, $cart->fee_total );
+ $this->assertEquals( 40.00, $cart->get_fee_total() );
+ $this->assertEquals( 27.00, $cart->get_cart_contents_total() );
+ $this->assertEquals( 90.40, $cart->get_total( 'edit' ) );
+ $this->assertEquals( 30.00, $cart->get_subtotal() );
+ $this->assertEquals( 6.00, $cart->get_subtotal_tax() );
+ $this->assertEquals( 5.40, $cart->get_cart_contents_tax() );
+ $this->assertEquals( 5.40, array_sum( $cart->get_cart_contents_taxes() ) );
+ $this->assertEquals( 6.00, $cart->get_fee_tax() );
+ $this->assertEquals( 6.00, array_sum( $cart->get_fee_taxes() ) );
+ $this->assertEquals( 3, $cart->get_discount_total() );
+ $this->assertEquals( 0.60, $cart->get_discount_tax() );
+ $this->assertEquals( 10, $cart->get_shipping_total() );
+ $this->assertEquals( 2, $cart->get_shipping_tax() );
+
$this->assertEquals( 27.00, $cart->cart_contents_total );
$this->assertEquals( 90.40, $cart->total );
$this->assertEquals( 36.00, $cart->subtotal );
$this->assertEquals( 30.00, $cart->subtotal_ex_tax );
$this->assertEquals( 11.40, $cart->tax_total );
- $this->assertEquals( 2.40, $cart->discount_cart );
+ $this->assertEquals( 3, $cart->discount_cart );
$this->assertEquals( 0.60, $cart->discount_cart_tax );
$this->assertEquals( 40.00, $cart->fee_total );
$this->assertEquals( 10, $cart->shipping_total );