| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 |
- <?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'];
- // Don't allow users without capabilities to create new categories.
- } elseif ( ! current_user_can( 'manage_product_terms' ) ) {
- break;
- } 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;
- }
- }
|