Merge remote-tracking branch 'origin/feature/product-csv-import-export' into HEAD

This commit is contained in:
claudiulodro 2017-05-25 12:41:26 -07:00
commit df06d6f942
9 changed files with 123 additions and 70 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -673,10 +673,6 @@ table.wc_status_table {
}
}
.edit-php.post-type-product .top .bulkactions {
display: none;
}
#woocommerce-fields.inline-edit-col {
clear: left;
@ -5635,10 +5631,6 @@ table.bar_chart {
-webkit-appearance: none;
border: none;
display: none;
}
/* All good till now. Now we'll style the background */
progress::-webkit-progress-bar {
background: #f5f5f5;
border: 2px solid #eee;
border-radius: 4px;
@ -5646,13 +5638,35 @@ table.bar_chart {
box-shadow: 0 1px 0px 0 rgba(255, 255, 255, 0.2);
}
/* Now the value part */
progress::-webkit-progress-bar {
background: transparent none;
border: 0;
border-radius: 4px;
padding: 0;
box-shadow: none;
}
progress::-webkit-progress-value {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background:
-webkit-linear-gradient(top, rgba(255, 255, 255, 0.25), rgba(0, 0, 0, 0.2)),
#A46497;
background: #A46497;
background: linear-gradient( top, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}
progress::-moz-progress-bar {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background: #A46497;
background: linear-gradient( top, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}
progress::-ms-fill {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background: #A46497;
background: linear-gradient( to bottom, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}

View File

@ -38,7 +38,7 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( 0 < $failed ) {
$results [] = sprintf(
/* translators: %d: products count */
_n( 'Failed to import %s product.', 'Failed to import %s products.', $failed, 'woocommerce' ),
_n( 'Failed to import %s product', 'Failed to import %s products', $failed, 'woocommerce' ),
'<strong>' . number_format_i18n( $failed ) . '</strong>'
);
}

View File

@ -176,6 +176,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',
'menu_order' => $product->get_menu_order( 'edit' ),
'post_name' => $product->get_slug( 'edit' ),
'post_type' => 'product',
);
if ( $product->get_date_created( 'edit' ) ) {
$post_data['post_date'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() );

View File

@ -153,6 +153,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ),
'post_modified' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
'post_type' => 'product_variation',
);
/**

View File

@ -135,22 +135,34 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
* Process a single item and save.
*
* @param array $data Raw CSV data.
* @return WC_Product|WC_Error
* @return array|WC_Error
*/
protected function process_item( $data ) {
try {
$object = $this->prepare_product( $data );
$object = $this->get_product_object( $data );
$updating = false;
if ( is_wp_error( $object ) ) {
return $object;
}
if ( $object->get_id() && 'importing' !== $object->get_status() ) {
$updating = true;
}
if ( 'variation' === $object->get_type() ) {
$object = $this->save_variation_data( $object, $data );
} else {
$object = $this->save_product_data( $object, $data );
}
$object = apply_filters( 'woocommerce_product_import_pre_insert_product_object', $object, $data );
$object->save();
// Clean cache for updated products.
$this->clear_cache( $object );
return $object->get_id();
return array(
'id' => $object->get_id(),
'updated' => $updating,
);
} catch ( WC_Data_Exception $e ) {
return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
} catch ( Exception $e ) {
@ -158,29 +170,13 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
}
}
/**
* Clear product cache.
*
* @param WC_Product $object Product instance.
*/
protected function clear_cache( $object ) {
$id = $object->get_id();
if ( 'variation' === $object->get_type() ) {
$id = $object->get_parent_id();
}
wc_delete_product_transients( $id );
wp_cache_delete( 'product-' . $id, 'products' );
}
/**
* Prepare a single product for create or update.
*
* @param array $data Row data.
* @return WC_Product|WP_Error
*/
protected function prepare_product( $data ) {
protected function get_product_object( $data ) {
$id = isset( $data['id'] ) ? absint( $data['id'] ) : 0;
// Type is the most important part here because we need to be using the correct class and methods.
@ -206,16 +202,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
return new WP_Error( 'woocommerce_product_csv_importer_invalid_id', sprintf( __( 'Invalid product ID %d.', 'woocommerce' ), $id ), array( 'id' => $id, 'status' => 401 ) );
}
} else {
$product = new WC_Product_Simple();
$product = new WC_Product_Simple( $id );
}
if ( 'variation' === $product->get_type() ) {
$product = $this->save_variation_data( $product, $data );
} else {
$product = $this->save_product_data( $product, $data );
}
return apply_filters( 'woocommerce_product_import_pre_insert_product_object', $product, $data );
return apply_filters( 'woocommerce_product_import_get_product_object', $product, $data );
}
/**
@ -246,6 +236,8 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
// Status.
if ( isset( $data['published'] ) ) {
$product->set_status( $data['published'] ? 'publish' : 'draft' );
} elseif ( 'importing' === $product->get_status() ) {
$product->set_status( 'publish' );
}
// Slug.
@ -572,8 +564,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
}
// Status.
if ( isset( $data['status'] ) ) {
$variation->set_status( false === $data['status'] ? 'private' : 'publish' );
if ( isset( $data['published'] ) ) {
$variation->set_status( $data['published'] ? 'publish' : 'draft' );
} elseif ( 'importing' === $variation->get_status() ) {
$variation->set_status( 'publish' );
}
// SKU.

View File

@ -94,7 +94,14 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Parse relative field and return product ID.
* Handle `id:xx` and SKUs.
*
* Handles `id:xx` and SKUs.
*
* If mapping to an id: and the product ID does not exist, this link is not
* valid.
*
* If mapping to a SKU and the product ID does not exist, a temporary object
* will be created so it can be updated later.
*
* @param string $field Field value.
* @return int|string
@ -108,7 +115,25 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
return intval( $matches[1] );
}
return wc_get_product_id_by_sku( $field );
if ( $id = wc_get_product_id_by_sku( $field ) ) {
return $id;
}
try {
$product = new WC_Product_Simple();
$product->set_name( 'Import placeholder for ' . $field );
$product->set_status( 'importing' );
$product->set_sku( $field );
$id = $product->save();
if ( $id && ! is_wp_error( $id ) ) {
return $id;
}
} catch ( Exception $e ) {
return '';
}
return '';
}
/**
@ -373,6 +398,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
protected function expand_data( $data ) {
$data = apply_filters( 'woocommerce_product_importer_pre_expand_data', $data );
// Product ID and SKU mapping.
if ( empty( $data['id'] ) && ! empty( $data['sku'] ) && ( $id = wc_get_product_id_by_sku( $data['sku'] ) ) ) {
$data['id'] = $id;
}
// Meta data.
$data['meta_data'] = array();
$data['attributes'] = array();
@ -397,6 +427,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
// Type, virtual and downlodable.
if ( isset( $data['type'] ) ) {
$data['type'] = array_map( 'strtolower', $data['type'] );
$data['virtual'] = in_array( 'virtual', $data['type'], true );
$data['downloadable'] = in_array( 'downloadable', $data['type'], true );
@ -479,6 +510,10 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
// Parse the data.
foreach ( $this->raw_data as $row ) {
// Skip empty rows.
if ( ! count( array_filter( $row ) ) ) {
continue;
}
$data = array();
foreach ( $row as $id => $value ) {
@ -533,18 +568,26 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
);
foreach ( $this->parsed_data as $parsed_data_key => $parsed_data ) {
// Don't import products with IDs or SKUs that already exist if option is true.
// Do not import products with IDs or SKUs that already exist if option
// is true UNLESS this is a dummy product created during this import.
if ( ! $this->params['update_existing'] ) {
$id = isset( $parsed_data['id'] ) ? absint( $parsed_data['id'] ) : 0;
$sku = isset( $parsed_data['sku'] ) ? esc_attr( $parsed_data['sku'] ) : '';
$id = isset( $parsed_data['id'] ) ? absint( $parsed_data['id'] ) : 0;
$sku = isset( $parsed_data['sku'] ) ? esc_attr( $parsed_data['sku'] ) : '';
if ( $id && wc_get_product( $id ) ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_csv_importer_error', __( 'A product with this ID already exists.', 'woocommerce' ), array( 'id' => $id, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
} elseif ( $sku && wc_get_product_id_by_sku( $sku ) ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_csv_importer_error', __( 'A product with this SKU already exists.', 'woocommerce' ), array( 'sku' => $sku, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
if ( $id ) {
$product = wc_get_product( $id );
if ( $product && 'importing' !== $product->get_status() ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_csv_importer_error', __( 'A product with this ID already exists.', 'woocommerce' ), array( 'id' => $id, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
}
} elseif ( $sku && ( $id_from_sku = wc_get_product_id_by_sku( $sku ) ) ) {
$product = wc_get_product( $id_from_sku );
if ( $product && 'importing' !== $product->get_status() ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_csv_importer_error', __( 'A product with this SKU already exists.', 'woocommerce' ), array( 'sku' => $sku, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
}
}
}
@ -552,11 +595,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
if ( is_wp_error( $result ) ) {
$result->add_data( array( 'row' => $this->get_row_id( $parsed_data ) ) );
$data['failed'][] = $result;
} elseif ( ! empty( $parsed_data['id'] ) ) {
$data['updated'][] = $result;
$data['failed'][] = $result['id'];
} elseif ( $result['updated'] ) {
$data['updated'][] = $result['id'];
} else {
$data['imported'][] = $result;
$data['imported'][] = $result['id'];
}
}

View File

@ -50,7 +50,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
// Exclude imported products.
foreach ( $results['imported'] as $id ) {
wp_delete_post( $id );
wp_delete_post( $id, true );
}
}
@ -150,7 +150,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'type' => 'simple',
'sku' => 'PRODUCT-01',
'name' => 'Imported Product 1',
'published' => 1,
'published' => true,
'regular_price' => '40',
'meta_data' => array(),
'attributes' => array(),
@ -164,7 +164,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'type' => 'simple',
'sku' => 'PRODUCT-02',
'name' => 'Imported Product 2',
'published' => 1,
'published' => true,
'regular_price' => '41',
'meta_data' => array(),
'attributes' => array(),
@ -178,7 +178,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
'type' => 'simple',
'sku' => 'PRODUCT-03',
'name' => 'Imported Product 3',
'published' => 1,
'published' => true,
'regular_price' => '42',
'meta_data' => array(),
'attributes' => array(),