Added login request
This commit is contained in:
@@ -0,0 +1,781 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract Product importer
|
||||
*
|
||||
* @package WooCommerce/Import
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include dependencies.
|
||||
*/
|
||||
if ( ! class_exists( 'WC_Importer_Interface', false ) ) {
|
||||
include_once WC_ABSPATH . 'includes/interfaces/class-wc-importer-interface.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Product_Importer Class.
|
||||
*/
|
||||
abstract class WC_Product_Importer implements WC_Importer_Interface {
|
||||
|
||||
/**
|
||||
* CSV file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $file = '';
|
||||
|
||||
/**
|
||||
* The file position after the last read.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $file_position = 0;
|
||||
|
||||
/**
|
||||
* Importer parameters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $params = array();
|
||||
|
||||
/**
|
||||
* Raw keys - CSV raw headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $raw_keys = array();
|
||||
|
||||
/**
|
||||
* Mapped keys - CSV headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mapped_keys = array();
|
||||
|
||||
/**
|
||||
* Raw data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $raw_data = array();
|
||||
|
||||
/**
|
||||
* Raw data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $file_positions = array();
|
||||
|
||||
/**
|
||||
* Parsed data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parsed_data = array();
|
||||
|
||||
/**
|
||||
* Start time of current import.
|
||||
*
|
||||
* (default value: 0)
|
||||
*
|
||||
* @var int
|
||||
* @access protected
|
||||
*/
|
||||
protected $start_time = 0;
|
||||
|
||||
/**
|
||||
* Get file raw headers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_raw_keys() {
|
||||
return $this->raw_keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file mapped headers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_mapped_keys() {
|
||||
return ! empty( $this->mapped_keys ) ? $this->mapped_keys : $this->raw_keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_raw_data() {
|
||||
return $this->raw_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parsed data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_parsed_data() {
|
||||
return apply_filters( 'woocommerce_product_importer_parsed_data', $this->parsed_data, $this->get_raw_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get importer parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_params() {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file pointer position from the last read.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_file_position() {
|
||||
return $this->file_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file pointer position as a percentage of file size.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_percent_complete() {
|
||||
$size = filesize( $this->file );
|
||||
if ( ! $size ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return absint( min( round( ( $this->file_position / $size ) * 100 ), 100 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single product for create or update.
|
||||
*
|
||||
* @param array $data Item data.
|
||||
* @return WC_Product|WP_Error
|
||||
*/
|
||||
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.
|
||||
if ( isset( $data['type'] ) ) {
|
||||
$types = array_keys( wc_get_product_types() );
|
||||
$types[] = 'variation';
|
||||
|
||||
if ( ! in_array( $data['type'], $types, true ) ) {
|
||||
return new WP_Error( 'woocommerce_product_importer_invalid_type', __( 'Invalid product type.', 'woocommerce' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$classname = WC_Product_Factory::get_classname_from_product_type( $data['type'] );
|
||||
|
||||
if ( ! class_exists( $classname ) ) {
|
||||
$classname = 'WC_Product_Simple';
|
||||
}
|
||||
|
||||
$product = new $classname( $id );
|
||||
} elseif ( ! empty( $data['id'] ) ) {
|
||||
$product = wc_get_product( $id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return new WP_Error(
|
||||
'woocommerce_product_csv_importer_invalid_id',
|
||||
/* translators: %d: product ID */
|
||||
sprintf( __( 'Invalid product ID %d.', 'woocommerce' ), $id ),
|
||||
array(
|
||||
'id' => $id,
|
||||
'status' => 401,
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$product = new WC_Product_Simple( $id );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_product_import_get_product_object', $product, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single item and save.
|
||||
*
|
||||
* @throws Exception If item cannot be processed.
|
||||
* @param array $data Raw CSV data.
|
||||
* @return array|WC_Error
|
||||
*/
|
||||
protected function process_item( $data ) {
|
||||
try {
|
||||
do_action( 'woocommerce_product_import_before_process_item', $data );
|
||||
|
||||
// Get product ID from SKU if created during the importation.
|
||||
if ( empty( $data['id'] ) && ! empty( $data['sku'] ) ) {
|
||||
$product_id = wc_get_product_id_by_sku( $data['sku'] );
|
||||
|
||||
if ( $product_id ) {
|
||||
$data['id'] = $product_id;
|
||||
}
|
||||
}
|
||||
|
||||
$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 ( 'external' === $object->get_type() ) {
|
||||
unset( $data['manage_stock'], $data['stock_status'], $data['backorders'] );
|
||||
}
|
||||
|
||||
if ( 'importing' === $object->get_status() ) {
|
||||
$object->set_status( 'publish' );
|
||||
$object->set_slug( '' );
|
||||
}
|
||||
|
||||
$result = $object->set_props( array_diff_key( $data, array_flip( array( 'meta_data', 'raw_image_id', 'raw_gallery_image_ids', 'raw_attributes' ) ) ) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
throw new Exception( $result->get_error_message() );
|
||||
}
|
||||
|
||||
if ( 'variation' === $object->get_type() ) {
|
||||
$this->set_variation_data( $object, $data );
|
||||
} else {
|
||||
$this->set_product_data( $object, $data );
|
||||
}
|
||||
|
||||
$this->set_image_data( $object, $data );
|
||||
$this->set_meta_data( $object, $data );
|
||||
|
||||
$object = apply_filters( 'woocommerce_product_import_pre_insert_product_object', $object, $data );
|
||||
$object->save();
|
||||
|
||||
do_action( 'woocommerce_product_import_inserted_product_object', $object, $data );
|
||||
|
||||
return array(
|
||||
'id' => $object->get_id(),
|
||||
'updated' => $updating,
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 'woocommerce_product_importer_error', $e->getMessage(), array( 'status' => $e->getCode() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw image URLs to IDs and set.
|
||||
*
|
||||
* @param WC_Product $product Product instance.
|
||||
* @param array $data Item data.
|
||||
*/
|
||||
protected function set_image_data( &$product, $data ) {
|
||||
// Image URLs need converting to IDs before inserting.
|
||||
if ( isset( $data['raw_image_id'] ) ) {
|
||||
$product->set_image_id( $this->get_attachment_id_from_url( $data['raw_image_id'], $product->get_id() ) );
|
||||
}
|
||||
|
||||
// Gallery image URLs need converting to IDs before inserting.
|
||||
if ( isset( $data['raw_gallery_image_ids'] ) ) {
|
||||
$gallery_image_ids = array();
|
||||
|
||||
foreach ( $data['raw_gallery_image_ids'] as $image_id ) {
|
||||
$gallery_image_ids[] = $this->get_attachment_id_from_url( $image_id, $product->get_id() );
|
||||
}
|
||||
$product->set_gallery_image_ids( $gallery_image_ids );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append meta data.
|
||||
*
|
||||
* @param WC_Product $product Product instance.
|
||||
* @param array $data Item data.
|
||||
*/
|
||||
protected function set_meta_data( &$product, $data ) {
|
||||
if ( isset( $data['meta_data'] ) ) {
|
||||
foreach ( $data['meta_data'] as $meta ) {
|
||||
$product->update_meta_data( $meta['key'], $meta['value'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set product data.
|
||||
*
|
||||
* @param WC_Product $product Product instance.
|
||||
* @param array $data Item data.
|
||||
* @throws Exception If data cannot be set.
|
||||
*/
|
||||
protected function set_product_data( &$product, $data ) {
|
||||
if ( isset( $data['raw_attributes'] ) ) {
|
||||
$attributes = array();
|
||||
$default_attributes = array();
|
||||
$existing_attributes = $product->get_attributes();
|
||||
|
||||
foreach ( $data['raw_attributes'] as $position => $attribute ) {
|
||||
$attribute_id = 0;
|
||||
|
||||
// Get ID if is a global attribute.
|
||||
if ( ! empty( $attribute['taxonomy'] ) ) {
|
||||
$attribute_id = $this->get_attribute_taxonomy_id( $attribute['name'] );
|
||||
}
|
||||
|
||||
// Set attribute visibility.
|
||||
if ( isset( $attribute['visible'] ) ) {
|
||||
$is_visible = $attribute['visible'];
|
||||
} else {
|
||||
$is_visible = 1;
|
||||
}
|
||||
|
||||
// Get name.
|
||||
$attribute_name = $attribute_id ? wc_attribute_taxonomy_name_by_id( $attribute_id ) : $attribute['name'];
|
||||
|
||||
// Set if is a variation attribute based on existing attributes if possible so updates via CSV do not change this.
|
||||
$is_variation = 0;
|
||||
|
||||
if ( $existing_attributes ) {
|
||||
foreach ( $existing_attributes as $existing_attribute ) {
|
||||
if ( $existing_attribute->get_name() === $attribute_name ) {
|
||||
$is_variation = $existing_attribute->get_variation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $attribute_id ) {
|
||||
if ( isset( $attribute['value'] ) ) {
|
||||
$options = array_map( 'wc_sanitize_term_text_based', $attribute['value'] );
|
||||
$options = array_filter( $options, 'strlen' );
|
||||
} else {
|
||||
$options = array();
|
||||
}
|
||||
|
||||
// Check for default attributes and set "is_variation".
|
||||
if ( ! empty( $attribute['default'] ) && in_array( $attribute['default'], $options, true ) ) {
|
||||
$default_term = get_term_by( 'name', $attribute['default'], $attribute_name );
|
||||
|
||||
if ( $default_term && ! is_wp_error( $default_term ) ) {
|
||||
$default = $default_term->slug;
|
||||
} else {
|
||||
$default = sanitize_title( $attribute['default'] );
|
||||
}
|
||||
|
||||
$default_attributes[ $attribute_name ] = $default;
|
||||
$is_variation = 1;
|
||||
}
|
||||
|
||||
if ( ! empty( $options ) ) {
|
||||
$attribute_object = new WC_Product_Attribute();
|
||||
$attribute_object->set_id( $attribute_id );
|
||||
$attribute_object->set_name( $attribute_name );
|
||||
$attribute_object->set_options( $options );
|
||||
$attribute_object->set_position( $position );
|
||||
$attribute_object->set_visible( $is_visible );
|
||||
$attribute_object->set_variation( $is_variation );
|
||||
$attributes[] = $attribute_object;
|
||||
}
|
||||
} elseif ( isset( $attribute['value'] ) ) {
|
||||
// Check for default attributes and set "is_variation".
|
||||
if ( ! empty( $attribute['default'] ) && in_array( $attribute['default'], $attribute['value'], true ) ) {
|
||||
$default_attributes[ sanitize_title( $attribute['name'] ) ] = $attribute['default'];
|
||||
$is_variation = 1;
|
||||
}
|
||||
|
||||
$attribute_object = new WC_Product_Attribute();
|
||||
$attribute_object->set_name( $attribute['name'] );
|
||||
$attribute_object->set_options( $attribute['value'] );
|
||||
$attribute_object->set_position( $position );
|
||||
$attribute_object->set_visible( $is_visible );
|
||||
$attribute_object->set_variation( $is_variation );
|
||||
$attributes[] = $attribute_object;
|
||||
}
|
||||
}
|
||||
|
||||
$product->set_attributes( $attributes );
|
||||
|
||||
// Set variable default attributes.
|
||||
if ( $product->is_type( 'variable' ) ) {
|
||||
$product->set_default_attributes( $default_attributes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set variation data.
|
||||
*
|
||||
* @param WC_Product $variation Product instance.
|
||||
* @param array $data Item data.
|
||||
* @return WC_Product|WP_Error
|
||||
* @throws Exception If data cannot be set.
|
||||
*/
|
||||
protected function set_variation_data( &$variation, $data ) {
|
||||
$parent = false;
|
||||
|
||||
// Check if parent exist.
|
||||
if ( isset( $data['parent_id'] ) ) {
|
||||
$parent = wc_get_product( $data['parent_id'] );
|
||||
|
||||
if ( $parent ) {
|
||||
$variation->set_parent_id( $parent->get_id() );
|
||||
}
|
||||
}
|
||||
|
||||
// Stop if parent does not exists.
|
||||
if ( ! $parent ) {
|
||||
return new WP_Error( 'woocommerce_product_importer_missing_variation_parent_id', __( 'Variation cannot be imported: Missing parent ID or parent does not exist yet.', 'woocommerce' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['raw_attributes'] ) ) {
|
||||
$attributes = array();
|
||||
$parent_attributes = $this->get_variation_parent_attributes( $data['raw_attributes'], $parent );
|
||||
|
||||
foreach ( $data['raw_attributes'] as $attribute ) {
|
||||
$attribute_id = 0;
|
||||
|
||||
// Get ID if is a global attribute.
|
||||
if ( ! empty( $attribute['taxonomy'] ) ) {
|
||||
$attribute_id = $this->get_attribute_taxonomy_id( $attribute['name'] );
|
||||
}
|
||||
|
||||
if ( $attribute_id ) {
|
||||
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
|
||||
} else {
|
||||
$attribute_name = sanitize_title( $attribute['name'] );
|
||||
}
|
||||
|
||||
if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
|
||||
$attribute_value = isset( $attribute['value'] ) ? current( $attribute['value'] ) : '';
|
||||
|
||||
if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
|
||||
// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
|
||||
$term = get_term_by( 'name', $attribute_value, $attribute_name );
|
||||
|
||||
if ( $term && ! is_wp_error( $term ) ) {
|
||||
$attribute_value = $term->slug;
|
||||
} else {
|
||||
$attribute_value = sanitize_title( $attribute_value );
|
||||
}
|
||||
}
|
||||
|
||||
$attributes[ $attribute_key ] = $attribute_value;
|
||||
}
|
||||
|
||||
$variation->set_attributes( $attributes );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variation parent attributes and set "is_variation".
|
||||
*
|
||||
* @param array $attributes Attributes list.
|
||||
* @param WC_Product $parent Parent product data.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_variation_parent_attributes( $attributes, $parent ) {
|
||||
$parent_attributes = $parent->get_attributes();
|
||||
$require_save = false;
|
||||
|
||||
foreach ( $attributes as $attribute ) {
|
||||
$attribute_id = 0;
|
||||
|
||||
// Get ID if is a global attribute.
|
||||
if ( ! empty( $attribute['taxonomy'] ) ) {
|
||||
$attribute_id = $this->get_attribute_taxonomy_id( $attribute['name'] );
|
||||
}
|
||||
|
||||
if ( $attribute_id ) {
|
||||
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
|
||||
} else {
|
||||
$attribute_name = sanitize_title( $attribute['name'] );
|
||||
}
|
||||
|
||||
// Check if attribute handle variations.
|
||||
if ( isset( $parent_attributes[ $attribute_name ] ) && ! $parent_attributes[ $attribute_name ]->get_variation() ) {
|
||||
// Re-create the attribute to CRUD save and generate again.
|
||||
$parent_attributes[ $attribute_name ] = clone $parent_attributes[ $attribute_name ];
|
||||
$parent_attributes[ $attribute_name ]->set_variation( 1 );
|
||||
|
||||
$require_save = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save variation attributes.
|
||||
if ( $require_save ) {
|
||||
$parent->set_attributes( array_values( $parent_attributes ) );
|
||||
$parent->save();
|
||||
}
|
||||
|
||||
return $parent_attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment ID.
|
||||
*
|
||||
* @param string $url Attachment URL.
|
||||
* @param int $product_id Product ID.
|
||||
* @return int
|
||||
* @throws Exception If attachment cannot be loaded.
|
||||
*/
|
||||
public function get_attachment_id_from_url( $url, $product_id ) {
|
||||
if ( empty( $url ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$id = 0;
|
||||
$upload_dir = wp_upload_dir( null, false );
|
||||
$base_url = $upload_dir['baseurl'] . '/';
|
||||
|
||||
// Check first if attachment is inside the WordPress uploads directory, or we're given a filename only.
|
||||
if ( false !== strpos( $url, $base_url ) || false === strpos( $url, '://' ) ) {
|
||||
// Search for yyyy/mm/slug.extension or slug.extension - remove the base URL.
|
||||
$file = str_replace( $base_url, '', $url );
|
||||
$args = array(
|
||||
'post_type' => 'attachment',
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array( // @codingStandardsIgnoreLine.
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_wp_attached_file',
|
||||
'value' => '^' . $file,
|
||||
'compare' => 'REGEXP',
|
||||
),
|
||||
array(
|
||||
'key' => '_wp_attached_file',
|
||||
'value' => '/' . $file,
|
||||
'compare' => 'LIKE',
|
||||
),
|
||||
array(
|
||||
'key' => '_wc_attachment_source',
|
||||
'value' => '/' . $file,
|
||||
'compare' => 'LIKE',
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// This is an external URL, so compare to source.
|
||||
$args = array(
|
||||
'post_type' => 'attachment',
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array( // @codingStandardsIgnoreLine.
|
||||
array(
|
||||
'value' => $url,
|
||||
'key' => '_wc_attachment_source',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$ids = get_posts( $args ); // @codingStandardsIgnoreLine.
|
||||
|
||||
if ( $ids ) {
|
||||
$id = current( $ids );
|
||||
}
|
||||
|
||||
// Upload if attachment does not exists.
|
||||
if ( ! $id && stristr( $url, '://' ) ) {
|
||||
$upload = wc_rest_upload_image_from_url( $url );
|
||||
|
||||
if ( is_wp_error( $upload ) ) {
|
||||
throw new Exception( $upload->get_error_message(), 400 );
|
||||
}
|
||||
|
||||
$id = wc_rest_set_uploaded_image_as_attachment( $upload, $product_id );
|
||||
|
||||
if ( ! wp_attachment_is_image( $id ) ) {
|
||||
/* translators: %s: image URL */
|
||||
throw new Exception( sprintf( __( 'Not able to attach "%s".', 'woocommerce' ), $url ), 400 );
|
||||
}
|
||||
|
||||
// Save attachment source for future reference.
|
||||
update_post_meta( $id, '_wc_attachment_source', $url );
|
||||
}
|
||||
|
||||
if ( ! $id ) {
|
||||
/* translators: %s: image URL */
|
||||
throw new Exception( sprintf( __( 'Unable to use image "%s".', 'woocommerce' ), $url ), 400 );
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute taxonomy ID from the imported data.
|
||||
* If does not exists register a new attribute.
|
||||
*
|
||||
* @param string $raw_name Attribute name.
|
||||
* @return int
|
||||
* @throws Exception If taxonomy cannot be loaded.
|
||||
*/
|
||||
public function get_attribute_taxonomy_id( $raw_name ) {
|
||||
global $wpdb, $wc_product_attributes;
|
||||
|
||||
// These are exported as labels, so convert the label to a name if possible first.
|
||||
$attribute_labels = wp_list_pluck( wc_get_attribute_taxonomies(), 'attribute_label', 'attribute_name' );
|
||||
$attribute_name = array_search( $raw_name, $attribute_labels, true );
|
||||
|
||||
if ( ! $attribute_name ) {
|
||||
$attribute_name = wc_sanitize_taxonomy_name( $raw_name );
|
||||
}
|
||||
|
||||
$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute_name );
|
||||
|
||||
// Get the ID from the name.
|
||||
if ( $attribute_id ) {
|
||||
return $attribute_id;
|
||||
}
|
||||
|
||||
// If the attribute does not exist, create it.
|
||||
$attribute_id = wc_create_attribute( array(
|
||||
'name' => $raw_name,
|
||||
'slug' => $attribute_name,
|
||||
'type' => 'select',
|
||||
'order_by' => 'menu_order',
|
||||
'has_archives' => false,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $attribute_id ) ) {
|
||||
throw new Exception( $attribute_id->get_error_message(), 400 );
|
||||
}
|
||||
|
||||
// Register as taxonomy while importing.
|
||||
$taxonomy_name = wc_attribute_taxonomy_name( $attribute_name );
|
||||
register_taxonomy(
|
||||
$taxonomy_name,
|
||||
apply_filters( 'woocommerce_taxonomy_objects_' . $taxonomy_name, array( 'product' ) ),
|
||||
apply_filters( 'woocommerce_taxonomy_args_' . $taxonomy_name, array(
|
||||
'labels' => array(
|
||||
'name' => $raw_name,
|
||||
),
|
||||
'hierarchical' => true,
|
||||
'show_ui' => false,
|
||||
'query_var' => true,
|
||||
'rewrite' => false,
|
||||
) )
|
||||
);
|
||||
|
||||
// Set product attributes global.
|
||||
$wc_product_attributes = array();
|
||||
|
||||
foreach ( wc_get_attribute_taxonomies() as $taxonomy ) {
|
||||
$wc_product_attributes[ wc_attribute_taxonomy_name( $taxonomy->attribute_name ) ] = $taxonomy;
|
||||
}
|
||||
|
||||
return $attribute_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory exceeded
|
||||
*
|
||||
* Ensures the batch process never exceeds 90%
|
||||
* of the maximum WordPress memory.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function memory_exceeded() {
|
||||
$memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
|
||||
$current_memory = memory_get_usage( true );
|
||||
$return = false;
|
||||
if ( $current_memory >= $memory_limit ) {
|
||||
$return = true;
|
||||
}
|
||||
return apply_filters( 'woocommerce_product_importer_memory_exceeded', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_memory_limit() {
|
||||
if ( function_exists( 'ini_get' ) ) {
|
||||
$memory_limit = ini_get( 'memory_limit' );
|
||||
} else {
|
||||
// Sensible default.
|
||||
$memory_limit = '128M';
|
||||
}
|
||||
|
||||
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
|
||||
// Unlimited, set to 32GB.
|
||||
$memory_limit = '32000M';
|
||||
}
|
||||
return intval( $memory_limit ) * 1024 * 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time exceeded.
|
||||
*
|
||||
* Ensures the batch never exceeds a sensible time limit.
|
||||
* A timeout limit of 30s is common on shared hosting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function time_exceeded() {
|
||||
$finish = $this->start_time + apply_filters( 'woocommerce_product_importer_default_time_limit', 20 ); // 20 seconds
|
||||
$return = false;
|
||||
if ( time() >= $finish ) {
|
||||
$return = true;
|
||||
}
|
||||
return apply_filters( 'woocommerce_product_importer_time_exceeded', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode CSV cell values using commas by default, and handling escaped
|
||||
* separators.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param string $value Value to explode.
|
||||
* @return array
|
||||
*/
|
||||
protected function explode_values( $value ) {
|
||||
$value = str_replace( '\\,', '::separator::', $value );
|
||||
$values = explode( ',', $value );
|
||||
$values = array_map( array( $this, 'explode_values_formatter' ), $values );
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove formatting and trim each value.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param string $value Value to format.
|
||||
* @return string
|
||||
*/
|
||||
protected function explode_values_formatter( $value ) {
|
||||
return trim( str_replace( '::separator::', ',', $value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The exporter prepends a ' to fields that start with a - which causes
|
||||
* issues with negative numbers. This removes the ' if the input is still a valid
|
||||
* number after removal.
|
||||
*
|
||||
* @since 3.3.0
|
||||
* @param string $value A numeric string that may or may not have ' prepended.
|
||||
* @return string
|
||||
*/
|
||||
protected function unescape_negative_number( $value ) {
|
||||
if ( 0 === strpos( $value, "'-" ) ) {
|
||||
$unescaped = trim( $value, "'" );
|
||||
if ( is_numeric( $unescaped ) ) {
|
||||
return $unescaped;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product CSV importer
|
||||
*
|
||||
* @package WooCommerce/Import
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include dependencies.
|
||||
*/
|
||||
if ( ! class_exists( 'WC_Product_Importer', false ) ) {
|
||||
include_once dirname( __FILE__ ) . '/abstract-wc-product-importer.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Product_CSV_Importer Class.
|
||||
*/
|
||||
class WC_Product_CSV_Importer extends WC_Product_Importer {
|
||||
|
||||
/**
|
||||
* Tracks current row being parsed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $parsing_raw_data_index = 0;
|
||||
|
||||
/**
|
||||
* Initialize importer.
|
||||
*
|
||||
* @param string $file File to read.
|
||||
* @param array $params Arguments for the parser.
|
||||
*/
|
||||
public function __construct( $file, $params = array() ) {
|
||||
$default_args = array(
|
||||
'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.
|
||||
'enclosure' => '"', // The character used to wrap text in the CSV.
|
||||
'escape' => "\0", // PHP uses '\' as the default escape character. This is not RFC-4180 compliant. This disables the escape character.
|
||||
);
|
||||
|
||||
$this->params = wp_parse_args( $params, $default_args );
|
||||
$this->file = $file;
|
||||
|
||||
if ( isset( $this->params['mapping']['from'], $this->params['mapping']['to'] ) ) {
|
||||
$this->params['mapping'] = array_combine( $this->params['mapping']['from'], $this->params['mapping']['to'] );
|
||||
}
|
||||
|
||||
$this->read_file();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file.
|
||||
*/
|
||||
protected function read_file() {
|
||||
$handle = fopen( $this->file, 'r' ); // @codingStandardsIgnoreLine.
|
||||
|
||||
if ( false !== $handle ) {
|
||||
$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
|
||||
|
||||
// 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] );
|
||||
}
|
||||
|
||||
if ( 0 !== $this->params['start_pos'] ) {
|
||||
fseek( $handle, (int) $this->params['start_pos'] );
|
||||
}
|
||||
|
||||
while ( 1 ) {
|
||||
$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
|
||||
|
||||
if ( false !== $row ) {
|
||||
$this->raw_data[] = $row;
|
||||
$this->file_positions[ count( $this->raw_data ) ] = ftell( $handle );
|
||||
|
||||
if ( ( $this->params['end_pos'] > 0 && ftell( $handle ) >= $this->params['end_pos'] ) || 0 === --$this->params['lines'] ) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->file_position = ftell( $handle );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->params['mapping'] ) ) {
|
||||
$this->set_mapped_keys();
|
||||
}
|
||||
|
||||
if ( $this->params['parse'] ) {
|
||||
$this->set_parsed_data();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse relative field and return product ID.
|
||||
*
|
||||
* 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 $value Field value.
|
||||
* @return int|string
|
||||
*/
|
||||
public function parse_relative_field( $value ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $value ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// IDs are prefixed with id:.
|
||||
if ( preg_match( '/^id:(\d+)$/', $value, $matches ) ) {
|
||||
$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.
|
||||
$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.
|
||||
|
||||
if ( $original_id ) {
|
||||
return absint( $original_id );
|
||||
}
|
||||
|
||||
// See if the given ID maps to a valid product allready.
|
||||
$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.
|
||||
|
||||
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'] ) {
|
||||
$product = new WC_Product_Simple();
|
||||
$product->set_name( 'Import placeholder for ' . $id );
|
||||
$product->set_status( 'importing' );
|
||||
$product->add_meta_data( '_original_id', $id, true );
|
||||
$id = $product->save();
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
$id = wc_get_product_id_by_sku( $value );
|
||||
|
||||
if ( $id ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
try {
|
||||
$product = new WC_Product_Simple();
|
||||
$product->set_name( 'Import placeholder for ' . $value );
|
||||
$product->set_status( 'importing' );
|
||||
$product->set_sku( $value );
|
||||
$id = $product->save();
|
||||
|
||||
if ( $id && ! is_wp_error( $id ) ) {
|
||||
return $id;
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the ID field.
|
||||
*
|
||||
* If we're not doing an update, create a placeholder product so mapping works
|
||||
* for rows following this one.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return int
|
||||
*/
|
||||
public function parse_id_field( $value ) {
|
||||
global $wpdb;
|
||||
|
||||
$id = absint( $value );
|
||||
|
||||
if ( ! $id ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// See if this maps to an ID placeholder already.
|
||||
$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.
|
||||
|
||||
if ( $original_id ) {
|
||||
return absint( $original_id );
|
||||
}
|
||||
|
||||
// Not updating? Make sure we have a new placeholder for this ID.
|
||||
if ( ! $this->params['update_existing'] ) {
|
||||
$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 ) : '';
|
||||
|
||||
// If row has a SKU, make sure placeholder was not made already.
|
||||
if ( $id_from_sku ) {
|
||||
return $id_from_sku;
|
||||
}
|
||||
|
||||
$product = new WC_Product_Simple();
|
||||
$product->set_name( 'Import placeholder for ' . $id );
|
||||
$product->set_status( 'importing' );
|
||||
$product->add_meta_data( '_original_id', $id, true );
|
||||
|
||||
// If row has a SKU, make sure placeholder has it too.
|
||||
if ( $row_sku ) {
|
||||
$product->set_sku( $row_sku );
|
||||
}
|
||||
$id = $product->save();
|
||||
}
|
||||
|
||||
return $id && ! is_wp_error( $id ) ? $id : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse relative comma-delineated field and return product ID.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array
|
||||
*/
|
||||
public function parse_relative_comma_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_filter( array_map( array( $this, 'parse_relative_field' ), $this->explode_values( $value ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a comma-delineated field from a CSV.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array
|
||||
*/
|
||||
public function parse_comma_field( $value ) {
|
||||
if ( empty( $value ) && '0' !== $value ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map( 'wc_clean', $this->explode_values( $value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a field that is generally '1' or '0' but can be something else.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return bool|string
|
||||
*/
|
||||
public function parse_bool_field( $value ) {
|
||||
if ( '0' === $value ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( '1' === $value ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't return explicit true or false for empty fields or values like 'notify'.
|
||||
return wc_clean( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a float value field.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return float|string
|
||||
*/
|
||||
public function parse_float_field( $value ) {
|
||||
if ( '' === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Remove the ' prepended to fields that start with - if needed.
|
||||
$value = $this->unescape_negative_number( $value );
|
||||
|
||||
return floatval( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the stock qty field.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return float|string
|
||||
*/
|
||||
public function parse_stock_quantity_field( $value ) {
|
||||
if ( '' === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Remove the ' prepended to fields that start with - if needed.
|
||||
$value = $this->unescape_negative_number( $value );
|
||||
|
||||
return wc_stock_amount( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a category field from a CSV.
|
||||
* Categories are separated by commas and subcategories are "parent > subcategory".
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array of arrays with "parent" and "name" keys.
|
||||
*/
|
||||
public function parse_categories_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$row_terms = $this->explode_values( $value );
|
||||
$categories = array();
|
||||
|
||||
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.
|
||||
// @codingStandardsIgnoreStart
|
||||
$term = term_exists( $_term, 'product_cat', $parent );
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
if ( is_array( $term ) ) {
|
||||
$term_id = $term['term_id'];
|
||||
} else {
|
||||
$term = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
|
||||
|
||||
if ( is_wp_error( $term ) ) {
|
||||
break; // We cannot continue if the term cannot be inserted.
|
||||
}
|
||||
|
||||
$term_id = $term['term_id'];
|
||||
}
|
||||
|
||||
// Only requires assign the last category.
|
||||
if ( ( 1 + $index ) === $total ) {
|
||||
$categories[] = $term_id;
|
||||
} else {
|
||||
// Store parent to be able to insert or query categories based in parent ID.
|
||||
$parent = $term_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a tag field from a CSV.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array
|
||||
*/
|
||||
public function parse_tags_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$names = $this->explode_values( $value );
|
||||
$tags = array();
|
||||
|
||||
foreach ( $names as $name ) {
|
||||
$term = get_term_by( 'name', $name, 'product_tag' );
|
||||
|
||||
if ( ! $term || is_wp_error( $term ) ) {
|
||||
$term = (object) wp_insert_term( $name, 'product_tag' );
|
||||
}
|
||||
|
||||
if ( ! is_wp_error( $term ) ) {
|
||||
$tags[] = $term->term_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a shipping class field from a CSV.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return int
|
||||
*/
|
||||
public function parse_shipping_class_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$term = get_term_by( 'name', $value, 'product_shipping_class' );
|
||||
|
||||
if ( ! $term || is_wp_error( $term ) ) {
|
||||
$term = (object) wp_insert_term( $value, 'product_shipping_class' );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $term ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $term->term_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse images list from a CSV. Images can be filenames or URLs.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return array
|
||||
*/
|
||||
public function parse_images_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$images = array();
|
||||
|
||||
foreach ( $this->explode_values( $value ) as $image ) {
|
||||
if ( stristr( $image, '://' ) ) {
|
||||
$images[] = esc_url_raw( $image );
|
||||
} else {
|
||||
$images[] = sanitize_file_name( $image );
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dates from a CSV.
|
||||
* Dates requires the format YYYY-MM-DD and time is optional.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return string|null
|
||||
*/
|
||||
public function parse_date_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 ) ) {
|
||||
// Don't include the time if the field had time in it.
|
||||
return current( explode( ' ', $value ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse backorders from a CSV.
|
||||
*
|
||||
* @param string $value Field value.
|
||||
* @return string
|
||||
*/
|
||||
public function parse_backorders_field( $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
return 'no';
|
||||
}
|
||||
|
||||
$value = $this->parse_bool_field( $value );
|
||||
|
||||
if ( 'notify' === $value ) {
|
||||
return 'notify';
|
||||
} elseif ( is_bool( $value ) ) {
|
||||
return $value ? 'yes' : 'no';
|
||||
}
|
||||
|
||||
return 'no';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an int value field
|
||||
*
|
||||
* @param int $value field value.
|
||||
* @return int
|
||||
*/
|
||||
public function parse_int_field( $value ) {
|
||||
// Remove the ' prepended to fields that start with - if needed.
|
||||
$value = $this->unescape_negative_number( $value );
|
||||
|
||||
return intval( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatting callback.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_formating_callback() {
|
||||
|
||||
/**
|
||||
* Columns not mentioned here will get parsed with 'wc_clean'.
|
||||
* column_name => callback.
|
||||
*/
|
||||
$data_formatting = array(
|
||||
'id' => array( $this, 'parse_id_field' ),
|
||||
'type' => array( $this, 'parse_comma_field' ),
|
||||
'published' => array( $this, 'parse_float_field' ),
|
||||
'featured' => array( $this, 'parse_bool_field' ),
|
||||
'date_on_sale_from' => array( $this, 'parse_date_field' ),
|
||||
'date_on_sale_to' => array( $this, 'parse_date_field' ),
|
||||
'name' => array( $this, 'parse_skip_field' ),
|
||||
'short_description' => array( $this, 'parse_skip_field' ),
|
||||
'description' => array( $this, 'parse_skip_field' ),
|
||||
'manage_stock' => array( $this, 'parse_bool_field' ),
|
||||
'backorders' => array( $this, 'parse_backorders_field' ),
|
||||
'stock_status' => array( $this, 'parse_bool_field' ),
|
||||
'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' ),
|
||||
'purchase_note' => 'wp_filter_post_kses',
|
||||
'price' => 'wc_format_decimal',
|
||||
'regular_price' => 'wc_format_decimal',
|
||||
'stock_quantity' => array( $this, 'parse_stock_quantity_field' ),
|
||||
'category_ids' => array( $this, 'parse_categories_field' ),
|
||||
'tag_ids' => array( $this, 'parse_tags_field' ),
|
||||
'shipping_class_id' => array( $this, 'parse_shipping_class_field' ),
|
||||
'images' => array( $this, 'parse_images_field' ),
|
||||
'parent_id' => array( $this, 'parse_relative_field' ),
|
||||
'grouped_products' => array( $this, 'parse_relative_comma_field' ),
|
||||
'upsell_ids' => array( $this, 'parse_relative_comma_field' ),
|
||||
'cross_sell_ids' => array( $this, 'parse_relative_comma_field' ),
|
||||
'download_limit' => array( $this, 'parse_int_field' ),
|
||||
'download_expiry' => array( $this, 'parse_int_field' ),
|
||||
'product_url' => 'esc_url_raw',
|
||||
'menu_order' => 'intval',
|
||||
);
|
||||
|
||||
/**
|
||||
* Match special column names.
|
||||
*/
|
||||
$regex_match_data_formatting = array(
|
||||
'/attributes:value*/' => array( $this, 'parse_comma_field' ),
|
||||
'/attributes:visible*/' => array( $this, 'parse_bool_field' ),
|
||||
'/attributes:taxonomy*/' => array( $this, 'parse_bool_field' ),
|
||||
'/downloads:url*/' => array( $this, 'parse_download_file_field' ),
|
||||
'/meta:*/' => 'wp_kses_post', // Allow some HTML in meta fields.
|
||||
);
|
||||
|
||||
$callbacks = array();
|
||||
|
||||
// Figure out the parse function for each column.
|
||||
foreach ( $this->get_mapped_keys() as $index => $heading ) {
|
||||
$callback = 'wc_clean';
|
||||
|
||||
if ( isset( $data_formatting[ $heading ] ) ) {
|
||||
$callback = $data_formatting[ $heading ];
|
||||
} else {
|
||||
foreach ( $regex_match_data_formatting as $regex => $callback ) {
|
||||
if ( preg_match( $regex, $heading ) ) {
|
||||
$callback = $callback;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$callbacks[] = $callback;
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_product_importer_formatting_callbacks', $callbacks, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if strings starts with determined word.
|
||||
*
|
||||
* @param string $haystack Complete sentence.
|
||||
* @param string $needle Excerpt.
|
||||
* @return bool
|
||||
*/
|
||||
protected function starts_with( $haystack, $needle ) {
|
||||
return substr( $haystack, 0, strlen( $needle ) ) === $needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand special and internal data into the correct formats for the product CRUD.
|
||||
*
|
||||
* @param array $data Data to import.
|
||||
* @return array
|
||||
*/
|
||||
protected function expand_data( $data ) {
|
||||
$data = apply_filters( 'woocommerce_product_importer_pre_expand_data', $data );
|
||||
|
||||
// Images field maps to image and gallery id fields.
|
||||
if ( isset( $data['images'] ) ) {
|
||||
$images = $data['images'];
|
||||
$data['raw_image_id'] = array_shift( $images );
|
||||
|
||||
if ( ! empty( $images ) ) {
|
||||
$data['raw_gallery_image_ids'] = $images;
|
||||
}
|
||||
unset( $data['images'] );
|
||||
}
|
||||
|
||||
// Type, virtual and downloadable are all stored in the same column.
|
||||
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 );
|
||||
|
||||
// Convert type to string.
|
||||
$data['type'] = current( array_diff( $data['type'], array( 'virtual', 'downloadable' ) ) );
|
||||
}
|
||||
|
||||
// Status is mapped from a special published field.
|
||||
if ( isset( $data['published'] ) ) {
|
||||
$statuses = array(
|
||||
-1 => 'draft',
|
||||
0 => 'private',
|
||||
1 => 'publish',
|
||||
);
|
||||
$data['status'] = isset( $statuses[ $data['published'] ] ) ? $statuses[ $data['published'] ] : -1;
|
||||
|
||||
unset( $data['published'] );
|
||||
}
|
||||
|
||||
if ( isset( $data['stock_quantity'] ) ) {
|
||||
if ( '' === $data['stock_quantity'] ) {
|
||||
$data['manage_stock'] = false;
|
||||
$data['stock_status'] = isset( $data['stock_status'] ) ? $data['stock_status'] : true;
|
||||
} else {
|
||||
$data['manage_stock'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Stock is bool or 'backorder'.
|
||||
if ( isset( $data['stock_status'] ) ) {
|
||||
if ( 'backorder' === $data['stock_status'] ) {
|
||||
$data['stock_status'] = 'onbackorder';
|
||||
} else {
|
||||
$data['stock_status'] = $data['stock_status'] ? 'instock' : 'outofstock';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare grouped products.
|
||||
if ( isset( $data['grouped_products'] ) ) {
|
||||
$data['children'] = $data['grouped_products'];
|
||||
unset( $data['grouped_products'] );
|
||||
}
|
||||
|
||||
// Handle special column names which span multiple columns.
|
||||
$attributes = array();
|
||||
$downloads = array();
|
||||
$meta_data = array();
|
||||
|
||||
foreach ( $data as $key => $value ) {
|
||||
if ( $this->starts_with( $key, 'attributes:name' ) ) {
|
||||
if ( ! empty( $value ) ) {
|
||||
$attributes[ str_replace( 'attributes:name', '', $key ) ]['name'] = $value;
|
||||
}
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'attributes:value' ) ) {
|
||||
$attributes[ str_replace( 'attributes:value', '', $key ) ]['value'] = $value;
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'attributes:taxonomy' ) ) {
|
||||
$attributes[ str_replace( 'attributes:taxonomy', '', $key ) ]['taxonomy'] = wc_string_to_bool( $value );
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'attributes:visible' ) ) {
|
||||
$attributes[ str_replace( 'attributes:visible', '', $key ) ]['visible'] = wc_string_to_bool( $value );
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'attributes:default' ) ) {
|
||||
if ( ! empty( $value ) ) {
|
||||
$attributes[ str_replace( 'attributes:default', '', $key ) ]['default'] = $value;
|
||||
}
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'downloads:name' ) ) {
|
||||
if ( ! empty( $value ) ) {
|
||||
$downloads[ str_replace( 'downloads:name', '', $key ) ]['name'] = $value;
|
||||
}
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'downloads:url' ) ) {
|
||||
if ( ! empty( $value ) ) {
|
||||
$downloads[ str_replace( 'downloads:url', '', $key ) ]['url'] = $value;
|
||||
}
|
||||
unset( $data[ $key ] );
|
||||
|
||||
} elseif ( $this->starts_with( $key, 'meta:' ) ) {
|
||||
$meta_data[] = array(
|
||||
'key' => str_replace( 'meta:', '', $key ),
|
||||
'value' => $value,
|
||||
);
|
||||
unset( $data[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $attributes ) ) {
|
||||
// Remove empty attributes and clear indexes.
|
||||
foreach ( $attributes as $attribute ) {
|
||||
if ( empty( $attribute['name'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['raw_attributes'][] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
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'] ),
|
||||
'file' => $file['url'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $meta_data ) ) {
|
||||
$data['meta_data'] = $meta_data;
|
||||
}
|
||||
|
||||
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();
|
||||
$use_mb = function_exists( 'mb_convert_encoding' );
|
||||
|
||||
// Parse the data.
|
||||
foreach ( $this->raw_data as $row_index => $row ) {
|
||||
// Skip empty rows.
|
||||
if ( ! count( array_filter( $row ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->parsing_raw_data_index = $row_index;
|
||||
|
||||
$data = array();
|
||||
|
||||
do_action( 'woocommerce_product_importer_before_set_parsed_data', $row, $mapped_keys );
|
||||
|
||||
foreach ( $row as $id => $value ) {
|
||||
// Skip ignored columns.
|
||||
if ( empty( $mapped_keys[ $id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 );
|
||||
}
|
||||
|
||||
$data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value );
|
||||
}
|
||||
|
||||
$this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string to identify the row from parsed data.
|
||||
*
|
||||
* @param array $parsed_data Parsed data.
|
||||
* @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 ) {
|
||||
/* translators: %d: product ID */
|
||||
$row_data[] = sprintf( __( 'ID %d', 'woocommerce' ), $id );
|
||||
}
|
||||
if ( $sku ) {
|
||||
/* translators: %s: product SKU */
|
||||
$row_data[] = sprintf( __( 'SKU %s', 'woocommerce' ), $sku );
|
||||
}
|
||||
|
||||
return implode( ', ', $row_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process importer.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function import() {
|
||||
$this->start_time = time();
|
||||
$index = 0;
|
||||
$update_existing = $this->params['update_existing'];
|
||||
$data = array(
|
||||
'imported' => array(),
|
||||
'failed' => array(),
|
||||
'updated' => array(),
|
||||
'skipped' => array(),
|
||||
);
|
||||
|
||||
foreach ( $this->parsed_data as $parsed_data_key => $parsed_data ) {
|
||||
do_action( 'woocommerce_product_import_before_import', $parsed_data );
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
if ( $sku ) {
|
||||
$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();
|
||||
}
|
||||
|
||||
if ( $id_exists && ! $update_existing ) {
|
||||
$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 ),
|
||||
) );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $sku_exists && ! $update_existing ) {
|
||||
$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 ),
|
||||
) );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $update_existing && ( $id || $sku ) && ! $id_exists && ! $sku_exists ) {
|
||||
$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 ),
|
||||
) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->process_item( $parsed_data );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$result->add_data( array( 'row' => $this->get_row_id( $parsed_data ) ) );
|
||||
$data['failed'][] = $result;
|
||||
} elseif ( $result['updated'] ) {
|
||||
$data['updated'][] = $result['id'];
|
||||
} else {
|
||||
$data['imported'][] = $result['id'];
|
||||
}
|
||||
|
||||
$index ++;
|
||||
|
||||
if ( $this->params['prevent_timeouts'] && ( $this->time_exceeded() || $this->memory_exceeded() ) ) {
|
||||
$this->file_position = $this->file_positions[ $index ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user