2017-05-15 22:49:53 +00:00
< ? php
/**
* WooCommerce Product CSV importer
*
* @ package WooCommerce / Import
* @ version 3.1 . 0
*/
2017-09-29 19:07:15 +00:00
2017-05-15 22:49:53 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ;
}
/**
* Include dependencies .
*/
2017-05-16 04:02:46 +00:00
if ( ! class_exists ( 'WC_Product_Importer' , false ) ) {
2018-01-09 10:56:57 +00:00
include_once dirname ( __FILE__ ) . '/abstract-wc-product-importer.php' ;
2017-05-15 22:49:53 +00:00
}
/**
2017-05-15 23:23:42 +00:00
* WC_Product_CSV_Importer Class .
2017-05-15 22:49:53 +00:00
*/
2017-05-16 04:02:46 +00:00
class WC_Product_CSV_Importer extends WC_Product_Importer {
2017-05-15 22:49:53 +00:00
2018-02-05 14:33:34 +00:00
/**
* Tracks current row being parsed .
*
* @ var integer
*/
protected $parsing_raw_data_index = 0 ;
2017-05-15 22:49:53 +00:00
/**
* Initialize importer .
*
2017-09-29 19:07:15 +00:00
* @ param string $file File to read .
* @ param array $params Arguments for the parser .
2017-05-15 22:49:53 +00:00
*/
public function __construct ( $file , $params = array () ) {
$default_args = array (
2017-06-14 18:35:22 +00:00
'start_pos' => 0 , // File pointer start.
'end_pos' => - 1 , // File pointer end.
'lines' => - 1 , // Max lines to read.
'mapping' => array (), // Column mapping. csv_heading => schema_heading.
'parse' => false , // Whether to sanitize and format data.
'update_existing' => false , // Whether to update existing items.
'delimiter' => ',' , // CSV delimiter.
'prevent_timeouts' => true , // Check memory and time usage and abort if reaching limit.
2017-07-12 11:11:44 +00:00
'enclosure' => '"' , // The character used to wrap text in the CSV.
2018-04-10 17:08:56 +00:00
'escape' => " \0 " , // PHP uses '\' as the default escape character. This is not RFC-4180 compliant. This disables the escape character.
2017-05-15 22:49:53 +00:00
);
$this -> params = wp_parse_args ( $params , $default_args );
$this -> file = $file ;
2017-07-06 17:51:36 +00:00
if ( isset ( $this -> params [ 'mapping' ][ 'from' ], $this -> params [ 'mapping' ][ 'to' ] ) ) {
$this -> params [ 'mapping' ] = array_combine ( $this -> params [ 'mapping' ][ 'from' ], $this -> params [ 'mapping' ][ 'to' ] );
}
2017-05-15 22:49:53 +00:00
$this -> read_file ();
}
/**
* Read file .
*/
protected function read_file () {
2018-09-18 18:06:34 +00:00
if ( ! WC_Product_CSV_Importer_Controller :: is_file_valid_csv ( $this -> file ) ) {
wp_die ( __ ( 'Invalid file type. The importer supports CSV and TXT file formats.' , 'woocommerce' ) );
}
2018-01-09 10:56:57 +00:00
$handle = fopen ( $this -> file , 'r' ); // @codingStandardsIgnoreLine.
if ( false !== $handle ) {
2018-04-10 17:08:56 +00:00
$this -> raw_keys = version_compare ( PHP_VERSION , '5.3' , '>=' ) ? fgetcsv ( $handle , 0 , $this -> params [ 'delimiter' ], $this -> params [ 'enclosure' ], $this -> params [ 'escape' ] ) : fgetcsv ( $handle , 0 , $this -> params [ 'delimiter' ], $this -> params [ 'enclosure' ] ); // @codingStandardsIgnoreLine
2017-05-15 22:49:53 +00:00
2017-06-26 19:08:05 +00:00
// Remove BOM signature from the first item.
if ( isset ( $this -> raw_keys [ 0 ] ) ) {
$this -> raw_keys [ 0 ] = $this -> remove_utf8_bom ( $this -> raw_keys [ 0 ] );
}
2017-05-15 22:49:53 +00:00
if ( 0 !== $this -> params [ 'start_pos' ] ) {
fseek ( $handle , ( int ) $this -> params [ 'start_pos' ] );
}
2018-01-10 17:28:26 +00:00
while ( 1 ) {
2018-04-10 17:08:56 +00:00
$row = version_compare ( PHP_VERSION , '5.3' , '>=' ) ? fgetcsv ( $handle , 0 , $this -> params [ 'delimiter' ], $this -> params [ 'enclosure' ], $this -> params [ 'escape' ] ) : fgetcsv ( $handle , 0 , $this -> params [ 'delimiter' ], $this -> params [ 'enclosure' ] ); // @codingStandardsIgnoreLine
2018-01-09 10:56:57 +00:00
2018-01-10 17:28:26 +00:00
if ( false !== $row ) {
$this -> raw_data [] = $row ;
$this -> file_positions [ count ( $this -> raw_data ) ] = ftell ( $handle );
2017-05-15 22:49:53 +00:00
2018-01-10 17:28:26 +00:00
if ( ( $this -> params [ 'end_pos' ] > 0 && ftell ( $handle ) >= $this -> params [ 'end_pos' ] ) || 0 === -- $this -> params [ 'lines' ] ) {
break ;
}
} else {
2017-05-19 13:33:40 +00:00
break ;
2017-05-15 22:49:53 +00:00
}
}
2017-05-17 21:34:49 +00:00
$this -> file_position = ftell ( $handle );
2017-05-15 22:49:53 +00:00
}
if ( ! empty ( $this -> params [ 'mapping' ] ) ) {
$this -> set_mapped_keys ();
}
if ( $this -> params [ 'parse' ] ) {
$this -> set_parsed_data ();
}
}
2017-06-26 19:08:05 +00:00
/**
* Remove UTF - 8 BOM signature .
*
* @ param string $string String to handle .
* @ return string
*/
protected function remove_utf8_bom ( $string ) {
if ( 'efbbbf' === substr ( bin2hex ( $string ), 0 , 6 ) ) {
$string = substr ( $string , 3 );
}
return $string ;
}
2017-05-15 22:49:53 +00:00
/**
* Set file mapped keys .
*/
protected function set_mapped_keys () {
$mapping = $this -> params [ 'mapping' ];
foreach ( $this -> raw_keys as $key ) {
$this -> mapped_keys [] = isset ( $mapping [ $key ] ) ? $mapping [ $key ] : $key ;
}
}
2017-05-18 21:32:03 +00:00
/**
* Parse relative field and return product ID .
2017-05-25 13:54:49 +00:00
*
* 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 .
2017-05-18 21:32:03 +00:00
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-18 23:15:42 +00:00
* @ return int | string
2017-05-18 21:32:03 +00:00
*/
2017-08-10 20:23:31 +00:00
public function parse_relative_field ( $value ) {
2017-06-14 15:37:07 +00:00
global $wpdb ;
2017-08-10 20:23:31 +00:00
if ( empty ( $value ) ) {
2017-05-18 23:15:42 +00:00
return '' ;
}
2017-12-07 14:12:20 +00:00
// IDs are prefixed with id:.
2017-08-10 20:23:31 +00:00
if ( preg_match ( '/^id:(\d+)$/' , $value , $matches ) ) {
2017-12-07 14:12:20 +00:00
$id = intval ( $matches [ 1 ] );
// If original_id is found, use that instead of the given ID since a new placeholder must have been created already.
2018-01-09 10:56:57 +00:00
$original_id = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT post_id FROM { $wpdb -> postmeta } WHERE meta_key = '_original_id' AND meta_value = %s; " , $id ) ); // WPCS: db call ok, cache ok.
2017-06-14 15:37:07 +00:00
2017-06-26 16:40:20 +00:00
if ( $original_id ) {
2017-06-26 17:41:51 +00:00
return absint ( $original_id );
2017-12-07 14:12:20 +00:00
}
// See if the given ID maps to a valid product allready.
2018-01-09 10:56:57 +00:00
$existing_id = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT ID FROM { $wpdb -> posts } WHERE post_type IN ( 'product', 'product_variation' ) AND ID = %d; " , $id ) ); // WPCS: db call ok, cache ok.
2017-12-07 14:12:20 +00:00
if ( $existing_id ) {
return absint ( $existing_id );
}
// If we're not updating existing posts, we may need a placeholder product to map to.
if ( ! $this -> params [ 'update_existing' ] ) {
2017-06-26 16:40:20 +00:00
$product = new WC_Product_Simple ();
2017-06-26 17:41:51 +00:00
$product -> set_name ( 'Import placeholder for ' . $id );
2017-06-26 16:40:20 +00:00
$product -> set_status ( 'importing' );
2017-06-26 17:41:51 +00:00
$product -> add_meta_data ( '_original_id' , $id , true );
2017-06-26 16:40:20 +00:00
$id = $product -> save ();
}
return $id ;
2017-05-18 21:32:03 +00:00
}
2018-01-09 10:56:57 +00:00
$id = wc_get_product_id_by_sku ( $value );
if ( $id ) {
2017-05-25 13:54:49 +00:00
return $id ;
}
try {
$product = new WC_Product_Simple ();
2017-08-10 20:23:31 +00:00
$product -> set_name ( 'Import placeholder for ' . $value );
2017-05-25 13:54:49 +00:00
$product -> set_status ( 'importing' );
2017-08-10 20:23:31 +00:00
$product -> set_sku ( $value );
2017-05-25 13:54:49 +00:00
$id = $product -> save ();
if ( $id && ! is_wp_error ( $id ) ) {
return $id ;
}
} catch ( Exception $e ) {
return '' ;
}
return '' ;
2017-05-18 21:32:03 +00:00
}
2017-06-14 15:37:07 +00:00
/**
* Parse the ID field .
*
* If we ' re not doing an update , create a placeholder product so mapping works
* for rows following this one .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-06-14 15:37:07 +00:00
* @ return int
*/
2017-08-10 20:23:31 +00:00
public function parse_id_field ( $value ) {
2017-06-26 17:41:51 +00:00
global $wpdb ;
2017-08-10 20:23:31 +00:00
$id = absint ( $value );
2017-06-26 17:41:51 +00:00
if ( ! $id ) {
return 0 ;
}
// See if this maps to an ID placeholder already.
2018-01-09 10:56:57 +00:00
$original_id = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT post_id FROM { $wpdb -> postmeta } WHERE meta_key = '_original_id' AND meta_value = %s; " , $id ) ); // WPCS: db call ok, cache ok.
2017-06-26 17:41:51 +00:00
if ( $original_id ) {
return absint ( $original_id );
}
2017-06-14 15:37:07 +00:00
// Not updating? Make sure we have a new placeholder for this ID.
2017-06-26 17:41:51 +00:00
if ( ! $this -> params [ 'update_existing' ] ) {
2018-02-05 14:33:34 +00:00
$mapped_keys = $this -> get_mapped_keys ();
$sku_column_index = absint ( array_search ( 'sku' , $mapped_keys , true ) );
$row_sku = isset ( $this -> raw_data [ $this -> parsing_raw_data_index ][ $sku_column_index ] ) ? $this -> raw_data [ $this -> parsing_raw_data_index ][ $sku_column_index ] : '' ;
$id_from_sku = $row_sku ? wc_get_product_id_by_sku ( $row_sku ) : '' ;
2018-01-09 10:56:57 +00:00
2017-07-14 11:33:14 +00:00
// If row has a SKU, make sure placeholder was not made already.
2018-02-05 14:33:34 +00:00
if ( $id_from_sku ) {
return $id_from_sku ;
2017-07-14 11:33:14 +00:00
}
2017-06-14 15:37:07 +00:00
$product = new WC_Product_Simple ();
2017-06-26 17:41:51 +00:00
$product -> set_name ( 'Import placeholder for ' . $id );
2017-06-14 15:37:07 +00:00
$product -> set_status ( 'importing' );
2017-06-26 17:41:51 +00:00
$product -> add_meta_data ( '_original_id' , $id , true );
2017-07-14 11:33:14 +00:00
// If row has a SKU, make sure placeholder has it too.
2018-02-05 14:33:34 +00:00
if ( $row_sku ) {
$product -> set_sku ( $row_sku );
2017-07-14 11:33:14 +00:00
}
2017-06-26 17:41:51 +00:00
$id = $product -> save ();
2017-06-14 15:37:07 +00:00
}
2017-06-26 17:41:51 +00:00
return $id && ! is_wp_error ( $id ) ? $id : 0 ;
2017-06-14 15:37:07 +00:00
}
2017-05-18 21:32:03 +00:00
/**
2017-07-17 10:10:52 +00:00
* Parse relative comma - delineated field and return product ID .
2017-05-18 21:32:03 +00:00
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-18 21:32:03 +00:00
* @ return array
*/
2017-08-10 20:23:31 +00:00
public function parse_relative_comma_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-18 21:32:03 +00:00
return array ();
}
2017-08-10 20:23:31 +00:00
return array_filter ( array_map ( array ( $this , 'parse_relative_field' ), $this -> explode_values ( $value ) ) );
2017-05-18 21:32:03 +00:00
}
2017-05-15 22:49:53 +00:00
/**
* Parse a comma - delineated field from a CSV .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-15 22:49:53 +00:00
* @ return array
*/
2017-08-10 20:23:31 +00:00
public function parse_comma_field ( $value ) {
2017-09-29 19:07:15 +00:00
if ( empty ( $value ) && '0' !== $value ) {
2017-05-15 22:49:53 +00:00
return array ();
}
2017-08-10 20:23:31 +00:00
return array_map ( 'wc_clean' , $this -> explode_values ( $value ) );
2017-05-15 22:49:53 +00:00
}
/**
* Parse a field that is generally '1' or '0' but can be something else .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-15 22:49:53 +00:00
* @ return bool | string
*/
2017-08-10 20:23:31 +00:00
public function parse_bool_field ( $value ) {
if ( '0' === $value ) {
2017-05-15 22:49:53 +00:00
return false ;
}
2017-08-10 20:23:31 +00:00
if ( '1' === $value ) {
2017-05-15 22:49:53 +00:00
return true ;
}
// Don't return explicit true or false for empty fields or values like 'notify'.
2017-08-10 20:23:31 +00:00
return wc_clean ( $value );
2017-05-15 22:49:53 +00:00
}
/**
* Parse a float value field .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-15 22:49:53 +00:00
* @ return float | string
*/
2017-08-10 20:23:31 +00:00
public function parse_float_field ( $value ) {
if ( '' === $value ) {
return $value ;
2017-05-15 22:49:53 +00:00
}
2017-11-22 19:48:19 +00:00
// Remove the ' prepended to fields that start with - if needed.
$value = $this -> unescape_negative_number ( $value );
2017-08-10 20:23:31 +00:00
return floatval ( $value );
2017-05-15 22:49:53 +00:00
}
2017-08-11 10:07:12 +00:00
/**
* Parse the stock qty field .
*
* @ param string $value Field value .
* @ return float | string
*/
public function parse_stock_quantity_field ( $value ) {
if ( '' === $value ) {
return $value ;
}
2017-11-22 19:48:19 +00:00
// Remove the ' prepended to fields that start with - if needed.
$value = $this -> unescape_negative_number ( $value );
2017-08-11 10:07:12 +00:00
return wc_stock_amount ( $value );
}
2017-05-15 22:49:53 +00:00
/**
* Parse a category field from a CSV .
* Categories are separated by commas and subcategories are " parent > subcategory " .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-15 22:49:53 +00:00
* @ return array of arrays with " parent " and " name " keys .
*/
2017-08-10 20:23:31 +00:00
public function parse_categories_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-15 22:49:53 +00:00
return array ();
}
2017-08-10 20:23:31 +00:00
$row_terms = $this -> explode_values ( $value );
2017-05-15 22:49:53 +00:00
$categories = array ();
2017-05-24 00:06:16 +00:00
foreach ( $row_terms as $row_term ) {
$parent = null ;
$_terms = array_map ( 'trim' , explode ( '>' , $row_term ) );
$total = count ( $_terms );
foreach ( $_terms as $index => $_term ) {
// Check if category exists. Parent must be empty string or null if doesn't exists.
$term = term_exists ( $_term , 'product_cat' , $parent );
if ( is_array ( $term ) ) {
$term_id = $term [ 'term_id' ];
2018-08-24 18:32:48 +00:00
// Don't allow users without capabilities to create new categories.
} elseif ( ! current_user_can ( 'manage_product_terms' ) ) {
break ;
2017-05-24 00:06:16 +00:00
} else {
2018-01-09 10:56:57 +00:00
$term = wp_insert_term ( $_term , 'product_cat' , array ( 'parent' => intval ( $parent ) ) );
2017-08-14 09:40:00 +00:00
if ( is_wp_error ( $term ) ) {
break ; // We cannot continue if the term cannot be inserted.
}
2017-05-24 00:06:16 +00:00
$term_id = $term [ 'term_id' ];
}
// Only requires assign the last category.
2017-05-24 06:26:17 +00:00
if ( ( 1 + $index ) === $total ) {
2017-05-24 00:06:16 +00:00
$categories [] = $term_id ;
} else {
// Store parent to be able to insert or query categories based in parent ID.
$parent = $term_id ;
}
2017-05-15 22:49:53 +00:00
}
}
return $categories ;
}
2017-05-22 21:54:30 +00:00
/**
* Parse a tag field from a CSV .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-22 21:54:30 +00:00
* @ return array
*/
2017-08-10 20:23:31 +00:00
public function parse_tags_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-22 21:54:30 +00:00
return array ();
}
2017-08-10 20:23:31 +00:00
$names = $this -> explode_values ( $value );
2017-05-22 21:54:30 +00:00
$tags = array ();
foreach ( $names as $name ) {
2017-05-22 22:24:33 +00:00
$term = get_term_by ( 'name' , $name , 'product_tag' );
2017-05-24 09:47:12 +00:00
if ( ! $term || is_wp_error ( $term ) ) {
2017-05-24 00:11:21 +00:00
$term = ( object ) wp_insert_term ( $name , 'product_tag' );
2017-05-22 21:54:30 +00:00
}
2017-05-22 22:24:33 +00:00
2017-08-14 09:40:00 +00:00
if ( ! is_wp_error ( $term ) ) {
$tags [] = $term -> term_id ;
}
2017-05-22 21:54:30 +00:00
}
return $tags ;
}
/**
* Parse a shipping class field from a CSV .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-22 21:54:30 +00:00
* @ return int
*/
2017-08-10 20:23:31 +00:00
public function parse_shipping_class_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-26 05:21:57 +00:00
return 0 ;
}
2017-08-10 20:23:31 +00:00
$term = get_term_by ( 'name' , $value , 'product_shipping_class' );
2017-05-22 22:24:33 +00:00
2017-05-24 09:47:12 +00:00
if ( ! $term || is_wp_error ( $term ) ) {
2017-08-10 20:23:31 +00:00
$term = ( object ) wp_insert_term ( $value , 'product_shipping_class' );
2017-05-22 21:54:30 +00:00
}
2017-08-14 09:40:00 +00:00
if ( is_wp_error ( $term ) ) {
return 0 ;
}
2017-05-22 22:24:33 +00:00
return $term -> term_id ;
2017-05-22 21:54:30 +00:00
}
2017-05-24 06:14:54 +00:00
/**
2017-08-08 14:00:03 +00:00
* Parse images list from a CSV . Images can be filenames or URLs .
2017-05-24 06:14:54 +00:00
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-24 06:14:54 +00:00
* @ return array
*/
2017-08-10 20:23:31 +00:00
public function parse_images_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-24 06:14:54 +00:00
return array ();
}
2017-08-08 14:00:03 +00:00
$images = array ();
2017-08-10 20:23:31 +00:00
foreach ( $this -> explode_values ( $value ) as $image ) {
2017-08-08 14:00:03 +00:00
if ( stristr ( $image , '://' ) ) {
$images [] = esc_url_raw ( $image );
} else {
$images [] = sanitize_file_name ( $image );
}
}
return $images ;
2017-05-24 06:14:54 +00:00
}
2017-05-26 22:25:44 +00:00
/**
* Parse dates from a CSV .
2017-07-10 16:19:59 +00:00
* Dates requires the format YYYY - MM - DD and time is optional .
2017-05-26 22:25:44 +00:00
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-26 22:25:44 +00:00
* @ return string | null
*/
2017-08-10 20:23:31 +00:00
public function parse_date_field ( $value ) {
if ( empty ( $value ) ) {
2017-05-26 22:25:44 +00:00
return null ;
}
2017-08-10 20:23:31 +00:00
if ( preg_match ( '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])([ 01-9:]*)$/' , $value ) ) {
2017-07-10 16:19:59 +00:00
// Don't include the time if the field had time in it.
2017-08-10 20:23:31 +00:00
return current ( explode ( ' ' , $value ) );
2017-05-26 22:25:44 +00:00
}
return null ;
}
2017-05-26 22:39:20 +00:00
/**
* Parse backorders from a CSV .
*
2017-08-10 20:23:31 +00:00
* @ param string $value Field value .
2017-05-26 22:39:20 +00:00
* @ return string
*/
2017-08-10 20:23:31 +00:00
public function parse_backorders_field ( $value ) {
if ( empty ( $value ) ) {
2018-03-23 09:21:59 +00:00
return 'no' ;
2017-05-26 22:39:20 +00:00
}
2017-08-10 20:23:31 +00:00
$value = $this -> parse_bool_field ( $value );
2017-05-26 22:39:20 +00:00
2017-08-10 20:23:31 +00:00
if ( 'notify' === $value ) {
2017-05-26 22:39:20 +00:00
return 'notify' ;
2017-08-10 20:23:31 +00:00
} elseif ( is_bool ( $value ) ) {
return $value ? 'yes' : 'no' ;
2017-05-26 22:39:20 +00:00
}
2018-03-23 09:19:02 +00:00
return 'no' ;
2017-05-26 22:39:20 +00:00
}
2017-08-10 20:17:56 +00:00
/**
* Just skip current field .
*
* By default is applied wc_clean () to all not listed fields
* in self :: get_formating_callback (), use this method to skip any formating .
*
* @ param string $value Field value .
* @ return string
*/
public function parse_skip_field ( $value ) {
return $value ;
}
2017-10-25 12:54:02 +00:00
/**
* Parse download file urls , we should allow shortcodes here .
*
* Allow shortcodes if present , othersiwe esc_url the value .
*
* @ param string $value Field value .
* @ return string
*/
public function parse_download_file_field ( $value ) {
// Absolute file paths.
if ( 0 === strpos ( $value , 'http' ) ) {
return esc_url_raw ( $value );
}
// Relative and shortcode paths.
return wc_clean ( $value );
}
2018-04-17 14:37:33 +00:00
/**
* Parse an int value field
*
* @ param int $value field value .
* @ return int
*/
public function parse_int_field ( $value ) {
2018-04-26 15:06:04 +00:00
// Remove the ' prepended to fields that start with - if needed.
2018-04-17 14:37:33 +00:00
$value = $this -> unescape_negative_number ( $value );
return intval ( $value );
}
2017-05-15 22:49:53 +00:00
/**
2017-05-19 21:50:09 +00:00
* Get formatting callback .
2017-05-15 22:49:53 +00:00
*
* @ return array
*/
2017-05-19 21:50:09 +00:00
protected function get_formating_callback () {
2017-05-15 22:49:53 +00:00
/**
2017-05-18 21:28:24 +00:00
* Columns not mentioned here will get parsed with 'wc_clean' .
2017-05-15 22:49:53 +00:00
* column_name => callback .
*/
$data_formatting = array (
2017-06-14 15:37:07 +00:00
'id' => array ( $this , 'parse_id_field' ),
2017-05-19 00:09:25 +00:00
'type' => array ( $this , 'parse_comma_field' ),
2017-12-06 20:32:35 +00:00
'published' => array ( $this , 'parse_float_field' ),
2017-05-15 22:49:53 +00:00
'featured' => array ( $this , 'parse_bool_field' ),
2017-05-26 22:25:44 +00:00
'date_on_sale_from' => array ( $this , 'parse_date_field' ),
'date_on_sale_to' => array ( $this , 'parse_date_field' ),
2017-08-10 20:17:56 +00:00
'name' => array ( $this , 'parse_skip_field' ),
'short_description' => array ( $this , 'parse_skip_field' ),
'description' => array ( $this , 'parse_skip_field' ),
2017-05-15 22:49:53 +00:00
'manage_stock' => array ( $this , 'parse_bool_field' ),
2018-06-19 11:40:27 +00:00
'low_stock_amount' => array ( $this , 'parse_stock_quantity_field' ),
2017-05-26 22:39:20 +00:00
'backorders' => array ( $this , 'parse_backorders_field' ),
2017-05-19 00:09:25 +00:00
'stock_status' => array ( $this , 'parse_bool_field' ),
2017-05-15 22:49:53 +00:00
'sold_individually' => array ( $this , 'parse_bool_field' ),
'width' => array ( $this , 'parse_float_field' ),
'length' => array ( $this , 'parse_float_field' ),
'height' => array ( $this , 'parse_float_field' ),
'weight' => array ( $this , 'parse_float_field' ),
'reviews_allowed' => array ( $this , 'parse_bool_field' ),
2017-05-26 14:57:17 +00:00
'purchase_note' => 'wp_filter_post_kses' ,
2017-05-15 22:49:53 +00:00
'price' => 'wc_format_decimal' ,
'regular_price' => 'wc_format_decimal' ,
2017-08-11 10:07:12 +00:00
'stock_quantity' => array ( $this , 'parse_stock_quantity_field' ),
2017-05-24 06:14:54 +00:00
'category_ids' => array ( $this , 'parse_categories_field' ),
'tag_ids' => array ( $this , 'parse_tags_field' ),
'shipping_class_id' => array ( $this , 'parse_shipping_class_field' ),
2017-05-26 14:57:17 +00:00
'images' => array ( $this , 'parse_images_field' ),
2017-05-18 21:32:03 +00:00
'parent_id' => array ( $this , 'parse_relative_field' ),
2017-05-28 19:37:18 +00:00
'grouped_products' => array ( $this , 'parse_relative_comma_field' ),
2017-05-18 21:32:03 +00:00
'upsell_ids' => array ( $this , 'parse_relative_comma_field' ),
'cross_sell_ids' => array ( $this , 'parse_relative_comma_field' ),
2018-04-17 14:37:33 +00:00
'download_limit' => array ( $this , 'parse_int_field' ),
'download_expiry' => array ( $this , 'parse_int_field' ),
2017-05-26 18:26:55 +00:00
'product_url' => 'esc_url_raw' ,
2017-08-22 20:22:39 +00:00
'menu_order' => 'intval' ,
2017-05-15 22:49:53 +00:00
);
/**
2017-05-18 21:28:24 +00:00
* Match special column names .
2017-05-15 22:49:53 +00:00
*/
$regex_match_data_formatting = array (
2017-05-30 21:46:32 +00:00
'/attributes:value*/' => array ( $this , 'parse_comma_field' ),
'/attributes:visible*/' => array ( $this , 'parse_bool_field' ),
'/attributes:taxonomy*/' => array ( $this , 'parse_bool_field' ),
2017-10-25 12:54:02 +00:00
'/downloads:url*/' => array ( $this , 'parse_download_file_field' ),
2017-05-30 21:46:32 +00:00
'/meta:*/' => 'wp_kses_post' , // Allow some HTML in meta fields.
2017-05-15 22:49:53 +00:00
);
2017-05-19 21:50:09 +00:00
$callbacks = array ();
2017-05-15 22:49:53 +00:00
// Figure out the parse function for each column.
2017-05-19 21:50:09 +00:00
foreach ( $this -> get_mapped_keys () as $index => $heading ) {
$callback = 'wc_clean' ;
2017-05-15 22:49:53 +00:00
if ( isset ( $data_formatting [ $heading ] ) ) {
2017-05-19 21:50:09 +00:00
$callback = $data_formatting [ $heading ];
2017-05-15 22:49:53 +00:00
} else {
foreach ( $regex_match_data_formatting as $regex => $callback ) {
if ( preg_match ( $regex , $heading ) ) {
2017-05-19 21:50:09 +00:00
$callback = $callback ;
2017-05-15 22:49:53 +00:00
break ;
}
}
}
2017-05-19 21:50:09 +00:00
$callbacks [] = $callback ;
}
2017-06-26 22:49:18 +00:00
return apply_filters ( 'woocommerce_product_importer_formatting_callbacks' , $callbacks , $this );
2017-05-19 21:50:09 +00:00
}
/**
* Check if strings starts with determined word .
*
* @ param string $haystack Complete sentence .
* @ param string $needle Excerpt .
* @ return bool
*/
protected function starts_with ( $haystack , $needle ) {
2017-05-24 06:26:17 +00:00
return substr ( $haystack , 0 , strlen ( $needle ) ) === $needle ;
2017-05-19 21:50:09 +00:00
}
/**
2017-05-26 14:57:17 +00:00
* Expand special and internal data into the correct formats for the product CRUD .
2017-05-19 21:50:09 +00:00
*
* @ param array $data Data to import .
* @ return array
*/
protected function expand_data ( $data ) {
$data = apply_filters ( 'woocommerce_product_importer_pre_expand_data' , $data );
2017-05-26 14:57:17 +00:00
// Images field maps to image and gallery id fields.
if ( isset ( $data [ 'images' ] ) ) {
$images = $data [ 'images' ];
$data [ 'raw_image_id' ] = array_shift ( $images );
2017-05-24 06:14:54 +00:00
if ( ! empty ( $images ) ) {
2017-05-26 14:57:17 +00:00
$data [ 'raw_gallery_image_ids' ] = $images ;
2017-05-24 06:14:54 +00:00
}
2017-05-26 14:57:17 +00:00
unset ( $data [ 'images' ] );
2017-05-24 06:14:54 +00:00
}
2017-05-26 14:57:17 +00:00
// Type, virtual and downloadable are all stored in the same column.
2017-05-19 21:50:09 +00:00
if ( isset ( $data [ 'type' ] ) ) {
2017-05-25 13:54:49 +00:00
$data [ 'type' ] = array_map ( 'strtolower' , $data [ 'type' ] );
2017-05-19 21:50:09 +00:00
$data [ 'virtual' ] = in_array ( 'virtual' , $data [ 'type' ], true );
$data [ 'downloadable' ] = in_array ( 'downloadable' , $data [ 'type' ], true );
// Convert type to string.
$data [ 'type' ] = current ( array_diff ( $data [ 'type' ], array ( 'virtual' , 'downloadable' ) ) );
}
2017-08-23 09:45:18 +00:00
// Status is mapped from a special published field.
if ( isset ( $data [ 'published' ] ) ) {
2018-01-09 10:56:57 +00:00
$statuses = array (
2017-12-06 20:32:35 +00:00
- 1 => 'draft' ,
0 => 'private' ,
1 => 'publish' ,
);
$data [ 'status' ] = isset ( $statuses [ $data [ 'published' ] ] ) ? $statuses [ $data [ 'published' ] ] : - 1 ;
2017-08-23 09:45:18 +00:00
unset ( $data [ 'published' ] );
}
2017-05-26 18:41:44 +00:00
if ( isset ( $data [ 'stock_quantity' ] ) ) {
2017-08-11 10:07:12 +00:00
if ( '' === $data [ 'stock_quantity' ] ) {
$data [ 'manage_stock' ] = false ;
$data [ 'stock_status' ] = isset ( $data [ 'stock_status' ] ) ? $data [ 'stock_status' ] : true ;
} else {
$data [ 'manage_stock' ] = true ;
}
2017-05-26 18:26:55 +00:00
}
2017-11-22 19:48:19 +00:00
// Stock is bool or 'backorder'.
2017-05-26 14:57:17 +00:00
if ( isset ( $data [ 'stock_status' ] ) ) {
2017-11-22 19:48:19 +00:00
if ( 'backorder' === $data [ 'stock_status' ] ) {
$data [ 'stock_status' ] = 'onbackorder' ;
2017-11-22 19:57:59 +00:00
} else {
$data [ 'stock_status' ] = $data [ 'stock_status' ] ? 'instock' : 'outofstock' ;
2017-11-22 19:48:19 +00:00
}
2017-05-26 18:26:55 +00:00
}
2017-05-28 19:37:18 +00:00
// Prepare grouped products.
if ( isset ( $data [ 'grouped_products' ] ) ) {
$data [ 'children' ] = $data [ 'grouped_products' ];
unset ( $data [ 'grouped_products' ] );
}
2017-05-26 14:57:17 +00:00
// Handle special column names which span multiple columns.
$attributes = array ();
$downloads = array ();
$meta_data = array ();
2017-05-19 21:50:09 +00:00
foreach ( $data as $key => $value ) {
if ( $this -> starts_with ( $key , 'attributes:name' ) ) {
if ( ! empty ( $value ) ) {
2017-05-26 14:57:17 +00:00
$attributes [ str_replace ( 'attributes:name' , '' , $key ) ][ 'name' ] = $value ;
2017-05-19 21:50:09 +00:00
}
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'attributes:value' ) ) {
2017-05-30 22:26:31 +00:00
$attributes [ str_replace ( 'attributes:value' , '' , $key ) ][ 'value' ] = $value ;
2017-05-19 21:50:09 +00:00
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
2017-05-30 21:46:32 +00:00
} elseif ( $this -> starts_with ( $key , 'attributes:taxonomy' ) ) {
$attributes [ str_replace ( 'attributes:taxonomy' , '' , $key ) ][ 'taxonomy' ] = wc_string_to_bool ( $value );
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'attributes:visible' ) ) {
2017-05-30 22:26:31 +00:00
$attributes [ str_replace ( 'attributes:visible' , '' , $key ) ][ 'visible' ] = wc_string_to_bool ( $value );
2017-05-24 06:42:15 +00:00
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'attributes:default' ) ) {
2017-05-26 21:44:22 +00:00
if ( ! empty ( $value ) ) {
$attributes [ str_replace ( 'attributes:default' , '' , $key ) ][ 'default' ] = $value ;
}
2017-05-19 21:50:09 +00:00
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'downloads:name' ) ) {
2017-05-19 21:50:09 +00:00
if ( ! empty ( $value ) ) {
2017-05-26 14:57:17 +00:00
$downloads [ str_replace ( 'downloads:name' , '' , $key ) ][ 'name' ] = $value ;
2017-05-19 21:50:09 +00:00
}
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'downloads:url' ) ) {
2017-05-19 21:50:09 +00:00
if ( ! empty ( $value ) ) {
2017-05-26 14:57:17 +00:00
$downloads [ str_replace ( 'downloads:url' , '' , $key ) ][ 'url' ] = $value ;
2017-05-19 21:50:09 +00:00
}
unset ( $data [ $key ] );
2017-05-26 15:54:43 +00:00
} elseif ( $this -> starts_with ( $key , 'meta:' ) ) {
2017-05-26 14:57:17 +00:00
$meta_data [] = array (
2017-05-19 21:50:09 +00:00
'key' => str_replace ( 'meta:' , '' , $key ),
'value' => $value ,
);
unset ( $data [ $key ] );
}
2017-05-15 22:49:53 +00:00
}
2017-05-26 14:57:17 +00:00
if ( ! empty ( $attributes ) ) {
2017-05-30 22:26:31 +00:00
// Remove empty attributes and clear indexes.
foreach ( $attributes as $attribute ) {
if ( empty ( $attribute [ 'name' ] ) ) {
continue ;
}
$data [ 'raw_attributes' ][] = $attribute ;
}
2017-05-26 14:57:17 +00:00
}
2017-05-26 15:54:43 +00:00
2017-05-26 14:57:17 +00:00
if ( ! empty ( $downloads ) ) {
$data [ 'downloads' ] = array ();
foreach ( $downloads as $key => $file ) {
if ( empty ( $file [ 'url' ] ) ) {
continue ;
}
$data [ 'downloads' ][] = array (
'name' => $file [ 'name' ] ? $file [ 'name' ] : wc_get_filename_from_url ( $file [ 'url' ] ),
2017-05-30 18:01:48 +00:00
'file' => $file [ 'url' ],
2017-05-26 14:57:17 +00:00
);
}
}
2017-05-26 15:54:43 +00:00
2017-05-26 14:57:17 +00:00
if ( ! empty ( $meta_data ) ) {
$data [ 'meta_data' ] = $meta_data ;
}
2017-05-19 21:50:09 +00:00
return $data ;
}
/**
* Map and format raw data to known fields .
*/
protected function set_parsed_data () {
$parse_functions = $this -> get_formating_callback ();
$mapped_keys = $this -> get_mapped_keys ();
2017-07-03 11:07:07 +00:00
$use_mb = function_exists ( 'mb_convert_encoding' );
2017-05-19 21:50:09 +00:00
2017-05-15 22:49:53 +00:00
// Parse the data.
2018-02-05 14:33:34 +00:00
foreach ( $this -> raw_data as $row_index => $row ) {
2017-05-25 13:54:49 +00:00
// Skip empty rows.
if ( ! count ( array_filter ( $row ) ) ) {
continue ;
}
2018-02-05 14:33:34 +00:00
$this -> parsing_raw_data_index = $row_index ;
2017-05-19 21:50:09 +00:00
$data = array ();
2017-06-22 11:19:19 +00:00
do_action ( 'woocommerce_product_importer_before_set_parsed_data' , $row , $mapped_keys );
2017-06-21 14:49:21 +00:00
2017-05-19 21:50:09 +00:00
foreach ( $row as $id => $value ) {
2017-05-18 21:28:24 +00:00
// Skip ignored columns.
2017-05-19 21:50:09 +00:00
if ( empty ( $mapped_keys [ $id ] ) ) {
2017-05-18 21:28:24 +00:00
continue ;
}
2017-07-03 11:07:07 +00:00
// Convert UTF8.
if ( $use_mb ) {
$encoding = mb_detect_encoding ( $value , mb_detect_order (), true );
if ( $encoding ) {
$value = mb_convert_encoding ( $value , 'UTF-8' , $encoding );
} else {
$value = mb_convert_encoding ( $value , 'UTF-8' , 'UTF-8' );
}
} else {
$value = wp_check_invalid_utf8 ( $value , true );
}
2017-07-18 13:20:14 +00:00
$data [ $mapped_keys [ $id ] ] = call_user_func ( $parse_functions [ $id ], $value );
2017-05-15 22:49:53 +00:00
}
2017-05-18 21:28:24 +00:00
2017-06-26 20:49:48 +00:00
$this -> parsed_data [] = apply_filters ( 'woocommerce_product_importer_parsed_data' , $this -> expand_data ( $data ), $this );
2017-05-15 22:49:53 +00:00
}
}
2017-05-16 04:22:00 +00:00
2017-05-23 14:05:32 +00:00
/**
* Get a string to identify the row from parsed data .
*
2017-09-29 19:07:15 +00:00
* @ param array $parsed_data Parsed data .
2017-05-23 14:05:32 +00:00
* @ return string
*/
protected function get_row_id ( $parsed_data ) {
$id = isset ( $parsed_data [ 'id' ] ) ? absint ( $parsed_data [ 'id' ] ) : 0 ;
$sku = isset ( $parsed_data [ 'sku' ] ) ? esc_attr ( $parsed_data [ 'sku' ] ) : '' ;
$name = isset ( $parsed_data [ 'name' ] ) ? esc_attr ( $parsed_data [ 'name' ] ) : '' ;
$row_data = array ();
if ( $name ) {
$row_data [] = $name ;
}
if ( $id ) {
2018-01-09 10:56:57 +00:00
/* translators: %d: product ID */
2017-05-26 15:54:43 +00:00
$row_data [] = sprintf ( __ ( 'ID %d' , 'woocommerce' ), $id );
2017-05-23 14:05:32 +00:00
}
if ( $sku ) {
2018-01-09 10:56:57 +00:00
/* translators: %s: product SKU */
2017-05-26 15:54:43 +00:00
$row_data [] = sprintf ( __ ( 'SKU %s' , 'woocommerce' ), $sku );
2017-05-23 14:05:32 +00:00
}
return implode ( ', ' , $row_data );
}
2017-05-16 04:22:00 +00:00
/**
* Process importer .
*
2017-06-28 11:06:33 +00:00
* Do not import products with IDs or SKUs that already exist if option
* update existing is false , and likewise , if updating products , do not
* process rows which do not exist if an ID / SKU is provided .
*
2017-05-16 04:22:00 +00:00
* @ return array
*/
public function import () {
2017-06-14 16:04:47 +00:00
$this -> start_time = time ();
$index = 0 ;
2017-06-28 11:06:33 +00:00
$update_existing = $this -> params [ 'update_existing' ];
2017-06-14 16:04:47 +00:00
$data = array (
2018-01-09 10:56:57 +00:00
'imported' => array (),
'failed' => array (),
'updated' => array (),
'skipped' => array (),
2017-05-16 04:22:00 +00:00
);
2017-05-23 14:05:32 +00:00
foreach ( $this -> parsed_data as $parsed_data_key => $parsed_data ) {
2017-08-23 07:37:41 +00:00
do_action ( 'woocommerce_product_import_before_import' , $parsed_data );
2017-06-28 11:06:33 +00:00
$id = isset ( $parsed_data [ 'id' ] ) ? absint ( $parsed_data [ 'id' ] ) : 0 ;
$sku = isset ( $parsed_data [ 'sku' ] ) ? esc_attr ( $parsed_data [ 'sku' ] ) : '' ;
$id_exists = false ;
$sku_exists = false ;
if ( $id ) {
$product = wc_get_product ( $id );
$id_exists = $product && 'importing' !== $product -> get_status ();
2018-02-05 14:33:34 +00:00
}
if ( $sku ) {
2018-01-09 10:56:57 +00:00
$id_from_sku = wc_get_product_id_by_sku ( $sku );
$product = $id_from_sku ? wc_get_product ( $id_from_sku ) : false ;
$sku_exists = $product && 'importing' !== $product -> get_status ();
2017-06-28 11:06:33 +00:00
}
2017-05-25 13:54:49 +00:00
2017-06-28 11:06:33 +00:00
if ( $id_exists && ! $update_existing ) {
2018-01-09 10:56:57 +00:00
$data [ 'skipped' ][] = new WP_Error ( 'woocommerce_product_importer_error' , __ ( 'A product with this ID already exists.' , 'woocommerce' ), array (
'id' => $id ,
'row' => $this -> get_row_id ( $parsed_data ),
) );
2017-06-28 11:06:33 +00:00
continue ;
}
if ( $sku_exists && ! $update_existing ) {
2018-01-09 10:56:57 +00:00
$data [ 'skipped' ][] = new WP_Error ( 'woocommerce_product_importer_error' , __ ( 'A product with this SKU already exists.' , 'woocommerce' ), array (
'sku' => $sku ,
'row' => $this -> get_row_id ( $parsed_data ),
) );
2017-06-28 11:06:33 +00:00
continue ;
}
if ( $update_existing && ( $id || $sku ) && ! $id_exists && ! $sku_exists ) {
2018-01-09 10:56:57 +00:00
$data [ 'skipped' ][] = new WP_Error ( 'woocommerce_product_importer_error' , __ ( 'No matching product exists to update.' , 'woocommerce' ), array (
'id' => $id ,
'sku' => $sku ,
'row' => $this -> get_row_id ( $parsed_data ),
) );
2017-06-28 11:06:33 +00:00
continue ;
2017-05-19 17:58:31 +00:00
}
2017-05-16 04:22:00 +00:00
$result = $this -> process_item ( $parsed_data );
if ( is_wp_error ( $result ) ) {
2017-05-23 14:05:32 +00:00
$result -> add_data ( array ( 'row' => $this -> get_row_id ( $parsed_data ) ) );
2018-01-09 10:56:57 +00:00
$data [ 'failed' ][] = $result ;
2017-05-25 13:54:49 +00:00
} elseif ( $result [ 'updated' ] ) {
2018-01-09 10:56:57 +00:00
$data [ 'updated' ][] = $result [ 'id' ];
2017-05-16 04:22:00 +00:00
} else {
2017-05-25 17:16:10 +00:00
$data [ 'imported' ][] = $result [ 'id' ];
2017-05-16 04:22:00 +00:00
}
2017-06-14 13:02:47 +00:00
$index ++ ;
2017-06-14 18:35:22 +00:00
if ( $this -> params [ 'prevent_timeouts' ] && ( $this -> time_exceeded () || $this -> memory_exceeded () ) ) {
2017-06-14 13:02:47 +00:00
$this -> file_position = $this -> file_positions [ $index ];
break ;
}
2017-05-16 04:22:00 +00:00
}
return $data ;
}
2017-05-15 22:49:53 +00:00
}