Merge pull request #12798 from woocommerce/orphan-variations-fix-12614

Orphan variations fix
This commit is contained in:
Mike Jolley 2017-01-11 16:23:14 +00:00 committed by GitHub
commit 91c68d211f
8 changed files with 205 additions and 93 deletions

View File

@ -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

View File

@ -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' ),
);

View File

@ -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 );
}
}

View File

@ -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 );
}
}
/**

View File

@ -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 );
}
}

View File

@ -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 );
}

View File

@ -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.
*