Merge pull request #12798 from woocommerce/orphan-variations-fix-12614
Orphan variations fix
This commit is contained in:
commit
91c68d211f
|
@ -114,12 +114,6 @@ jQuery( function( $ ) {
|
|||
|
||||
}).change();
|
||||
|
||||
$( document.body ).on( 'woocommerce-product-type-change', function( e, select_val ) {
|
||||
if ( 'variable' !== select_val && 0 < $( '#variable_product_options' ).find( 'input[name^=variable_sku]' ).length && $( document.body ).triggerHandler( 'woocommerce-display-product-type-alert', select_val ) !== false ) {
|
||||
window.alert( woocommerce_admin_meta_boxes.i18n_product_type_alert );
|
||||
}
|
||||
});
|
||||
|
||||
$( 'input#_downloadable, input#_virtual' ).change( function() {
|
||||
show_and_hide_panels();
|
||||
});
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -285,7 +285,6 @@ class WC_Admin_Assets {
|
|||
'i18n_download_permission_fail' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'woocommerce' ),
|
||||
'i18n_permission_revoke' => __( 'Are you sure you want to revoke access to this download?', 'woocommerce' ),
|
||||
'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),
|
||||
'i18n_product_type_alert' => __( 'Your product has variations! Before changing the product type, it is a good idea to delete the variations to avoid errors in the stock reports.', 'woocommerce' ),
|
||||
'i18n_delete_note' => __( 'Are you sure you wish to delete this note? This action cannot be undone.', 'woocommerce' ),
|
||||
);
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ class WC_Post_Data {
|
|||
add_action( 'transition_post_status', array( __CLASS__, 'transition_post_status' ), 10, 3 );
|
||||
add_action( 'woocommerce_product_set_stock_status', array( __CLASS__, 'delete_product_query_transients' ) );
|
||||
add_action( 'woocommerce_product_set_visibility', array( __CLASS__, 'delete_product_query_transients' ) );
|
||||
add_action( 'woocommerce_product_type_changed', array( __CLASS__, 'product_type_changed' ), 10, 3 );
|
||||
|
||||
add_action( 'edit_term', array( __CLASS__, 'edit_term' ), 10, 3 );
|
||||
add_action( 'edited_term', array( __CLASS__, 'edited_term' ), 10, 3 );
|
||||
|
@ -117,6 +118,22 @@ class WC_Post_Data {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle type changes.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param WC_Product $product
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
*/
|
||||
public static function product_type_changed( $product, $from, $to ) {
|
||||
if ( 'variable' === $from ) {
|
||||
// If the product is no longer variable, we should ensure all variations are removed.
|
||||
$data_store = WC_Data_Store::load( 'product-variable' );
|
||||
$data_store->delete_variations( $product );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When editing a term, check for product attributes.
|
||||
* @param id $term_id
|
||||
|
@ -239,7 +256,6 @@ class WC_Post_Data {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -249,56 +265,35 @@ class WC_Post_Data {
|
|||
* @param mixed $id ID of post being deleted
|
||||
*/
|
||||
public static function delete_post( $id ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! current_user_can( 'delete_posts' ) ) {
|
||||
if ( ! current_user_can( 'delete_posts' ) || ! $id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$post_type = get_post_type( $id );
|
||||
|
||||
$post_type = get_post_type( $id );
|
||||
|
||||
switch ( $post_type ) {
|
||||
case 'product' :
|
||||
|
||||
$child_product_variations = get_children( 'post_parent=' . $id . '&post_type=product_variation' );
|
||||
|
||||
if ( ! empty( $child_product_variations ) ) {
|
||||
foreach ( $child_product_variations as $child ) {
|
||||
wp_delete_post( $child->ID, true );
|
||||
}
|
||||
}
|
||||
|
||||
$child_products = get_children( 'post_parent=' . $id . '&post_type=product' );
|
||||
|
||||
if ( ! empty( $child_products ) ) {
|
||||
foreach ( $child_products as $child ) {
|
||||
$child_post = array();
|
||||
$child_post['ID'] = $child->ID;
|
||||
$child_post['post_parent'] = 0;
|
||||
wp_update_post( $child_post );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $parent_id = wp_get_post_parent_id( $id ) ) {
|
||||
wc_delete_product_transients( $parent_id );
|
||||
}
|
||||
switch ( $post_type ) {
|
||||
case 'product' :
|
||||
$data_store = WC_Data_Store::load( 'product-variable' );
|
||||
$data_store->delete_variations( $id, true );
|
||||
|
||||
if ( $parent_id = wp_get_post_parent_id( $id ) ) {
|
||||
wc_delete_product_transients( $parent_id );
|
||||
}
|
||||
break;
|
||||
case 'product_variation' :
|
||||
wc_delete_product_transients( wp_get_post_parent_id( $id ) );
|
||||
case 'product_variation' :
|
||||
wc_delete_product_transients( wp_get_post_parent_id( $id ) );
|
||||
break;
|
||||
case 'shop_order' :
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
case 'shop_order' :
|
||||
global $wpdb;
|
||||
|
||||
if ( ! is_null( $refunds ) ) {
|
||||
foreach ( $refunds as $refund ) {
|
||||
wp_delete_post( $refund->ID, true );
|
||||
}
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
|
||||
if ( ! is_null( $refunds ) ) {
|
||||
foreach ( $refunds as $refund ) {
|
||||
wp_delete_post( $refund->ID, true );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,22 +303,28 @@ class WC_Post_Data {
|
|||
* @param mixed $id
|
||||
*/
|
||||
public static function trash_post( $id ) {
|
||||
global $wpdb;
|
||||
if ( ! $id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$post_type = get_post_type( $id );
|
||||
|
||||
$post_type = get_post_type( $id );
|
||||
// If this is an order, trash any refunds too.
|
||||
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
|
||||
foreach ( $refunds as $refund ) {
|
||||
$wpdb->update( $wpdb->posts, array( 'post_status' => 'trash' ), array( 'ID' => $refund->ID ) );
|
||||
}
|
||||
|
||||
delete_transient( 'woocommerce_processing_order_count' );
|
||||
wc_delete_shop_order_transients( $id );
|
||||
foreach ( $refunds as $refund ) {
|
||||
$wpdb->update( $wpdb->posts, array( 'post_status' => 'trash' ), array( 'ID' => $refund->ID ) );
|
||||
}
|
||||
|
||||
wc_delete_shop_order_transients( $id );
|
||||
|
||||
// If this is a product, trash children variations.
|
||||
} elseif ( 'product' === $post_type ) {
|
||||
$data_store = WC_Data_Store::load( 'product-variable' );
|
||||
$data_store->delete_variations( $id, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,32 +334,28 @@ class WC_Post_Data {
|
|||
* @param mixed $id
|
||||
*/
|
||||
public static function untrash_post( $id ) {
|
||||
global $wpdb;
|
||||
if ( ! $id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $id > 0 ) {
|
||||
$post_type = get_post_type( $id );
|
||||
|
||||
$post_type = get_post_type( $id );
|
||||
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( in_array( $post_type, wc_get_order_types( 'order-count' ) ) ) {
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
|
||||
$refunds = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order_refund' AND post_parent = %d", $id ) );
|
||||
|
||||
foreach ( $refunds as $refund ) {
|
||||
$wpdb->update( $wpdb->posts, array( 'post_status' => 'wc-completed' ), array( 'ID' => $refund->ID ) );
|
||||
}
|
||||
|
||||
delete_transient( 'woocommerce_processing_order_count' );
|
||||
wc_delete_shop_order_transients( $id );
|
||||
} elseif ( 'product' === $post_type ) {
|
||||
// Check if SKU is valid before untrash the product.
|
||||
$sku = get_post_meta( $id, '_sku', true );
|
||||
|
||||
if ( ! empty( $sku ) ) {
|
||||
if ( ! wc_product_has_unique_sku( $id, $sku ) ) {
|
||||
update_post_meta( $id, '_sku', '' );
|
||||
}
|
||||
}
|
||||
foreach ( $refunds as $refund ) {
|
||||
$wpdb->update( $wpdb->posts, array( 'post_status' => 'wc-completed' ), array( 'ID' => $refund->ID ) );
|
||||
}
|
||||
|
||||
wc_delete_shop_order_transients( $id );
|
||||
|
||||
} elseif ( 'product' === $post_type ) {
|
||||
$data_store = WC_Data_Store::load( 'product-variable' );
|
||||
$data_store->untrash_variations( $id );
|
||||
|
||||
wc_product_force_unique_sku( $id );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -586,8 +586,16 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
|||
* @since 2.7.0
|
||||
*/
|
||||
protected function update_version_and_type( &$product ) {
|
||||
wp_set_object_terms( $product->get_id(), $product->get_type(), 'product_type' );
|
||||
$old_type = WC_Product_Factory::get_product_type( $product->get_id() );
|
||||
$new_type = $product->get_type();
|
||||
|
||||
wp_set_object_terms( $product->get_id(), $new_type, 'product_type' );
|
||||
update_post_meta( $product->get_id(), '_product_version', WC_VERSION );
|
||||
|
||||
// Action for the transition.
|
||||
if ( $old_type !== $new_type ) {
|
||||
do_action( 'woocommerce_product_type_changed', $product, $old_type, $new_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,17 +28,20 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
parent::read_product_data( $product );
|
||||
|
||||
// Set directly since individual data needs changed at the WC_Product_Variation level -- these datasets just pull.
|
||||
$this->read_children( $product );
|
||||
$this->read_variation_attributes( $product );
|
||||
$children = $this->read_children( $product );
|
||||
$product->set_children( $children['all'] );
|
||||
$product->set_visible_children( $children['visible'] );
|
||||
$product->set_variation_attributes( $this->read_variation_attributes( $product ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads variation child IDs.
|
||||
*
|
||||
* @param WC_Product
|
||||
* @param bool $force_read True to bypass the transient.
|
||||
* @return WC_Product
|
||||
* @return array
|
||||
*/
|
||||
public function read_children( &$product, $force_read = false ) {
|
||||
private function read_children( &$product, $force_read = false ) {
|
||||
$children_transient_name = 'wc_product_children_' . $product->get_id();
|
||||
$children = get_transient( $children_transient_name );
|
||||
|
||||
|
@ -65,14 +68,17 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
set_transient( $children_transient_name, $children, DAY_IN_SECONDS * 30 );
|
||||
}
|
||||
|
||||
$product->set_children( wp_parse_id_list( (array) $children['all'] ) );
|
||||
$product->set_visible_children( wp_parse_id_list( (array) $children['visible'] ) );
|
||||
$children['all'] = wp_parse_id_list( (array) $children['all'] );
|
||||
$children['visible'] = wp_parse_id_list( (array) $children['visible'] );
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an array of attributes used for variations, as well as their possible values.
|
||||
*
|
||||
* @param WC_Product
|
||||
* @return array
|
||||
*/
|
||||
private function read_variation_attributes( &$product ) {
|
||||
global $wpdb;
|
||||
|
@ -122,7 +128,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
}
|
||||
}
|
||||
|
||||
$product->set_variation_attributes( $variation_attributes );
|
||||
return $variation_attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -277,7 +283,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
public function child_is_in_stock( $product ) {
|
||||
global $wpdb;
|
||||
$children = $product->get_visible_children( 'edit' );
|
||||
$oufofstock_children = $wpdb->get_var( "SELECT COUNT( post_id ) FROM $wpdb->postmeta WHERE meta_key = '_stock_status' AND meta_value = 'instock' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" );
|
||||
$oufofstock_children = $children ? $wpdb->get_var( "SELECT COUNT( post_id ) FROM $wpdb->postmeta WHERE meta_key = '_stock_status' AND meta_value = 'instock' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . " )" ) : 0;
|
||||
return $children > $oufofstock_children;
|
||||
}
|
||||
|
||||
|
@ -328,7 +334,9 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
}
|
||||
}
|
||||
if ( $changed ) {
|
||||
$this->read_children( $product, true );
|
||||
$children = $this->read_children( $product, true );
|
||||
$product->set_children( $children['all'] );
|
||||
$product->set_visible_children( $children['visible'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -366,4 +374,56 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
|
|||
public function sync_stock_status( &$product ) {
|
||||
$product->set_stock_status( $product->child_is_in_stock() ? 'instock' : 'outofstock' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete variations of a product.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param int $product_id
|
||||
* @param $force_delete False to trash.
|
||||
*/
|
||||
public function delete_variations( $product_id, $force_delete = false ) {
|
||||
$variation_ids = wp_parse_id_list( get_posts( array(
|
||||
'post_parent' => $product_id,
|
||||
'post_type' => 'product_variation',
|
||||
'fields' => 'ids',
|
||||
'post_status' => 'any',
|
||||
'numberposts' => -1,
|
||||
) ) );
|
||||
|
||||
if ( ! empty( $variation_ids ) ) {
|
||||
foreach ( $variation_ids as $variation_id ) {
|
||||
if ( $force_delete ) {
|
||||
wp_delete_post( $variation_id, true );
|
||||
} else {
|
||||
wp_trash_post( $variation_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete_transient( 'wc_product_children_' . $product_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Untrash variations.
|
||||
*
|
||||
* @param int $product_id
|
||||
*/
|
||||
public function untrash_variations( $product_id ) {
|
||||
$variation_ids = wp_parse_id_list( get_posts( array(
|
||||
'post_parent' => $product_id,
|
||||
'post_type' => 'product_variation',
|
||||
'fields' => 'ids',
|
||||
'post_status' => 'trash',
|
||||
'numberposts' => -1,
|
||||
) ) );
|
||||
|
||||
if ( ! empty( $variation_ids ) ) {
|
||||
foreach ( $variation_ids as $variation_id ) {
|
||||
wp_untrash_post( $variation_id );
|
||||
}
|
||||
}
|
||||
|
||||
delete_transient( 'wc_product_children_' . $product_id );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,4 +61,19 @@ interface WC_Product_Variable_Data_Store_Interface {
|
|||
* @param WC_Product|int $product
|
||||
*/
|
||||
public function sync_price( &$product );
|
||||
|
||||
/**
|
||||
* Delete variations of a product.
|
||||
*
|
||||
* @param int $product_id
|
||||
* @param $force_delete False to trash.
|
||||
*/
|
||||
public function delete_variations( $product_id, $force_delete = false );
|
||||
|
||||
/**
|
||||
* Untrash variations.
|
||||
*
|
||||
* @param int $product_id
|
||||
*/
|
||||
public function untrash_variations( $product_id );
|
||||
}
|
||||
|
|
|
@ -535,8 +535,6 @@ function wc_get_product_types() {
|
|||
* @return bool
|
||||
*/
|
||||
function wc_product_has_unique_sku( $product_id, $sku ) {
|
||||
global $wpdb;
|
||||
|
||||
$data_store = WC_Data_Store::load( 'product' );
|
||||
$sku_found = $data_store->is_existing_sku( $product_id, $sku );
|
||||
|
||||
|
@ -547,6 +545,47 @@ function wc_product_has_unique_sku( $product_id, $sku ) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a unique SKU.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param integer $product_id
|
||||
*/
|
||||
function wc_product_force_unique_sku( $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( $product ) {
|
||||
try {
|
||||
$current_sku = $product->get_sku();
|
||||
$new_sku = wc_product_generate_unique_sku( $product_id, $current_sku );
|
||||
|
||||
if ( $current_sku !== $new_sku ) {
|
||||
$product->set_sku( $new_sku );
|
||||
$product->save();
|
||||
}
|
||||
} catch ( Exception $e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively appends a suffix until a unique SKU is found.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param integer $product_id
|
||||
* @param string $sku
|
||||
* @param integer $index
|
||||
* @return string
|
||||
*/
|
||||
function wc_product_generate_unique_sku( $product_id, $sku, $index = 0 ) {
|
||||
$generated_sku = 0 < $index ? $sku . '-' . $index : $sku;
|
||||
|
||||
if ( ! wc_product_has_unique_sku( $product_id, $generated_sku ) ) {
|
||||
$generated_sku = wc_product_generate_unique_sku( $product_id, $sku, ( $index + 1 ) );
|
||||
}
|
||||
|
||||
return $generated_sku;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product ID by SKU.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue