+
+
+
+
+
+
+
+
+ Check again.', 'woocommerce' ),
+ esc_html( implode( ', ', $missing_tables ) ),
+ wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' )
+ )
+ );
+ } else {
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %1%s: Missing tables (seperated by ",") */
+ __( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s.', 'woocommerce' ),
+ esc_html( implode( ', ', $missing_tables ) )
+ )
+ );
+ }
+ ?>
+
+
diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php
index 17378bfbf1b33..4ca59ad08858d 100644
--- a/includes/class-wc-cart.php
+++ b/includes/class-wc-cart.php
@@ -765,7 +765,6 @@ public function check_cart_item_validity() {
public function check_cart_item_stock() {
$error = new WP_Error();
$product_qty_in_cart = $this->get_cart_item_quantities();
- $hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
$current_session_order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
foreach ( $this->get_cart() as $cart_item_key => $values ) {
@@ -784,7 +783,7 @@ public function check_cart_item_stock() {
}
// Check stock based on all items in the cart and consider any held stock within pending orders.
- $held_stock = ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product, $current_session_order_id ) : 0;
+ $held_stock = wc_get_held_stock_quantity( $product, $current_session_order_id );
$required_stock = $product_qty_in_cart[ $product->get_stock_managed_by_id() ];
if ( $product->get_stock_quantity() < ( $held_stock + $required_stock ) ) {
diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php
index 5477531273d7f..6645d1f06cf21 100644
--- a/includes/class-wc-checkout.php
+++ b/includes/class-wc-checkout.php
@@ -388,12 +388,30 @@ public function create_order( $data ) {
// Save the order.
$order_id = $order->save();
+ /**
+ * Action hook fired after an order is created used to add custom meta to the order.
+ *
+ * @since 3.0.0
+ */
do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data );
+ /**
+ * Action hook fired after an order is created.
+ *
+ * @since 4.3.0
+ */
+ do_action( 'woocommerce_checkout_order_created', $order );
+
return $order_id;
} catch ( Exception $e ) {
if ( $order && $order instanceof WC_Order ) {
$order->get_data_store()->release_held_coupons( $order );
+ /**
+ * Action hook fired when an order is discarded due to Exception.
+ *
+ * @since 4.3.0
+ */
+ do_action( 'woocommerce_checkout_order_exception', $order );
}
return new WP_Error( 'checkout-error', $e->getMessage() );
}
diff --git a/includes/class-wc-comments.php b/includes/class-wc-comments.php
index d39bb7b0a8525..ab4e6945f01eb 100644
--- a/includes/class-wc-comments.php
+++ b/includes/class-wc-comments.php
@@ -146,7 +146,7 @@ public static function exclude_webhook_comments_from_feed_where( $where ) {
*/
public static function check_comment_rating( $comment_data ) {
// If posting a comment (not trackback etc) and not logged in.
- if ( ! is_admin() && isset( $_POST['comment_post_ID'], $_POST['rating'], $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) && empty( $_POST['rating'] ) && '' === $comment_data['comment_type'] && wc_review_ratings_enabled() && wc_review_ratings_required() ) { // WPCS: input var ok, CSRF ok.
+ if ( ! is_admin() && isset( $_POST['comment_post_ID'], $_POST['rating'], $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) && empty( $_POST['rating'] ) && self::is_default_comment_type( $comment_data['comment_type'] ) && wc_review_ratings_enabled() && wc_review_ratings_required() ) { // WPCS: input var ok, CSRF ok.
wp_die( esc_html__( 'Please rate the product.', 'woocommerce' ) );
exit;
}
@@ -406,12 +406,26 @@ public static function get_rating_counts_for_product( &$product ) {
* @return array
*/
public static function update_comment_type( $comment_data ) {
- if ( ! is_admin() && isset( $_POST['comment_post_ID'], $comment_data['comment_type'] ) && '' === $comment_data['comment_type'] && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) ) { // WPCS: input var ok, CSRF ok.
+ if ( ! is_admin() && isset( $_POST['comment_post_ID'], $comment_data['comment_type'] ) && self::is_default_comment_type( $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) ) { // WPCS: input var ok, CSRF ok.
$comment_data['comment_type'] = 'review';
}
return $comment_data;
}
+
+ /**
+ * Determines if a comment is of the default type.
+ *
+ * Prior to WordPress 5.5, '' was the default comment type.
+ * As of 5.5, the default type is 'comment'.
+ *
+ * @since 4.3.0
+ * @param string $comment_type Comment type.
+ * @return bool
+ */
+ private static function is_default_comment_type( $comment_type ) {
+ return ( '' === $comment_type || 'comment' === $comment_type );
+ }
}
WC_Comments::init();
diff --git a/includes/class-wc-countries.php b/includes/class-wc-countries.php
index 405a0d26cc746..7c0bb934c7cb6 100644
--- a/includes/class-wc-countries.php
+++ b/includes/class-wc-countries.php
@@ -474,7 +474,7 @@ public function country_dropdown_options( $selected_country = '', $selected_stat
echo ' selected="selected"';
}
- echo '>' . esc_html( $value ) . ' — ' . ( $escape ? esc_js( $state_value ) : $state_value ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo '>' . esc_html( $value ) . ' — ' . ( $escape ? esc_html( $state_value ) : $state_value ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '';
@@ -483,7 +483,7 @@ public function country_dropdown_options( $selected_country = '', $selected_stat
if ( $selected_country === $key && '*' === $selected_state ) {
echo ' selected="selected"';
}
- echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_js( $value ) : $value ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_html( $value ) : $value ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
}
diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php
index ea62645c57d77..80a4336aba501 100644
--- a/includes/class-wc-form-handler.php
+++ b/includes/class-wc-form-handler.php
@@ -869,11 +869,12 @@ private static function add_to_cart_handler_grouped( $product_id ) {
*/
private static function add_to_cart_handler_variable( $product_id ) {
try {
- $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $missing_attributes = array();
- $variations = array();
- $adding_to_cart = wc_get_product( $product_id );
+ $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $missing_attributes = array();
+ $variations = array();
+ $variation_attributes = array();
+ $adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
@@ -881,6 +882,9 @@ private static function add_to_cart_handler_variable( $product_id ) {
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
+ $variation_attributes = $adding_to_cart->get_variation_attributes();
+ // Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
+ $variation_attributes = array_filter( $variation_attributes );
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
@@ -911,6 +915,9 @@ private static function add_to_cart_handler_variable( $product_id ) {
}
}
+ // Merge variation attributes and posted attributes.
+ $posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
+
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
@@ -939,8 +946,8 @@ private static function add_to_cart_handler_variable( $product_id ) {
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
- if ( isset( $posted_attributes[ $attribute_key ] ) ) {
- $value = $posted_attributes[ $attribute_key ];
+ if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
+ $value = $posted_and_variation_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index a219d7d8323a8..409a5567de417 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -287,6 +287,7 @@ public static function install() {
WC()->wpdb_table_fix();
self::remove_admin_notices();
self::create_tables();
+ self::verify_base_tables();
self::create_options();
self::create_roles();
self::setup_environment();
@@ -296,6 +297,7 @@ public static function install() {
self::maybe_enable_setup_wizard();
self::update_wc_version();
self::maybe_update_db_version();
+ self::maybe_enable_homescreen();
delete_transient( 'wc_installing' );
@@ -303,6 +305,54 @@ public static function install() {
do_action( 'woocommerce_installed' );
}
+ /**
+ * Check if all the base tables are present.
+ *
+ * @param bool $modify_notice Whether to modify notice based on if all tables are present.
+ * @param bool $execute Whether to execute get_schema queries as well.
+ *
+ * @return array List of querues.
+ */
+ public static function verify_base_tables( $modify_notice = true, $execute = false ) {
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+
+ if ( $execute ) {
+ self::create_tables();
+ }
+ $queries = dbDelta( self::get_schema(), false );
+ $missing_tables = array();
+ foreach ( $queries as $table_name => $result ) {
+ if ( "Created table $table_name" === $result ) {
+ $missing_tables[] = $table_name;
+ }
+ }
+
+ if ( 0 < count( $missing_tables ) ) {
+ if ( $modify_notice ) {
+ WC_Admin_Notices::add_notice( 'base_tables_missing' );
+ }
+ update_option( 'woocommerce_schema_missing_tables', $missing_tables );
+ } else {
+ if ( $modify_notice ) {
+ WC_Admin_Notices::remove_notice( 'base_tables_missing' );
+ }
+ update_option( 'woocommerce_schema_version', WC()->db_version );
+ delete_option( 'woocommerce_schema_missing_tables' );
+ }
+ return $missing_tables;
+ }
+
+ /**
+ * Check if the homepage should be enabled and set the appropriate option if thats the case.
+ *
+ * @since 4.3.0
+ */
+ private static function maybe_enable_homescreen() {
+ if ( self::is_new_install() && ! get_option( 'woocommerce_homescreen_enabled' ) ) {
+ add_option( 'woocommerce_homescreen_enabled', 'yes' );
+ }
+ }
+
/**
* Reset any notices added to admin.
*
@@ -621,6 +671,11 @@ public static function create_terms() {
/**
* Set up the database tables which the plugin needs to function.
+ * WARNING: If you are modifying this method, make sure that its safe to call regardless of the state of database.
+ *
+ * This is called from `install` method and is executed in-sync when WC is installed or updated. This can also be called optionally from `verify_base_tables`.
+ *
+ * TODO: Add all crucial tables that we have created from workers in the past.
*
* Tables:
* woocommerce_attribute_taxonomies - Table for storing attribute taxonomies - these are user defined
@@ -946,6 +1001,14 @@ private static function get_schema() {
slug varchar(200) NOT NULL DEFAULT '',
PRIMARY KEY (tax_rate_class_id),
UNIQUE KEY slug (slug($max_index_length))
+) $collate;
+CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
+ `order_id` bigint(20) NOT NULL,
+ `product_id` bigint(20) NOT NULL,
+ `stock_quantity` double NOT NULL DEFAULT 0,
+ `timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`order_id`, `product_id`)
) $collate;
";
@@ -980,6 +1043,7 @@ public static function get_tables() {
"{$wpdb->prefix}woocommerce_shipping_zones",
"{$wpdb->prefix}woocommerce_tax_rate_locations",
"{$wpdb->prefix}woocommerce_tax_rates",
+ "{$wpdb->prefix}wc_reserved_stock",
);
/**
@@ -1187,7 +1251,7 @@ private static function create_files() {
}
// Install files and folders for uploading files and prevent hotlinking.
- $upload_dir = wp_upload_dir();
+ $upload_dir = wp_get_upload_dir();
$download_method = get_option( 'woocommerce_file_download_method', 'force' );
$files = array(
@@ -1206,19 +1270,16 @@ private static function create_files() {
'file' => 'index.html',
'content' => '',
),
- );
-
- if ( 'redirect' !== $download_method ) {
- $files[] = array(
+ array(
'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
'file' => '.htaccess',
- 'content' => 'deny from all',
- );
- }
+ 'content' => 'redirect' === $download_method ? 'Options -Indexes' : 'deny from all',
+ ),
+ );
foreach ( $files as $file ) {
if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) {
- $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'w' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
+ $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
if ( $file_handle ) {
fwrite( $file_handle, $file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
diff --git a/includes/class-wc-post-types.php b/includes/class-wc-post-types.php
index 7de16656db5de..ab9dcbc07d2f1 100644
--- a/includes/class-wc-post-types.php
+++ b/includes/class-wc-post-types.php
@@ -59,6 +59,7 @@ public static function register_taxonomies() {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
+ 'label' => _x( 'Product type', 'Taxonomy name', 'woocommerce' ),
)
)
);
@@ -75,6 +76,7 @@ public static function register_taxonomies() {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
+ 'label' => _x( 'Product visibility', 'Taxonomy name', 'woocommerce' ),
)
)
);
diff --git a/includes/class-wc-shipping.php b/includes/class-wc-shipping.php
index 8027b84a6939e..a803c400f2f69 100644
--- a/includes/class-wc-shipping.php
+++ b/includes/class-wc-shipping.php
@@ -332,7 +332,26 @@ public function calculate_shipping_for_package( $package = array(), $package_key
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
- $package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package ); // + instead of array_merge maintains numeric keys
+ /**
+ * Fires before getting shipping rates for a package.
+ *
+ * @since 4.3.0
+ * @param array $package Package of cart items.
+ * @param WC_Shipping_Method $shipping_method Shipping method instance.
+ */
+ do_action( 'woocommerce_before_get_rates_for_package', $package, $shipping_method );
+
+ // Use + instead of array_merge to maintain numeric keys.
+ $package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package );
+
+ /**
+ * Fires after getting shipping rates for a package.
+ *
+ * @since 4.3.0
+ * @param array $package Package of cart items.
+ * @param WC_Shipping_Method $shipping_method Shipping method instance.
+ */
+ do_action( 'woocommerce_after_get_rates_for_package', $package, $shipping_method );
}
}
diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php
index 6b1b3334185a0..42c78cd5c9eab 100644
--- a/includes/class-woocommerce.php
+++ b/includes/class-woocommerce.php
@@ -22,6 +22,15 @@ final class WooCommerce {
*/
public $version = '4.3.0';
+ /**
+ * WooCommerce Schema version.
+ *
+ * @since 4.3 started with version string 430.
+ *
+ * @var string
+ */
+ public $db_version = '430';
+
/**
* The single instance of the class.
*
@@ -229,7 +238,7 @@ private function define_constants() {
$this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' );
$this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' );
$this->define( 'WC_TEMPLATE_DEBUG_MODE', false );
- $this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.0' );
+ $this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.2' );
$this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' );
$this->define( 'WC_PHP_MIN_REQUIREMENTS_NOTICE', 'wp_php_min_requirements_' . WC_NOTICE_MIN_PHP_VERSION . '_' . WC_NOTICE_MIN_WP_VERSION );
}
@@ -246,6 +255,7 @@ private function define_tables() {
'order_itemmeta' => 'woocommerce_order_itemmeta',
'wc_product_meta_lookup' => 'wc_product_meta_lookup',
'wc_tax_rate_classes' => 'wc_tax_rate_classes',
+ 'wc_reserved_stock' => 'wc_reserved_stock',
);
foreach ( $tables as $name => $table ) {
diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php
index 6c2baf5afff56..e417c1d9c6a36 100644
--- a/includes/data-stores/class-wc-product-data-store-cpt.php
+++ b/includes/data-stores/class-wc-product-data-store-cpt.php
@@ -881,8 +881,8 @@ public function get_on_sale_products() {
$outofstock_where = ' AND exclude_join.object_id IS NULL';
}
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_results(
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"
SELECT posts.ID as id, posts.post_parent as parent_id
FROM {$wpdb->posts} AS posts
@@ -900,8 +900,8 @@ public function get_on_sale_products() {
)
GROUP BY posts.ID
"
- // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
);
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
@@ -1603,7 +1603,7 @@ public function search_products( $term, $type = '', $include_variations = false,
foreach ( $search_terms as $search_term ) {
$like = '%' . $wpdb->esc_like( $search_term ) . '%';
- $term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // @codingStandardsIgnoreLine.
+ $term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$searchand = ' AND ';
}
@@ -2062,4 +2062,23 @@ protected function get_primary_key_for_lookup_table( $table ) {
}
return '';
}
+
+ /**
+ * Returns query statement for getting current `_stock` of a product.
+ *
+ * @internal MAX function below is used to make sure result is a scalar.
+ * @param int $product_id Product ID.
+ * @return string|void Query statement.
+ */
+ public function get_query_for_stock( $product_id ) {
+ global $wpdb;
+ return $wpdb->prepare(
+ "
+ SELECT COALESCE ( MAX( meta_value ), 0 ) FROM $wpdb->postmeta as meta_table
+ WHERE meta_table.meta_key = '_stock'
+ AND meta_table.post_id = %d
+ ",
+ $product_id
+ );
+ }
}
diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php
index 78c92c9f8573b..53b7242251fff 100644
--- a/includes/export/class-wc-product-csv-exporter.php
+++ b/includes/export/class-wc-product-csv-exporter.php
@@ -656,10 +656,10 @@ protected function prepare_attributes_for_export( $product, &$row ) {
if ( 0 === strpos( $attribute_name, 'pa_' ) ) {
$option_term = get_term_by( 'slug', $attribute, $attribute_name ); // @codingStandardsIgnoreLine.
- $row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? str_replace( ',', '\\,', $option_term->name ) : $attribute;
+ $row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? str_replace( ',', '\\,', $option_term->name ) : str_replace( ',', '\\,', $attribute );
$row[ 'attributes:taxonomy' . $i ] = 1;
} else {
- $row[ 'attributes:value' . $i ] = $attribute;
+ $row[ 'attributes:value' . $i ] = str_replace( ',', '\\,', $attribute );
$row[ 'attributes:taxonomy' . $i ] = 0;
}
diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php
index 14eddea04a17e..06a8bb3e8bcf0 100644
--- a/includes/import/class-wc-product-csv-importer.php
+++ b/includes/import/class-wc-product-csv-importer.php
@@ -400,21 +400,22 @@ public function parse_categories_field( $value ) {
$total = count( $_terms );
foreach ( $_terms as $index => $_term ) {
- // Check if category exists. Parent must be empty string or null if doesn't exists.
- $term = term_exists( $_term, 'product_cat', $parent );
-
- if ( is_array( $term ) ) {
- $term_id = $term['term_id'];
- // Don't allow users without capabilities to create new categories.
- } elseif ( ! current_user_can( 'manage_product_terms' ) ) {
+ // Don't allow users without capabilities to create new categories.
+ if ( ! current_user_can( 'manage_product_terms' ) ) {
break;
- } 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 = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
+ if ( is_wp_error( $term ) ) {
+ if ( $term->get_error_code() === 'term_exists' ) {
+ // When term exists, error data should contain existing term id.
+ $term_id = $term->get_error_data();
+ } else {
+ break; // We cannot continue on any other error.
+ }
+ } else {
+ // New term.
$term_id = $term['term_id'];
}
diff --git a/includes/shortcodes/class-wc-shortcode-checkout.php b/includes/shortcodes/class-wc-shortcode-checkout.php
index 6316d07e2f895..31413213a72c5 100644
--- a/includes/shortcodes/class-wc-shortcode-checkout.php
+++ b/includes/shortcodes/class-wc-shortcode-checkout.php
@@ -84,9 +84,8 @@ private static function order_pay( $order_id ) {
// Pay for existing order.
if ( isset( $_GET['pay_for_order'], $_GET['key'] ) && $order_id ) { // WPCS: input var ok, CSRF ok.
try {
- $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // WPCS: input var ok, CSRF ok.
- $order = wc_get_order( $order_id );
- $hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
+ $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // WPCS: input var ok, CSRF ok.
+ $order = wc_get_order( $order_id );
// Order or payment link is invalid.
if ( ! $order || $order->get_id() !== $order_id || ! hash_equals( $order->get_order_key(), $order_key ) ) {
@@ -158,7 +157,7 @@ private static function order_pay( $order_id ) {
}
// Check stock based on all items in the cart and consider any held stock within pending orders.
- $held_stock = ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product, $order->get_id() ) : 0;
+ $held_stock = wc_get_held_stock_quantity( $product, $order->get_id() );
$required_stock = $quantities[ $product->get_stock_managed_by_id() ];
if ( ! apply_filters( 'woocommerce_pay_order_product_has_enough_stock', ( $product->get_stock_quantity() >= ( $held_stock + $required_stock ) ), $product, $order ) ) {
diff --git a/includes/shortcodes/class-wc-shortcode-products.php b/includes/shortcodes/class-wc-shortcode-products.php
index 4e31f8418efd8..b195676a60741 100644
--- a/includes/shortcodes/class-wc-shortcode-products.php
+++ b/includes/shortcodes/class-wc-shortcode-products.php
@@ -507,7 +507,7 @@ protected function set_visibility_query_args( &$query_args ) {
}
/**
- * Set product as visible when quering for hidden products.
+ * Set product as visible when querying for hidden products.
*
* @since 3.2.0
* @param bool $visibility Product visibility.
diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php
index 82697a05791b8..1890045fd9c72 100644
--- a/includes/tracks/class-wc-site-tracking.php
+++ b/includes/tracks/class-wc-site-tracking.php
@@ -44,17 +44,18 @@ public static function is_tracking_enabled() {
return true;
}
+ /**
+ * Register scripts required to record events from javascript.
+ */
+ public static function register_scripts() {
+ wp_register_script( 'woo-tracks', 'https://stats.wp.com/w.js', array( 'wp-hooks' ), gmdate( 'YW' ), false );
+ }
+
/**
* Add scripts required to record events from javascript.
*/
public static function enqueue_scripts() {
-
- // Add w.js to the page.
- wp_enqueue_script( 'woo-tracks', 'https://stats.wp.com/w.js', array( 'wp-hooks' ), gmdate( 'YW' ), false );
-
- // Expose tracking via a function in the wcTracks global namespace directly before wc_print_js.
- add_filter( 'admin_footer', array( __CLASS__, 'add_tracking_function' ), 24 );
-
+ wp_enqueue_script( 'woo-tracks' );
}
/**
@@ -65,7 +66,12 @@ public static function add_tracking_function() {
customer->get_taxable_address();
/* translators: %s: country name */
$estimated_text = WC()->customer->is_customer_outside_base() && ! WC()->customer->has_calculated_shipping() ? sprintf( ' ' . __( 'estimated for %s', 'woocommerce' ), WC()->countries->estimated_for_prefix( $taxable_address[0] ) . WC()->countries->countries[ $taxable_address[0] ] ) : '';
- /* translators: %s: tax information */
- $value .= '