Merge pull request #13202 from woocommerce/fix-13199

Duplicate products; fix variation creation and add actions
This commit is contained in:
Mike Jolley 2017-02-16 16:18:53 +00:00 committed by GitHub
commit 2505abeb21
4 changed files with 150 additions and 87 deletions

View File

@ -94,32 +94,50 @@ class WC_Admin_Duplicate_Product {
wp_die( sprintf( __( 'Product creation failed, could not find original product: %s', 'woocommerce' ), $product_id ) );
}
// Filter to allow us to unset/remove data we don't want to copy to the duplicate. @since 2.6
$meta_to_exclude = array_filter( apply_filters( 'woocommerce_duplicate_product_exclude_meta', array() ) );
$duplicate = clone $product;
$duplicate->set_id( 0 );
$duplicate->save();
$duplicate->set_total_sales( 0 );
if ( '' !== $product->get_sku() ) {
$duplicate->set_sku( wc_product_generate_unique_sku( 0, $product->get_sku() ) );
}
$duplicate->set_status( 'draft' );
$sku = $duplicate->get_sku();
if ( '' !== $duplicate->get_sku() ) {
wc_product_force_unique_sku( $duplicate->get_id() );
foreach ( $meta_to_exclude as $meta_key ) {
$duplicate->delete_meta_data( $meta_key );
}
$exclude = apply_filters( 'woocommerce_duplicate_product_exclude_children', false );
if ( ! $exclude && ( $product->is_type( 'variable' ) || $product->is_type( 'grouped' ) ) ) {
foreach( $product->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
// This action can be used to modify the object further before it is created - it will be passed by reference. @since 2.7
do_action( 'woocommerce_product_duplicate_before_save', $duplicate, $product );
// Save parent product.
$duplicate->save();
if ( ! apply_filters( 'woocommerce_duplicate_product_exclude_children', false ) && ( $product->is_type( 'variable' ) || $product->is_type( 'grouped' ) ) ) {
foreach ( $product->get_children() as $child_id ) {
$child = wc_get_product( $child_id );
$child_duplicate = clone $child;
$child_duplicate->set_parent_id( $duplicate->get_id() );
$child_duplicate->set_id( 0 );
$child_duplicate->save();
if ( '' !== $child_duplicate->get_sku() ) {
wc_product_force_unique_sku( $child_duplicate->get_id() );
if ( '' !== $child->get_sku() ) {
$child_duplicate->set_sku( wc_product_generate_unique_sku( 0, $child->get_sku() ) );
}
foreach ( $meta_to_exclude as $meta_key ) {
$child_duplicate->delete_meta_data( $meta_key );
}
// This action can be used to modify the object further before it is created - it will be passed by reference. @since 2.7
do_action( 'woocommerce_product_duplicate_before_save', $child_duplicate, $child );
$child_duplicate->save();
}
}
// Hook rename to match other woocommerce_product_* hooks, and to move away from depending on a response from the wp_posts table.
// New hook returns new id and old id.
do_action( 'woocommerce_product_duplicate', $duplicate, $product );
wc_do_deprecated_action( 'woocommerce_duplicate_product', array( $duplicate->get_id(), $this->get_product_to_duplicate( $product_id ) ), '2.7', 'Use woocommerce_product_duplicate action instead.' );

View File

@ -98,14 +98,12 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( $id && ! is_wp_error( $id ) ) {
$product->set_id( $id );
$this->updated_props = array();
$this->update_post_meta( $product );
$this->handle_updated_props( $product );
$this->update_terms( $product );
$this->update_visibility( $product );
$this->update_attributes( $product );
$this->update_post_meta( $product, true );
$this->update_terms( $product, true );
$this->update_visibility( $product, true );
$this->update_attributes( $product, true );
$this->update_version_and_type( $product );
$this->handle_updated_props( $product );
$product->save_meta_data();
$product->apply_changes();
@ -162,24 +160,22 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( array_intersect( array( 'description', 'short_description', 'name', 'parent_id', 'reviews_allowed', 'status', 'menu_order' ), array_keys( $changes ) ) ) {
wp_update_post( array(
'ID' => $product->get_id(),
'post_content' => $product->get_description(),
'post_excerpt' => $product->get_short_description(),
'post_title' => $product->get_name(),
'post_parent' => $product->get_parent_id(),
'comment_status' => $product->get_reviews_allowed() ? 'open' : 'closed',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'menu_order' => $product->get_menu_order(),
'post_content' => $product->get_description( 'edit' ),
'post_excerpt' => $product->get_short_description( 'edit' ),
'post_title' => $product->get_name( 'edit' ),
'post_parent' => $product->get_parent_id( 'edit' ),
'comment_status' => $product->get_reviews_allowed( 'edit' ) ? 'open' : 'closed',
'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',
'menu_order' => $product->get_menu_order( 'edit' ),
) );
}
$this->updated_props = array();
$this->update_post_meta( $product );
$this->handle_updated_props( $product );
$this->update_terms( $product );
$this->update_visibility( $product );
$this->update_attributes( $product );
$this->update_version_and_type( $product );
$this->handle_updated_props( $product );
$product->save_meta_data();
$product->apply_changes();
@ -385,10 +381,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Helper method that updates all the post meta for a product based on it's settings in the WC_Product class.
*
* @param WC_Product
* @param bool Force update. Used during create.
* @since 2.7.0
*/
protected function update_post_meta( &$product ) {
protected function update_post_meta( &$product, $force = false ) {
$meta_key_to_props = array(
'_sku' => 'sku',
'_regular_price' => 'regular_price',
@ -424,11 +420,12 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
// Make sure to take extra data (like product url or text for external products) into account.
$extra_data_keys = $product->get_extra_data_keys();
foreach ( $extra_data_keys as $key ) {
$meta_key_to_props[ '_' . $key ] = $key;
}
$props_to_update = $this->get_props_to_update( $product, $meta_key_to_props );
$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props );
foreach ( $props_to_update as $meta_key => $prop ) {
$value = $product->{"get_$prop"}( 'edit' );
@ -474,19 +471,18 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
}
}
if ( $this->update_downloads( $product ) ) {
if ( $this->update_downloads( $product, $force ) ) {
$this->updated_props[] = 'downloads';
}
}
/**
* Handle updated meta props after updating meta.
* Handle updated meta props after updating meta data.
*
* @since 2.7.0
* @param WC_Product $product
*/
protected function handle_updated_props( &$product ) {
if ( in_array( 'date_on_sale_from', $this->updated_props ) || in_array( 'date_on_sale_to', $this->updated_props ) || in_array( 'regular_price', $this->updated_props ) || in_array( 'sale_price', $this->updated_props ) ) {
if ( $product->is_on_sale( 'edit' ) ) {
update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) );
@ -505,25 +501,30 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock_status' : 'woocommerce_product_set_stock_status' , $product->get_id(), $product->get_stock_status(), $product );
}
// Trigger action so 3rd parties can deal with updated props.
do_action( 'woocommerce_product_object_updated_props', $product, $this->updated_props );
// After handling, we can reset the props array.
$this->updated_props = array();
}
/**
* For all stored terms in all taxonomies, save them to the DB.
*
* @param WC_Product
* @param bool Force update. Used during create.
* @since 2.7.0
*/
protected function update_terms( &$product ) {
protected function update_terms( &$product, $force = false ) {
$changes = $product->get_changes();
if ( array_key_exists( 'category_ids', $changes ) ) {
if ( $force || array_key_exists( 'category_ids', $changes ) ) {
wp_set_post_terms( $product->get_id(), $product->get_category_ids( 'edit' ), 'product_cat', false );
}
if ( array_key_exists( 'tag_ids', $changes ) ) {
if ( $force || array_key_exists( 'tag_ids', $changes ) ) {
wp_set_post_terms( $product->get_id(), $product->get_tag_ids( 'edit' ), 'product_tag', false );
}
if ( array_key_exists( 'shipping_class_id', $changes ) ) {
if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) {
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
}
}
@ -532,12 +533,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Update visibility terms based on props.
*
* @since 2.7.0
* @param bool Force update. Used during create.
* @param WC_Product
*/
protected function update_visibility( &$product ) {
protected function update_visibility( &$product, $force = false ) {
$changes = $product->get_changes();
if ( array_intersect( array( 'featured', 'stock_status', 'average_rating', 'catalog_visibility' ), array_keys( $changes ) ) ) {
if ( $force || array_intersect( array( 'featured', 'stock_status', 'average_rating', 'catalog_visibility' ), array_keys( $changes ) ) ) {
$terms = array();
if ( $product->get_featured() ) {
@ -575,12 +577,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Update attributes which are a mix of terms and meta data.
*
* @param WC_Product
* @param bool Force update. Used during create.
* @since 2.7.0
*/
protected function update_attributes( &$product ) {
protected function update_attributes( &$product, $force = false ) {
$changes = $product->get_changes();
if ( array_key_exists( 'attributes', $changes ) ) {
if ( $force || array_key_exists( 'attributes', $changes ) ) {
$attributes = $product->get_attributes();
$meta_values = array();
@ -622,12 +625,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
*
* @since 2.7.0
* @param WC_Product $product
* @param bool Force update. Used during create.
* @return bool If updated or not.
*/
protected function update_downloads( &$product ) {
protected function update_downloads( &$product, $force = false ) {
$changes = $product->get_changes();
if ( array_key_exists( 'downloads', $changes ) ) {
if ( $force || array_key_exists( 'downloads', $changes ) ) {
$downloads = $product->get_downloads();
$meta_values = array();

View File

@ -16,10 +16,35 @@ class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implem
* Helper method that updates all the post meta for a grouped product.
*
* @param WC_Product
* @param bool Force update. Used during create.
* @since 2.7.0
*/
protected function update_post_meta( &$product ) {
if ( update_post_meta( $product->get_id(), '_children', $product->get_children( 'edit' ) ) ) {
protected function update_post_meta( &$product, $force = false ) {
$meta_key_to_props = array(
'_children' => 'children',
);
$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props );
foreach ( $props_to_update as $meta_key => $prop ) {
$value = $product->{"get_$prop"}( 'edit' );
$updated = update_post_meta( $product->get_id(), $meta_key, $value );
if ( $updated ) {
$this->updated_props[] = $prop;
}
}
parent::update_post_meta( $product );
}
/**
* Handle updated meta props after updating meta data.
*
* @since 2.7.0
* @param WC_Product $product
*/
protected function handle_updated_props( &$product ) {
if ( in_array( 'children', $this->updated_props ) ) {
$child_prices = array();
foreach ( $product->get_children( 'edit' ) as $child_id ) {
$child = wc_get_product( $child_id );
@ -34,11 +59,8 @@ class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implem
add_post_meta( $product->get_id(), '_price', min( $child_prices ) );
add_post_meta( $product->get_id(), '_price', max( $child_prices ) );
}
$this->extra_data_saved = true;
}
parent::update_post_meta( $product );
parent::handle_updated_props( $product );
}
/**

View File

@ -113,13 +113,11 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
if ( $id && ! is_wp_error( $id ) ) {
$product->set_id( $id );
$this->updated_props = array();
$this->update_post_meta( $product );
$this->update_post_meta( $product, true );
$this->update_terms( $product, true );
$this->update_attributes( $product, true );
$this->handle_updated_props( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$product->save_meta_data();
$product->apply_changes();
@ -138,23 +136,25 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
* @param WC_Product
*/
public function update( &$product ) {
$post_data = array(
'ID' => $product->get_id(),
'post_title' => $this->generate_product_title( $product ),
'post_parent' => $product->get_parent_id(),
'comment_status' => 'closed',
'post_status' => $product->get_status() ? $product->get_status() : 'publish',
'menu_order' => $product->get_menu_order(),
);
$changes = $product->get_changes();
$title = $this->generate_product_title( $product );
wp_update_post( $post_data );
// Only update the post when the post data changes.
if ( $title !== $product->get_name( 'edit' ) || array_intersect( array( 'parent_id', 'status', 'menu_order' ), array_keys( $changes ) ) ) {
wp_update_post( array(
'ID' => $product->get_id(),
'post_title' => $title,
'post_parent' => $product->get_parent_id( 'edit' ),
'comment_status' => 'closed',
'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',
'menu_order' => $product->get_menu_order( 'edit' ),
) );
}
$this->updated_props = array();
$this->update_post_meta( $product );
$this->handle_updated_props( $product );
$this->update_terms( $product );
$this->update_attributes( $product );
$this->handle_updated_props( $product );
$product->save_meta_data();
$product->apply_changes();
@ -283,9 +283,14 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
*
* @since 2.7.0
* @param WC_Product
* @param bool Force update. Used during create.
*/
protected function update_terms( &$product ) {
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
protected function update_terms( &$product, $force = false ) {
$changes = $product->get_changes();
if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) {
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
}
}
/**
@ -293,21 +298,26 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
*
* @since 2.7.0
* @param WC_Product
* @param bool Force update. Used during create.
*/
protected function update_attributes( &$product ) {
global $wpdb;
$attributes = $product->get_attributes();
$updated_attribute_keys = array();
foreach ( $attributes as $key => $value ) {
update_post_meta( $product->get_id(), 'attribute_' . $key, $value );
$updated_attribute_keys[] = 'attribute_' . $key;
}
protected function update_attributes( &$product, $force = false ) {
$changes = $product->get_changes();
// Remove old taxonomies attributes so data is kept up to date - first get attribute key names.
$delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", array_map( 'esc_sql', $updated_attribute_keys ) ) . "' ) AND post_id = %d;", $product->get_id() ) );
if ( $force || array_key_exists( 'attributes', $changes ) ) {
global $wpdb;
$attributes = $product->get_attributes();
$updated_attribute_keys = array();
foreach ( $attributes as $key => $value ) {
update_post_meta( $product->get_id(), 'attribute_' . $key, $value );
$updated_attribute_keys[] = 'attribute_' . $key;
}
foreach ( $delete_attribute_keys as $key ) {
delete_post_meta( $product->get_id(), $key );
// Remove old taxonomies attributes so data is kept up to date - first get attribute key names.
$delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", array_map( 'esc_sql', $updated_attribute_keys ) ) . "' ) AND post_id = %d;", $product->get_id() ) );
foreach ( $delete_attribute_keys as $key ) {
delete_post_meta( $product->get_id(), $key );
}
}
}
@ -316,12 +326,21 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
*
* @since 2.7.0
* @param WC_Product
* @param bool Force update. Used during create.
*/
public function update_post_meta( &$product ) {
$updated_description = update_post_meta( $product->get_id(), '_variation_description', $product->get_description() );
public function update_post_meta( &$product, $force = false ) {
$meta_key_to_props = array(
'_variation_description' => 'description',
);
if ( $updated_description ) {
$this->updated_props[] = 'description';
$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props );
foreach ( $props_to_update as $meta_key => $prop ) {
$value = $product->{"get_$prop"}( 'edit' );
$updated = update_post_meta( $product->get_id(), $meta_key, $value );
if ( $updated ) {
$this->updated_props[] = $prop;
}
}
parent::update_post_meta( $product );