Merge pull request #26260 from woocommerce/fix/25524
Fix the visibility of partially out of stock variable products when using the layered nav widget
This commit is contained in:
commit
df94b6570d
|
@ -1501,6 +1501,16 @@ class WC_Product extends WC_Abstract_Legacy_Product {
|
|||
* @return bool
|
||||
*/
|
||||
public function is_visible() {
|
||||
$visible = $this->is_visible_core();
|
||||
return apply_filters( 'woocommerce_product_is_visible', $visible, $this->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the product is visible in the catalog (doesn't trigger filters).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_visible_core() {
|
||||
$visible = 'visible' === $this->get_catalog_visibility() || ( is_search() && 'search' === $this->get_catalog_visibility() ) || ( ! is_search() && 'catalog' === $this->get_catalog_visibility() );
|
||||
|
||||
if ( 'trash' === $this->get_status() ) {
|
||||
|
@ -1521,7 +1531,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
|
|||
$visible = false;
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_product_is_visible', $visible, $this->get_id() );
|
||||
return $visible;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -149,6 +149,10 @@ class WC_Install {
|
|||
'wc_update_400_reset_action_scheduler_migration_status',
|
||||
'wc_update_400_db_version',
|
||||
),
|
||||
'4.4.0' => array(
|
||||
'wc_update_440_insert_attribute_terms_for_variable_products',
|
||||
'wc_update_440_db_version',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -752,14 +756,14 @@ class WC_Install {
|
|||
AND CONSTRAINT_NAME = 'fk_{$wpdb->prefix}wc_download_log_permission_id'
|
||||
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||
AND TABLE_NAME = '{$wpdb->prefix}wc_download_log'"
|
||||
); // WPCS: unprepared SQL ok.
|
||||
);
|
||||
if ( 0 === (int) $fk_result->fk_count ) {
|
||||
$wpdb->query(
|
||||
"ALTER TABLE `{$wpdb->prefix}wc_download_log`
|
||||
ADD CONSTRAINT `fk_{$wpdb->prefix}wc_download_log_permission_id`
|
||||
FOREIGN KEY (`permission_id`)
|
||||
REFERENCES `{$wpdb->prefix}woocommerce_downloadable_product_permissions` (`permission_id`) ON DELETE CASCADE;"
|
||||
); // WPCS: unprepared SQL ok.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -283,10 +283,11 @@ class WC_Product_Variable extends WC_Product {
|
|||
* Get an array of available variations for the current product.
|
||||
*
|
||||
* @param bool $render_variations Prepares a data array for each variant for output in the add to cart form. Pass `false` to only return the available variations as objects.
|
||||
* @return array
|
||||
* @param bool $return_array_of_data If true, return an array of data for the variation; if false, return a WC_Product_Variation object.
|
||||
*
|
||||
* @return array|WC_Product_Variation
|
||||
*/
|
||||
public function get_available_variations( $render_variations = true ) {
|
||||
|
||||
public function get_available_variations( $render_variations = true, $return_array_of_data = true ) {
|
||||
$variation_ids = $this->get_children();
|
||||
$available_variations = array();
|
||||
|
||||
|
@ -309,7 +310,7 @@ class WC_Product_Variable extends WC_Product {
|
|||
}
|
||||
|
||||
if ( $render_variations ) {
|
||||
$available_variations[] = $this->get_available_variation( $variation );
|
||||
$available_variations[] = $return_array_of_data ? $this->get_available_variation( $variation ) : $variation;
|
||||
} else {
|
||||
$available_variations[] = $variation;
|
||||
}
|
||||
|
@ -322,6 +323,27 @@ class WC_Product_Variable extends WC_Product {
|
|||
return $available_variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given variation is currently available.
|
||||
*
|
||||
* @param WC_Product_Variation $variation Variation to check.
|
||||
*
|
||||
* @return bool True if the variation is available, false otherwise.
|
||||
*/
|
||||
private function variation_is_available( WC_Product_Variation $variation ) {
|
||||
// Hide out of stock variations if 'Hide out of stock items from the catalog' is checked.
|
||||
if ( ! $variation || ! $variation->exists() || ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $variation->is_in_stock() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter 'woocommerce_hide_invisible_variations' to optionally hide invisible variations (disabled variations and variations with empty price).
|
||||
if ( apply_filters( 'woocommerce_hide_invisible_variations', true, $this->get_id(), $variation ) && ! $variation->variation_is_visible() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of data for a variation. Used in the add to cart form.
|
||||
*
|
||||
|
@ -572,6 +594,95 @@ class WC_Product_Variable extends WC_Product {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the product is visible in the catalog (doesn't trigger filters).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_visible_core() {
|
||||
if ( ! $this->parent_is_visible_core() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query_filters = $this->get_layered_nav_chosen_attributes();
|
||||
if ( empty( $query_filters ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are attribute filters in the request, a variable product will be visible
|
||||
* if at least one of the available variations matches the filters.
|
||||
*/
|
||||
|
||||
$attributes_with_terms = array();
|
||||
array_walk(
|
||||
$query_filters,
|
||||
function( $value, $key ) use ( &$attributes_with_terms ) {
|
||||
$attributes_with_terms[ $key ] = $value['terms'];
|
||||
}
|
||||
);
|
||||
|
||||
$variations = $this->get_available_variations( true, false );
|
||||
foreach ( $variations as $variation ) {
|
||||
if ( $this->variation_matches_filters( $variation, $attributes_with_terms ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given variation matches the active attribute filters.
|
||||
*
|
||||
* @param WC_Product_Variation $variation The variation to check.
|
||||
* @param array $query_filters The active filters as an array of attribute_name => [term1, term2...].
|
||||
*
|
||||
* @return bool True if the variation matches the active attribute filters.
|
||||
*/
|
||||
private function variation_matches_filters( WC_Product_Variation $variation, array $query_filters ) {
|
||||
// Get the variation attributes as an array of attribute_name => attribute_value.
|
||||
// The array_filter will filter out attributes having a value of '', these correspond
|
||||
// to "Any..." variations that don't participate in filtering.
|
||||
$variation_attributes = array_filter( $variation->get_variation_attributes( false ) );
|
||||
|
||||
$variation_attribute_names_in_filters = array_intersect( array_keys( $query_filters ), array_keys( $variation_attributes ) );
|
||||
if ( empty( $variation_attribute_names_in_filters ) ) {
|
||||
// The variation doesn't have any attribute that participates in filtering so we consider it a match.
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( $variation_attribute_names_in_filters as $attribute_name ) {
|
||||
if ( ! in_array( $variation_attributes[ $attribute_name ], $query_filters[ $attribute_name ], true ) ) {
|
||||
// Multiple filters interact with AND logic, so as soon as one of them
|
||||
// doesn't match then the variation doesn't match.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* What does is_visible_core in the parent class say?
|
||||
* This method exists to ease unit testing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function parent_is_visible_core() {
|
||||
return parent::is_visible_core();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of attributes and terms selected with the layered nav widget.
|
||||
* This method exists to ease unit testing.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_layered_nav_chosen_attributes() {
|
||||
return WC()->query::get_layered_nav_chosen_attributes();
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sync with child variations.
|
||||
|
|
|
@ -110,15 +110,18 @@ class WC_Product_Variation extends WC_Product_Simple {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get variation attribute values. Keys are prefixed with attribute_, as stored.
|
||||
* Get variation attribute values. Keys are prefixed with attribute_, as stored, unless $with_prefix is false.
|
||||
*
|
||||
* @return array of attributes and their values for this variation
|
||||
* @param bool $with_prefix Whether keys should be prepended with attribute_ or not, default is true.
|
||||
* @return array of attributes and their values for this variation.
|
||||
*/
|
||||
public function get_variation_attributes() {
|
||||
public function get_variation_attributes( $with_prefix = true ) {
|
||||
$attributes = $this->get_attributes();
|
||||
$variation_attributes = array();
|
||||
$prefix = $with_prefix ? 'attribute_' : '';
|
||||
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
$variation_attributes[ 'attribute_' . $key ] = $value;
|
||||
$variation_attributes[ $prefix . $key ] = $value;
|
||||
}
|
||||
return $variation_attributes;
|
||||
}
|
||||
|
@ -580,4 +583,22 @@ class WC_Product_Variation extends WC_Product_Simple {
|
|||
|
||||
return $valid_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete variation, set the ID to 0, and return result.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param bool $force_delete Should the variation be deleted permanently.
|
||||
* @return bool result
|
||||
*/
|
||||
public function delete( $force_delete = false ) {
|
||||
$variation_id = $this->get_id();
|
||||
|
||||
if ( ! parent::delete( $force_delete ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wp_delete_object_term_relationships( $variation_id, wc_get_attribute_taxonomy_names() );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class WC_Query {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $_chosen_attributes;
|
||||
private static $chosen_attributes;
|
||||
|
||||
/**
|
||||
* Constructor for the query class. Hooks in methods.
|
||||
|
@ -45,6 +45,7 @@ class WC_Query {
|
|||
add_action( 'parse_request', array( $this, 'parse_request' ), 0 );
|
||||
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
|
||||
add_filter( 'the_posts', array( $this, 'remove_product_query_filters' ) );
|
||||
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ) );
|
||||
add_filter( 'get_pagenum_link', array( $this, 'remove_add_to_cart_pagination' ), 10, 1 );
|
||||
}
|
||||
$this->init_query_vars();
|
||||
|
@ -54,7 +55,8 @@ class WC_Query {
|
|||
* Get any errors from querystring.
|
||||
*/
|
||||
public function get_errors() {
|
||||
$error = ! empty( $_GET['wc_error'] ) ? sanitize_text_field( wp_unslash( $_GET['wc_error'] ) ) : ''; // WPCS: input var ok, CSRF ok.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$error = ! empty( $_GET['wc_error'] ) ? sanitize_text_field( wp_unslash( $_GET['wc_error'] ) ) : '';
|
||||
|
||||
if ( $error && ! wc_has_notice( $error, 'error' ) ) {
|
||||
wc_add_notice( $error, 'error' );
|
||||
|
@ -217,14 +219,16 @@ class WC_Query {
|
|||
public function parse_request() {
|
||||
global $wp;
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
// Map query vars to their keys, or get them if endpoints are not supported.
|
||||
foreach ( $this->get_query_vars() as $key => $var ) {
|
||||
if ( isset( $_GET[ $var ] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
$wp->query_vars[ $key ] = sanitize_text_field( wp_unslash( $_GET[ $var ] ) ); // WPCS: input var ok, CSRF ok.
|
||||
if ( isset( $_GET[ $var ] ) ) {
|
||||
$wp->query_vars[ $key ] = sanitize_text_field( wp_unslash( $_GET[ $var ] ) );
|
||||
} elseif ( isset( $wp->query_vars[ $var ] ) ) {
|
||||
$wp->query_vars[ $key ] = $wp->query_vars[ $var ];
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -363,6 +367,61 @@ class WC_Query {
|
|||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the request is filtering by attributes via layered nav plugin we need to adjust the total posts count
|
||||
* to account for variable products having stock in some variations but not in others.
|
||||
* We do that by just checking if each product is visible.
|
||||
*
|
||||
* We also cache the post visibility so that it isn't checked again when displaying the posts list.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param int $count Original posts count, as supplied by the found_posts filter.
|
||||
*
|
||||
* @return int Adjusted posts count.
|
||||
*/
|
||||
public function adjust_posts_count( $count ) {
|
||||
$posts = $this->get_current_posts();
|
||||
if ( is_null( $posts ) ) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ( $posts as $post ) {
|
||||
$id = is_object( $post ) ? $post->ID : $post;
|
||||
$product = wc_get_product( $id );
|
||||
if ( ! is_object( $product ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( $product->is_visible() ) {
|
||||
wc_set_loop_product_visibility( $id, true );
|
||||
$count++;
|
||||
} else {
|
||||
wc_set_loop_product_visibility( $id, false );
|
||||
}
|
||||
}
|
||||
|
||||
wc_set_loop_prop( 'total', $count );
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance version of get_layered_nav_chosen_attributes, needed for unit tests.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_layered_nav_chosen_attributes_inst() {
|
||||
return self::get_layered_nav_chosen_attributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the posts (or the ids of the posts) found in the current WP loop.
|
||||
*
|
||||
* @return array Array of posts or post ids.
|
||||
*/
|
||||
protected function get_current_posts() {
|
||||
return $GLOBALS['wp_query']->posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* WP SEO meta description.
|
||||
*
|
||||
|
@ -447,7 +506,8 @@ class WC_Query {
|
|||
public function get_catalog_ordering_args( $orderby = '', $order = '' ) {
|
||||
// Get ordering from query string unless defined.
|
||||
if ( ! $orderby ) {
|
||||
$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( (string) wp_unslash( $_GET['orderby'] ) ) : wc_clean( get_query_var( 'orderby' ) ); // WPCS: sanitization ok, input var ok, CSRF ok.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( (string) wp_unslash( $_GET['orderby'] ) ) : wc_clean( get_query_var( 'orderby' ) );
|
||||
|
||||
if ( ! $orderby_value ) {
|
||||
if ( is_search() ) {
|
||||
|
@ -522,12 +582,15 @@ class WC_Query {
|
|||
public function price_filter_post_clauses( $args, $wp_query ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! $wp_query->is_main_query() || ( ! isset( $_GET['max_price'] ) && ! isset( $_GET['min_price'] ) ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$current_min_price = isset( $_GET['min_price'] ) ? floatval( wp_unslash( $_GET['min_price'] ) ) : 0; // WPCS: input var ok, CSRF ok.
|
||||
$current_max_price = isset( $_GET['max_price'] ) ? floatval( wp_unslash( $_GET['max_price'] ) ) : PHP_INT_MAX; // WPCS: input var ok, CSRF ok.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$current_min_price = isset( $_GET['min_price'] ) ? floatval( wp_unslash( $_GET['min_price'] ) ) : 0;
|
||||
$current_max_price = isset( $_GET['max_price'] ) ? floatval( wp_unslash( $_GET['max_price'] ) ) : PHP_INT_MAX;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
/**
|
||||
* Adjust if the store taxes are not displayed how they are stored.
|
||||
|
@ -666,9 +729,11 @@ class WC_Query {
|
|||
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
// Filter by rating.
|
||||
if ( isset( $_GET['rating_filter'] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
$rating_filter = array_filter( array_map( 'absint', explode( ',', $_GET['rating_filter'] ) ) ); // WPCS: input var ok, CSRF ok, Sanitization ok.
|
||||
if ( isset( $_GET['rating_filter'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$rating_filter = array_filter( array_map( 'absint', explode( ',', wp_unslash( $_GET['rating_filter'] ) ) ) );
|
||||
$rating_terms = array();
|
||||
for ( $i = 1; $i <= 5; $i ++ ) {
|
||||
if ( in_array( $i, $rating_filter, true ) && isset( $product_visibility_terms[ 'rated-' . $i ] ) ) {
|
||||
|
@ -685,6 +750,7 @@ class WC_Query {
|
|||
);
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( ! empty( $product_visibility_not_in ) ) {
|
||||
$tax_query[] = array(
|
||||
|
@ -753,8 +819,9 @@ class WC_Query {
|
|||
$term = substr( $term, 1 );
|
||||
}
|
||||
|
||||
$like = '%' . $wpdb->esc_like( $term ) . '%';
|
||||
$sql[] = $wpdb->prepare( "(($wpdb->posts.post_title $like_op %s) $andor_op ($wpdb->posts.post_excerpt $like_op %s) $andor_op ($wpdb->posts.post_content $like_op %s))", $like, $like, $like ); // unprepared SQL ok.
|
||||
$like = '%' . $wpdb->esc_like( $term ) . '%';
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql[] = $wpdb->prepare( "(($wpdb->posts.post_title $like_op %s) $andor_op ($wpdb->posts.post_excerpt $like_op %s) $andor_op ($wpdb->posts.post_content $like_op %s))", $like, $like, $like );
|
||||
}
|
||||
|
||||
if ( ! empty( $sql ) && ! is_user_logged_in() ) {
|
||||
|
@ -770,11 +837,12 @@ class WC_Query {
|
|||
* @return array
|
||||
*/
|
||||
public static function get_layered_nav_chosen_attributes() {
|
||||
if ( ! is_array( self::$_chosen_attributes ) ) {
|
||||
self::$_chosen_attributes = array();
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! is_array( self::$chosen_attributes ) ) {
|
||||
self::$chosen_attributes = array();
|
||||
|
||||
if ( ! empty( $_GET ) ) { // WPCS: input var ok, CSRF ok.
|
||||
foreach ( $_GET as $key => $value ) { // WPCS: input var ok, CSRF ok.
|
||||
if ( ! empty( $_GET ) ) {
|
||||
foreach ( $_GET as $key => $value ) {
|
||||
if ( 0 === strpos( $key, 'filter_' ) ) {
|
||||
$attribute = wc_sanitize_taxonomy_name( str_replace( 'filter_', '', $key ) );
|
||||
$taxonomy = wc_attribute_taxonomy_name( $attribute );
|
||||
|
@ -784,14 +852,15 @@ class WC_Query {
|
|||
continue;
|
||||
}
|
||||
|
||||
$query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ), true ) ? wc_clean( wp_unslash( $_GET[ 'query_type_' . $attribute ] ) ) : ''; // WPCS: sanitization ok, input var ok, CSRF ok.
|
||||
self::$_chosen_attributes[ $taxonomy ]['terms'] = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding.
|
||||
self::$_chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
|
||||
$query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ), true ) ? wc_clean( wp_unslash( $_GET[ 'query_type_' . $attribute ] ) ) : '';
|
||||
self::$chosen_attributes[ $taxonomy ]['terms'] = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding.
|
||||
self::$chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return self::$_chosen_attributes;
|
||||
return self::$chosen_attributes;
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -804,7 +873,6 @@ class WC_Query {
|
|||
return remove_query_arg( 'add-to-cart', $url );
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Return a meta query for filtering by rating.
|
||||
*
|
||||
|
@ -819,7 +887,7 @@ class WC_Query {
|
|||
* Returns a meta query to handle product visibility.
|
||||
*
|
||||
* @deprecated 3.0.0 Replaced with taxonomy.
|
||||
* @param string $compare (default: 'IN')
|
||||
* @param string $compare (default: 'IN').
|
||||
* @return array
|
||||
*/
|
||||
public function visibility_meta_query( $compare = 'IN' ) {
|
||||
|
@ -830,7 +898,7 @@ class WC_Query {
|
|||
* Returns a meta query to handle product stock status.
|
||||
*
|
||||
* @deprecated 3.0.0 Replaced with taxonomy.
|
||||
* @param string $status (default: 'instock')
|
||||
* @param string $status (default: 'instock').
|
||||
* @return array
|
||||
*/
|
||||
public function stock_status_meta_query( $status = 'instock' ) {
|
||||
|
@ -869,6 +937,8 @@ class WC_Query {
|
|||
/**
|
||||
* Search post excerpt.
|
||||
*
|
||||
* @param string $where Where clause.
|
||||
*
|
||||
* @deprecated 3.2.0 - Not needed anymore since WordPress 4.5.
|
||||
*/
|
||||
public function search_post_excerpt( $where = '' ) {
|
||||
|
@ -878,10 +948,10 @@ class WC_Query {
|
|||
|
||||
/**
|
||||
* Remove the posts_where filter.
|
||||
*
|
||||
* @deprecated 3.2.0 - Nothing to remove anymore because search_post_excerpt() is deprecated.
|
||||
*/
|
||||
public function remove_posts_where() {
|
||||
wc_deprecated_function( 'WC_Query::remove_posts_where', '3.2.0', 'Nothing to remove anymore because search_post_excerpt() is deprecated.' );
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
$query_args = array( 'attribute_name' => wc_variation_attribute_name( $attribute['name'] ) ) + $child_ids;
|
||||
$values = array_unique(
|
||||
$wpdb->get_col(
|
||||
$wpdb->prepare( // wpcs: PreparedSQLPlaceholders replacement count ok.
|
||||
$wpdb->prepare(
|
||||
"SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN {$query_in}", // @codingStandardsIgnoreLine.
|
||||
$query_args
|
||||
)
|
||||
|
@ -661,6 +661,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
if ( $force_delete ) {
|
||||
do_action( 'woocommerce_before_delete_product_variation', $variation_id );
|
||||
wp_delete_post( $variation_id, true );
|
||||
wp_delete_object_term_relationships( $variation_id, wc_get_attribute_taxonomy_names() );
|
||||
do_action( 'woocommerce_delete_product_variation', $variation_id );
|
||||
} else {
|
||||
wp_trash_post( $variation_id );
|
||||
|
|
|
@ -473,10 +473,12 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
|
|||
|
||||
if ( $force || array_key_exists( 'attributes', $changes ) ) {
|
||||
global $wpdb;
|
||||
|
||||
$product_id = $product->get_id();
|
||||
$attributes = $product->get_attributes();
|
||||
$updated_attribute_keys = array();
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
update_post_meta( $product->get_id(), 'attribute_' . $key, wp_slash( $value ) );
|
||||
update_post_meta( $product_id, 'attribute_' . $key, wp_slash( $value ) );
|
||||
$updated_attribute_keys[] = 'attribute_' . $key;
|
||||
}
|
||||
|
||||
|
@ -486,13 +488,27 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
|
|||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
|
||||
"SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE %s AND meta_key NOT IN ( '" . implode( "','", array_map( 'esc_sql', $updated_attribute_keys ) ) . "' ) AND post_id = %d",
|
||||
$wpdb->esc_like( 'attribute_' ) . '%',
|
||||
$product->get_id()
|
||||
$product_id
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $delete_attribute_keys as $key ) {
|
||||
delete_post_meta( $product->get_id(), $key );
|
||||
delete_post_meta( $product_id, $key );
|
||||
}
|
||||
|
||||
// Set the attributes as regular taxonomy terms too...
|
||||
$variation_attributes = array_keys( $product->get_variation_attributes( false ) );
|
||||
foreach ( $attributes as $name => $value ) {
|
||||
if ( '' !== $value && in_array( $name, $variation_attributes, true ) && term_exists( $value, $name ) ) {
|
||||
wp_set_post_terms( $product_id, array( $value ), $name );
|
||||
} elseif ( taxonomy_exists( $name ) ) {
|
||||
wp_delete_object_term_relationships( $product_id, $name );
|
||||
}
|
||||
}
|
||||
|
||||
// ...and remove old taxonomy terms.
|
||||
$attributes_to_delete = array_diff( wc_get_attribute_taxonomy_names(), array_keys( $attributes ) );
|
||||
wp_delete_object_term_relationships( $product_id, $attributes_to_delete );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,13 @@ defined( 'ABSPATH' ) || exit;
|
|||
function wc_template_redirect() {
|
||||
global $wp_query, $wp;
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
// When default permalinks are enabled, redirect shop page to post type archive url.
|
||||
if ( ! empty( $_GET['page_id'] ) && '' === get_option( 'permalink_structure' ) && wc_get_page_id( 'shop' ) === absint( $_GET['page_id'] ) && get_post_type_archive_link( 'product' ) ) { // WPCS: input var ok, CSRF ok.
|
||||
if ( ! empty( $_GET['page_id'] ) && '' === get_option( 'permalink_structure' ) && wc_get_page_id( 'shop' ) === absint( $_GET['page_id'] ) && get_post_type_archive_link( 'product' ) ) {
|
||||
wp_safe_redirect( get_post_type_archive_link( 'product' ) );
|
||||
exit;
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
// When on the checkout with an empty cart, redirect to cart page.
|
||||
if ( is_page( wc_get_page_id( 'checkout' ) ) && wc_get_page_id( 'checkout' ) !== wc_get_page_id( 'cart' ) && WC()->cart->is_empty() && empty( $wp->query_vars['order-pay'] ) && ! isset( $wp->query_vars['order-received'] ) && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_redirect_empty_cart', true ) ) {
|
||||
|
@ -33,7 +35,7 @@ function wc_template_redirect() {
|
|||
}
|
||||
|
||||
// Logout.
|
||||
if ( isset( $wp->query_vars['customer-logout'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'customer-logout' ) ) { // WPCS: input var ok, CSRF ok.
|
||||
if ( isset( $wp->query_vars['customer-logout'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'customer-logout' ) ) {
|
||||
wp_safe_redirect( str_replace( '&', '&', wp_logout_url( wc_get_page_permalink( 'myaccount' ) ) ) );
|
||||
exit;
|
||||
}
|
||||
|
@ -96,9 +98,11 @@ add_action( 'template_redirect', 'wc_send_frame_options_header' );
|
|||
* @since 2.5.3
|
||||
*/
|
||||
function wc_prevent_endpoint_indexing() {
|
||||
if ( is_wc_endpoint_url() || isset( $_GET['download_file'] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
@header( 'X-Robots-Tag: noindex' ); // @codingStandardsIgnoreLine
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
if ( is_wc_endpoint_url() || isset( $_GET['download_file'] ) ) {
|
||||
@header( 'X-Robots-Tag: noindex' );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
}
|
||||
add_action( 'template_redirect', 'wc_prevent_endpoint_indexing' );
|
||||
|
||||
|
@ -232,6 +236,29 @@ function wc_set_loop_prop( $prop, $value = '' ) {
|
|||
$GLOBALS['woocommerce_loop'][ $prop ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current visbility for a product in the woocommerce_loop global.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param int $product_id Product it to cache visbiility for.
|
||||
* @param bool $value The poduct visibility value to cache.
|
||||
*/
|
||||
function wc_set_loop_product_visibility( $product_id, $value ) {
|
||||
wc_set_loop_prop( "product_visibility_$product_id", $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached current visibility for a product from the woocommerce_loop global.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param int $product_id Product id to get the cached visibility for.
|
||||
*
|
||||
* @return bool|null The cached product visibility, or null if on visibility has been cached for that product.
|
||||
*/
|
||||
function wc_get_loop_product_visibility( $product_id ) {
|
||||
return wc_get_loop_prop( "product_visibility_$product_id", null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the WooCommerce loop be displayed?
|
||||
*
|
||||
|
@ -704,7 +731,8 @@ function wc_product_class( $class = '', $product_id = null ) {
|
|||
*/
|
||||
function wc_query_string_form_fields( $values = null, $exclude = array(), $current_key = '', $return = false ) {
|
||||
if ( is_null( $values ) ) {
|
||||
$values = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$values = $_GET;
|
||||
} elseif ( is_string( $values ) ) {
|
||||
$url_parts = wp_parse_url( $values );
|
||||
$values = array();
|
||||
|
@ -1022,7 +1050,8 @@ if ( ! function_exists( 'woocommerce_demo_store' ) ) {
|
|||
|
||||
$notice_id = md5( $notice );
|
||||
|
||||
echo apply_filters( 'woocommerce_demo_store', '<p class="woocommerce-store-notice demo_store" data-notice-id="' . esc_attr( $notice_id ) . '" style="display:none;">' . wp_kses_post( $notice ) . ' <a href="#" class="woocommerce-store-notice__dismiss-link">' . esc_html__( 'Dismiss', 'woocommerce' ) . '</a></p>', $notice ); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo apply_filters( 'woocommerce_demo_store', '<p class="woocommerce-store-notice demo_store" data-notice-id="' . esc_attr( $notice_id ) . '" style="display:none;">' . wp_kses_post( $notice ) . ' <a href="#" class="woocommerce-store-notice__dismiss-link">' . esc_html__( 'Dismiss', 'woocommerce' ) . '</a></p>', $notice );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1062,7 +1091,8 @@ if ( ! function_exists( 'woocommerce_page_title' ) ) {
|
|||
$page_title = apply_filters( 'woocommerce_page_title', $page_title );
|
||||
|
||||
if ( $echo ) {
|
||||
echo $page_title; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $page_title;
|
||||
} else {
|
||||
return $page_title;
|
||||
}
|
||||
|
@ -1087,7 +1117,8 @@ if ( ! function_exists( 'woocommerce_product_loop_start' ) ) {
|
|||
$loop_start = apply_filters( 'woocommerce_product_loop_start', ob_get_clean() );
|
||||
|
||||
if ( $echo ) {
|
||||
echo $loop_start; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $loop_start;
|
||||
} else {
|
||||
return $loop_start;
|
||||
}
|
||||
|
@ -1110,7 +1141,8 @@ if ( ! function_exists( 'woocommerce_product_loop_end' ) ) {
|
|||
$loop_end = apply_filters( 'woocommerce_product_loop_end', ob_get_clean() );
|
||||
|
||||
if ( $echo ) {
|
||||
echo $loop_end; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $loop_end;
|
||||
} else {
|
||||
return $loop_end;
|
||||
}
|
||||
|
@ -1139,7 +1171,8 @@ if ( ! function_exists( 'woocommerce_template_loop_category_title' ) ) {
|
|||
echo esc_html( $category->name );
|
||||
|
||||
if ( $category->count > 0 ) {
|
||||
echo apply_filters( 'woocommerce_subcategory_count_html', ' <mark class="count">(' . esc_html( $category->count ) . ')</mark>', $category ); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo apply_filters( 'woocommerce_subcategory_count_html', ' <mark class="count">(' . esc_html( $category->count ) . ')</mark>', $category );
|
||||
}
|
||||
?>
|
||||
</h2>
|
||||
|
@ -1199,7 +1232,8 @@ if ( ! function_exists( 'woocommerce_taxonomy_archive_description' ) ) {
|
|||
$term = get_queried_object();
|
||||
|
||||
if ( $term && ! empty( $term->description ) ) {
|
||||
echo '<div class="term-description">' . wc_format_content( $term->description ) . '</div>'; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="term-description">' . wc_format_content( $term->description ) . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1220,7 +1254,8 @@ if ( ! function_exists( 'woocommerce_product_archive_description' ) ) {
|
|||
if ( $shop_page ) {
|
||||
$description = wc_format_content( $shop_page->post_content );
|
||||
if ( $description ) {
|
||||
echo '<div class="page-description">' . $description . '</div>'; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="page-description">' . $description . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1276,7 +1311,8 @@ if ( ! function_exists( 'woocommerce_template_loop_product_thumbnail' ) ) {
|
|||
* Get the product thumbnail for the loop.
|
||||
*/
|
||||
function woocommerce_template_loop_product_thumbnail() {
|
||||
echo woocommerce_get_product_thumbnail(); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo woocommerce_get_product_thumbnail();
|
||||
}
|
||||
}
|
||||
if ( ! function_exists( 'woocommerce_template_loop_price' ) ) {
|
||||
|
@ -1368,7 +1404,9 @@ if ( ! function_exists( 'woocommerce_catalog_ordering' ) ) {
|
|||
);
|
||||
|
||||
$default_orderby = wc_get_loop_prop( 'is_search' ) ? 'relevance' : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) );
|
||||
$orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : $default_orderby; // WPCS: sanitization ok, input var ok, CSRF ok.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : $default_orderby;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( wc_get_loop_prop( 'is_search' ) ) {
|
||||
$catalog_orderby_options = array_merge( array( 'relevance' => __( 'Relevance', 'woocommerce' ) ), $catalog_orderby_options );
|
||||
|
@ -1700,7 +1738,8 @@ if ( ! function_exists( 'woocommerce_quantity_input' ) ) {
|
|||
wc_get_template( 'global/quantity-input.php', $args );
|
||||
|
||||
if ( $echo ) {
|
||||
echo ob_get_clean(); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo ob_get_clean();
|
||||
} else {
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
@ -1780,7 +1819,8 @@ if ( ! function_exists( 'woocommerce_sort_product_tabs' ) ) {
|
|||
|
||||
// Make sure the $tabs parameter is an array.
|
||||
if ( ! is_array( $tabs ) ) {
|
||||
trigger_error( 'Function woocommerce_sort_product_tabs() expects an array as the first parameter. Defaulting to empty array.' ); // @codingStandardsIgnoreLine
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
trigger_error( 'Function woocommerce_sort_product_tabs() expects an array as the first parameter. Defaulting to empty array.' );
|
||||
$tabs = array();
|
||||
}
|
||||
|
||||
|
@ -1817,7 +1857,8 @@ if ( ! function_exists( 'woocommerce_comments' ) ) {
|
|||
* @param int $depth Depth.
|
||||
*/
|
||||
function woocommerce_comments( $comment, $args, $depth ) {
|
||||
$GLOBALS['comment'] = $comment; // WPCS: override ok.
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$GLOBALS['comment'] = $comment;
|
||||
wc_get_template(
|
||||
'single-product/review.php',
|
||||
array(
|
||||
|
@ -2443,7 +2484,8 @@ if ( ! function_exists( 'woocommerce_output_product_categories' ) ) {
|
|||
return false;
|
||||
}
|
||||
|
||||
echo $args['before']; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['before'];
|
||||
|
||||
foreach ( $product_categories as $category ) {
|
||||
wc_get_template(
|
||||
|
@ -2454,7 +2496,8 @@ if ( ! function_exists( 'woocommerce_output_product_categories' ) ) {
|
|||
);
|
||||
}
|
||||
|
||||
echo $args['after']; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2839,7 +2882,8 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
|
|||
if ( $args['return'] ) {
|
||||
return $field;
|
||||
} else {
|
||||
echo $field; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2882,7 +2926,8 @@ if ( ! function_exists( 'get_product_search_form' ) ) {
|
|||
return $form;
|
||||
}
|
||||
|
||||
echo $form; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $form;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2951,8 +2996,10 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
|
|||
|
||||
// Get selected value.
|
||||
if ( false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product ) {
|
||||
$selected_key = 'attribute_' . sanitize_title( $args['attribute'] );
|
||||
$args['selected'] = isset( $_REQUEST[ $selected_key ] ) ? wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) ) : $args['product']->get_variation_default_attribute( $args['attribute'] ); // WPCS: input var ok, CSRF ok, sanitization ok.
|
||||
$selected_key = 'attribute_' . sanitize_title( $args['attribute'] );
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$args['selected'] = isset( $_REQUEST[ $selected_key ] ) ? wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) ) : $args['product']->get_variation_default_attribute( $args['attribute'] );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
$options = $args['options'];
|
||||
|
@ -2999,7 +3046,8 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
|
|||
|
||||
$html .= '</select>';
|
||||
|
||||
echo apply_filters( 'woocommerce_dropdown_variation_attribute_options_html', $html, $args ); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo apply_filters( 'woocommerce_dropdown_variation_attribute_options_html', $html, $args );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3236,7 +3284,8 @@ if ( ! function_exists( 'wc_display_item_meta' ) ) {
|
|||
$html = apply_filters( 'woocommerce_display_item_meta', $html, $item, $args );
|
||||
|
||||
if ( $args['echo'] ) {
|
||||
echo $html; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $html;
|
||||
} else {
|
||||
return $html;
|
||||
}
|
||||
|
@ -3290,7 +3339,8 @@ if ( ! function_exists( 'wc_display_item_downloads' ) ) {
|
|||
$html = apply_filters( 'woocommerce_display_item_downloads', $html, $item, $args );
|
||||
|
||||
if ( $args['echo'] ) {
|
||||
echo $html; // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $html;
|
||||
} else {
|
||||
return $html;
|
||||
}
|
||||
|
@ -3698,3 +3748,5 @@ function wc_get_pay_buttons() {
|
|||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
// phpcs:enable Generic.Commenting.Todo.TaskFound
|
||||
|
|
|
@ -28,6 +28,7 @@ function wc_update_200_file_paths() {
|
|||
$old_file_path = trim( $existing_file_path->meta_value );
|
||||
|
||||
if ( ! empty( $old_file_path ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
$file_paths = serialize( array( md5( $old_file_path ) => $old_file_path ) );
|
||||
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_key = '_file_paths', meta_value = %s WHERE meta_id = %d", $file_paths, $existing_file_path->meta_id ) );
|
||||
|
@ -53,11 +54,11 @@ function wc_update_200_permalinks() {
|
|||
|
||||
$base_slug = $shop_page_id > 0 && get_post( $shop_page_id ) ? get_page_uri( $shop_page_id ) : 'shop';
|
||||
|
||||
$category_base = get_option( 'woocommerce_prepend_shop_page_to_urls' ) == 'yes' ? trailingslashit( $base_slug ) : '';
|
||||
$category_base = 'yes' === get_option( 'woocommerce_prepend_shop_page_to_urls' ) ? trailingslashit( $base_slug ) : '';
|
||||
$category_slug = get_option( 'woocommerce_product_category_slug' ) ? get_option( 'woocommerce_product_category_slug' ) : _x( 'product-category', 'slug', 'woocommerce' );
|
||||
$tag_slug = get_option( 'woocommerce_product_tag_slug' ) ? get_option( 'woocommerce_product_tag_slug' ) : _x( 'product-tag', 'slug', 'woocommerce' );
|
||||
|
||||
if ( 'yes' == get_option( 'woocommerce_prepend_shop_page_to_products' ) ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_prepend_shop_page_to_products' ) ) {
|
||||
$product_base = trailingslashit( $base_slug );
|
||||
} else {
|
||||
$product_slug = get_option( 'woocommerce_product_slug' );
|
||||
|
@ -68,7 +69,7 @@ function wc_update_200_permalinks() {
|
|||
}
|
||||
}
|
||||
|
||||
if ( get_option( 'woocommerce_prepend_category_to_products' ) == 'yes' ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_prepend_category_to_products' ) ) {
|
||||
$product_base .= trailingslashit( '%product_cat%' );
|
||||
}
|
||||
|
||||
|
@ -90,16 +91,16 @@ function wc_update_200_permalinks() {
|
|||
*/
|
||||
function wc_update_200_subcat_display() {
|
||||
// Update subcat display settings.
|
||||
if ( get_option( 'woocommerce_shop_show_subcategories' ) == 'yes' ) {
|
||||
if ( get_option( 'woocommerce_hide_products_when_showing_subcategories' ) == 'yes' ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_shop_show_subcategories' ) ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_products_when_showing_subcategories' ) ) {
|
||||
update_option( 'woocommerce_shop_page_display', 'subcategories' );
|
||||
} else {
|
||||
update_option( 'woocommerce_shop_page_display', 'both' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( get_option( 'woocommerce_show_subcategories' ) == 'yes' ) {
|
||||
if ( get_option( 'woocommerce_hide_products_when_showing_subcategories' ) == 'yes' ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_show_subcategories' ) ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_products_when_showing_subcategories' ) ) {
|
||||
update_option( 'woocommerce_category_archive_display', 'subcategories' );
|
||||
} else {
|
||||
update_option( 'woocommerce_category_archive_display', 'both' );
|
||||
|
@ -128,7 +129,7 @@ function wc_update_200_taxrates() {
|
|||
|
||||
foreach ( $states as $state ) {
|
||||
|
||||
if ( '*' == $state ) {
|
||||
if ( '*' === $state ) {
|
||||
$state = '';
|
||||
}
|
||||
|
||||
|
@ -160,7 +161,7 @@ function wc_update_200_taxrates() {
|
|||
|
||||
$location_type = ( 'postcode' === $tax_rate['location_type'] ) ? 'postcode' : 'city';
|
||||
|
||||
if ( '*' == $tax_rate['state'] ) {
|
||||
if ( '*' === $tax_rate['state'] ) {
|
||||
$tax_rate['state'] = '';
|
||||
}
|
||||
|
||||
|
@ -246,7 +247,7 @@ function wc_update_200_line_items() {
|
|||
)
|
||||
);
|
||||
|
||||
// Add line item meta.
|
||||
// Add line item meta.
|
||||
if ( $item_id ) {
|
||||
wc_add_order_item_meta( $item_id, '_qty', absint( $order_item['qty'] ) );
|
||||
wc_add_order_item_meta( $item_id, '_tax_class', $order_item['tax_class'] );
|
||||
|
@ -324,7 +325,7 @@ function wc_update_200_line_items() {
|
|||
)
|
||||
);
|
||||
|
||||
// Add line item meta.
|
||||
// Add line item meta.
|
||||
if ( $item_id ) {
|
||||
wc_add_order_item_meta( $item_id, 'compound', absint( isset( $order_tax['compound'] ) ? $order_tax['compound'] : 0 ) );
|
||||
wc_add_order_item_meta( $item_id, 'tax_amount', wc_clean( $order_tax['cart_tax'] ) );
|
||||
|
@ -393,6 +394,8 @@ function wc_update_200_db_version() {
|
|||
function wc_update_209_brazillian_state() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
|
||||
// Update brazillian state codes.
|
||||
$wpdb->update(
|
||||
$wpdb->postmeta,
|
||||
|
@ -434,6 +437,8 @@ function wc_update_209_brazillian_state() {
|
|||
'meta_value' => 'BH',
|
||||
)
|
||||
);
|
||||
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -492,6 +497,7 @@ function wc_update_210_file_paths() {
|
|||
}
|
||||
}
|
||||
if ( $needs_update ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
$new_value = serialize( $new_value );
|
||||
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_key = %s, meta_value = %s WHERE meta_id = %d", '_downloadable_files', $new_value, $existing_file_path->meta_id ) );
|
||||
|
@ -857,6 +863,8 @@ function wc_update_240_api_keys() {
|
|||
* @return void
|
||||
*/
|
||||
function wc_update_240_webhooks() {
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
|
||||
/**
|
||||
* Webhooks.
|
||||
* Make sure order.update webhooks get the woocommerce_order_edit_status hook.
|
||||
|
@ -873,6 +881,8 @@ function wc_update_240_webhooks() {
|
|||
$webhook = new WC_Webhook( $order_update_webhook->ID );
|
||||
$webhook->set_topic( 'order.updated' );
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -993,6 +1003,8 @@ function wc_update_250_currency() {
|
|||
update_option( 'woocommerce_currency', 'LAK' );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
|
||||
// Update LAK currency code.
|
||||
$wpdb->update(
|
||||
$wpdb->postmeta,
|
||||
|
@ -1005,6 +1017,7 @@ function wc_update_250_currency() {
|
|||
)
|
||||
);
|
||||
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1184,6 +1197,8 @@ function wc_update_260_db_version() {
|
|||
* @return void
|
||||
*/
|
||||
function wc_update_300_webhooks() {
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
|
||||
/**
|
||||
* Make sure product.update webhooks get the woocommerce_product_quick_edit_save
|
||||
* and woocommerce_product_bulk_edit_save hooks.
|
||||
|
@ -1200,6 +1215,8 @@ function wc_update_300_webhooks() {
|
|||
$webhook = new WC_Webhook( $product_update_webhook->ID );
|
||||
$webhook->set_topic( 'product.updated' );
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1601,7 +1618,7 @@ function wc_update_330_product_stock_status() {
|
|||
AND t3.meta_key = '_backorders' AND ( t3.meta_value = 'yes' OR t3.meta_value = 'notify' )",
|
||||
$min_stock_amount
|
||||
)
|
||||
); // WPCS: db call ok, unprepared SQL ok, cache ok.
|
||||
);
|
||||
|
||||
if ( empty( $post_ids ) ) {
|
||||
return;
|
||||
|
@ -1609,12 +1626,14 @@ function wc_update_330_product_stock_status() {
|
|||
|
||||
$post_ids = array_map( 'absint', $post_ids );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
||||
// Set the status to onbackorder for those products.
|
||||
$wpdb->query(
|
||||
"UPDATE $wpdb->postmeta
|
||||
SET meta_value = 'onbackorder'
|
||||
WHERE meta_key = '_stock_status' AND post_id IN ( " . implode( ',', $post_ids ) . ' )'
|
||||
); // WPCS: db call ok, unprepared SQL ok, cache ok.
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2065,7 +2084,8 @@ function wc_update_390_move_maxmind_database() {
|
|||
$new_path = apply_filters( 'woocommerce_geolocation_local_database_path', $new_path, 2 );
|
||||
$new_path = apply_filters( 'woocommerce_maxmind_geolocation_database_path', $new_path );
|
||||
|
||||
@rename( $old_path, $new_path ); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
@rename( $old_path, $new_path );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2110,3 +2130,48 @@ function wc_update_400_reset_action_scheduler_migration_status() {
|
|||
function wc_update_400_db_version() {
|
||||
WC_Install::update_db_version( '4.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register attributes as terms for variable products, in increments of 100 products.
|
||||
*
|
||||
* @return bool true if there are more products to process.
|
||||
*/
|
||||
function wc_update_440_insert_attribute_terms_for_variable_products() {
|
||||
$state_option_name = 'woocommerce_' . __FUNCTION__ . '_state';
|
||||
|
||||
$page = intval( get_option( $state_option_name, 1 ) );
|
||||
$products = wc_get_products(
|
||||
array(
|
||||
'type' => 'variable',
|
||||
'limit' => 100,
|
||||
'page' => $page,
|
||||
)
|
||||
);
|
||||
if ( empty( $products ) ) {
|
||||
delete_option( $state_option_name );
|
||||
return false;
|
||||
}
|
||||
|
||||
$attribute_taxonomy_names = wc_get_attribute_taxonomy_names();
|
||||
foreach ( $products as $product ) {
|
||||
$variation_ids = $product->get_children();
|
||||
foreach ( $variation_ids as $variation_id ) {
|
||||
$variation = wc_get_product( $variation_id );
|
||||
$variation_attributes = $variation->get_attributes();
|
||||
foreach ( $variation_attributes as $attr_name => $attr_value ) {
|
||||
wp_set_post_terms( $variation_id, array( $attr_value ), $attr_name );
|
||||
}
|
||||
$attributes_to_delete = array_diff( $attribute_taxonomy_names, array_keys( $variation_attributes ) );
|
||||
wp_delete_object_term_relationships( $variation_id, $attributes_to_delete );
|
||||
}
|
||||
}
|
||||
|
||||
return update_option( $state_option_name, $page + 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update DB version.
|
||||
*/
|
||||
function wc_update_440_db_version() {
|
||||
WC_Install::update_db_version( '4.4.0' );
|
||||
}
|
||||
|
|
|
@ -344,49 +344,77 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
|
||||
global $wpdb;
|
||||
|
||||
$tax_query = WC_Query::get_main_tax_query();
|
||||
$meta_query = WC_Query::get_main_meta_query();
|
||||
$main_tax_query = $this->get_main_tax_query();
|
||||
$meta_query = $this->get_main_meta_query();
|
||||
|
||||
if ( 'or' === $query_type ) {
|
||||
foreach ( $tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
unset( $tax_query[ $key ] );
|
||||
$non_variable_tax_query_sql = array( 'where' => '' );
|
||||
$is_and_query = 'and' === $query_type;
|
||||
|
||||
foreach ( $main_tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
if ( $is_and_query ) {
|
||||
$non_variable_tax_query_sql = $this->convert_tax_query_to_sql( array( $query ) );
|
||||
}
|
||||
unset( $main_tax_query[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
$meta_query = new WP_Meta_Query( $meta_query );
|
||||
$tax_query = new WP_Tax_Query( $tax_query );
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
|
||||
$exclude_variable_products_tax_query_sql = $this->get_extra_tax_query_sql( 'product_type', array( 'variable' ), 'NOT IN' );
|
||||
|
||||
$meta_query_sql = ( new WP_Meta_Query( $meta_query ) )->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$main_tax_query_sql = $this->convert_tax_query_to_sql( $main_tax_query );
|
||||
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||
|
||||
// Generate query.
|
||||
$query = array();
|
||||
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, terms.term_id as term_count_id";
|
||||
$query['from'] = "FROM {$wpdb->posts}";
|
||||
$query['join'] = "
|
||||
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id
|
||||
INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id
|
||||
INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->terms} AS terms USING( term_id )
|
||||
" . $tax_query_sql['join'] . $meta_query_sql['join'];
|
||||
{$main_tax_query_sql['join']} {$meta_query_sql['join']}"; // Not an omission, really no more JOINs required.
|
||||
|
||||
$variable_where_part = "
|
||||
OR ({$wpdb->posts}.post_type = 'product_variation'
|
||||
AND NOT EXISTS (
|
||||
SELECT ID FROM {$wpdb->posts} AS parent
|
||||
WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
|
||||
))
|
||||
";
|
||||
|
||||
$search_sql = '';
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$search_sql = ' AND ' . $search;
|
||||
}
|
||||
|
||||
$query['where'] = "
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'"
|
||||
. $tax_query_sql['where'] . $meta_query_sql['where'] .
|
||||
'AND terms.term_id IN (' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||
WHERE
|
||||
{$wpdb->posts}.post_status = 'publish'
|
||||
{$main_tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND (
|
||||
(
|
||||
{$wpdb->posts}.post_type = 'product'
|
||||
{$exclude_variable_products_tax_query_sql['where']}
|
||||
{$non_variable_tax_query_sql['where']}
|
||||
)
|
||||
{$variable_where_part}
|
||||
)
|
||||
AND terms.term_id IN {$term_ids_sql}
|
||||
{$search_sql}";
|
||||
|
||||
$search = WC_Query::get_main_search_query_sql();
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$query['where'] .= ' AND ' . $search;
|
||||
}
|
||||
|
||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
||||
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
|
||||
$query = implode( ' ', $query );
|
||||
$query_sql = implode( ' ', $query );
|
||||
|
||||
// We have a query - let's see if cached results of this query already exist.
|
||||
$query_hash = md5( $query );
|
||||
$query_hash = md5( $query_sql );
|
||||
|
||||
// Maybe store a transient of the count values.
|
||||
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
|
||||
|
@ -397,17 +425,88 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
}
|
||||
|
||||
if ( ! isset( $cached_counts[ $query_hash ] ) ) {
|
||||
$results = $wpdb->get_results( $query, ARRAY_A ); // @codingStandardsIgnoreLine
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
||||
$results = $wpdb->get_results( $query_sql, ARRAY_A );
|
||||
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
|
||||
$cached_counts[ $query_hash ] = $counts;
|
||||
if ( true === $cache ) {
|
||||
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for WC_Query::get_main_tax_query() to ease unit testing.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_main_tax_query() {
|
||||
return WC_Query::get_main_tax_query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for WC_Query::get_main_search_query_sql() to ease unit testing.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @return string
|
||||
*/
|
||||
protected function get_main_search_query_sql() {
|
||||
return WC_Query::get_main_search_query_sql();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for WC_Query::get_main_search_queryget_main_meta_query to ease unit testing.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_main_meta_query() {
|
||||
return WC_Query::get_main_meta_query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tax query SQL for a given set of taxonomy, terms and operator.
|
||||
* Uses an intermediate WP_Tax_Query object.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param string $taxonomy Taxonomy name.
|
||||
* @param array $terms Terms to include in the query.
|
||||
* @param string $operator Query operator, as supported by WP_Tax_Query; e.g. "NOT IN".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_extra_tax_query_sql( $taxonomy, $terms, $operator ) {
|
||||
$query = array(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'slug',
|
||||
'terms' => $terms,
|
||||
'operator' => $operator,
|
||||
'include_children' => false,
|
||||
),
|
||||
);
|
||||
|
||||
return $this->convert_tax_query_to_sql( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tax query array to SQL using an intermediate WP_Tax_Query object.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @param array $query Query array in the same format accepted by WP_Tax_Query constructor.
|
||||
*
|
||||
* @return array Query SQL as returned by WP_Tax_Query->get_sql.
|
||||
*/
|
||||
private function convert_tax_query_to_sql( $query ) {
|
||||
global $wpdb;
|
||||
|
||||
return ( new WP_Tax_Query( $query ) )->get_sql( $wpdb->posts, 'ID' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show list based layered nav.
|
||||
*
|
||||
|
@ -442,8 +541,9 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
continue;
|
||||
}
|
||||
|
||||
$filter_name = 'filter_' . wc_attribute_taxonomy_slug( $taxonomy );
|
||||
$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_name ] ) ) ) : array(); // WPCS: input var ok, CSRF ok.
|
||||
$filter_name = 'filter_' . wc_attribute_taxonomy_slug( $taxonomy );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_name ] ) ) ) : array();
|
||||
$current_filter = array_map( 'sanitize_title', $current_filter );
|
||||
|
||||
if ( ! in_array( $term->slug, $current_filter, true ) ) {
|
||||
|
@ -487,7 +587,8 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
$term_html .= ' ' . apply_filters( 'woocommerce_layered_nav_count', '<span class="count">(' . absint( $count ) . ')</span>', $count, $term );
|
||||
|
||||
echo '<li class="woocommerce-widget-layered-nav-list__item wc-layered-nav-term ' . ( $option_is_set ? 'woocommerce-widget-layered-nav-list__item--chosen chosen' : '' ) . '">';
|
||||
echo apply_filters( 'woocommerce_layered_nav_term_html', $term_html, $term, $link, $count ); // WPCS: XSS ok.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo apply_filters( 'woocommerce_layered_nav_term_html', $term_html, $term, $link, $count );
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
global $product;
|
||||
|
||||
// Ensure visibility.
|
||||
if ( empty( $product ) || ! $product->is_visible() ) {
|
||||
if ( empty( $product ) || false === wc_get_loop_product_visibility( $product->get_id() ) || ! $product->is_visible() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -23,7 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
?>
|
||||
<p class="woocommerce-result-count">
|
||||
<?php
|
||||
if ( 1 === $total ) {
|
||||
// phpcs:disable WordPress.Security
|
||||
if ( 1 === intval( $total ) ) {
|
||||
_e( 'Showing the single result', 'woocommerce' );
|
||||
} elseif ( $total <= $per_page || -1 === $per_page ) {
|
||||
/* translators: %d: total results */
|
||||
|
@ -34,5 +35,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
/* translators: 1: first result 2: last result 3: total results */
|
||||
printf( _nx( 'Showing %1$d–%2$d of %3$d result', 'Showing %1$d–%2$d of %3$d results', $total, 'with first and last result', 'woocommerce' ), $first, $last, $total );
|
||||
}
|
||||
// phpcs:enable WordPress.Security
|
||||
?>
|
||||
</p>
|
||||
|
|
|
@ -103,14 +103,19 @@ class WC_Helper_Product {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a dummy variation product.
|
||||
* Create a dummy variation product or configure an existing product object with dummy data.
|
||||
*
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param WC_Product_Variable|null $product Product object to configure, or null to create a new one.
|
||||
* @return WC_Product_Variable
|
||||
*/
|
||||
public static function create_variation_product() {
|
||||
$product = new WC_Product_Variable();
|
||||
public static function create_variation_product( $product = null ) {
|
||||
$is_new_product = is_null( $product );
|
||||
if ( $is_new_product ) {
|
||||
$product = new WC_Product_Variable();
|
||||
}
|
||||
|
||||
$product->set_props(
|
||||
array(
|
||||
'name' => 'Dummy Variable Product',
|
||||
|
@ -120,96 +125,132 @@ class WC_Helper_Product {
|
|||
|
||||
$attributes = array();
|
||||
|
||||
$attribute = new WC_Product_Attribute();
|
||||
$attribute_data = self::create_attribute( 'size', array( 'small', 'large', 'huge' ) );
|
||||
$attribute->set_id( $attribute_data['attribute_id'] );
|
||||
$attribute->set_name( $attribute_data['attribute_taxonomy'] );
|
||||
$attribute->set_options( $attribute_data['term_ids'] );
|
||||
$attribute->set_position( 1 );
|
||||
$attribute->set_visible( true );
|
||||
$attribute->set_variation( true );
|
||||
$attributes[] = $attribute;
|
||||
|
||||
$attribute = new WC_Product_Attribute();
|
||||
$attribute_data = self::create_attribute( 'colour', array( 'red', 'blue' ) );
|
||||
$attribute->set_id( $attribute_data['attribute_id'] );
|
||||
$attribute->set_name( $attribute_data['attribute_taxonomy'] );
|
||||
$attribute->set_options( $attribute_data['term_ids'] );
|
||||
$attribute->set_position( 1 );
|
||||
$attribute->set_visible( true );
|
||||
$attribute->set_variation( true );
|
||||
$attributes[] = $attribute;
|
||||
|
||||
$attribute = new WC_Product_Attribute();
|
||||
$attribute_data = self::create_attribute( 'number', array( '0', '1', '2' ) );
|
||||
$attribute->set_id( $attribute_data['attribute_id'] );
|
||||
$attribute->set_name( $attribute_data['attribute_taxonomy'] );
|
||||
$attribute->set_options( $attribute_data['term_ids'] );
|
||||
$attribute->set_position( 1 );
|
||||
$attribute->set_visible( true );
|
||||
$attribute->set_variation( true );
|
||||
$attributes[] = $attribute;
|
||||
$attributes[] = self::create_product_attribute_object( 'size', array( 'small', 'large', 'huge' ) );
|
||||
$attributes[] = self::create_product_attribute_object( 'colour', array( 'red', 'blue' ) );
|
||||
$attributes[] = self::create_product_attribute_object( 'number', array( '0', '1', '2' ) );
|
||||
|
||||
$product->set_attributes( $attributes );
|
||||
$product->save();
|
||||
|
||||
$variation_1 = new WC_Product_Variation();
|
||||
$variation_1->set_props(
|
||||
array(
|
||||
'parent_id' => $product->get_id(),
|
||||
'sku' => 'DUMMY SKU VARIABLE SMALL',
|
||||
'regular_price' => 10,
|
||||
)
|
||||
);
|
||||
$variation_1->set_attributes( array( 'pa_size' => 'small' ) );
|
||||
$variation_1->save();
|
||||
$variations = array();
|
||||
|
||||
$variation_2 = new WC_Product_Variation();
|
||||
$variation_2->set_props(
|
||||
array(
|
||||
'parent_id' => $product->get_id(),
|
||||
'sku' => 'DUMMY SKU VARIABLE LARGE',
|
||||
'regular_price' => 15,
|
||||
)
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE SMALL',
|
||||
10,
|
||||
array( 'pa_size' => 'small' )
|
||||
);
|
||||
$variation_2->set_attributes( array( 'pa_size' => 'large' ) );
|
||||
$variation_2->save();
|
||||
|
||||
$variation_3 = new WC_Product_Variation();
|
||||
$variation_3->set_props(
|
||||
array(
|
||||
'parent_id' => $product->get_id(),
|
||||
'sku' => 'DUMMY SKU VARIABLE HUGE RED 0',
|
||||
'regular_price' => 16,
|
||||
)
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE LARGE',
|
||||
15,
|
||||
array( 'pa_size' => 'large' )
|
||||
);
|
||||
$variation_3->set_attributes(
|
||||
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE HUGE RED 0',
|
||||
16,
|
||||
array(
|
||||
'pa_size' => 'huge',
|
||||
'pa_colour' => 'red',
|
||||
'pa_number' => '0',
|
||||
)
|
||||
);
|
||||
$variation_3->save();
|
||||
|
||||
$variation_4 = new WC_Product_Variation();
|
||||
$variation_4->set_props(
|
||||
array(
|
||||
'parent_id' => $product->get_id(),
|
||||
'sku' => 'DUMMY SKU VARIABLE HUGE RED 2',
|
||||
'regular_price' => 17,
|
||||
)
|
||||
);
|
||||
$variation_4->set_attributes(
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE HUGE RED 2',
|
||||
17,
|
||||
array(
|
||||
'pa_size' => 'huge',
|
||||
'pa_colour' => 'red',
|
||||
'pa_number' => '2',
|
||||
)
|
||||
);
|
||||
$variation_4->save();
|
||||
|
||||
return wc_get_product( $product->get_id() );
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE HUGE BLUE 2',
|
||||
18,
|
||||
array(
|
||||
'pa_size' => 'huge',
|
||||
'pa_colour' => 'blue',
|
||||
'pa_number' => '2',
|
||||
)
|
||||
);
|
||||
|
||||
$variations[] = self::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'DUMMY SKU VARIABLE HUGE BLUE ANY NUMBER',
|
||||
19,
|
||||
array(
|
||||
'pa_size' => 'huge',
|
||||
'pa_colour' => 'blue',
|
||||
'pa_number' => '',
|
||||
)
|
||||
);
|
||||
|
||||
if ( $is_new_product ) {
|
||||
return wc_get_product( $product->get_id() );
|
||||
}
|
||||
|
||||
$variation_ids = array_map(
|
||||
function( $variation ) {
|
||||
return $variation->get_id();
|
||||
},
|
||||
$variations
|
||||
);
|
||||
$product->set_children( $variation_ids );
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of WC_Product_Variation with the supplied parameters, optionally persisting it to the database.
|
||||
*
|
||||
* @param string $parent_id Parent product id.
|
||||
* @param string $sku SKU for the variation.
|
||||
* @param int $price Price of the variation.
|
||||
* @param array $attributes Attributes that define the variation, e.g. ['pa_color'=>'red'].
|
||||
* @param bool $save If true, the object will be saved to the database after being created and configured.
|
||||
*
|
||||
* @return WC_Product_Variation The created object.
|
||||
*/
|
||||
public static function create_product_variation_object( $parent_id, $sku, $price, $attributes, $save = true ) {
|
||||
$variation = new WC_Product_Variation();
|
||||
$variation->set_props(
|
||||
array(
|
||||
'parent_id' => $parent_id,
|
||||
'sku' => $sku,
|
||||
'regular_price' => $price,
|
||||
)
|
||||
);
|
||||
$variation->set_attributes( $attributes );
|
||||
if ( $save ) {
|
||||
$variation->save();
|
||||
}
|
||||
return $variation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of WC_Product_Attribute with the supplied parameters.
|
||||
*
|
||||
* @param string $raw_name Attribute raw name (without 'pa_' prefix).
|
||||
* @param array $terms Possible values for the attribute.
|
||||
*
|
||||
* @return WC_Product_Attribute The created attribute object.
|
||||
*/
|
||||
public static function create_product_attribute_object( $raw_name = 'size', $terms = array( 'small' ) ) {
|
||||
$attribute = new WC_Product_Attribute();
|
||||
$attribute_data = self::create_attribute( $raw_name, $terms );
|
||||
$attribute->set_id( $attribute_data['attribute_id'] );
|
||||
$attribute->set_name( $attribute_data['attribute_taxonomy'] );
|
||||
$attribute->set_options( $attribute_data['term_ids'] );
|
||||
$attribute->set_position( 1 );
|
||||
$attribute->set_visible( true );
|
||||
$attribute->set_variation( true );
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -66,20 +66,20 @@ class WC_Tests_Admin_Duplicate_Product extends WC_Unit_Test_Case {
|
|||
array(
|
||||
'dummy-variable-product-small-2',
|
||||
'dummy-variable-product-large-2',
|
||||
'dummy-variable-product-3',
|
||||
'dummy-variable-product-4',
|
||||
),
|
||||
array(
|
||||
'dummy-variable-product-small-3',
|
||||
'dummy-variable-product-large-3',
|
||||
'dummy-variable-product-5',
|
||||
'dummy-variable-product-6',
|
||||
),
|
||||
array(
|
||||
'dummy-variable-product-small-3',
|
||||
'dummy-variable-product-large-3',
|
||||
'dummy-variable-product-9',
|
||||
'dummy-variable-product-10',
|
||||
),
|
||||
array(
|
||||
'dummy-variable-product-small-4',
|
||||
'dummy-variable-product-large-4',
|
||||
'dummy-variable-product-7',
|
||||
'dummy-variable-product-8',
|
||||
'dummy-variable-product-13',
|
||||
'dummy-variable-product-14',
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -88,7 +88,7 @@ class WC_Tests_Admin_Duplicate_Product extends WC_Unit_Test_Case {
|
|||
|
||||
$duplicate_children = $duplicate->get_children();
|
||||
|
||||
$this->assertEquals( 4, count( $duplicate_children ) );
|
||||
$this->assertEquals( 6, count( $duplicate_children ) );
|
||||
foreach ( $slug_match as $key => $slug ) {
|
||||
$child = wc_get_product( $duplicate_children[ $key ] );
|
||||
$this->assertEquals( $slug, $child->get_slug() );
|
||||
|
|
|
@ -2067,7 +2067,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
$tax_rate = array(
|
||||
$tax_rate = array(
|
||||
'tax_rate_country' => '',
|
||||
'tax_rate_state' => '',
|
||||
'tax_rate' => '10.0000',
|
||||
|
@ -2112,7 +2112,14 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
|
||||
$product = WC_Helper_Product::create_variation_product();
|
||||
$variations = $product->get_available_variations();
|
||||
$variation = array_pop( $variations );
|
||||
$variation = current(
|
||||
array_filter(
|
||||
$variations,
|
||||
function( $variation ) {
|
||||
return 'DUMMY SKU VARIABLE HUGE RED 2' === $variation['sku'];
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Add variation with add_to_cart_action.
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
|
@ -2137,7 +2144,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
$variation['variation_id'],
|
||||
array(
|
||||
'attribute_pa_size' => 'huge',
|
||||
'attribute_pa_colour' => 'red',
|
||||
'attribute_pa_colour' => 'red',
|
||||
'attribute_pa_number' => '2',
|
||||
)
|
||||
);
|
||||
|
@ -2166,7 +2173,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
$variation = array_pop( $variations );
|
||||
|
||||
// Attempt adding variation with add_to_cart_action, specifying a different colour.
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['attribute_pa_colour'] = 'green';
|
||||
WC_Form_Handler::add_to_cart_action( false );
|
||||
$notices = WC()->session->get( 'wc_notices', array() );
|
||||
|
@ -2197,7 +2204,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
$variation = array_shift( $variations );
|
||||
|
||||
// Attempt adding variation with add_to_cart_action, specifying attributes not defined in the variation.
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['attribute_pa_colour'] = 'red';
|
||||
$_REQUEST['attribute_pa_number'] = '1';
|
||||
WC_Form_Handler::add_to_cart_action( false );
|
||||
|
@ -2231,7 +2238,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
|
|||
$variation = array_shift( $variations );
|
||||
|
||||
// Attempt adding variation with add_to_cart_action, without specifying attribute_pa_colour.
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['add-to-cart'] = $variation['variation_id'];
|
||||
$_REQUEST['attribute_pa_number'] = '0';
|
||||
WC_Form_Handler::add_to_cart_action( false );
|
||||
$notices = WC()->session->get( 'wc_notices', array() );
|
||||
|
|
|
@ -216,29 +216,38 @@ class WC_Tests_Product_Data_Store extends WC_Unit_Test_Case {
|
|||
|
||||
$product = new WC_Product_Variable( $product->get_id() );
|
||||
|
||||
$this->assertEquals( 4, count( $product->get_children() ) );
|
||||
$this->assertEquals( 6, count( $product->get_children() ) );
|
||||
|
||||
$expected_prices['price'][ $children[0] ] = 8.00;
|
||||
$expected_prices['price'][ $children[1] ] = 15.00;
|
||||
$expected_prices['price'][ $children[2] ] = 16.00;
|
||||
$expected_prices['price'][ $children[3] ] = 17.00;
|
||||
$expected_prices['price'][ $children[4] ] = 18.00;
|
||||
$expected_prices['price'][ $children[5] ] = 19.00;
|
||||
|
||||
$expected_prices['regular_price'][ $children[0] ] = 10.00;
|
||||
$expected_prices['regular_price'][ $children[1] ] = 15.00;
|
||||
$expected_prices['regular_price'][ $children[2] ] = 16.00;
|
||||
$expected_prices['regular_price'][ $children[3] ] = 17.00;
|
||||
$expected_prices['regular_price'][ $children[4] ] = 18.00;
|
||||
$expected_prices['regular_price'][ $children[5] ] = 19.00;
|
||||
|
||||
$expected_prices['sale_price'][ $children[0] ] = 8.00;
|
||||
$expected_prices['sale_price'][ $children[1] ] = 15.00;
|
||||
$expected_prices['sale_price'][ $children[2] ] = 16.00;
|
||||
$expected_prices['sale_price'][ $children[3] ] = 17.00;
|
||||
$expected_prices['sale_price'][ $children[4] ] = 18.00;
|
||||
$expected_prices['sale_price'][ $children[5] ] = 19.00;
|
||||
|
||||
$this->assertEquals( $expected_prices, $product->get_variation_prices() );
|
||||
|
||||
$expected_attributes = array(
|
||||
'pa_size' => array( 'small', 'large', 'huge' ),
|
||||
'pa_colour' => array( 'red' ),
|
||||
'pa_number' => array( '0', '2' ),
|
||||
'pa_colour' => array(
|
||||
0 => 'red',
|
||||
2 => 'blue',
|
||||
),
|
||||
'pa_number' => array( '0', '1', '2' ),
|
||||
);
|
||||
$this->assertEquals( $expected_attributes, $product->get_variation_attributes() );
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* @since 2.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* WC_Tests_Product_Functions class.
|
||||
*/
|
||||
/**
|
||||
* WC_Tests_Product_Functions class.
|
||||
*/
|
||||
class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,7 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
|
|||
'type' => 'variation',
|
||||
)
|
||||
);
|
||||
$this->assertCount( 4, $products );
|
||||
$this->assertCount( 6, $products );
|
||||
|
||||
// Test parent.
|
||||
$products = wc_get_products(
|
||||
|
@ -80,7 +80,7 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
|
|||
'parent' => $variation->get_id(),
|
||||
)
|
||||
);
|
||||
$this->assertCount( 4, $products );
|
||||
$this->assertCount( 6, $products );
|
||||
|
||||
// Test parent_exclude.
|
||||
$products = wc_get_products(
|
||||
|
|
|
@ -159,7 +159,7 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
|
|||
* @param string $expected_stock_status The expected stock status of the product after being saved.
|
||||
*/
|
||||
public function test_stock_status_on_save_when_managing_stock( $stock_quantity, $notify_no_stock_amount, $accepts_backorders, $expected_stock_status ) {
|
||||
list($product, $child1, $child2) = $this->get_variable_product_with_children();
|
||||
list( $product, $child1, $child2 ) = $this->get_variable_product_with_children();
|
||||
|
||||
update_option( 'woocommerce_notify_no_stock_amount', $notify_no_stock_amount );
|
||||
|
||||
|
@ -176,4 +176,199 @@ class WC_Tests_Product_Variable extends WC_Unit_Test_Case {
|
|||
|
||||
$this->assertEquals( $expected_stock_status, $product->get_stock_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup for a test for is_visible.
|
||||
*
|
||||
* @param array $filtering_attributes Simulated filtering attributes as an array of attribute_name => [term1, term2...].
|
||||
* @param bool $hide_out_of_stock_products Should the woocommerce_hide_out_of_stock_items option be set?.
|
||||
* @param bool $is_visible_from_parent Return value of is_visible from base class.
|
||||
*
|
||||
* @return WC_Product_Variable A properly configured instance of WC_Product_Variable to test.
|
||||
*/
|
||||
private function prepare_visibility_test( $filtering_attributes, $hide_out_of_stock_products = true, $is_visible_from_parent = true ) {
|
||||
foreach ( $filtering_attributes as $attribute_name => $terms ) {
|
||||
$filtering_attributes[ $attribute_name ]['query_type'] = 'ANY_QUERY_TYPE';
|
||||
$filtering_attributes[ $attribute_name ]['terms'] = $terms;
|
||||
}
|
||||
|
||||
update_option( 'woocommerce_hide_out_of_stock_items', $hide_out_of_stock_products ? 'yes' : 'no' );
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Product_Variable::class )
|
||||
->setMethods( array( 'parent_is_visible_core', 'get_layered_nav_chosen_attributes' ) )
|
||||
->getMock();
|
||||
|
||||
$sut = WC_Helper_Product::create_variation_product( $sut, true );
|
||||
$sut->save();
|
||||
|
||||
$sut->method( 'parent_is_visible_core' )->willReturn( $is_visible_from_parent );
|
||||
$sut->method( 'get_layered_nav_chosen_attributes' )->willReturn( $filtering_attributes );
|
||||
|
||||
return $sut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the stock status for the attribute-based variations of a product.
|
||||
*
|
||||
* @param WC_Product_Variable $product Product with the variations to configure.
|
||||
* @param array $attributes An array of attribute_name => [attribute_values], only the matching variations will have stock.
|
||||
*/
|
||||
private function set_variations_with_stock( $product, $attributes ) {
|
||||
$variation_ids = $product->get_children();
|
||||
foreach ( $variation_ids as $id ) {
|
||||
$variation = wc_get_product( $id );
|
||||
$attribute_matches = true;
|
||||
foreach ( $attributes as $name => $values ) {
|
||||
if ( ! in_array( $variation->get_attribute( $name ), $values, true ) ) {
|
||||
$attribute_matches = false;
|
||||
}
|
||||
}
|
||||
$variation->set_stock_status( $attribute_matches ? 'instock' : 'outofstock' );
|
||||
$variation->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The product should be invisible when the parent 'is_visible' method returns false.
|
||||
*/
|
||||
public function test_is_invisible_when_parent_is_visible_returns_false() {
|
||||
$sut = $this->prepare_visibility_test( array(), '', false, false );
|
||||
|
||||
$this->assertFalse( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox The product should be visible when no nav filtering is supplied if at least one variation has stock.
|
||||
*
|
||||
* Note that if no variations have stock the base is_visible will already return false.
|
||||
*/
|
||||
public function test_is_visible_when_no_filtering_supplied_and_at_least_one_variation_has_stock() {
|
||||
$sut = $this->prepare_visibility_test( array(), '' );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test product visibility when the variation requested in nav filtering has no stock, result depends on woocommerce_hide_out_of_stock_items option.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
* @param bool $expected_visibility Expected value of is_visible for the tested product.
|
||||
*
|
||||
* @testWith [true, false]
|
||||
* [false, true]
|
||||
*/
|
||||
public function test_visibility_when_supplied_filter_has_no_stock( $hide_out_of_stock, $expected_visibility ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertEquals( $expected_visibility, $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should always be visible when only one of the variations requested in nav filtering has stock.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*/
|
||||
public function test_visibility_when_multiple_filter_values_supplied_and_only_one_has_stock( $hide_out_of_stock ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'small', 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should be visible when all of the variations requested in nav filtering have stock.
|
||||
*
|
||||
* @param bool $hide_out_of_stock Value for woocommerce_hide_out_of_stock_items.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*/
|
||||
public function test_visibility_when_multiple_filter_values_supplied_and_all_of_them_have_stock( $hide_out_of_stock ) {
|
||||
$sut = $this->prepare_visibility_test( array( 'pa_size' => array( 'small', 'large' ) ), $hide_out_of_stock );
|
||||
|
||||
$this->set_variations_with_stock( $sut, array( 'pa_size' => array( 'small', 'large' ) ) );
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should be visible when multiple filters are present, and there's a variation matching all of them.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_all_of_them_match() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '2' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Product should not be visible when multiple filters are present, and there are no variations matching all of them.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_one_of_them_does_not_match() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'small', 'huge' ),
|
||||
'pa_colour' => array( 'red' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '2' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFalse( $sut->is_visible() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Attributes having "Any..." as value should not count when searching for matching attributes.
|
||||
*/
|
||||
public function test_visibility_when_multiple_filters_are_used_and_an_attribute_has_any_value() {
|
||||
$sut = $this->prepare_visibility_test(
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_number' => array( '34' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
$this->set_variations_with_stock(
|
||||
$sut,
|
||||
array(
|
||||
'pa_size' => array( 'huge' ),
|
||||
'pa_colour' => array( 'blue' ),
|
||||
'pa_number' => array( '' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertTrue( $sut->is_visible() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,4 +91,43 @@ class WC_Tests_Product_Variation extends WC_Unit_Test_Case {
|
|||
$variable_product = WC_Helper_Product::create_variation_product();
|
||||
new WC_Product_Variation( $variable_product->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that get_variation_attributes returns the appropriate values.
|
||||
*
|
||||
* @param bool $with_prefix Parameter for get_variation_attributes.
|
||||
* @param string $expected_prefix Expected prefix on the returned attribute names.
|
||||
*
|
||||
* @testWith [true, "attribute_"]
|
||||
* [false, ""]
|
||||
*/
|
||||
public function test_get_variation_attributes( $with_prefix, $expected_prefix ) {
|
||||
$product = WC_Helper_Product::create_variation_product();
|
||||
$sut = wc_get_product( $product->get_children()[2] );
|
||||
|
||||
$expected = array(
|
||||
$expected_prefix . 'pa_size' => 'huge',
|
||||
$expected_prefix . 'pa_colour' => 'red',
|
||||
$expected_prefix . 'pa_number' => '0',
|
||||
);
|
||||
|
||||
$actual = $sut->get_variation_attributes( $with_prefix );
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that the delete method removes the attribute terms for the variation.
|
||||
*/
|
||||
public function test_delete_removes_attribute_terms() {
|
||||
$product = WC_Helper_Product::create_variation_product();
|
||||
$sut = wc_get_product( $product->get_children()[2] );
|
||||
$id = $sut->get_id();
|
||||
|
||||
$sut->delete( true );
|
||||
|
||||
$attribute_names = wc_get_attribute_taxonomy_names();
|
||||
$variation_attribute_terms = wp_get_post_terms( $id, $attribute_names );
|
||||
|
||||
$this->assertEmpty( $variation_attribute_terms );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
$this->assertTrue( wc_has_notice( 'test', 'error' ) );
|
||||
|
||||
// Clean up.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
unset( $_GET['wc_error'] );
|
||||
wc_clear_notices();
|
||||
|
||||
|
@ -182,6 +183,7 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
* @group core-only
|
||||
*/
|
||||
public function test_get_catalog_ordering_args() {
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
$data = array(
|
||||
array(
|
||||
'orderby' => 'menu_order',
|
||||
|
@ -297,6 +299,7 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
),
|
||||
),
|
||||
);
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
|
||||
foreach ( $data as $test ) {
|
||||
$result = WC()->query->get_catalog_ordering_args( $test['orderby'], $test['order'] );
|
||||
|
@ -310,11 +313,13 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
public function test_get_catalog_ordering_args_GET() {
|
||||
$_GET['orderby'] = 'price-desc';
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
$expected = array(
|
||||
'orderby' => 'price',
|
||||
'order' => 'DESC',
|
||||
'meta_key' => '',
|
||||
);
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
|
||||
$this->assertEquals( $expected, WC()->query->get_catalog_ordering_args() );
|
||||
|
||||
|
@ -341,9 +346,11 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
'include_children' => true,
|
||||
);
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
$query_args = array(
|
||||
'tax_query' => array( $tax_query ),
|
||||
);
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
|
||||
WC()->query->product_query( new WP_Query( $query_args ) );
|
||||
$tax_queries = WC_Query::get_main_tax_query();
|
||||
|
@ -360,9 +367,11 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
'compare' => '=',
|
||||
);
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery
|
||||
$query_args = array(
|
||||
'meta_query' => array( $meta_query ),
|
||||
);
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery
|
||||
|
||||
WC()->query->product_query( new WP_Query( $query_args ) );
|
||||
$meta_queries = WC_Query::get_main_meta_query();
|
||||
|
@ -428,4 +437,84 @@ class WC_Tests_WC_Query extends WC_Unit_Test_Case {
|
|||
|
||||
WC()->query->remove_ordering_args();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup for a test for adjust_posts.
|
||||
*
|
||||
* @param bool $with_nav_filtering_data Should WC_Query::get_layered_nav_chosen_attributes return filtering data?.
|
||||
* @param bool $use_objects If true, get_current_posts will return objects with an ID property; if false, it will returns the ids.
|
||||
*
|
||||
* @return array An array where the first element is the instance of WC_Query, and the second is an array of sample products created.
|
||||
*/
|
||||
private function setup_adjust_posts_test( $with_nav_filtering_data, $use_objects ) {
|
||||
update_option( 'woocommerce_hide_out_of_stock_items', 'yes' );
|
||||
|
||||
if ( $with_nav_filtering_data ) {
|
||||
$nav_filtering_data = array( 'pa_something' => array( 'terms' => array( 'foo', 'bar' ) ) );
|
||||
} else {
|
||||
$nav_filtering_data = array();
|
||||
}
|
||||
|
||||
$products = array();
|
||||
$posts = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$product = WC_Helper_Product::create_simple_product();
|
||||
array_push( $products, $product );
|
||||
$post = $use_objects ? (object) array( 'ID' => $product->get_id() ) : $product->get_id();
|
||||
array_push( $posts, $post );
|
||||
}
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Query::class )
|
||||
->setMethods( array( 'get_current_posts', 'get_layered_nav_chosen_attributes_inst' ) )
|
||||
->getMock();
|
||||
|
||||
$sut->method( 'get_current_posts' )->willReturn( $posts );
|
||||
$sut->method( 'get_layered_nav_chosen_attributes_inst' )->willReturn( $nav_filtering_data );
|
||||
|
||||
return array( $sut, $products );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $with_nav_filtering_data Should WC_Query::get_layered_nav_chosen_attributes return filtering data?.
|
||||
* @param bool $use_objects If true, get_current_posts will return objects with an ID property; if false, it will returns the ids.
|
||||
*
|
||||
* @testdox adjust_posts should return the number of visible products and create product visibility loop variables
|
||||
* @testWith [true, true]
|
||||
* [false, false]
|
||||
* [true, false]
|
||||
* [false, true]
|
||||
*/
|
||||
public function test_adjust_posts_count_with_nav_filtering_attributes( $with_nav_filtering_data, $use_objects ) {
|
||||
list($sut, $products) = $this->setup_adjust_posts_test( $with_nav_filtering_data, $use_objects );
|
||||
|
||||
$products[0]->set_stock_status( 'outofstock' );
|
||||
$products[0]->save();
|
||||
$products[1]->set_stock_status( 'outofstock' );
|
||||
$products[1]->save();
|
||||
|
||||
$this->assertEquals( 3, $sut->adjust_posts_count( 34 ) );
|
||||
$this->assertEquals( 3, wc_get_loop_prop( 'total' ) );
|
||||
$this->assertEquals( false, wc_get_loop_product_visibility( $products[0]->get_id() ) );
|
||||
$this->assertEquals( false, wc_get_loop_product_visibility( $products[1]->get_id() ) );
|
||||
foreach ( array_slice( $products, 2 ) as $product ) {
|
||||
$this->assertEquals( true, wc_get_loop_product_visibility( $product->get_id() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox adjust_posts should return the input unmodified if get_current_posts returns null.
|
||||
*/
|
||||
public function test_adjust_posts_count_when_there_are_no_posts() {
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Query::class )
|
||||
->setMethods( array( 'get_current_posts', 'get_layered_nav_chosen_attributes_inst' ) )
|
||||
->getMock();
|
||||
|
||||
$sut->method( 'get_current_posts' )->willReturn( null );
|
||||
|
||||
$this->assertEquals( 34, $sut->adjust_posts_count( 34 ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Data Store Tests for variable products: WC_Product_Variable_Data_Store.
|
||||
*
|
||||
* @package WooCommerce\Tests\Product
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Tests_Product_Variable_Data_Store
|
||||
*/
|
||||
class WC_Tests_Product_Variable_Data_Store extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* @testdox Test that "delete" on a variation removes the associated attribute terms too.
|
||||
*/
|
||||
public function test_attribute_terms_are_deleted_for_deleted_variations() {
|
||||
$product = WC_Helper_Product::create_variation_product();
|
||||
$variation = wc_get_product( $product->get_children()[2] );
|
||||
$variation_id = $variation->get_id();
|
||||
|
||||
$sut = new WC_Product_Variable_Data_Store_CPT();
|
||||
$sut->delete_variations( $product->get_id(), true );
|
||||
|
||||
$attribute_names = wc_get_attribute_taxonomy_names();
|
||||
$variation_attribute_terms = wp_get_post_terms( $variation_id, $attribute_names );
|
||||
|
||||
$this->assertEmpty( $variation_attribute_terms );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/**
|
||||
* Data Store Tests for product variations: WC_Product_Variation_Data_Store.
|
||||
*
|
||||
* @package WooCommerce\Tests\Product
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Tests_Product_Variation_Data_Store
|
||||
*/
|
||||
class WC_Tests_Product_Variation_Data_Store extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Create and save a variable product with size and category attributes, then create a corresponding
|
||||
* variation object with size "small" and color "red", and return it without saving it to database.
|
||||
*
|
||||
* @return WC_Product_Variation The created variation object.
|
||||
*/
|
||||
private function create_variation_object_for_existing_variable_product() {
|
||||
$attr_size = WC_Helper_Product::create_product_attribute_object( 'size', array( 'small', 'large' ) );
|
||||
$attr_color = WC_Helper_Product::create_product_attribute_object( 'color', array( 'red', 'blue' ) );
|
||||
|
||||
$product = new WC_Product_Variable();
|
||||
$product->set_attributes( array( $attr_size, $attr_color ) );
|
||||
$product->save();
|
||||
|
||||
$variation = WC_Helper_Product::create_product_variation_object(
|
||||
$product->get_id(),
|
||||
'SMALL RED THING',
|
||||
10,
|
||||
array(
|
||||
'pa_size' => 'small',
|
||||
'pa_color' => 'red',
|
||||
)
|
||||
);
|
||||
|
||||
return $variation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a simplified list with the attribute terms for a variation object.
|
||||
*
|
||||
* @param int $variation_id Id of the variation product.
|
||||
*
|
||||
* @return array Attributes as an "attribute"=>"term" associative array.
|
||||
*/
|
||||
private function get_attribute_terms_for_variation( $variation_id ) {
|
||||
$attribute_names = wc_get_attribute_taxonomy_names();
|
||||
$variation_attribute_terms = wp_get_post_terms( $variation_id, $attribute_names );
|
||||
$terms = array();
|
||||
foreach ( $variation_attribute_terms as $term ) {
|
||||
$terms[ $term->taxonomy ] = $term->name;
|
||||
}
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that attribute terms are created for new variations.
|
||||
*/
|
||||
public function test_attribute_terms_are_created_for_new_variations() {
|
||||
$variation = $this->create_variation_object_for_existing_variable_product();
|
||||
|
||||
$sut = new WC_Product_Variation_Data_Store_CPT();
|
||||
$sut->create( $variation );
|
||||
|
||||
$terms = $this->get_attribute_terms_for_variation( $variation->get_id() );
|
||||
|
||||
$expected = array(
|
||||
'pa_size' => 'small',
|
||||
'pa_color' => 'red',
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $terms );
|
||||
|
||||
$variation->set_attributes(
|
||||
array(
|
||||
'pa_size' => 'large',
|
||||
'pa_color' => 'blue',
|
||||
)
|
||||
);
|
||||
|
||||
$sut->update( $variation );
|
||||
|
||||
$terms = $this->get_attribute_terms_for_variation( $variation->get_id() );
|
||||
|
||||
$expected = array(
|
||||
'pa_size' => 'large',
|
||||
'pa_color' => 'blue',
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $terms );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that attribute terms are updated for updated variations.
|
||||
*/
|
||||
public function test_attribute_terms_are_updated_for_modified_variations() {
|
||||
$variation = $this->create_variation_object_for_existing_variable_product();
|
||||
|
||||
$sut = new WC_Product_Variation_Data_Store_CPT();
|
||||
$sut->create( $variation );
|
||||
|
||||
$new_attributes = array(
|
||||
'pa_size' => 'small',
|
||||
'pa_color' => 'red',
|
||||
);
|
||||
$variation->set_attributes( $new_attributes );
|
||||
$sut->update( $variation );
|
||||
|
||||
$terms = $this->get_attribute_terms_for_variation( $variation->get_id() );
|
||||
|
||||
$this->assertEquals( $new_attributes, $terms );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that attribute terms are removed for variations updated with "Any" value.
|
||||
*/
|
||||
public function test_attribute_terms_are_removed_for_variations_set_to_any_attribute_value() {
|
||||
$variation = $this->create_variation_object_for_existing_variable_product();
|
||||
|
||||
$sut = new WC_Product_Variation_Data_Store_CPT();
|
||||
$sut->create( $variation );
|
||||
|
||||
$new_attributes = array(
|
||||
'pa_size' => 'small',
|
||||
'pa_color' => '',
|
||||
);
|
||||
$variation->set_attributes( $new_attributes );
|
||||
$sut->update( $variation );
|
||||
|
||||
$terms = $this->get_attribute_terms_for_variation( $variation->get_id() );
|
||||
|
||||
$expected = array( 'pa_size' => 'small' );
|
||||
|
||||
$this->assertEquals( $expected, $terms );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,479 @@
|
|||
<?php
|
||||
/**
|
||||
* Testing WC_Widget_Layered_Nav functionality.
|
||||
*
|
||||
* @package WooCommerce/Tests/Widgets
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for testing WC_Widget_Layered_Nav functionality.
|
||||
*/
|
||||
class WC_Tests_Widget_Layered_Nav extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Get an instance of the tested widget, and simulate filtering in the incoming request.
|
||||
*
|
||||
* @param string $filter_operation Operation supplied in the filter, 'or' or 'and'.
|
||||
* @param array $filter_colors Slugs of the colors supplied in the filters.
|
||||
* @param array $filter_styles Slugs of the styles supplied in the filters.
|
||||
*
|
||||
* @return WC_Widget_Layered_Nav An instance of WC_Widget_Layered_Nav ready to test.
|
||||
*/
|
||||
private function get_widget( $filter_operation, $filter_colors = array(), $filter_styles = array() ) {
|
||||
$tax_query = array(
|
||||
'relation' => 'and',
|
||||
0 => array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'terms' => array(
|
||||
get_term_by( 'slug', 'outofstock', 'product_visibility' )->term_taxonomy_id,
|
||||
get_term_by( 'slug', 'exclude-from-catalog', 'product_visibility' )->term_taxonomy_id,
|
||||
),
|
||||
'field' => 'term_taxonomy_id',
|
||||
'operator' => 'NOT IN',
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! empty( $filter_colors ) ) {
|
||||
array_push(
|
||||
$tax_query,
|
||||
array(
|
||||
'taxonomy' => 'pa_color',
|
||||
'terms' => $filter_colors,
|
||||
'field' => 'slug',
|
||||
'operator' => $filter_operation,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $filter_styles ) ) {
|
||||
array_push(
|
||||
$tax_query,
|
||||
array(
|
||||
'taxonomy' => 'pa_style',
|
||||
'terms' => $filter_styles,
|
||||
'field' => 'slug',
|
||||
'operator' => $filter_operation,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$sut = $this
|
||||
->getMockBuilder( WC_Widget_Layered_Nav::class )
|
||||
->setMethods( array( 'get_main_tax_query', 'get_main_meta_query', 'get_main_search_query_sql' ) )
|
||||
->getMock();
|
||||
|
||||
$sut->method( 'get_main_tax_query' )->willReturn( $tax_query );
|
||||
$sut->method( 'get_main_meta_query' )->willReturn( array() );
|
||||
$sut->method( 'get_main_search_query_sql' )->willReturn( null );
|
||||
|
||||
return $sut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple or variable product that has color attributes.
|
||||
* If a variable product is created, a variation will be created for each color.
|
||||
*
|
||||
* @param string $name Name of the product.
|
||||
* @param array $colors_in_stock Slugs of the colors whose variations will have stock. If null, a simple product is created.
|
||||
* @param array $colors_disabled Slugs of the colors whose variations will be disabled, N/A for a simple product.
|
||||
* @param array $styles Array where the key is the colors and the value is the style that will have the variation for that color.
|
||||
*
|
||||
* @return WC_Product_Simple|WC_Product_Variable The created product.
|
||||
*/
|
||||
private function create_colored_product( $name, $colors_in_stock, $colors_disabled = array(), $styles = array() ) {
|
||||
$create_as_simple = is_null( $colors_in_stock );
|
||||
$main_product = $create_as_simple ? new WC_Product_Simple() : new WC_Product_Variable();
|
||||
|
||||
$main_product->set_props(
|
||||
array(
|
||||
'name' => $name,
|
||||
'sku' => 'SKU for' . $name,
|
||||
)
|
||||
);
|
||||
|
||||
$existing_colors = array( 'black', 'brown', 'blue', 'green', 'pink', 'yellow' );
|
||||
$existing_styles = array( 'classic', 'sport' );
|
||||
$attributes = array(
|
||||
WC_Helper_Product::create_product_attribute_object( 'color', $existing_colors ),
|
||||
WC_Helper_Product::create_product_attribute_object( 'style', $existing_styles ),
|
||||
);
|
||||
$main_product->set_attributes( $attributes );
|
||||
$main_product->save();
|
||||
|
||||
if ( $create_as_simple ) {
|
||||
return $main_product;
|
||||
}
|
||||
|
||||
$variation_objects = array();
|
||||
foreach ( $existing_colors as $color ) {
|
||||
$variation_attributes = array(
|
||||
'pa_color' => $color,
|
||||
'pa_style' => array_key_exists( $color, $styles ) ? $styles[ $color ] : '',
|
||||
);
|
||||
$variation_object = WC_Helper_Product::create_product_variation_object(
|
||||
$main_product->get_id(),
|
||||
"SKU for $color $name",
|
||||
10,
|
||||
$variation_attributes
|
||||
);
|
||||
if ( ! in_array( $color, $colors_in_stock, true ) ) {
|
||||
$variation_object->set_stock_status( 'outofstock' );
|
||||
}
|
||||
$variation_object->save();
|
||||
|
||||
if ( in_array( $color, $colors_disabled, true ) ) {
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $variation_object->get_id(),
|
||||
'post_status' => 'draft',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
array_push( $variation_objects, $variation_object->get_id() );
|
||||
}
|
||||
|
||||
$main_product->set_children( $variation_objects );
|
||||
|
||||
return $main_product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a protected method in an object.
|
||||
*
|
||||
* @param object $object Object whose method will be invoked.
|
||||
* @param string $method Name of the method to invoke.
|
||||
* @param array $args Arguments for the method.
|
||||
*
|
||||
* @return mixed Result from the method invocation.
|
||||
* @throws ReflectionException Error when dealing with reflection.
|
||||
*/
|
||||
private function invoke_protected( $object, $method, $args ) {
|
||||
$class = new ReflectionClass( $object );
|
||||
$method = $class->getMethod( $method );
|
||||
$method->setAccessible( true );
|
||||
return $method->invokeArgs( $object, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the get_filtered_term_product_counts method on an instance the widget,
|
||||
* for a given filtering request, and returns the resulting counts.
|
||||
*
|
||||
* @param string $operator Operator in the filtering request.
|
||||
* @param array $colors Slugs of the colors included in the filtering request.
|
||||
* @param array $styles Array where the key is the colors and the value is the style that will have the variation for that color.
|
||||
*
|
||||
* @return array An associative array where the keys are the color slugs and the values are the counts for each color.
|
||||
* @throws ReflectionException Error when dealing with reflection to invoke the method.
|
||||
*/
|
||||
private function run_get_filtered_term_product_counts( $operator, $colors, $styles = array() ) {
|
||||
$sut = $this->get_widget( $operator, $colors, $styles );
|
||||
|
||||
$color_terms = get_terms( 'pa_color', array( 'hide_empty' => '1' ) );
|
||||
$color_term_ids = wp_list_pluck( $color_terms, 'term_id' );
|
||||
$color_term_names = wp_list_pluck( $color_terms, 'slug' );
|
||||
$color_names_by_id = array_combine( $color_term_ids, $color_term_names );
|
||||
|
||||
$counts = $this->invoke_protected(
|
||||
$sut,
|
||||
'get_filtered_term_product_counts',
|
||||
array(
|
||||
$color_term_ids,
|
||||
'pa_color',
|
||||
$operator,
|
||||
)
|
||||
);
|
||||
|
||||
$counts_by_name = array();
|
||||
foreach ( $counts as $id => $count ) {
|
||||
$counts_by_name[ $color_names_by_id[ $id ] ] = $count;
|
||||
}
|
||||
|
||||
return $counts_by_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the status of a post to 'draft'.
|
||||
*
|
||||
* @param int $post_id Id of the post to change.
|
||||
*/
|
||||
private function set_post_as_draft( $post_id ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query( 'update ' . $wpdb->posts . " set post_status='draft' where ID=" . $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_product_count_per_attribute.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_provider_for_test_product_count_per_attribute() {
|
||||
return array(
|
||||
// OR filtering, no attributes selected.
|
||||
// Should count all the visible variations of all the products.
|
||||
array(
|
||||
'or',
|
||||
array(),
|
||||
false,
|
||||
array(
|
||||
'black' => 1,
|
||||
'brown' => 2,
|
||||
'blue' => 2,
|
||||
'green' => 2,
|
||||
'pink' => 1,
|
||||
'yellow' => 1,
|
||||
),
|
||||
),
|
||||
|
||||
// OR filtering, some attributes selected
|
||||
// (doesn't matter, the result is the same as in the previous case).
|
||||
array(
|
||||
'or',
|
||||
array( 'black', 'green' ),
|
||||
false,
|
||||
array(
|
||||
'black' => 1,
|
||||
'brown' => 2,
|
||||
'blue' => 2,
|
||||
'green' => 2,
|
||||
'pink' => 1,
|
||||
'yellow' => 1,
|
||||
),
|
||||
),
|
||||
|
||||
// OR filtering, no attributes selected. Simple product is created too.
|
||||
// Now it should include all the attributes of the simple product too.
|
||||
array(
|
||||
'or',
|
||||
array(),
|
||||
true,
|
||||
array(
|
||||
'black' => 2,
|
||||
'brown' => 3,
|
||||
'blue' => 3,
|
||||
'green' => 3,
|
||||
'pink' => 2,
|
||||
'yellow' => 2,
|
||||
),
|
||||
),
|
||||
|
||||
// OR filtering, some attributes selected, Simple product is created too.
|
||||
// Again, the attributes selected don't change the result.
|
||||
array(
|
||||
'or',
|
||||
array( 'black', 'green' ),
|
||||
true,
|
||||
array(
|
||||
'black' => 2,
|
||||
'brown' => 3,
|
||||
'blue' => 3,
|
||||
'green' => 3,
|
||||
'pink' => 2,
|
||||
'yellow' => 2,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, no attributes selected.
|
||||
// Should count all the visible variations of all the products as in the 'or' case.
|
||||
array(
|
||||
'and',
|
||||
array(),
|
||||
false,
|
||||
array(
|
||||
'black' => 1,
|
||||
'brown' => 2,
|
||||
'blue' => 2,
|
||||
'green' => 2,
|
||||
'pink' => 1,
|
||||
'yellow' => 1,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, one attribute selected.
|
||||
// Should still count the visible variations for all products as in the 'or' case.
|
||||
array(
|
||||
'and',
|
||||
array( 'green' ),
|
||||
false,
|
||||
array(
|
||||
'black' => 1,
|
||||
'brown' => 2,
|
||||
'blue' => 2,
|
||||
'green' => 2,
|
||||
'pink' => 1,
|
||||
'yellow' => 1,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, more than one attribute selected.
|
||||
// Should still count the visible variations for all products as in the 'or' case.
|
||||
array(
|
||||
'and',
|
||||
array( 'green', 'pink' ),
|
||||
false,
|
||||
array(
|
||||
'black' => 1,
|
||||
'brown' => 2,
|
||||
'blue' => 2,
|
||||
'green' => 2,
|
||||
'pink' => 1,
|
||||
'yellow' => 1,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, no attributes selected, include simple product too.
|
||||
// Same case as 'or': it should include all the attributes of the simple product too.
|
||||
array(
|
||||
'and',
|
||||
array(),
|
||||
true,
|
||||
array(
|
||||
'black' => 2,
|
||||
'brown' => 3,
|
||||
'blue' => 3,
|
||||
'green' => 3,
|
||||
'pink' => 2,
|
||||
'yellow' => 2,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, select one attribute, include simple product too.
|
||||
// Again, the simple product is now included in all counters, since it has the selected attribute.
|
||||
array(
|
||||
'and',
|
||||
array( 'green' ),
|
||||
true,
|
||||
array(
|
||||
'black' => 2,
|
||||
'brown' => 3,
|
||||
'blue' => 3,
|
||||
'green' => 3,
|
||||
'pink' => 2,
|
||||
'yellow' => 2,
|
||||
),
|
||||
),
|
||||
|
||||
// AND filtering, select a couple of attributes, include simple product too.
|
||||
// The simple product is still included too in all counters, since it has all of the selected attributes.
|
||||
array(
|
||||
'and',
|
||||
array( 'green', 'pink' ),
|
||||
true,
|
||||
array(
|
||||
'black' => 2,
|
||||
'brown' => 3,
|
||||
'blue' => 3,
|
||||
'green' => 3,
|
||||
'pink' => 2,
|
||||
'yellow' => 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that the counters are correct for different filtering combinations, see the data provider method for details.
|
||||
*
|
||||
* @dataProvider data_provider_for_test_product_count_per_attribute
|
||||
*
|
||||
* @param string $filter_operator Filtering operator to use, 'or' or 'and'.
|
||||
* @param array $filter_terms Slugs of the colors selected for filtering.
|
||||
* @param bool $create_simple_product_too If true, create one simple product too. If false, create only the variable products.
|
||||
* @param array $expected_counts An associative array where the keys are the color slugs and the values are the counts for each color.
|
||||
*/
|
||||
public function test_product_count_per_attribute( $filter_operator, $filter_terms, $create_simple_product_too, $expected_counts ) {
|
||||
if ( $create_simple_product_too ) {
|
||||
$this->create_colored_product( 'Something with many colors', null );
|
||||
}
|
||||
$this->create_colored_product( 'Big shoes', array( 'black', 'brown' ) );
|
||||
$this->create_colored_product( 'Medium shoes', array( 'blue', 'brown' ) );
|
||||
$this->create_colored_product( 'Small shoes', array( 'blue', 'green' ) );
|
||||
$this->create_colored_product( 'Kids shoes', array( 'green', 'pink', 'yellow' ) );
|
||||
|
||||
$counts = $this->run_get_filtered_term_product_counts( $filter_operator, $filter_terms );
|
||||
$this->assertEquals( $expected_counts, $counts );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox Test that the counters are correct when using more than one filter simultaneously.
|
||||
*
|
||||
*/
|
||||
public function test_product_count_per_multiple_attributes() {
|
||||
$this->create_colored_product( 'Big shoes', array( 'black', 'brown' ) );
|
||||
$this->create_colored_product(
|
||||
'Medium shoes',
|
||||
array( 'blue', 'brown' ),
|
||||
array(),
|
||||
array(
|
||||
'blue' => 'sport',
|
||||
'brown' => 'classic',
|
||||
)
|
||||
);
|
||||
$this->create_colored_product(
|
||||
'Small shoes',
|
||||
array( 'blue', 'green' ),
|
||||
array(),
|
||||
array(
|
||||
'blue' => 'classic',
|
||||
'green' => 'sport',
|
||||
)
|
||||
);
|
||||
$this->create_colored_product(
|
||||
'Kids shoes',
|
||||
array( 'green', 'pink', 'yellow', 'blue' ),
|
||||
array(),
|
||||
array(
|
||||
'green' => 'classic',
|
||||
'blue' => 'classic',
|
||||
)
|
||||
);
|
||||
|
||||
$counts = $this->run_get_filtered_term_product_counts( 'IN', array( 'blue' ), array( 'classic' ) );
|
||||
$expected_counts = array(
|
||||
'brown' => 1,
|
||||
'blue' => 2,
|
||||
'green' => 1,
|
||||
);
|
||||
$this->assertEquals( $expected_counts, $counts );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox When a variable product is not published, none of its variations should be included in the counts.
|
||||
*
|
||||
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
|
||||
*/
|
||||
public function test_product_count_per_attribute_with_parent_not_published() {
|
||||
$this->create_colored_product( 'Big shoes', array( 'black', 'brown' ) );
|
||||
$medium = $this->create_colored_product( 'Medium shoes', array( 'blue', 'brown' ) );
|
||||
$this->set_post_as_draft( $medium->get_id() );
|
||||
|
||||
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
|
||||
|
||||
$expected = array(
|
||||
'black' => 1,
|
||||
'brown' => 1,
|
||||
);
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox When a variation is not published it should not be included in the counts (but other variations of the same product should).
|
||||
*
|
||||
* @throws ReflectionException Error when dealing with reflection to invoke the tested method.
|
||||
*/
|
||||
public function test_product_count_per_attribute_with_variation_not_published() {
|
||||
$this->create_colored_product( 'Big shoes', array( 'black', 'brown' ) );
|
||||
$this->create_colored_product( 'Medium shoes', array( 'blue', 'brown' ), array( 'brown' ) );
|
||||
|
||||
$actual = $this->run_get_filtered_term_product_counts( 'or', array() );
|
||||
|
||||
$expected = array(
|
||||
'black' => 1,
|
||||
'brown' => 1,
|
||||
'blue' => 1,
|
||||
);
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue